第 2 章:跑起你的第一个 agent 回路
用 Claude Agent SDK 的 query() 跑起最小的 agent 回路,看清它替你跑的那台引擎:感知、调用工具、观察、决策。
小满 · 取物长廊
到现在为止,都是你把文件喂给小满。今天,它要自己去取。
草稿章节。跑通格式用的第一版,正式索引前会再打磨。
本章目标
用 Claude Agent SDK 跑起你的第一个真正的 agent 回路。还是贯穿全书的那个 PR 审查 agent,你会写下审查契约、只给它一个 Read 工具、用 query() 把它跑起来,然后看它自己去读 src/payments/refund.py、再交回一份审查清单。读完你能看清每个 agent 框架内部跑的是什么,也就是微软「Tool Use Design Pattern」那一课讲的四步循环:模型感知、决定用哪个工具、工具运行、模型观察结果,然后循环。
第 1 章里,你写的审查契约默认 diff 已经粘好了。这一章变了一点:agent 自己去取上下文。不再是你粘文件,而是它请求去读。就这一个改变(模型主动请求工具,而不是由你把一切预先喂齐),把一个 prompt 变成了一个 agent。而 query() 替你把这个循环跑完,你只需把目标、工具和约束交给它。
前置准备
- 完成第 1 章:审查契约、项目脚手架和可用的 API key(
ANTHROPIC_API_KEY)。 - 装好 Claude Agent SDK:Python 用
pip install claude-agent-sdk(需 Python 3.10+,CLI 已随包自带),TypeScript 用npm install @anthropic-ai/claude-agent-sdk(需 Node 18+)。 - 一个可以安全折腾的仓库,里面有那个要审查的文件
src/payments/refund.py。
动手做
1. 把审查契约写成 system prompt,并只给一个工具
用第 1 章那份审查契约当系统提示词。关键的一步是 allowed_tools:它把会被自动放行的工具收成一个 Read,于是这个回路只能读文件,不能写,也不能跑命令。工具越少,回路就越好懂、越安全。
import anyio
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, TextBlock, ToolUseBlock
CONTRACT = """你是一个 PR 审查者。要评判任何文件,必须先用 Read 读它的内容。
读够了就输出审查清单:severity、file:line、一句话风险。绝不臆造你没读过的代码。"""
options = ClaudeAgentOptions(
system_prompt=CONTRACT,
allowed_tools=["Read"], # 只自动放行读文件这一个工具
max_turns=5, # 给回路封顶
cwd="./repo", # 把它锁在仓库目录里
)
import { query } from "@anthropic-ai/claude-agent-sdk";
const CONTRACT = `你是一个 PR 审查者。要评判任何文件,必须先用 Read 读它的内容。
读够了就输出审查清单:severity、file:line、一句话风险。绝不臆造你没读过的代码。`;
const options = {
systemPrompt: CONTRACT,
allowedTools: ["Read"], // 只自动放行读文件这一个工具
maxTurns: 5, // 给回路封顶
cwd: "./repo", // 把它锁在仓库目录里
};
2. 用 query() 跑起来,给它一个目标
query() 接受一个目标和这套选项,返回一串消息。目标就是触发指令:它点名审查什么,但故意不带文件正文。query() 替你把整个回路跑完,你不用自己写「发请求、判断、执行工具、回灌、再发」那一圈。
async def main():
async for message in query(
prompt="审查 src/payments/refund.py 的 bug 和风险。",
options=options,
):
handle(message) # 下一步定义
anyio.run(main)
const q = query({
prompt: "审查 src/payments/refund.py 的 bug 和风险。",
options,
});
for await (const message of q) {
handle(message); // 下一步定义
}
3. 遍历消息流,看见这四步
回路跑的时候,query() 一条条吐出消息。你要做的只是读它们。一条 AssistantMessage 里可能有两种块:一个 ToolUseBlock(模型请求调用工具,比如 Read),或一个 TextBlock(模型的文字答复)。这一处分支就是 agent 的核心,其余全是管道,而管道由 SDK 替你接好了。
def handle(message):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, ToolUseBlock):
print(f"[调用工具] {block.name} {block.input}") # 感知 -> 决定用 Read
elif isinstance(block, TextBlock):
print(block.text) # 观察结果后的答复
function handle(message) {
if (message.type === "assistant") {
for (const block of message.message.content) {
if (block.type === "tool_use") {
console.log(`[调用工具] ${block.name}`, block.input); // 感知 -> 决定用 Read
} else if (block.type === "text") {
console.log(block.text); // 观察结果后的答复
}
}
}
}
4. 给回路设上限,并把它关在仓库里
两道护栏不是可选项。max_turns 给回路设上限:一个迷糊的模型会无限请求读取,到上限就停。allowed_tools 只放行 Read,再加 cwd 把它锁在仓库目录里,模型就碰不到磁盘上别的东西。给它的能力越窄,你越敢用它,这一点会一直延续到第 11 章的护栏。
习得「自己伸手」小满不用再等你把 diff 递到手上,它能自己调用 Read 工具、读进文件里取上下文了。
一条真实 trace
在退款文件上跑这个回路,消息流长这样,四步、一次工具调用:
[调用工具] Read {'file_path': 'src/payments/refund.py'} <- 感知 + 决定
(SDK 执行 Read,把文件内容回灌给模型) <- 工具运行 + 观察
- [ ] (high) refund.py:13 : `<` 拒绝了金额恰好等于总额的退款;
全额退款静默失败。 <- 没有再调工具,回路结束
如何验证
- 跑一次,确认它恰好调用一次
Read,然后用第 1 章的清单形状给出答复。 - 把每条消息打印出来,你应当依次看到:工具请求(
Read)、然后是最终的文字审查。 - 把
allowed_tools改成[](一个工具都不给),再跑。模型读不了文件,要么如实说「我没法读」,要么干净地停下,而不是臆造代码,这正好印证了契约里「绝不臆造你没读过的代码」那句。 - 把
max_turns设成1,给它一个含糊目标(「审查所有东西」)。看它在封顶处停下,这就证明了护栏为何必要。
习得「读懂回路」你能把每条消息打印出来,看清它感知、调用、观察、决策这四步,从此任何 agent 出错你都问得出「那一步消息流里有什么」。
原理
query() 替你跑的,其实就是一个很简单的调度器:把工具列给模型、执行模型挑中的那个、把结果作为新上下文回灌,循环到模型不再调工具为止。运行时里没有藏着什么智能,唯一的「思考」发生在模型那一端。各种框架(包括这个 SDK)会加上重试、并行工具调用、流式、会话存储,但它们包的都是同一个四步循环。懂了这个,你就能用一个问题调试任何 agent:它出错那一步时,消息流里到底有什么?
小结
你现在跑通了每个 agent 都在跑的四步:感知、调用工具、观察、决策。你没有自己手写回路,因为 SDK 替你跑了,但你看清了它跑的是什么。审查 agent 现在能自己取上下文,不用别人喂一段 diff 给它。下一章讲上下文工程:当那串消息越来越长,怎么决定里面放什么(哪些文件、各放多少、丢掉什么),让回路在长审查里不犯糊涂。
常见坑
- 没有迭代上限。 不设
max_turns,迷糊的模型会无限循环。务必设一个。 - 工具放得太宽。 默认情况下 agent 能用整套 Claude Code 工具(含 Write、Bash)。一个只读审查任务,就把
allowed_tools收成["Read"],别让它能改你的代码。 - 不限定工作目录。 不设
cwd,模型可能读到仓库以外的文件。把它锁在项目目录里。 - 只读最终文字,忽略工具块。 调试时你要的恰恰是中间的
ToolUseBlock,它告诉你模型到底请求了什么、在哪一步。
一扇一直锁着的门,咔哒松开了。小满第一次自己走进存放文件的地方取东西,收工还怯生生补一句:「……还有两个文件我没敢看,要不要?」你没教过它问这个。取物长廊,亮了。
刚点亮 取物长廊 · 地图已点亮 3 / 16
来源
- Claude Agent SDK 文档(Python) · official