第 2 章
有了 Claude Code,为什么还要用 Agent SDK?
这问题我被问过很多次。
有一次我在群里说“Agent SDK 不是 Agent 框架”,有人追着问:“那我直接用 Claude Code 不就行了?你们怎么总爱造轮子?”
我当时的回答不太好,大意是“SDK 更灵活”。这话等于没说。
后来我干脆把问题拆成五个,谁再问,我就按这五个答。今天一次性写出来。
Q1:Claude Code 和 Claude Agent SDK,到底啥关系?
说人话:Claude Code 是成品,Agent SDK 是同一套 Harness 的库接口。
先把“谁在干什么”画出来:
你的代码 / 你的产品
│
▼
Claude Agent SDK ← 你写的 Python / TS 代码在这一层
│
▼
Claude Code CLI ← 循环、工具、权限、上下文、压缩、会话都在这
│
▼
模型 API注意中间那层。SDK 不实现 Agent Loop。
循环、工具执行、权限判定、上下文组装、压缩、会话落盘,全在 Claude Code CLI 里。SDK 干两件事:把你的配置翻译成 CLI 的启动参数和控制请求;把 CLI 的消息流、回调翻译回你的代码。
所以你不能“import 一个循环”,只能驱动一个已经跑起来的循环。
我一般这么跟人解释:Claude Code 是台装好的机器,SDK 是这台机器的控制面板。你能按按钮、能接外设、能读仪表盘,但你不能把发动机拆了换一个。
| Claude Code | Claude Agent SDK | |
|---|---|---|
| 谁在用 | 人 | 程序 |
| 形态 | 终端里的 CLI 产品 | Python / TypeScript 库 |
| 循环 | 内置 | 同一套,在 CLI 里 |
| 工具 | 内置 + MCP | 内置 + MCP + 进程内工具 |
| 权限 | 交互 UI + settings / hooks | can_use_tool + 权限模式 + hooks |
| 输出 | 终端文本 | 消息流、结构化输出 |
| 部署 | 本地 / CI | 嵌进产品、多租户服务 |
| 定制 | 设置、技能、子 Agent | 上述全部 + 程序化控制 |
所以别误会:用了 SDK,不等于你写了一个 Claude Code。 你只是换了个入口,发动机还是那台。
SDK 本身是开源的,CLI 是闭源的。你看得到控制面板的代码,看不到发动机内部。这也是为什么这本书不带你读 CLI 源码——读不到,也不该猜。
Q2:既然有 Claude Code,为什么还要 SDK?
先问一句:你是想自己用,还是想让程序用?
如果你是自己用——坐在终端里,让 AI 帮你读代码、改文件、跑测试——Claude Code 已经够好了。真的,别造。造出来大概率还不如它。
但如果你想让你的程序用,事情就变了。
我见过三个很典型的场景。
第一个,客服后台。 用户点“申请退款”,后台要查订单、看政策、判断能不能退、超过 500 块转人工。这一串动作不能让客服自己去终端里敲命令,得由你的服务在背后跑。
第二个,内部数据平台。 运营想看“上周华东区的退款率”,Agent 需要调你们自己的 Python 函数、查数据库、把结果整理成表格。这些工具就跑在你的进程里,不是一个 CLI 能接进去的。
第三个,CI 批处理。 每天晚上跑 50 个仓库的依赖升级,每个仓库一个 Agent,跑完收集结构化结果,失败的进工单系统。你要的是能编排、能拿 JSON、能重试,而不是 50 个终端窗口。
这三个场景的共同点是:Agent 不是主角,你的产品才是。 Agent 只是里面的一颗零件。
Claude Code 能不能无头跑?能,claude -p 就是干这个的。但你拿到的是文本和退出码,后面的事都得自己来:解析输出、管理进程、做审批、处理并发、记录成本。SDK 把这些变成了 API。
所以 SDK 的定位不是“更高级的 Claude Code”,是“把 Claude Code 嵌进程序的接口”。
Q3:有哪些事是 Claude Code 做不了、SDK 能做的?
先纠正一个说法:与其说“做不了”,不如说“它不是为这件事设计的”。
Claude Code 可以通过 MCP 接外部工具,可以通过 settings 配权限,可以通过 hooks 做拦截。它不是一个封闭的黑盒。但它的产品形态决定了,很多事它只能做到“够用”,做不到“可编程”。
我把常见的差异列成五条:
| 你要做的事 | Claude Code | SDK |
|---|---|---|
| 把 Agent 嵌进自己的服务 | 只能当子进程调,解析文本 | 原生消息流、控制请求、回调 |
| 用自己的工具和数据 | MCP 为主 | MCP + 进程内 @tool,直接调你的函数 |
| 用自己的策略和审批 | settings / hooks + 交互 UI | can_use_tool 接到你的风控引擎 |
| 程序化编排、结构化输出 | 脚本接口有限 | typed messages、output_format |
| 多租户、计量、配额 | 单机产品,不适合 | 给原语,隔离/计量仍要你写 |
每一条都可以展开,但先看一条最容易误解的:工具。
Claude Code 通过 MCP 接工具,这没问题。但 MCP 意味着你的工具要跑在一个 server 里,通过协议暴露出去。SDK 的进程内工具不用:一个 @tool 装饰器,函数就在你的 Python 进程里,参数直接是 dict,返回值直接回给 CLI。对“我就是要调一下公司内部的那个 Python 函数”这种需求,少一层协议,少一堆事。
比如你要接一个“查退款政策”的函数,大概就长这样:
from claude_agent_sdk import tool, create_sdk_mcp_server
@tool("refund_policy", "查询订单的退款政策", {"order_id": str})
async def refund_policy(args: dict) -> dict:
return {"content": [{"type": "text", "text": policy_for(args["order_id"])}]}
server = create_sdk_mcp_server("shop", tools=[refund_policy])函数在你进程里,参数是 dict,返回值是一个 MCP 风格的 content 列表。没有额外的 server 进程,也没有端口。
再说一条更隐蔽的:审批。
Claude Code 的审批是给人看的:终端弹出来,你按 y/n。SDK 的 can_use_tool 是给程序看的:退款金额、订单归属、租户 ID 传进来,你的代码返回 allow 或 deny,还能带上拒绝理由。这不是 UI 的差别,是“审批逻辑能不能接进你的业务系统”的差别。
审批大概长这样:
from claude_agent_sdk import PermissionResultAllow, PermissionResultDeny
async def approve(name, inp, ctx):
if name == "mcp__shop__create_refund" and inp.get("amount", 0) > 500:
return PermissionResultDeny(message="超过 500, 转人工")
return PermissionResultAllow()注意 mcp__shop__create_refund 这个前缀:SDK 会把 MCP 工具名拼成 mcp__<server>__<tool>。第一次写的时候很容易漏。
但 SDK 也不是万能的。下面这些事它做不了:
- 改循环。 你想自己实现 plan-execute-verify、自己决定什么时候压缩、自己调度工具——SDK 不给你这个口子。
- 改 Harness 内部。 工具怎么并发、压缩用什么算法、权限按什么顺序判,都在 CLI 里。你只能通过公开旋钮影响,不能替换。
- 替你写领域层。 你的工具、提示、项目知识、评分标准,SDK 不知道。
- 替你写产品层。 UI、登录、计费、数据存储、告警,SDK 不是应用框架。
- 保证跨版本行为不变。 CLI 升级,行为可能变。你得固定版本、做回归、留降级。
所以如果你要的是“改循环”,SDK 不是答案。用裸 Messages API,或者自己写一个 Harness——这也是第 3 章开始要做的事。
Q4:为什么我用了 SDK,还是不如 Claude Code?
这问题最扎心,也最值得展开。
还有个变量得先排除:你用的是同一个模型吗? 同一个端点?同样的 effort / thinking?如果你换了小模型、换了兼容端点、把 effort 调低了,那没什么好比的,先把这个变量固定住。
固定之后,差距通常在三层:
| 层 | 你可能的配置 | Claude Code 的配置 | 怎么查 |
|---|---|---|---|
| 模型层 | 小模型、兼容端点、低 effort | 同款模型 + 调过的参数 | 先固定模型和端点 |
| Harness 层 | 循环 + 几个工具,没权限/压缩/会话 | 工具、权限、上下文、压缩、会话、子 Agent、错误恢复 | 用第 16 章的控制矩阵对照 |
| 领域层 | 一句系统提示 + 通用工具 | 为编码任务打磨的提示、工具、项目知识、完成标准 | 用任务集评测,做 ablation |
我见过最典型的一种配错,长这样:
options = ClaudeAgentOptions(
tools=[],
setting_sources=[],
system_prompt="你是一个 coding agent",
max_turns=10,
)这段代码能跑,但它把 Claude Code 配成了 Hello World。
tools=[]:模型没有工具,读不了文件、改不了代码,只能聊天。setting_sources=[]:CLAUDE.md、项目设置不加载;你的项目约定和自定义技能看不到了(内置技能可能还在)。system_prompt只有一句:没有工作流,没有“什么叫做完”,没有“不许改测试”。- 没有权限、没有 Stop Hook、没有评测:它能改测试,也能提前宣布完成。
把这几条对成表,排障的时候好用:
| 你写的配置 | 你会看到的现象 | 先改什么 |
|---|---|---|
tools=[] | 只会聊天,不读文件 | 加 Read/Edit/Bash,或自己的工具 |
setting_sources=[] | 看不到 CLAUDE.md,不按项目约定 | 加载 project,或把约定写进提示 |
| 系统提示只有一句 | 提前停、乱改 | 写工作流 + 完成标准 |
| 没有权限 | 改测试、读 .env | tests/ 只读 + can_use_tool |
| 没有 Stop Hook | 没跑测试就说“完成” | Stop 检查测试是否跑过 |
| 没有评测 | 凭感觉调参 | 10 个任务 × 3 次,先量基线 |
我第一次这么配的时候,还觉得自己挺克制:工具越少越安全嘛。结果它连 README 都读不了,当然也不知道“不许改测试”这条规矩。后来我才明白,安全和能力不是靠“关掉一切”实现的。
然后你跑一个任务,发现它不如 Claude Code,得出结论:“SDK 不行。”
不是 SDK 不行,是你把 Harness 的配置全关了。
我一般建议先问自己十个问题。不用全答,能答出前三个,方向就清楚很多:
- 你和 Claude Code 用的是同一个模型、同一个端点吗?
cwd对吗?Agent 能读到项目文件吗?- 它有完成任务所需的工具吗——读、改、搜、跑、测?
- 工具描述说清了“什么时候用、返回什么”吗?
- 系统提示里有工作流和“完成标准”吗?
- 它看得到项目约定吗——
CLAUDE.md、README、目录结构? - 它会验证自己的工作吗?验证是强制的吗?
- 失败后它能恢复吗——工具错误、测试失败、上下文溢出、进程崩溃?
- 它停得对吗——太早宣布完成、死循环、预算耗尽?
- 你是在一组任务上量过,还是只看了一次运行?
这十个问题,基本就是这本书第二、三、四部分的内容。第 16 章会把它们收成一张控制矩阵。
如果只能记一句:先固定模型,再用同一组任务量,然后一次只改一个变量,再量。 别凭感觉调提示词,真的。
Q5:那我到底该用哪个?
我不想给你一个“看情况”的答案,那等于没答。先把选择画成树:
你是自己用,还是让程序用?
├─ 自己用 → Claude Code
└─ 让程序用
├─ 要改循环/自己编排? → 裸 API 或别的框架
└─ 不改循环
├─ 要接自己的工具/审批/多租户? → SDK
└─ 只是偶尔调一次? → Messages API下面这张表,按你的处境选:
| 你的处境 | 建议 |
|---|---|
| 自己在终端里写代码 | Claude Code |
| 要把 Agent 嵌进产品/服务 | Agent SDK |
| 要接自己的工具、数据、审批 | Agent SDK |
| 要多租户、计量、配额 | Agent SDK + 自己写隔离 |
| 要改循环、自己编排 | 裸 Messages API,或别的框架 |
| 只需要一次 LLM 调用 | Messages API,别引入进程 |
| 只是想做个小 demo,验证想法 | 先用 Claude Code 跑一遍 |
选型不是信仰,是成本问题。Claude Code 的成本是“你只能用它给的形态”;SDK 的成本是“你得自己配、自己测、自己运维”;裸 API 的成本是“循环、工具、权限、上下文全得自己写”。
如果你不确定,我有个很土但有效的办法:先用 Claude Code 跑一周。 哪一步让你最难受,那个“难受”通常就是你要自己造的理由。
是“我想把它接进客服系统”?那是 SDK。
是“我想让它按我们的审批流走”?那是 SDK + 权限回调。
是“我想让它先规划再执行,Claude Code 的计划模式不够用”?那可能得自己写循环。
是“我只是觉得每次都要开终端很烦”?那可能写个脚本包一下就行,不用造 Agent。
最后
这一章没写多少代码,因为它在回答“要不要造、造什么”。下一章开始,我们不再讨论选型,直接动手。
先写一个 20 行的 while True,让它把测试改掉;然后一行一行,把 Harness 补起来。
本章代码 这一章没有新增代码;第 1 章的示例仍然适用: https://github.com/kevinkelin/claude-agent-sdk-book/tree/master/code/lab/taskflow 下一章会新建
code/tiny/agent.py。