1. 从一次“卡住”的 Agent 说起:为什么你的循环跑不起来
很多人第一次写 Agent,代码看起来没问题:调 LLM、解析 tool_use、执行工具、把结果塞回消息历史,然后while True继续。但真跑起来,要么第一轮就退出,要么无限循环烧 token,要么工具结果回灌后模型像失忆一样重复调同一个工具。问题几乎都不在“循环”本身,而在循环的状态管理和退出条件。
Agent Loop 的本质,用一句话说清楚:LLM 在一个循环里,根据环境反馈决定下一步调什么工具,直到它自己判断任务完成。它和普通问答应用最大的区别是——控制权在模型手里,而不是在你预先写死的流程里。普通应用是“问一句答一句”,Agent 是“给个目标,它自己决定读哪个文件、跑哪条命令、什么时候停”。
这篇文章面向三类人:一是刚接触 Agent、想搞懂循环到底怎么转的开发者;二是用 Claude Code 类工具但不知道背后发生了什么的用户;三是想自己手搓一个最小 Agent、却被消息历史和 stop_reason 绕晕的人。我会用一个“找 TODO 并整理清单”的真实任务,把每一轮循环拆开给你看,再给一份可复制的最小配置和伪代码,最后教你怎么验证“自主迭代”到底有没有生效。
先说结论:Agent Loop 的核心伪代码不超过 10 行,但让它稳定跑起来,你需要盯住三件事——消息历史是否完整追加、stop_reason 是否被正确判断、最大轮次是否设了上限。这三件事任何一件出问题,Agent 就会从“自主执行”退化成“复读机”或者“死循环”。下面逐层拆。
2. TaoToken 前置准备:把模型调用这层先打通
在写循环之前,得先有一个能稳定调用的模型入口。Agent Loop 每一轮都要调一次 LLM,调用层不稳定,循环就没法验证。我用的是 TaoToken 的 API 入口,它兼容 Anthropic 风格的消息格式,tool_use 和 tool_result 的结构能直接对上,省去自己拼协议的成本。
你需要准备三样东西,我把它叫做“三件套”,缺一不可:
- Base URL:
https://taotoken.net/api - API Key:在控制台的 API Keys 页面生成,形如
sk-... - Model ID:比如
claude-sonnet-4-5这类具体模型标识,不要写模糊的别名
获取路径很直接:先访问官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 注册登录,然后进控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 创建 API Key,具体在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 这个页面。文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,遇到字段不确定时对着看。
这里有个容易踩的坑:很多人把 Base URL 写成带/v1或者带具体路径的形式,结果请求 404。记住,Base URL 就是https://taotoken.net/api,具体路径由 SDK 或你的请求代码拼接。另外 API Key 不要硬编码进提交到 Git 的文件里,用环境变量。
如果你用的是 Claude Code 这类工具,配置方式通常是改settings.json或者环境变量。下面这份是最小可用的环境变量配置,你可以直接复制:
export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_API_KEY="sk-你的key" export ANTHROPIC_MODEL="claude-sonnet-4-5"如果你更习惯用配置文件,Claude Code 的settings.json大致长这样,路径一般在~/.claude/settings.json:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的key", "ANTHROPIC_MODEL": "claude-sonnet-4-5" } }注意ANTHROPIC_BASE_URL后面不要加斜杠,也不要加/v1。我试过加斜杠,结果请求路径变成双斜杠,服务端返回 404,排查了半小时才发现是这个小问题。配置完之后,先用一个最简单的请求验证连通性,别急着写循环。
3. 可复制的最小 Agent Loop 配置与伪代码
现在进入核心部分。我把 Agent Loop 拆成“配置层”和“循环层”两块。配置层负责告诉程序去哪调模型、用什么工具;循环层就是那个 while。
先看工具定义。Agent 能调什么工具,取决于你怎么声明。以“搜索 TODO”为例,工具 schema 用 JSON 描述:
{ "name": "search", "description": "在指定目录下按正则搜索文件内容,返回匹配的行", "input_schema": { "type": "object", "properties": { "pattern": { "type": "string", "description": "搜索的正则表达式" }, "path": { "type": "string", "description": "搜索的目录路径" } }, "required": ["pattern", "path"] } }这个 schema 会被塞进每一轮请求的tools字段。模型看到它,才知道自己有个叫search的工具可以用。
然后是循环本体。下面这段伪代码是整个 Agent 的心脏,你可以直接照着翻译成 Python:
messages = [{"role": "user", "content": user_task}] max_turns = 20 for turn in range(max_turns): response = call_llm( model=MODEL_ID, messages=messages, tools=TOOLS ) # 把模型这一轮的回复追加进历史 messages.append({"role": "assistant", "content": response.content}) if response.stop_reason == "tool_use": tool_results = [] for block in response.content: if block.type == "tool_use": result = execute_tool(block.name, block.input) tool_results.append({ "type": "tool_result", "tool_use_id": block.id, "content": result }) # 工具结果作为 user 消息回灌 messages.append({"role": "user", "content": tool_results}) continue else: # stop_reason 是 end_turn,任务结束 print(response.content) break else: print("达到最大轮次,强制停止")这段代码里有三个关键点,每一个都对应一类常见 bug。
第一,messages.append必须把 assistant 的完整 content 追加进去,包括 tool_use 块。很多人只追加文本,结果下一轮模型不知道自己调过工具,就会重复调。
第二,tool_result 要作为user 角色的消息回灌,并且带上tool_use_id。这个 id 是模型生成的,用来把结果和请求对应起来。id 对不上,模型会报错或者忽略结果。
第三,stop_reason是唯一的退出信号。tool_use表示继续,end_turn表示结束。不要用“content 里有没有文本”来判断,因为模型可能同时返回文本和 tool_use。
如果你用 Claude Code 的 coding-plan 模式,配置会更省事,但底层还是这套循环。想长期跑编码类 Agent,可以看 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 这个入口,它把循环和工具链都封装好了。
4. 验证自主迭代是否生效:三个可观测信号
写完循环不代表它真的在“自主迭代”。你需要能观测到它每一轮在干什么。我总结了三个信号,只要这三个都正常,说明循环是活的。
信号一:轮次递增且工具调用不重复。在循环里打印每一轮的turn和block.name。如果连续两轮调同一个工具、同样的参数,说明消息历史没回灌成功,模型失忆了。正常的 Agent 应该像人一样,搜完 src 再搜 tests,而不是反复搜 src。
信号二:stop_reason 的变化轨迹。一个健康的多轮任务,stop_reason 序列应该是tool_use → tool_use → ... → end_turn。如果第一轮就是end_turn,说明模型没打算用工具,可能是工具描述写得太模糊,或者任务本身不需要工具。如果一直是tool_use直到撞上 max_turns,说明模型陷入了循环,需要检查工具返回的结果是不是让它“看不懂”。
信号三:最终输出引用了中间结果。任务结束时,模型的回答里应该包含它搜到的具体内容,比如“src/auth.js 第 42 行”。如果它只是泛泛而谈“我找到了几个 TODO”,说明工具结果没被真正利用。
你可以用一个真实任务来验证:让 Agent 在项目里找所有 TODO 并整理成清单。观察它是否先搜./src,再判断要不要搜./tests,最后汇总。这个任务我实测下来,健康的循环会跑 2 到 3 轮,第一轮搜 src,第二轮搜 tests,第三轮输出。如果只跑一轮就结束,或者跑了十几轮还在搜,就是循环有问题。
想直接看模型对话效果、不写代码验证,可以用 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite 这个入口,手动发任务,观察它调工具的节奏。
5. 常见报错排查:401、local proxy failed、reading choices
循环跑不起来,报错信息往往很迷惑。我把几个高频错误和对应原因列出来,你对着查。
401 Unauthorized。九成是 API Key 问题。检查三件事:Key 有没有复制完整(前后有没有空格)、环境变量有没有生效(echo $ANTHROPIC_API_KEY)、Base URL 和 Key 是不是配套的。如果 Key 是从别的平台拿的,配到 TaoToken 的 Base URL 上,必然 401。
local proxy failed / connection refused。这个报错通常出现在你本地起了代理但没起来,或者 Base URL 指向了本地地址。检查ANTHROPIC_BASE_URL是不是被其他配置覆盖了。Claude Code 会读多个层级的配置,项目级的settings.json可能覆盖全局的。用env | grep ANTHROPIC确认实际生效的值。
reading 'choices' of undefined。这个报错说明你的代码在按 OpenAI 的响应格式解析,但实际返回的是 Anthropic 风格。Anthropic 的响应里没有choices字段,内容在content数组里,停止信号在stop_reason。如果你混用了两套 SDK,就会出这个错。统一用一套,别一边用 OpenAI SDK 一边发 Anthropic 格式的请求。
OAuth 相关报错。如果你用的是 Claude Code 的 OAuth 登录模式,又同时配了 API Key,两者会冲突。选一种:要么纯 API Key,要么纯 OAuth。混用会导致鉴权头重复或者格式错误。
tool_use_id 不匹配。报错信息类似“tool_result 找不到对应的 tool_use”。原因是你在回灌结果时,tool_use_id写错了,或者把多个工具的结果合并成一条消息但只带了一个 id。每个 tool_result 必须单独对应一个 tool_use_id。
排查顺序建议:先确认 401(鉴权),再确认 Base URL(连通性),最后确认响应格式(解析)。大部分问题在前两步就能定位。
6. 把循环用起来:从单次问答到自主执行的最后一公里
理解了循环机制,接下来就是把它用到实际场景。Agent Loop 的价值不在于“能循环”,而在于“循环里模型能根据反馈调整策略”。前面那个修 login bug 的例子,9 轮循环里经历了“读代码 → 改代码 → 跑测试 → 测试失败 → 读测试 → 改测试 → 再跑测试 → 通过”,这种自我纠正是固定 workflow 做不到的。
如果你想让 Agent 长期跑编码任务,建议从 coding-plan 入手,它把循环、工具链、上下文管理都封装好了,你只需要给任务。入口在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。如果你要自己集成到现有系统,就照第 3 节的伪代码手搓,重点盯住消息历史和 stop_reason。
最后给一个实用技巧:在循环里加一个“轮次日志”,把每一轮的turn、stop_reason、调用的工具名和参数、工具返回的前 100 个字符打出来。这个日志在调试时比任何断点都好用,你能一眼看出模型在哪一轮“想歪了”。等 Agent 稳定跑起来,再把这个日志降级成 debug 级别。
Agent Loop 说到底就是一个 while 循环,但让它真正“自己跑起来”的,是循环里那些不起眼的状态管理细节。把消息历史喂全、把 stop_reason 判对、把最大轮次设好,你的 Agent 就能从聊天机器人变成能干活的执行体。