从零实现 OpenClaw (02):最小 Agent Loop —— 用 TaoToken 统一 Key 打通 ReAct 节拍
1. 为什么你的 Agent 只会“复读”而 OpenClaw 能自己找答案很多人第一次写 Agent写出来的是个“复读机”你问它北京天气它一本正经地回你“北京今天晴24 度”可你根本没给它接过任何天气接口。问题不在模型笨而在于你只给了它一张嘴没给它一双手更没给它一个“想—做—看—再想”的节拍器。OpenClaw 想做的就是给 AI 装上这个节拍器。所谓 Agent Loop智能体环路说白了就是一个 while 循环模型先输出一段“思考”再决定调用哪个工具程序真的去执行这个工具把真实结果塞回上下文模型看到结果后继续思考下一步直到它说“我得出最终答案了”。这个循环就是 ReActReason Act范式的工程落地。这篇文章面向的是已经会写 Python、但被各种 Agent 框架绕晕的人。我们不装 LangChain不装 AutoGPT就用一个文件、两百行代码把最小可用的 ReAct 循环跑通。LLM 通道这边我用 TaoToken 统一 Key 来接入好处是模型名一改就能在 GPT、Claude、DeepSeek 之间切换Loop 逻辑一行都不用动。跑完之后你会得到一个能真实调用工具、能根据报错自我修正的最小 Agent而不是一个只会背答案的聊天框。先说清楚它适合谁适合想搞明白 Agent 底层到底怎么转的人适合被框架黑盒坑过、想自己掌控每一行控制流的人也适合后面想往具身智能、硬件控制方向延伸的人。因为一旦你理解了“思考—行动—观察”这个节拍把它接到机械臂、传感器还是数据库上只是换一个工具函数的事。2. TaoToken 统一 Key 接入把模型通道先铺平在写 Loop 之前得先把“大脑”接上。OpenClaw 的设计原则是最小依赖所以我不建议你直接绑死某一家 SDK。这里用 TaoToken 作为统一入口它的 API 地址是https://taotoken.net/api兼容 OpenAI 的调用格式意味着你可以继续用熟悉的openai库或者litellm只改 base_url 和 key。第一步去控制台拿 Key。打开https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentopenclaw_agent_loop在 API Keys 页面创建一个新 Key复制出来。这个 Key 就是你后面所有模型调用的通行证别写死在代码里用环境变量。第二步配置环境变量。Linux 或 macOS 下export TAOTOKEN_API_KEYsk-你的key export TAOTOKEN_BASE_URLhttps://taotoken.net/apiWindows PowerShell$env:TAOTOKEN_API_KEYsk-你的key $env:TAOTOKEN_BASE_URLhttps://taotoken.net/api第三步装依赖。我们只需要两个包openai负责发请求pydantic负责数据结构校验。Agent 这种不确定性极高的系统里强类型校验是防止程序崩掉的最后一道墙。pip install openai pydantic这里解释一下为什么用统一 Key 而不是每家单独接。Agent Loop 里模型会被调用很多次一旦你想从 GPT 换到 Claude 做对比如果每家 SDK 都写一遍Loop 逻辑就得跟着改。用 TaoToken 之后模型名只是一个字符串参数换模型等于换一个字符串控制流完全不动。这对调试 ReAct 特别重要因为不同模型对格式的服从度不一样你需要快速切换来找到最稳的那个。配置好之后可以先做一次最小连通性测试确认 Key 和通道没问题import os from openai import OpenAI client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlos.environ[TAOTOKEN_BASE_URL], ) resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: 只回复两个字通了}], ) print(resp.choices[0].message.content)如果打印出“通了”说明通道已经铺平可以进入 Loop 的编写。如果这里就报错先别往下走直接跳到第 5 节排错把 401 和连接问题解决掉。3. 可复制配置core_loop.py 的完整骨架与 settings 片段这一节是全文的核心我给你一份可以直接复制运行的core_loop.py。它包含四块数据结构定义、工具注册表、系统提示词、以及最关键的 Loop 类。先看数据结构用 Pydantic 把每一步的思考、动作、观察都框起来。import os import re import json from typing import Optional from pydantic import BaseModel from openai import OpenAI class Action(BaseModel): tool_name: str tool_input: str class LoopStep(BaseModel): thought: str action: Optional[Action] None observation: Optional[str] None final_answer: Optional[str] None接着是工具注册表。第二篇我们先用最朴素的方式把工具写成静态方法靠getattr反射调用。第三篇再升级成装饰器自动注册。class SkillRegistry: staticmethod def get_weather(location: str) - str: db {北京: 晴转多云, 24°C, 上海: 小雨, 21°C} return db.get(location, f未找到 {location} 的天气信息) staticmethod def calculator(expression: str) - str: try: return str(eval(expression)) except Exception as e: return f计算出错: {str(e)} def execute(self, tool_name: str, tool_input: str) - str: if hasattr(self, tool_name): func getattr(self, tool_name) return func(tool_input) return f错误工具 {tool_name} 未定义系统提示词是 Agent 的灵魂律法格式约束必须极其精确否则模型会自由发挥。注意这里明确告诉它 Observation 由系统填充它自己不许写。SYSTEM_PROMPT 你是一个名为 OpenClaw 的任务处理智能体。 你必须严格遵循以下推理格式 Thought: 思考你当前处在任务的哪个阶段还需要什么信息。 Action: 你要使用的工具名称必须从 [get_weather, calculator] 中选择。 Action Input: 工具的参数必须是纯文本。 Observation: 工具返回的结果这一行由系统自动填充你不要写。 ...重复 Thought/Action/Observation Final Answer: 当你获得最终结论时用这一行结束任务。 注意每次输出只能包含一个 Thought 和一个 Action。然后是 Loop 本体。这里有两个工程细节必须强调一是stop[Observation:]强行掐断模型幻想工具输出的冲动二是把工具报错也当作 Observation 塞回去让模型自己纠错。class OpenClawCore: def __init__(self, model: str gpt-4o-mini): self.model model self.registry SkillRegistry() self.max_iterations 5 self.client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlos.environ[TAOTOKEN_BASE_URL], ) async def run(self, task: str): messages [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: task}, ] print(fOpenClaw 接收任务: {task}) for i in range(self.max_iterations): response self.client.chat.completions.create( modelself.model, messagesmessages, stop[Observation:], ) content response.choices[0].message.content print(f\n--- [迭代 {i1}] ---\n{content}) messages.append({role: assistant, content: content}) if Final Answer: in content: return content.split(Final Answer:)[-1].strip() action_match re.search(rAction:\s*(\w), content) input_match re.search(rAction Input:\s*(.*), content) if action_match and input_match: tool_name action_match.group(1).strip() tool_input input_match.group(1).strip() observation self.registry.execute(tool_name, tool_input) print(fObservation: {observation}) messages.append({role: user, content: fObservation: {observation}}) else: messages.append({role: user, content: 系统错误未能识别 Action 格式请重新尝试。}) return 任务达到最大迭代次数未能完成。如果你更喜欢用配置文件管理参数可以加一个settings.toml把模型名、最大迭代次数、base_url 都抽出来[llm] base_url https://taotoken.net/api model gpt-4o-mini max_iterations 5 [agent] name OpenClaw stop_sequence Observation:读取时用tomllibPython 3.11或tomli即可。这样换模型、调迭代上限都不用动主逻辑。注意 base_url 这里不带任何多余路径就是https://taotoken.net/apiKey 依然走环境变量不要写进 toml 提交到仓库。4. 验证一次完整 ReAct 往返从提问到真实工具结果代码写完了得跑一次真实的往返看看节拍是不是真的在转。写一个入口import asyncio async def main(): agent OpenClawCore(modelgpt-4o-mini) result await agent.run(北京今天天气怎么样顺便帮我算一下 23 乘以 17 等于多少) print(\n最终答案:, result) if __name__ __main__: asyncio.run(main())运行python core_loop.py你会看到类似这样的输出。第一轮模型先思考“我需要先查北京天气”然后输出 Action 调用get_weather参数是“北京”。因为设了停止符它到Observation:前就停了控制权回到 Python程序真的去查了本地字典把“晴转多云, 24°C”作为 Observation 塞回去。第二轮模型看到天气结果继续思考“天气拿到了还需要算乘法”于是调用calculator参数23*17。程序执行后返回391。第三轮模型确认两个子任务都完成输出Final Answer: 北京今天晴转多云24°C23 乘以 17 等于 391。这个过程里最关键的是天气和乘法结果都不是模型编的是 Python 真实执行后喂回去的。你可以故意把calculator的参数写成abc观察模型收到“计算出错”的 Observation 后下一轮会不会自己改成合法表达式。这就是 ReAct 的自我修复能力也是它和普通 Chatbot 的分水岭。如果你想验证模型切换是否真的无痛把model改成claude-3-5-sonnet或deepseek-chat再跑一次。只要 TaoToken 通道支持该模型Loop 逻辑完全不用改。不同模型对格式的服从度会有差异有的会多写一行 Observation有的 Action Input 带引号这时候你的正则要稍微宽容一点这也是为什么第 5 节的排错很重要。5. 本篇常见错排查401、local proxy failed 与 choices 解析异常跑不通是常态我把这一节按真实报错来写你对着改就行。报错一401 Unauthorized。这是最常见的。原因通常是环境变量没生效或者 Key 复制时带了空格。先在终端echo $TAOTOKEN_API_KEY确认能打印出来。如果是在 IDE 里跑注意 IDE 可能没继承你 shell 里 export 的变量需要在运行配置里手动加。还有一种情况是 Key 被禁用或额度用尽去控制台https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentopenclaw_agent_loop重新生成一个再试。报错二local proxy failed 或连接超时。这类报错说明请求根本没到服务端多半是本机网络环境或代理设置干扰。检查你的HTTP_PROXY、HTTPS_PROXY环境变量如果设了但指向一个不可用的地址请求就会卡死。临时清掉再跑unset HTTP_PROXY HTTPS_PROXY。另外确认 base_url 拼写正确是https://taotoken.net/api不要多加/v1之类的后缀也不要少写协议头。报错三reading choices 或 NoneType 报错。典型写法是response.choices[0]报NoneType object has no attribute choices或者list index out of range。这通常意味着请求返回了错误结构而不是正常的 completion。先打印完整response看内容常见原因是模型名写错服务端返回了错误对象。把model换成确认可用的名字比如gpt-4o-mini。还有一种可能是stop参数传了空列表或格式不对导致返回体异常。报错四模型不按格式输出正则匹配不到 Action。这不是异常是格式服从问题。表现是循环里一直走“未能识别 Action 格式”分支直到迭代耗尽。解决办法有三一是把系统提示词里的格式示例写得更死明确“Action Input 不要加引号”二是把正则放宽比如rAction\s*[:]\s*(\w)兼容中英文冒号三是换一个格式服从度更高的模型。实测下来指令跟随强的模型在这类结构化输出上稳定得多。报错五死循环反复调用同一个工具。模型拿到 Observation 后没有推进又调了一次同样的工具。这通常是 Observation 内容太模糊模型以为没成功。把工具返回值写得更明确比如成功时带上“查询成功”前缀。同时max_iterations是必须的保险丝别设太大5 到 8 足够验证。如果你用的是 Claude Code 这类工具做辅助开发配置时同样要写全三件套Base URL 填https://taotoken.net/apiKey 填你的 TaoToken KeyModel ID 填具体模型名。三者缺一请求就会失败。Cline 的 MCP 配置、Codex 的auth.json也是同理核心就是这三个字段对齐。6. 把节拍器接上更多工具从最小 Loop 到可扩展 Agent到这里最小 Agent Loop 已经跑通了。它只有两百行没有持久化记忆没有安全沙箱工具调用还靠正则解析但它证明了一件事智能不在于模型多大而在于反馈回路是否完整。模型负责想Python 负责做和看真实结果再喂回去让它接着想这个节拍一旦转起来Agent 就有了自主推进的雏形。下一步的扩展方向很清晰。工具注册表可以从静态方法升级成装饰器你写一个普通 Python 函数贴上claw_skill自动解析类型注解和 docstring 生成 JSON Schema模型就能学会用它。上下文管理也要跟上迭代十轮以上 Token 会膨胀需要保留系统提示和最近几轮观察把中间推理做摘要压缩。再往后把get_weather换成move_arm(x, y, z)Loop 输出的就是机械臂的意图底层驱动适配器负责毫秒级轨迹规划这就是从数字世界走向具身智能的路径。如果你想把模型通道和 Key 管理再省心一点长期跑编码类 Agent 可以看看 Coding Plan接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentopenclaw_agent_loop想先在网页里验证模型对 ReAct 格式的服从度可以直接用模型对话https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentopenclaw_agent_loop试几轮。把系统提示词粘进去看它会不会老老实实按 Thought/Action/Observation 输出心里就有底了。最后留一个我踩过的坑别急着给 Agent 加一堆工具。工具越多模型选错的概率越高格式也越容易乱。先用两三个工具把节拍跑稳确认每一轮 Observation 都真实回填、每一轮 Thought 都在推进再去扩工具集。节拍稳了后面加什么都是顺水推舟。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →