1. 从 AgentLoop 源码看配置入口:为什么 settings.json 才是架构的“总开关”
OpenClaw 里的 Nanobot 是一套很典型的 Agent 运行时,AgentLoop 是它真正干活的地方:从消息总线收消息、拼上下文、调模型、执行工具、把结果写回会话,再决定是继续循环还是收尾。很多人读源码时盯着_run_agent_loop()里的 while 循环看半天,却忽略了这些行为其实都由一份配置驱动——模型是谁、走哪个 API 通道、最大迭代多少次、温度多少、工作目录在哪,全部来自settings.json。
这篇是系列第三篇,聚焦 AgentLoop 的配置与验证。目标很明确:给你一份可以直接复制的settings.json骨架,把 TaoToken 的统一 Key 和 API 通道接进去,然后教你用几个检查动作确认 AgentLoop 真的按这份配置加载起来了。适合正在读 Nanobot 源码、想在自己机器上复现架构理解的同学,也适合已经跑通基础对话、准备把模型通道统一管理的开发者。
读源码最怕“看懂了但跑不起来”。AgentLoop 的初始化参数有十几个,散落在__init__里,如果一个个手动传,很容易漏。而settings.json的价值就在于:它把 AgentLoop 关心的所有开关集中到一处,加载器读完之后再分发给provider、context、tools这些组件。理解了这份配置,你就理解了 AgentLoop 的“输入面”。
2. TaoToken 前置:统一 Key 与 API 通道在 AgentLoop 里的位置
在 Nanobot 的架构里,AgentLoop 本身不直接持有 API Key,它拿到的是一个LLMProvider实例。Provider 负责和模型服务通信,Key、Base URL、模型名这些都在 Provider 层。所以接入 TaoToken 的本质,是让配置加载器构造出一个指向 TaoToken 的 Provider,再交给 AgentLoop。
TaoToken 在这里扮演的是统一模型通道:一个 Key 可以覆盖多种模型,Base URL 固定,模型名通过参数切换。对 AgentLoop 来说,它只关心provider.chat()能不能正常返回,不关心背后是哪家模型。这种解耦正好符合 Nanobot “精简”的设计哲学——Agent 循环只管调度,通道细节下沉到 Provider。
你需要先拿到两样东西:一个 API Key,以及确认要用的模型名。Key 在控制台的 API Keys 页面创建,模型名参考文档里的可用列表。这两样会写进settings.json的 provider 段。
注意:Key 属于敏感信息,不要提交到 Git 仓库。建议用环境变量占位,配置文件里只写引用名,加载时再替换。
相关入口我放在这里,按需取用:
- 获取 API Key:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=settings_json&utm_campaign=rewrite
- 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=settings_json&utm_campaign=rewrite
- 控制台:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=settings_json&utm_campaign=rewrite
3. 可复制配置:settings.json 骨架与 AgentLoop 字段映射
下面这份骨架是按 Nanobot 的配置结构整理的,字段名对齐 AgentLoop 初始化时读取的键。你可以直接复制,把api_key和model换成自己的值。
{ "provider": { "type": "openai_compatible", "base_url": "https://taotoken.net/api", "api_key": "${TAOTOKEN_API_KEY}", "model": "claude-sonnet-4-5", "temperature": 0.1, "max_tokens": 4096 }, "agent": { "max_iterations": 40, "memory_window": 100, "restrict_to_workspace": true, "workspace": "./workspace" }, "tools": { "exec": { "timeout": 30, "path_append": "" }, "web_search": { "api_key": "" } }, "channels": { "cli": { "enabled": true } } }这份配置和 AgentLoop 的对应关系是这样的:provider段决定LLMProvider怎么构造,base_url指向 TaoToken 的 API 地址,model决定self.model,temperature和max_tokens直接传给provider.chat()。agent段里的max_iterations就是_run_agent_loop()里那个 while 循环的上限,memory_window控制session.get_history()取多少条历史,restrict_to_workspace决定文件工具是否被限制在workspace目录内。
tools段对应_register_default_tools()里注册的那些工具。exec.timeout传给ExecTool,web_search.api_key传给WebSearchTool。如果你暂时不用搜索,留空即可,工具注册时不会因为空 Key 报错,只是调用时返回失败。
环境变量替换这一步,取决于你的加载器实现。如果用的是标准 JSON 解析,${TAOTOKEN_API_KEY}不会被自动替换,需要在代码里做一层处理:
import json import os def load_settings(path: str) -> dict: with open(path, "r", encoding="utf-8") as f: raw = f.read() # 替换 ${VAR} 形式的环境变量占位 for key, value in os.environ.items(): raw = raw.replace(f"${{{key}}}", value) return json.loads(raw)这样 Key 就只存在于环境变量里,配置文件可以安全地进版本库。设置环境变量的方式:
export TAOTOKEN_API_KEY="你的Key"Windows 下用set TAOTOKEN_API_KEY=你的Key,或者写进系统环境变量。
4. 验证请求:确认 AgentLoop 按配置加载并跑通一次循环
配置写好了,接下来要验证 AgentLoop 真的读到了这些值。分三步走:先验证 Provider 能通,再验证 AgentLoop 初始化参数正确,最后跑一次完整循环。
第一步,单独测 Provider。写一个最小脚本,用同一份配置构造 Provider 并发一条消息:
import asyncio from nanobot.provider import LLMProvider async def main(): provider = LLMProvider( base_url="https://taotoken.net/api", api_key="你的Key", model="claude-sonnet-4-5", ) resp = await provider.chat( messages=[{"role": "user", "content": "只回复两个字:收到"}], tools=[], model="claude-sonnet-4-5", temperature=0.1, max_tokens=64, ) print("content:", resp.content) print("has_tool_calls:", resp.has_tool_calls) asyncio.run(main())如果打印出content: 收到,说明通道是通的。这一步排除了 Key、Base URL、模型名的问题,后面出问题就只可能是 AgentLoop 的配置加载。
第二步,验证 AgentLoop 初始化。在构造 AgentLoop 之后,把关键属性打出来:
loop = AgentLoop( bus=bus, provider=provider, workspace=Path("./workspace"), model=settings["provider"]["model"], max_iterations=settings["agent"]["max_iterations"], temperature=settings["provider"]["temperature"], max_tokens=settings["provider"]["max_tokens"], memory_window=settings["agent"]["memory_window"], restrict_to_workspace=settings["agent"]["restrict_to_workspace"], ) print("model:", loop.model) print("max_iterations:", loop.max_iterations) print("temperature:", loop.temperature) print("memory_window:", loop.memory_window) print("tools:", list(loop.tools._tools.keys()))对照settings.json里的值逐个核对。tools那行会列出所有注册的工具名,正常情况下能看到read_file、write_file、edit_file、list_dir、exec、web_search、web_fetch、message、spawn这些。如果少了某个,说明对应的注册分支没走到,回去检查配置字段名是否拼错。
第三步,跑一次完整循环。发一条会触发工具调用的消息,比如让它读一个文件:
msg = InboundMessage( channel="cli", chat_id="local", sender_id="tester", content="读一下 workspace/README.md 的前 5 行", ) await loop._dispatch(msg)观察日志里有没有Tool call: read_file(...)这一行。如果有,说明_run_agent_loop()里的工具分支被正确执行了,AgentLoop 的完整链路是通的。日志里还会打印Response to cli:tester:后面跟回复预览,这就是最终返回。
5. 本篇常见错排查:AgentLoop 加载失败的几个典型现象
配置和验证过程中,最容易踩的坑集中在下面几类。
现象一:Provider 报 401 或鉴权失败。先确认环境变量有没有真正注入。在 Python 里print(os.environ.get("TAOTOKEN_API_KEY"))看一下,如果是None,说明 export 没生效或者加载器没做替换。另一个常见原因是 Key 前后带了空格或换行,从控制台复制时容易带上,strip 一下再写进配置。
现象二:AgentLoop 初始化报TypeError: unexpected keyword argument。这通常是配置字段名和__init__参数名对不上。比如配置里写max_iter,代码里是max_iterations,就会报这个。对照第 3 节的字段映射表逐个核对,或者直接看AgentLoop.__init__的签名。
现象三:工具列表为空。_register_default_tools()在__init__末尾调用,如果它抛了异常但被吞掉,工具就不会注册。检查exec_config是否正确构造,ExecToolConfig的导入是运行时导入,如果nanobot.config.schema路径不对会失败。另外restrict_to_workspace为 true 时,workspace目录必须存在,否则文件工具注册时可能报错。
现象四:循环跑满 max_iterations 还没结束。日志里会出现Max iterations (40) reached。这通常不是配置问题,而是模型一直在调工具不返回最终答案。可以先把max_iterations调小到 5 观察行为,确认是模型策略问题还是工具返回值有问题。工具返回内容过长也会让模型反复尝试,检查ExecTool的输出有没有被截断。
现象五:/stop没反应。run()里用asyncio.wait_for(..., timeout=1.0)保证每秒检查一次,如果事件循环被阻塞,/stop就处理不了。检查有没有在_dispatch之外的地方做了同步阻塞调用,比如同步的文件读写或网络请求。
提示:排查时把日志级别调到 DEBUG,
_run_agent_loop()里每次provider.chat()的入参和返回都会打出来,比猜快得多。
6. 语义一致 CTA:把配置跑通之后往哪走
配置跑通、AgentLoop 能正常加载之后,下一步通常是两件事:一是把模型通道固定下来长期用,二是开始做真正的编码或 Agent 任务。
如果你主要在做模型验证和对话调试,可以直接用模型对话页面快速切换模型对比效果:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=settings_json&utm_campaign=rewrite
如果你准备把 AgentLoop 用在长期编码任务上,或者要跑多轮工具调用的 Agent 流程,Coding Plan 更适合,额度和通道都按长任务场景设计:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=settings_json&utm_campaign=rewrite
配置过程中如果遇到接入层面的报错,先翻接入文档里的错误码对照:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=settings_json&utm_campaign=rewrite
我自己的习惯是:每改一次settings.json,先跑第 4 节的第一步 Provider 测试,再跑第二步初始化检查,最后才发消息。这三步顺序固定下来,出问题能立刻定位到是哪一层,比直接发消息看报错省时间。