【OpenClaw】通过 Nanobot 源码学习架构---(3)AgentLoop 的 TaoToken 接入与调试

发布时间:2026/10/2 12:18:58
【OpenClaw】通过 Nanobot 源码学习架构---(3)AgentLoop 的 TaoToken 接入与调试 1. 从 AgentLoop 源码看模型调用到底发生在哪一层OpenClaw 的代码量很大直接啃容易迷路所以我一直用 Nanobot 这个超轻量级的开源个人 AI 助手框架来对照学习。Nanobot 由 HKUDS 开源定位就是「Ultra-Lightweight OpenClaw」整个 Agent 核心逻辑压缩在几千行里非常适合拿来理解 AgentLoop 的架构。这一篇聚焦的是AgentLoop 里模型调用到底发生在哪一层以及怎么把它的模型端点改到 TaoToken 统一通道上让源码调试和真实请求验证能跑通。如果你之前看过这个系列的前两篇应该知道 Agent 是「业务执行者」负责把消息变成模型调用、工具执行和最终回复。而 AgentLoop 就是驱动这一切的核心引擎。它的职责链条很清晰从消息总线收消息构建上下文调用 LLM执行工具调用再把响应发回去。整个循环里真正跟外部模型服务打交道的地方只有一个——self.provider.chat()。这就是我们要接入 TaoToken 的关键切入点。为什么要在源码层面理解这个接入点因为很多人调 Agent 的时候模型报错只会看表面比如「401」「local proxy failed」「reading choices」这些但不知道错误是从哪一层抛出来的。一旦你清楚provider.chat()是唯一出口排查就有了方向要么是 provider 配置的 Base URL 不对要么是 Key 没传进去要么是模型 ID 写错了。Nanobot 的 AgentLoop 把 provider 作为依赖注入这意味着你可以在不改动循环逻辑的前提下把模型端点整体切到 TaoToken。TaoToken 在这里的角色是一个统一 Key/API 通道它把不同模型提供方的调用方式收敛成一套 OpenAI 兼容接口。对 AgentLoop 来说它不关心背后是哪个模型只要 provider 能返回标准的has_tool_calls、content、reasoning_content这些字段循环就能正常跑。所以接入的本质是让provider.chat()指向 TaoToken 的 Base URL并用 TaoToken 的 Key 和 Model ID 完成一次真实请求。这一篇我会按「先看源码接入点 → 再配 TaoToken → 然后跑一次完整 AgentLoop 请求 → 最后对照真实报错排查」的顺序来写。每一步都给可复制的配置片段和命令你可以跟着做。重点不是讲注册流程而是讲清楚源码里改哪里、为什么改那里、改完怎么验证。2. AgentLoop 的 provider.chat() 接入点与 TaoToken 前置准备先把 AgentLoop 里跟模型调用相关的结构捋一遍。在_run_agent_loop()这个核心方法里有一段关键代码response await self.provider.chat( messagesmessages, toolsself.tools.get_definitions(), modelself.model, temperatureself.temperature, max_tokensself.max_tokens, )这里的self.provider是在AgentLoop.__init__里注入的LLMProvider实例。也就是说AgentLoop 本身不关心模型从哪来它只依赖一个符合接口的 provider。这个设计对我们非常友好只要把 provider 的 Base URL 指向 TaoTokenAgentLoop 的其余逻辑一行都不用动。再看__init__里的相关参数def __init__( self, bus: MessageBus, provider: LLMProvider, workspace: Path, model: str | None None, max_iterations: int 40, temperature: float 0.1, max_tokens: int 4096, ... ): self.provider provider self.model model or provider.get_default_model()model如果没传就用provider.get_default_model()。这意味着接入 TaoToken 时Model ID 有两个地方可以控制一是 provider 的默认模型二是 AgentLoop 初始化时显式传入的model。调试阶段建议显式传避免默认值把你带偏。接下来是 TaoToken 的前置准备。你需要拿到三样东西Base URL、API Key、Model ID。Base URL 用https://taotoken.net/api注意这个地址不带任何查询参数。API Key 在控制台的 API Keys 页面创建创建后只显示一次记得立刻保存。Model ID 则取决于你要调用的具体模型在模型列表里能看到对应的标识符。这里有个容易踩的坑很多人把官网地址https://taotoken.net直接当 Base URL 填进去结果请求打到首页而不是 API 端点返回一堆 HTML。Base URL 必须是https://taotoken.net/api这是 OpenAI 兼容接口的根路径。如果你用的是某些 SDK它可能要求你填到/v1这一级那就填https://taotoken.net/api/v1具体看 SDK 的拼接规则。拿到这三样之后先别急着改 AgentLoop用一条 curl 命令验证通道是否通curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: 你的Model ID, messages: [{role: user, content: ping}], max_tokens: 16 }如果返回正常的 JSON 结构里面有choices字段说明通道没问题。如果返回 401就是 Key 的问题如果返回 404多半是 Base URL 路径拼错了。这一步过了再动源码。3. 可复制的 AgentLoop 接入配置片段现在进入实际配置。Nanobot 的 provider 配置通常走一个 settings 文件或者环境变量具体取决于你的部署方式。下面给一份可直接复制的 JSON 配置片段路径按 Nanobot 项目里config/settings.json的约定来写。如果你的项目用的是 TOML我在后面也补一份。先看 JSON 版本{ provider: { type: openai_compatible, base_url: https://taotoken.net/api/v1, api_key: sk-你的TaoTokenKey, default_model: 你的Model ID, timeout: 60 }, agent: { max_iterations: 40, temperature: 0.1, max_tokens: 4096, memory_window: 100 } }这份配置里base_url指向 TaoToken 的 OpenAI 兼容端点api_key填你创建的 Keydefault_model填 Model ID。agent段对应 AgentLoop 的初始化参数max_iterations控制最大迭代次数防止工具调用死循环。如果你用的是 TOML 格式等价配置如下[provider] type openai_compatible base_url https://taotoken.net/api/v1 api_key sk-你的TaoTokenKey default_model 你的Model ID timeout 60 [agent] max_iterations 40 temperature 0.1 max_tokens 4096 memory_window 100如果你不想把 Key 写进文件可以用环境变量注入。Nanobot 的 provider 初始化一般会读OPENAI_API_KEY和OPENAI_BASE_URL这类变量你可以这样设置export OPENAI_API_KEYsk-你的TaoTokenKey export OPENAI_BASE_URLhttps://taotoken.net/api/v1然后在代码里让 provider 从环境变量读取。这样做的另一个好处是切换 Key 不用改文件重启进程即可。接下来是 AgentLoop 初始化时显式传 model 的写法。假设你已经构建好了 provider 实例from nanobot.agent.loop import AgentLoop from nanobot.providers.openai_compatible import OpenAICompatibleProvider provider OpenAICompatibleProvider( base_urlhttps://taotoken.net/api/v1, api_keysk-你的TaoTokenKey, default_model你的Model ID, ) agent AgentLoop( busbus, providerprovider, workspacePath(./workspace), model你的Model ID, max_iterations40, temperature0.1, max_tokens4096, )注意这里model和 provider 的default_model保持一致避免两处不一致导致请求打到非预期模型。如果你要调试不同模型改这两处即可。还有一个细节toolsself.tools.get_definitions()会把工具定义一起发给模型。TaoToken 的 OpenAI 兼容接口支持 function calling所以工具调用链路是通的。但前提是你选的 Model ID 本身支持工具调用否则模型不会返回tool_callsAgentLoop 会直接走「无工具调用」分支把模型回复当最终结果返回。配置改完后建议先跑一个最小验证脚本确认 provider 能正常返回import asyncio from nanobot.providers.openai_compatible import OpenAICompatibleProvider async def main(): provider OpenAICompatibleProvider( base_urlhttps://taotoken.net/api/v1, api_keysk-你的TaoTokenKey, default_model你的Model ID, ) response await provider.chat( messages[{role: user, content: 只回复两个字通了}], tools[], model你的Model ID, temperature0.1, max_tokens32, ) print(content:, response.content) print(has_tool_calls:, response.has_tool_calls) asyncio.run(main())如果打印出content: 通了说明 provider 层已经打通。这一步过了再跑完整 AgentLoop 才有意义。4. 一次完整的 AgentLoop 请求验证与结果解读配置就绪后跑一次完整的 AgentLoop 请求。这里我用一个带工具调用的场景来验证因为只有工具调用才能真正走完_run_agent_loop()的循环分支验证接入是否彻底。先构造一个入站消息模拟从消息总线进来的用户请求import asyncio from pathlib import Path from nanobot.bus import MessageBus, InboundMessage from nanobot.agent.loop import AgentLoop from nanobot.providers.openai_compatible import OpenAICompatibleProvider async def main(): bus MessageBus() provider OpenAICompatibleProvider( base_urlhttps://taotoken.net/api/v1, api_keysk-你的TaoTokenKey, default_model你的Model ID, ) agent AgentLoop( busbus, providerprovider, workspacePath(./workspace), model你的Model ID, max_iterations10, temperature0.1, max_tokens2048, ) msg InboundMessage( channelcli, chat_idcli:test, sender_idtester, content列出当前工作目录下的文件然后告诉我一共有几个。, ) response await agent._process_message(msg) print(final_content:, response.content if response else None) asyncio.run(main())这段代码直接调用_process_message()跳过run()的常驻循环适合单次验证。执行后你会看到类似这样的日志INFO Processing message from cli:tester: 列出当前工作目录下的文件... INFO Tool call: list_dir({path: .}) INFO Tool call: exec({command: ls -1 | wc -l}) INFO Response to cli:tester: 当前工作目录下有 12 个文件。日志里Tool call那两行说明模型通过 TaoToken 返回了工具调用指令AgentLoop 执行了工具把结果追加回上下文再次调用模型最终得到自然语言回复。这就是完整的 AgentLoop 循环模型决策 → 工具执行 → 结果回填 → 再决策 → 终止。如果你只看到一次模型调用没有Tool call日志说明模型没有返回工具调用。可能的原因有两个一是 Model ID 不支持 function calling二是工具定义没传对。检查self.tools.get_definitions()返回的列表是否非空以及模型是否在支持工具调用的列表里。验证成功后你可以观察_run_agent_loop()的返回值final_content, tools_used, all_msgs await agent._run_agent_loop(initial_messages) print(tools_used:, tools_used) print(message_count:, len(all_msgs))tools_used会列出本次循环调用过的所有工具名all_msgs是完整的对话消息列表包含 assistant 消息、tool 结果等。这两个返回值对调试非常有用如果tools_used为空但你以为会调工具说明模型没按预期返回tool_calls如果all_msgs里 tool 结果缺失说明add_tool_result那一步出了问题。实测下来整个链路跑通后AgentLoop 的模型调用部分就完全托管给 TaoToken 了。你可以在控制台看到对应的请求记录包括 token 消耗和响应时间。这对排查「到底是模型慢还是工具慢」很有帮助。5. AgentLoop 接入 TaoToken 的常见报错排查接入过程中最容易碰到几类报错我按真实错误信息对照着讲。第一类是401 Unauthorized。这个错误从provider.chat()抛出说明 Key 没被接受。排查顺序先确认api_key字段有没有正确传入有没有多余空格再用第 2 节那条 curl 命令单独验证 Key 是否有效最后检查是不是把 Key 写进了错误的配置段。Nanobot 的 provider 初始化如果读的是环境变量确认OPENAI_API_KEY已经 export 且当前 shell 能读到。第二类是local proxy failed或连接被拒绝。这个错误通常出现在 Base URL 配置错误时。比如你把base_url写成了https://taotoken.net少了/api/v1请求会打到首页返回 HTML 而不是 JSONSDK 解析失败就会报这类错。正确写法是https://taotoken.net/api/v1。另外确认你的运行环境能正常访问外网本地防火墙没有拦截出站请求。第三类是reading choices相关的解析错误典型信息是KeyError: choices或list index out of range。这说明返回的 JSON 结构里没有choices字段。常见原因是 Model ID 写错了接口返回了错误对象而不是正常响应。检查model参数和 provider 的default_model是否一致以及 Model ID 是否在 TaoToken 的模型列表里存在。还有一种可能是max_tokens设得太小模型还没生成完整响应就被截断导致结构异常把max_tokens调到 1024 以上再试。第四类是 OAuth 或鉴权头格式问题。有些 SDK 默认用Authorization: Bearer keyTaoToken 的 OpenAI 兼容接口也是这个格式一般不会有问题。但如果你用的是自定义 provider 实现确认 header 拼装正确没有重复添加Bearer前缀。如果报错信息里出现OAuth字样多半是 SDK 走了另一套鉴权流程检查 provider 的type是否设成了openai_compatible。第五类是工具调用相关的问题。如果日志里出现Tool call但工具执行报错那不是 TaoToken 的问题而是工具本身的实现或参数问题。比如exec工具执行了不存在的命令或者list_dir的路径参数不对。这类错误在self.tools.execute()里抛出跟模型通道无关分开排查即可。为了快速定位问题建议在 provider 层加一段日志把请求的 URL、model 和响应状态打出来import logging logging.basicConfig(levellogging.DEBUG)Nanobot 的 provider 一般会记录请求详情打开 DEBUG 级别就能看到实际发出的 URL 和 payload。对照 payload 里的model字段和base_url基本能定位大部分配置问题。还有一个隐蔽的坑如果你同时设置了环境变量和配置文件里的 Key两者不一致时以代码里显式传入的为准。排查时先确认最终生效的是哪一个避免改了文件但环境变量覆盖了它。6. 把 AgentLoop 调试经验固化下来跑通一次完整请求之后建议把验证脚本保存成scripts/verify_agentloop.py以后每次改配置都先跑它。脚本里把 Base URL、Key、Model ID 抽成常量放在顶部改一处即可。这样你切换模型或轮换 Key 的时候不用翻遍整个项目。另外AgentLoop 的max_iterations默认是 40调试阶段建议调到 10 以内。因为一旦模型陷入工具调用循环40 次迭代会消耗大量 token而且日志会刷屏。等确认链路稳定后再调回默认值。如果你要长期跑编码类 Agent 任务可以考虑用 Coding Plan 这类按周期计费的方式比按 token 计费更可控。调试阶段则用 API Keys 配合模型对话页面快速验证单次请求两者分工明确。最后留一个实用技巧在_run_agent_loop()的while循环里临时加一行logger.info(iteration{}, messages{}, iteration, len(messages))能直观看到每一轮上下文增长情况。如果发现 messages 数量增长异常快说明工具结果太大需要检查工具返回内容是否做了截断。这个观察对优化 Agent 的上下文管理很有价值。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询