
1. 从 4000 行代码里看懂一个 Agent 内核NanoBot 是什么、能做什么、适合谁NanoBot 是 HKUDS 开源的一个超轻量智能体框架GitHub 上 42.1K Star、7.4K Fork核心代码只有约 4000 行。它把 OpenClaw 那套智能体能力做了极简复刻网页搜索、文件与代码操作、定时任务、记忆机制、多场景 Agent 模板一个都不少但体积缩小了 99%。如果你之前看过 OpenClaw 的源码被层层抽象劝退NanoBot 就是那个能让你一个下午读完主循环的版本。它适合三类人一是想研究 Agent 原理但不想啃几万行工程代码的开发者二是想快速搭一个私人助手行情分析、日程管理、知识库问答的动手派三是想拿它当底座做二次开发、加自定义 Skill 的人。核心价值就两个字可掌控。代码短、依赖少、启动快改起来没有心理负担。NanoBot 内置四个模板24h 实时行情分析师、全栈开发助手、私人日程管理、个人知识库。斜杠命令也很直白/help看帮助、/ping测响应、/skills列技能、/model切模型、/clear清会话。生态上还有 nanobot-webui、nanobot-desktop、nanobot-ts、NanoBot-Android 等移植和扩展项目说明这个内核的接口设计是站得住脚的。但真正跑起来很多人卡在第一步模型通道怎么配。NanoBot 本身不绑定任何一家模型它通过 provider 配置去调 LLM。如果你手上有多个模型的 Key一个个填、一个个切调试成本很高。这篇就围绕「用 TaoToken 统一 Key 跑通 NanoBot 全流程」来写从源码结构拆到可复制配置再到一次完整的调用验证。下面先讲清楚 NanoBot 的 AgentLoop 到底怎么转再动手接通道。2. NanoBot 源码结构与 AgentLoop 原理拆解loop.py 主循环到底怎么跑要看懂 NanoBot盯住四个文件就够了nanobot/agent/loop.py是主实现nanobot/agent/context.py负责上下文构建nanobot/agent/tools/registry.py管工具注册和执行nanobot/session/manager.py管会话。整个架构围绕 AgentLoop 展开它下面挂着 MessageBus消息总线、LLMProvider模型提供商、ContextBuilder上下文构建器、SessionManager会话管理器、ToolRegistry工具注册表、SubagentManager子代理管理器、MemoryStore记忆存储。AgentLoop 的__init__里有一堆参数值得注意max_iterations40是最大工具调用次数temperature0.1偏确定性memory_window100是会话历史窗口restrict_to_workspace控制工具是否只能在工作目录里动。初始化时会调_register_default_tools()把文件工具ReadFileTool、WriteFileTool、EditFileTool、ListDirTool、ExecTool、WebSearchTool、WebFetchTool、MessageTool、SpawnTool、CronTool 全部注册进 ToolRegistry。主循环run()的设计很巧用asyncio.wait_for(self.bus.consume_inbound(), timeout1.0)实现可中断的阻塞每条消息创建一个独立 task 支持并发遇到/stop就立即取消该会话的所有任务。_dispatch()外面套了一把全局锁_processing_lock防止并发修改会话状态、保证工具执行原子性、避免竞态条件。最核心的是_run_agent_loop()while 循环里每次调provider.chat()传入当前 messages 和self.tools.get_definitions()。如果 LLM 返回has_tool_calls就把 assistant 消息含 tool_calls加进 messages然后逐个执行工具把结果作为 tool 角色消息追加如果没有工具调用就_strip_think()清理后返回最终内容。达到max_iterations还没结束就返回提示。这个「调模型 → 有工具就执行 → 结果回灌 → 再调模型」的闭环就是所有 Agent 的通用骨架。工具执行走ToolRegistry.execute()先cast_params()做类型转换再validate_params()校验然后await tool.execute(params)异常统一捕获返回错误字符串而不是抛出。会话侧Session是个 dataclass存 key格式channel:chat_id、messages、last_consolidated等。_save_turn()会跳过空的 assistant 消息、截断过长的工具结果、去掉运行时上下文前缀。普通消息进来时如果len(session.messages) - session.last_consolidated memory_window就先触发_consolidate_memory()做记忆整合。MCP 服务器通过_connect_mcp()惰性连接用 AsyncExitStack 管理生命周期。理解到这一层你就知道该在哪里接模型通道了——就是 LLMProvider。3. 用 TaoToken 统一 Key 接入 NanoBot可复制的 provider 配置与 settings 片段NanoBot 的模型配置走 provider 机制默认支持 openrouter 这类写法。我们要做的是把 Base URL 指向 TaoToken 的 API 通道用一把 Key 统一管理模型调用。TaoToken 的 API 地址是https://taotoken.net/api官网在https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。先去控制台建 Key地址是https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewriteKey 管理页在https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite。NanoBot 的配置文件通常是config.json或config.yaml放在工作目录下。下面给一份可复制的 JSON 片段把 provider 换成 TaoToken 兼容的 OpenAI 风格通道{ providers: { taotoken: { apiKey: sk-你的TaoToken密钥, baseUrl: https://taotoken.net/api, type: openai } }, agents: { defaults: { model: claude-sonnet-4-5, provider: taotoken, maxIterations: 40, temperature: 0.1 } }, webSearch: { apiKey: BSA-你的搜索Key } }如果你更习惯 YAML等价写法如下providers: taotoken: apiKey: sk-你的TaoToken密钥 baseUrl: https://taotoken.net/api type: openai agents: defaults: model: claude-sonnet-4-5 provider: taotoken maxIterations: 40 temperature: 0.1 webSearch: apiKey: BSA-你的搜索Key这里三件套必须齐全Base URL 填https://taotoken.net/apiKey 填控制台生成的Model ID 填你要用的模型名比如claude-sonnet-4-5或gpt-4o。NanoBot 的 provider 层会把这些拼成标准的 chat completions 请求。如果你在代码里直接初始化 LLMProvider也可以这样写from nanobot.providers import OpenAIProvider provider OpenAIProvider( api_keysk-你的TaoToken密钥, base_urlhttps://taotoken.net/api, modelclaude-sonnet-4-5, )装好依赖后先跑nanobot onboard做初始化再nanobot gateway启动网关。源码方式则是git clone https://github.com/HKUDS/nanobot.git进目录pip install -e .。配置改完别急着上复杂任务先用一条最简单的消息验证通道是否通。模型对话可以在https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite里先手动确认 Key 有效再去跑 NanoBot。4. 一次完整调用验证从 nanobot agent 到工具调用成功返回配置就绪后最直接的验证是命令行单次调用。NanoBot 支持nanobot agent -m ...这种一次性消息模式适合排查通道问题nanobot agent -m What is 22?如果通道正常你会看到模型返回4并且日志里能看到一次provider.chat()调用。这一步只验证 LLM 通道不涉及工具。接着验证工具调用发一条会触发 WebSearchTool 的消息nanobot agent -m 搜索一下 NanoBot 的最新 star 数并告诉我正常流程是_run_agent_loop第一次调模型模型返回has_tool_calls工具名是web_searchToolRegistry.execute()执行搜索结果作为 tool 消息回灌第二次调模型模型基于搜索结果给出最终回答。日志里应该能看到tools_used包含web_search。如果这一步成功说明「模型通道 工具注册 消息回灌」整条链路都通了。再验证会话记忆。启动 gateway 后连续发两条消息nanobot gateway然后在对话里先发「我叫小明」再发「我叫什么」。第二条能答出「小明」说明 SessionManager 和_save_turn()正常工作。如果历史窗口超过memory_window还会触发_consolidate_memory()做整合日志里会有对应记录。验证文件工具时注意restrict_to_workspace这个开关。默认 False 时工具能访问更大范围生产环境建议设 True把操作限制在工作目录。发一条「在当前目录创建一个 test.txt 并写入 hello」看 WriteFileTool 是否成功。成功后ls应该能看到文件。最后验证斜杠命令在 gateway 对话里发/help、/skills、/model确认命令分发走的是_process_message()里的 Slash 分支而不是 AgentLoop。这一整套跑下来你对 NanoBot 的消息生命周期就有了实感。5. 常见报错排查401、local proxy failed、reading choices、OAuth 逐个击破接通道时最容易撞的几个错我按真实日志对照说。401 Unauthorized日志里通常是provider.chat() failed: 401。原因九成是 Key 错了或没带上。检查config.json里apiKey是不是完整的sk-开头字符串有没有多余空格或换行。如果你用的是环境变量注入确认变量名和代码里读的一致。还有一种情况是 Base URL 写成了https://taotoken.net少了/api请求打到了官网而不是 API 端点也会 401 或 404。正确写法是https://taotoken.net/api。local proxy failed / connection refused这类报错说明请求根本没出去。先确认本机网络能访问https://taotoken.net/api用curl -I https://taotoken.net/api看返回。如果 NanoBot 配了web_proxy参数但代理不可用也会报这个。把web_proxy去掉或指向可用地址。注意 NanoBot 的web_proxy是给 WebFetchTool 用的和 LLM 通道是两回事别混。Error reading choices / KeyError choices这个错说明返回体不是标准 chat completions 格式。常见于 Base URL 指错、或者 Model ID 填了一个该通道不支持的模型名。检查model字段换成通道支持的模型。还有一种可能是 provider 的type没设成openai导致解析逻辑不对。把type显式写成openai再试。OAuth / token expired如果你用的是需要 OAuth 的通道会看到 token 过期。TaoToken 走的是 API Key 模式正常不会出这个。如果出现检查是不是误配了别的 provider。另外 Codex 的auth.json那套是另一条路径NanoBot 不用它别把两套配置混在一起。工具调用报 Tool not found日志里是Error: Tool xxx not found.。说明模型返回的工具名和 ToolRegistry 里注册的对不上。检查_register_default_tools()是否执行以及工具名大小写。自定义 Skill 要确保name属性和注册时一致。max iterations reached不是报错但很常见。复杂任务 40 次不够用调大maxIterations。但更该查的是模型是不是陷入了工具调用死循环比如反复搜同一个词。看tools_used列表有没有重复项。排查顺序建议先nanobot agent -m hi确认 LLM 通道再测工具最后测会话。每步看日志定位别一上来就跑复杂任务。6. 把 NanoBot 当底座自定义 Skill 与长期编码场景的通道选择跑通之后NanoBot 真正的价值在于改。加一个自定义 Skill 只要继承 Pluginfrom nanobot.plugin import Plugin class MySkill(Plugin): name my_skill description 自定义技能 async def handle(self, message, context): return await self.reply(自定义回复!) plugin MySkill()注册后/skills就能看到。想覆盖默认行为在工作目录建一个AGENTS.md写自定义提示词ContextBuilder 会把它拼进上下文。调试 Agent 循环时设LOG_LEVELDEBUG在_run_agent_loop里打印 messages能看清每一轮模型看到了什么。如果你打算把 NanoBot 长期挂在后台跑编码任务或 Agent 工作流通道的稳定性和额度管理就比单次调用重要。这种场景可以看下 Coding Plan地址是https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite适合需要持续调用的开发场景。接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite里面有各语言的调用示例配 NanoBot 的 provider 时可以直接对照。Claude Code 那套 Anthropic 风格的接入也有对应页面https://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecode-anthropicutm_campaignrewrite如果你同时用 Claude Code 和 NanoBot统一走一个 Key 能省不少切换成本。最后提醒一句restrict_to_workspace在生产环境一定设 True别让 Agent 的文件工具跑出工作目录。NanoBot 代码短改起来快但权限边界要自己守好。