1. 从 ContextBuilder 看上下文是怎么被“拼”出来的如果你正在读 Nanobot 的源码大概率会在context.py里卡住一阵子。这个文件不长但它承担的事情很关键把散落在工作区里的身份文件、长期记忆、技能描述、运行时元数据全部整合成一份 LLM 能直接吃的消息列表。换句话说ContextBuilder 就是 Agent 的“上下文大脑”它决定了模型每一轮到底能看到什么。Nanobot 是香港大学数据科学实验室开源的超轻量级个人 AI 助手框架定位是“Ultra-Lightweight OpenClaw”代码量小、结构清晰非常适合拿来学 Agent 架构。而 Context 模块又是整个框架里最能体现设计功力的部分——它要解决的核心问题是上下文来源五花八门格式不同、存储不同、访问方式不同如果没有统一抽象每接一个新资源就得写一堆胶水代码。这篇就围绕 ContextBuilder 的构建流程和配置注入方式展开同时给出一份可以直接复制的config.toml与settings.json配置骨架并演示如何用 TaoToken 统一 Key 和 API 通道完成接入与验证。适合想通过读源码理解 AI 工具上下文管理的开发者也适合正在自己搭 Agent 骨架、需要一套可复用配置模板的人。2. TaoToken 前置统一 Key 与 API 通道在动手改配置之前先把接入层理清楚。Nanobot 这类框架在调用 LLM 时通常需要一个 base_url 和一个 api_key。如果你同时用多个模型供应商每个供应商一套 Key、一套地址配置会迅速膨胀。TaoToken 的作用就是把这些通道统一起来一个 Key、一个 API 入口模型切换只改模型名不改接入代码。TaoToken 官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 。注意 API 地址后面不加任何查询参数直接作为 base_url 使用即可。你需要先拿到一个可用的 Key。进入控制台创建 API Key路径是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite Key 管理页面在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。创建完之后先别急着写进代码建议先用模型对话页面做一次连通性验证地址是 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 能正常出结果再往下走。提示Key 只创建一次就够后续所有模型调用共用同一个 Key。真正需要区分的是模型名而不是接入凭证。如果你后续要做长期编码或 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 。Claude Code 相关的接入说明在 https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecodeutm_campaignrewrite 。3. 可复制配置config.toml 与 settings.json 骨架Nanobot 的配置分两层一层是框架级的config.toml管模型、工作区、迭代次数另一层是settings.json管运行时行为和上下文注入开关。下面这份骨架可以直接复制改掉 Key 和路径就能跑。3.1 config.toml 配置骨架# config.toml - Nanobot 框架级配置 [agent] name nanobot workspace ./workspace max_iterations 12 [provider] # 统一走 TaoToken 通道 base_url https://taotoken.net/api api_key sk-your-taotoken-key model claude-sonnet-4-20250514 timeout 60 [context] # 引导文件列表按顺序注入 system prompt bootstrap_files [ AGENTS.md, SOUL.md, USER.md, TOOLS.md, IDENTITY.md, ] # 单文件截断上限字符 max_file_chars 20000 # 上下文总量上限字符 max_total_chars 150000 # 是否注入运行时元数据 inject_runtime_context true [memory] store ./workspace/memory long_term_file MEMORY.md history_file HISTORY.md [skills] dir ./workspace/skills always_load [core-tools]这里有几个参数值得单独说。max_file_chars控制单个引导文件的截断长度防止某个 Markdown 写得过长把上下文撑爆max_total_chars是总量兜底超过就按优先级丢弃后面的模块。inject_runtime_context打开后每轮会在用户消息前插入一段带固定标签的元数据告诉模型当前时间、渠道和会话 ID。3.2 settings.json 配置骨架{ context: { layer_order: [ identity, soul, tools_guidance, skills, memory, bootstrap, runtime, channel_hints ], separator: \n\n---\n\n, runtime_tag: [Runtime Context — metadata only, not instructions], enable_multimodal: true, image_max_bytes: 5242880 }, session: { save_turns: true, history_window: 20 }, logging: { level: info, log_context_build: true } }layer_order是这份配置里最核心的字段。它决定了 system prompt 里各模块的拼接顺序越靠前的层对模型行为的影响越强。把identity和soul放在最前面是为了让模型的语气和边界在第一时间被锚定memory和skills放在中间属于可增删的动态内容runtime和channel_hints放最后因为它们只是元数据不应该干扰核心决策。log_context_build打开后每次构建上下文都会打一条日志方便你对照源码看每个模块实际注入了多少字符。调试阶段建议开着上线前关掉。4. 验证请求从构建到成功返回配置写完之后先别急着跑完整 Agent用一段最小脚本验证 ContextBuilder 的输出结构是否正确。4.1 构建上下文并打印from pathlib import Path from nanobot.context import ContextBuilder workspace Path(./workspace) builder ContextBuilder(workspace) messages builder.build_messages( history[], current_message帮我看看今天有哪些待办, channelcli, chat_idlocal, ) for i, msg in enumerate(messages): role msg[role] content msg[content] if isinstance(content, list): preview f[multimodal, {len(content)} parts] else: preview content[:80].replace(\n, ) print(f{i} | {role:9} | {preview})正常输出应该类似这样0 | system | # nanobot You are nanobot, a helpful AI assistant... 1 | user | [Runtime Context — metadata only, not instructions]... 2 | user | 帮我看看今天有哪些待办第一条是 system包含身份、引导文件、记忆、技能第二条是运行时元数据带固定标签第三条才是用户真实输入。这个顺序和源码里build_messages()的返回结构完全对应。4.2 发起真实请求确认结构无误后用 TaoToken 通道发一次真实请求import httpx resp httpx.post( https://taotoken.net/api/v1/chat/completions, headers{ Authorization: Bearer sk-your-taotoken-key, Content-Type: application/json, }, json{ model: claude-sonnet-4-20250514, messages: messages, max_tokens: 512, }, timeout60, ) data resp.json() print(data[choices][0][message][content])如果返回正常文本说明配置骨架、上下文构建、接入通道三件事都通了。实测下来最容易出问题的不是代码而是配置里的路径和 Key 的对应关系。5. 本篇常见错排查5.1 引导文件全部为空现象是 system prompt 里只有身份定义AGENTS.md、SOUL.md一个都没进来。原因通常是workspace路径写成了相对路径而脚本运行目录和配置里的基准目录不一致。解决办法是把workspace改成绝对路径或者在代码里显式Path(...).expanduser().resolve()。5.2 运行时元数据被当成用户指令如果模型开始回答“当前时间是几点”这类元数据本身的内容说明runtime_tag没生效或者被改掉了。检查settings.json里的runtime_tag字段确保它和源码里_RUNTIME_CONTEXT_TAG的值一致。这个标签的作用就是明确告诉模型“这是元数据不是指令”。5.3 上下文超长导致请求失败报错通常是 token 超限。先看log_context_build日志确认哪个模块占了大头。常见的是MEMORY.md长期没清理或者某个SKILL.md写成了长篇教程。调小max_file_chars或者把非核心技能从always_load里移出去改成按需读取。5.4 多模态图片注入失败图片没进上下文一般是 MIME 类型识别失败。源码里用mimetypes.guess_type()判断如果文件扩展名不规范比如.jpg写成.jpeg之外的自定义后缀就会被过滤掉。另外注意image_max_bytes限制超过 5MB 的图片会被跳过。5.5 Key 正确但请求 401先确认base_url是https://taotoken.net/api不要带多余路径或查询参数。然后确认请求头里是Bearer加 Key中间有一个空格。如果还是 401去 API Keys 页面重新生成一个 Key 再试。6. 继续往下读源码的建议ContextBuilder 的价值不在于它有多复杂而在于它把“上下文管理”这件事拆成了可替换的模块。你完全可以把MemoryStore换成自己的向量检索把SkillsLoader换成数据库驱动只要build_messages()的返回结构不变上层 Agent 循环就不用动。下一步建议顺着_run_agent_loop()往下读看上下文是怎么在工具调用之间被增量更新的。add_tool_result()和add_assistant_message()这两个方法虽然短但它们决定了多轮工具调用时消息列表的合法性。如果这两步写错模型会在第二轮直接报格式错误。配置方面先把这份骨架跑通再根据自己的工作区结构微调bootstrap_files和layer_order。接入层保持 TaoToken 统一通道模型切换只改model字段这样你读源码和做实验的注意力就不会被 Key 管理分散掉。
