Lab 00 · 最简 Agent Loop(对应 Part 1 心智模型)
墨叔:老哥,这是实战的”第零课”。别小看它——Claude Code 再花哨,内核也就是这个循环套了几层壳。你先把这一圈跑通、看明白,后面所有花样才有落脚点。
这个 lab 在练什么
把”Agent = 大厨 + 递纸条的人”这句话落成代码:
- 大厨 =
llm(模型,负责”想”) - 递纸条的人 =
run_loop(你写的循环,负责”把模型的意图落地、再把结果递回去”) - 厨房 =
get_weather(工具,真正干活的代码)
跑起来你会看到三行关键日志:[think] 模型要求调用工具 → [act] 工具结果 → [think] 模型说。这就是一次完整的 observe → think → act。
怎么跑
# 默认 mock:不需要任何 API key,离线即可看完整流程
python main.py
# 接真实模型(需先有 OPENAI_API_KEY)
export OPENAI_API_KEY=sk-xxx
AGENT_MODE=real python main.py环境要求:Python 3.10+。本 lab 除标准库外无任何第三方依赖。
你该看懂的几点
- 模型不直接执行代码。 它只输出”我想调 get_weather,参数是 {city: 北京}“。真正跑函数的是
run_loop里的tool.run()。这一层隔离,是后面所有安全机制的物理基础。 - 循环靠”工具调用”驱动。 模型一旦要求调工具,循环就
act;模型不再要求调工具(返回纯文本),循环就break。终止条件就这么简单。 - mock 不是”假货”,是”确定性替身”。
mock_responder按步数返回预设回复,让你在没有 key 时也能完整观察循环结构。真实模式下,同一个run_loop换上RealLLM即可,业务代码一行不用改——这就是”面向接口编程”的甜头。
思考题
【思考题】
- 如果把
max_steps设成 1,会发生什么?为什么? - mock 剧本里,第 2 步的 responder 其实”看不到”工具结果也能返回正确答案。真实场景里,模型靠什么知道工具返回了什么?(提示:看
common/runner.py里 tool 结果被加进了哪条消息) - 这个循环目前是”同步阻塞”的——模型想一步、工具跑一步。你觉得 Claude Code 的”一边想一边显示”是怎么在这个骨架之上加出来的?(呼应 Part 5 流式)
【参考答案】
max_steps=1时,循环只走一步就结束了。如果这唯一一步模型还在要求调工具(mock 的 step==1 正是这样),循环会因没拿到”最终文本回答”而在循环外补一句”(达到最大步数仍未给出最终回答)“。所以 max_steps 是安全阀,防止模型陷入”调工具→结果→又调工具”的死循环把上下文撑爆。- 真实模型靠的是
run_loop把工具结果作为一条role="tool"的消息追加进messages。下一轮llm.chat(messages, tools)时,模型能看到”我刚才调了 get_weather,返回了’晴,22℃’“,才能据此总结。mock 是我们人为跳过了这一步(直接写死答案),但真实链路里这条 tool 消息是模型感知世界的唯一窗口。 - “一边想一边显示”本质是在
run_loop的每一步on_step回调里做增量渲染:模型每吐一个 token 就推到前端,工具每出一段结果就追加显示。骨架不变,只是在”think”阶段从”等整句说完再显示”改成”流式吐字”,在”act”阶段把工具输出实时打印。Part 5 会展开。
下一步
跑通后,去 lab01 看工具怎么从”一个 stub”长成”有 schema、有错误处理、有安全边界”的真正工具集。