Aklman · Library

13 / 第十三章

组装 · 把每一格拼成一个能跑的 agent.

前面每章加一格,这一章把它们拼成一个你能真跑的 Python 工程 —— 离线一条命令,接上 key 就换真实 Claude。


前面每一章都在给那个研究助手加一格 —— 搜索、抓取、记忆、推理、沙箱、护栏、trace。这一章把它们拼成一个你能真跑的 Python 工程:离线一条命令就跑,接上 key 就换成真实的 Claude。读完你手里不再是十几段散落的片段,是一个核心部件齐全、能跑能改的基础版 agent。 工程在 content/books/build-agent/agent/,一个叫 research_agent 的小包。下面拆三件事:心脏为什么还是第二章那个循环、怎么跑、以及它为什么是「完备基础版」而不是生产级。

I

心脏没变,还是那个循环.

加了这么多格,中心那个 while 一行没变。这正是全书的论点:agent 就是 LLM 在循环里用工具、读反馈、定下一步;其余都是挂在这个循环上的格子。 Anthropic 把基本积木叫「augmented LLM」—— 检索、工具、记忆三样增强;agent 就是它在循环里跑。1注 1Anthropic · 「Building Effective Agents」(2024-12-19)—— 基本积木是「augmented LLM」(检索 / 工具 / 记忆);agent 就是「LLM 在循环里用工具、读环境反馈、决定下一步」。组装这一章正是把这句话落成代码。OpenAI 的说法一样直白:单 agent 是一个 run loop,跑到触发退出条件为止,「这个 while 循环是 agent 运转的核心」。2注 2OpenAI · 「A Practical Guide to Building Agents」(2025-04)—— 单 agent 就是一个 run loop,跑到触发退出条件(最终输出 / 无工具调用 / 出错 / 步数上限);原话「这个 while 循环是 agent 运转的核心」。我们的 loop.py 就是这句话的代码版,把后面各章的格子接上去:
for step in range(cfg.max_steps):                 # 停止条件(ch2)
    budget.check(tracer, cfg)                      # 预算闸(ch11):超了就熔断
    messages = compact(messages, threshold)        # 上下文减法(ch4)
    reply = model.generate(SYSTEM, tools, messages)   # 真 Claude 或离线 stub(ch2)
    messages.append({"role": "assistant", "content": reply.content})
    if reply.stop_reason != "tool_use":            # 模型给出最终答案 → 结束
        return final_text(reply)
    results = run_calls(reply.tool_uses)           # 工具+并行(ch3)·护栏(ch9)·沙箱(ch7)
    messages.append({"role": "user", "content": results})  # 结果回灌;错误即纠错信号(ch5)

loop.py 的核心:还是第二章那个循环,各格挂在上面

模型层是那把让它「能跑」的钥匙:model.py 把 provider 藏在一个接口后面,ANTHROPIC_API_KEY 就接真实的 Claude,没有就退回一个离线 stub 模型4注 4anthropic-sdk-python —— 官方 Python SDK,真实后端用 messages.create;本章的 model.py 在「有 ANTHROPIC_API_KEY 时接 Claude、没有时退回离线 stub」之间切换。引用发布版本而非 main。stub 不联网、不要 key,按一条 search → fetch → run_python → 报告 的固定轨迹走 —— 它存在的意义,是让你在零依赖下把整台机器跑起来、看清每一格,也让这套 harness 可回归测试。

II

跑起来:离线一条命令,真实多两步.

先证明它真能跑 —— 不要 key、不联网、不装任何包,离线 stub 模式一条命令就把整条链路走通。
cd content/books/build-agent/agent
python -m research_agent --offline "比较 RAG 与长上下文,各自适合什么场景"
cd content/books/build-agent/agent
pip install anthropic
export ANTHROPIC_API_KEY=sk-...     # PowerShell: $env:ANTHROPIC_API_KEY="sk-..."
python -m research_agent "比较 RAG 与长上下文,各自适合什么场景"

两种跑法:离线 stub(零依赖)与真实 Claude

离线那条真跑出来是这样(进度走 stderr,每一步都看得见)—— 注意 run_python 是在沙箱里真执行的,不是装样子:
[model: offline-stub]
▶ 开始:比较 RAG 与长上下文,各自适合什么场景
  → web_search({"query": "比较 RAG 与长上下文各自适合什么场景"})
  → fetch_page({"url": "https://zhuanlan.zhihu.com/p/8280024582"})
  → run_python({"code": "vals = [3, 7, 2, 8, 5]\nprint('sum=', sum(vals))…"})
■ 完成:4 步 · 3 次工具 · ~987 tok · ≈$0.0066 · cost-of-pass ≈$0.0066

离线 stub 模式的真实输出(节选)

工程的好处是每一格都是一个文件,名字就对着书里的章 —— 你能一眼看出哪段代码解决的是哪一章的问题:
loop.py
循环(ch2)—— 心脏,其余都挂在它上面。
model.py
模型层(ch2)—— 真实 Claude / 离线 stub,一个接口。
tools.py
工具(ch3)—— schema、错误回传、并行。
memory.py
记忆(ch4)—— 上下文减法 + 笔记 + tiny RAG。
sandbox.py
代码执行(ch7)—— 一次性工作区、超时、最小环境。
guardrails.py
安全(ch9)—— 最小授权 + 不可信标记 + 输出校验 + 人闸。
trace.py
可观测性与成本(ch10/11)—— trace + cost-of-pass + 预算闸。
一张图看清它们怎么咬合 —— 中间是循环,各格挂在上面,trace 和预算闸贯穿每一步:
  question


  loop (ch2) ──→ model.generate ──→ 只有文本? ──是──→ 报告 + cost-of-pass
     ▲                  │
     │               tool_use
     │                  ▼
  上下文减法(ch4)   护栏(ch9) → 工具·并行(ch3) → 沙箱(ch7)
     │                  │
     └──── 结果回灌 ◀────┘
 
  贯穿每一步:trace + 预算闸(ch10/11) · 事件级进度(streaming)

组装好的 agent:循环居中,各格在侧

III

它是完备基础版,不是生产级.

核心部件齐全、能跑能改 —— 但「能跑」不等于「能上生产」。把它的边界说清楚,每一条都在对应章里展开过,也都是你下一步该补的地方。
沙箱是 subprocess,不是真隔离
超时 + 一次性工作区 + 最小环境够挡误操作,但不隔离文件系统 / 网络 / 内核。跑任意不可信代码要上容器 / gVisor / microVM(ch7)。
「RAG」是关键词重叠
真实提升在 contextual chunk + 重排的检索管线里,不在这个最小骨架(ch4)。
循环是同步的
进度用回调流出;真要做流式 UI + 可中断,要上 async(streaming 章)。
原则化自查默认关
那道护栏的裁判得是个真实模型,离线 demo 跑不了(ch9 · Constitutional AI)。
怎么知道哪一格该补、补了值不值?看 trace 摘要里那个 cost-of-pass(成本 ÷ 成功率)。Efficient Agents 的发现是很多模块边际递减,复杂度该按任务难度配。3注 3Wang et al. · 「Efficient Agents」(2025,arXiv:2508.02694)—— 用 cost-of-pass = 成本 ÷ 成功率 度量每一格值不值,复杂度按任务难度配。trace 摘要里那个数,就是给「加东西前先量」一个抓手。所以加一格之前先量一下:成功率涨了多少、成本涨了多少 —— 用数字判断,别凭感觉。这正是全书第一章那句话在工程上的兑现:先做最简的,只在被证明值得时才加复杂度。 所以你现在手里有一个能跑的 agent。下一章把这套同样的机器对准最难、也最成熟的用例 —— 编码;再下一章回答:手写过一遍之后,何时该用框架、何时不必。 动手 · 把这个 agent 接到你自己的一个真问题上:
01

先离线跑一遍,看清每一步

agent/python -m research_agent --offline "你的问题",对着 stderr 看它搜、抓、算、报。
02

设上 key,换真实模型

pip install anthropic、设 ANTHROPIC_API_KEY,跑你自己真要查的问题,看真实 Claude 怎么自己决定调哪个工具。
03

改一格,用 cost-of-pass 判值不值

tools.py 加一个你自己的工具,或在 config.py 收紧白名单 / 预算。改前改后各跑一次,对比 trace 摘要里的 cost-of-pass —— 用数字决定这一格留不留。

把每一格拼起来, 心脏还是第二章那个循环。.

Wire every slot together, and the heart is still chapter 2's loop..

Aklman Library

讨论

讨论.

评论区初始化中…