Lesson 01 · 基础

没有循环,就没有 agent

Claude Code 的整个秘密可以写成一行:while stop_reason == "tool_use"

⏱ 约 10 分钟 · 📝 3 个可交互组件 · 🧑‍💻 基于 shareAI-lab · s01_agent_loop.py

一个 agent 到底在做什么?

当你在终端里跑 Claude Code、让它「把所有 TODO 注释整理成一份清单」,你看到的是:它自己决定先 grep、再 cat 几个文件、然后输出 Markdown。模型自己不会执行代码——它只会请求执行。真正让它看起来「有手有脚」的,是它外面那一圈 30 行左右的胶水代码。

这一课就是要把那圈胶水拆开看清楚。它叫 agent loop(智能体循环),结构就是:

while response.stop_reason == "tool_use":
    response = LLM(messages, tools)      # 1. ask the model
    execute_tools(response.tool_calls)   # 2. run what it asked for
    messages.append(tool_results)        # 3. feed results back

就这样。没了。production 的 Claude Code 在这上面叠了权限、hook、子 agent、worktree 隔离、记忆压缩——但内核还是这四行。

关键直觉:模型每次回复要么「我想调工具」,要么「我说完了」。只要还在前一种状态,循环就继续;一旦进入后一种状态,循环就退出。判据是响应里的 stop_reason 字段。

看 messages[] 一步步长出来

下面这个循环模拟的任务是:「当前目录有哪些文件?然后读一下 package.json」。点 Step,每按一次前进一个循环内的动作。左边是给人看的对话气泡,右边是真正塞给模型的 messages[] 数组——注意看它如何增长。

工具结果必须重新进入消息历史

新手最容易踩的坑,是把「执行工具」当成一个副作用——执行了就完事了。但模型下一轮推理只能看到 messages[]不在里面的东西它就不知道发生过。少 append 一步,整个循环就错了。

错法不是都一样的。常见两种:

  • 忘记 append:模型下一轮看不到结果,它会再问一次同一个工具——于是你得到一个无限循环。
  • append 了但丢掉 tool_use_id:Anthropic API 会直接报错 tool_result must have tool_use_id,循环崩在 API 调用那一步。

同一个任务:「统计项目里有多少个 Python 文件」。两个版本并排跑,看结局差别。

读懂 stop_reason

模型每次返回都会带一个 stop_reason。决定要不要继续循环,就看这个字段:

  • tool_use — 模型想调工具,继续循环
  • end_turn — 模型觉得说完了,退出循环
  • max_tokens — 生成到了 token 上限被截断,退出循环(一般当异常处理,提示用户输出不完整)。
  • stop_sequence — 碰到自定义停止串,退出循环

写错成「只要没 tool_use 就继续」,或者忽略 max_tokens 直接当作正常 end,都是常见 bug。

动手跑一下

本地克隆 shareAI-lab/learn-claude-code

git clone https://github.com/shareAI-lab/learn-claude-code
cd learn-claude-code
pip install -r requirements.txt
cp .env.example .env  # fill in ANTHROPIC_API_KEY
python agents/s01_agent_loop.py

然后让它做点真事:帮我数一下这个仓库里有多少 .py 文件。你会看到它发一条 ls -R、看到输出、再发一条 find . -name "*.py" | wc -l、然后告诉你答案。整个过程就是这个 loop 在循环。

Interactive

Widget 1 · Loop Stepper · messages[] 生长

看懂:工具结果是以特殊格式(tool_result 块,带 tool_use_id)被塞回 messages 的;这是模型下一轮推理时唯一知道「工具执行完了、结果是 X」的途径。

对话视图未开始
点 Step 开始 →
messages[] JSONlength: 0
[]
stop_reason:
Interactive

Widget 2 · Break the Loop · 正确 vs 忘记 append

看懂:只要漏一行 messages.append(tool_results),整个 agent 就不再是 agent——它变成一台每轮忘记前一轮的点读机。

while True:
    resp = LLM(msgs, tools)
    msgs.append({"role":"assistant",
                 "content": resp.content})
    if resp.stop_reason != "tool_use":
        return
    results = execute(resp.tool_uses)
    msgs.append({"role":"user",
                 "content": results})
模拟运行
while True:
    resp = LLM(msgs, tools)
    msgs.append({"role":"assistant",
                 "content": resp.content})
    if resp.stop_reason != "tool_use":
        return
    results = execute(resp.tool_uses)
    # BUG: forgot to append results
    # msgs.append(...)
模拟运行
Interactive

Widget 3 · Spot the stop_reason · 判断 4 个真实响应

每个都是真实可能的模型响应片段。点「继续」还是「退出」,即时看对错。

答对 0 / 4