1. OpenManus 的 agent 与 tool 模块到底怎么协作
OpenManus 是 MetaGPT 团队开源的一个通用智能体项目,核心目标是把「大模型思考 + 工具执行」这条链路跑通,让模型能自己决定调用浏览器、执行 Python 代码、搜索网页。它适合谁?适合已经跑通过基础 LLM 调用、想进一步理解多智能体工具调用机制、并且希望把模型请求统一收敛到一个稳定通道的开发者。我自己在本地把 OpenManus 拉起来跑过几轮,最直观的感受是:agent 负责「想」,tool 负责「做」,而两者之间靠一个统一的 LLM 通道串起来,这个通道一旦配置不对,agent 就会卡在 think 阶段反复空转。
先看 agent 模块的继承关系。项目核心代码在app目录下,app.agent里有 manus、planning、react、toolcall 等模块。Manus 继承 BrowserAgent,BrowserAgent、PlanningAgent、SWEAgent 都继承 ToolCallAgent,ToolCallAgent 继承 ReActAgent,ReActAgent 再继承 BaseAgent。这条链最关键的两个抽象方法是think()和act():
class ReActAgent(BaseAgent, ABC): name: str description: Optional[str] = None system_prompt: Optional[str] = None next_step_prompt: Optional[str] = None llm: Optional[LLM] = Field(default_factory=LLM) memory: Memory = Field(default_factory=Memory) state: AgentState = AgentState.IDLE max_steps: int = 10 current_step: int = 0 @abstractmethod async def think(self) -> bool: """Process current state and decide next action""" @abstractmethod async def act(self) -> str: """Execute decided actions""" async def step(self) -> str: should_act = await self.think() if not should_act: return "Thinking complete - no action needed" return await self.act()step()就是一次完整的「思考—行动」循环。think()里会调用 LLM,把当前 memory、system_prompt、可用工具列表一起发过去,让模型返回一个 tool_call;act()则解析这个 tool_call,找到对应工具并执行,把 observation 写回 memory。所以 agent 本身不干活,它只是调度器。
再看 tool 模块。app.tool下每个工具都继承BaseTool,实现execute()。浏览器工具browser_use_tool.py和 Python 执行工具python_execute.py是最重要的两个。以 PythonExecute 为例,它的parameters是一段 JSON Schema,告诉模型这个工具接受什么参数:
class PythonExecute(BaseTool): name: str = "python_execute" description: str = "Executes Python code string. Note: Only print outputs are visible..." parameters: dict = { "type": "object", "properties": { "code": { "type": "string", "description": "The Python code to execute.", }, }, "required": ["code"], }而 ToolCallAgent 的act()里会调用self.execute_tool(command),拿到 observation。也就是说,tool 的name、description、parameters会被序列化进 LLM 的 tools 参数,模型据此决定调哪个工具、传什么参数。理解这一点后你会发现:agent 和 tool 的耦合点其实只有两处——工具描述注入 prompt,以及 tool_call 解析后分发执行。真正容易出问题的,是中间那次 LLM 请求。
我实测下来,OpenManus 默认走的是 OpenAI 兼容接口,config/config.toml里配置base_url、api_key、model。如果你本地网络环境对某些域名不稳定,agent 的think()就会超时或返回空 tool_calls,表现就是「一直思考但不动手」。把 LLM 请求统一改到一个稳定的 OpenAI 兼容通道,是让 OpenManus 跑顺的最短路径。下面我就按「改 settings → 验证 tool 调用」的顺序,把可复制的配置和排障过程写清楚。
2. 把 OpenManus 的 settings 改到 TaoToken 统一通道
OpenManus 的配置入口是项目根目录下的config/config.toml,它由app/config.py读取,最终注入到LLM类。LLM 类在app/llm.py,内部用openaiSDK 发请求,所以只要base_url指向一个 OpenAI 兼容端点,api_key用对应平台的 Key,就能无缝切换。TaoToken 提供的就是这样一个统一通道,官网在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 端点是 https://taotoken.net/api 。
先说清楚三件套,这是后面所有配置的基础:
| 配置项 | 值 | 说明 |
|---|---|---|
| Base URL | https://taotoken.net/api | OpenAI 兼容端点,末尾不要多加/v1 |
| API Key | 在控制台创建 | 形如sk-开头的一串 |
| Model ID | 按控制台可用列表填 | 例如gpt-4o、claude-3-5-sonnet等 |
Key 的获取路径是:登录后进入控制台,在 API Keys 页面新建一个 Key。控制台地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API Keys 页面是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。创建后立刻复制保存,页面刷新后不再完整显示。
接下来改config/config.toml。OpenManus 的配置结构里,[llm]段是主模型,[llm.vision]是视觉模型(浏览器截图分析用)。把两段的base_url、api_key、model都指向 TaoToken:
[llm] model = "gpt-4o" base_url = "https://taotoken.net/api" api_key = "sk-你的TaoTokenKey" max_tokens = 4096 temperature = 0.0 [llm.vision] model = "gpt-4o" base_url = "https://taotoken.net/api" api_key = "sk-你的TaoTokenKey" max_tokens = 4096 temperature = 0.0如果你更习惯用环境变量,OpenManus 也支持在.env里覆盖。项目根目录复制一份.env.example为.env,写入:
LLM_API_KEY=sk-你的TaoTokenKey LLM_BASE_URL=https://taotoken.net/api LLM_MODEL=gpt-4o注意config.toml和环境变量的优先级:app/config.py里通常先读 toml,再用环境变量覆盖,所以两者都写时以环境变量为准。我建议只保留一处,避免排查时自己绕晕。
改完配置后,先别急着跑完整 agent,先用一个最小脚本验证 LLM 通道本身是通的。在项目根目录新建check_llm.py:
import asyncio from app.llm import LLM from app.schema import Message async def main(): llm = LLM() resp = await llm.ask([ Message.user_message("只回复两个字:通了") ]) print("LLM 返回:", resp) if __name__ == "__main__": asyncio.run(main())运行python check_llm.py。如果打印出「通了」,说明 Base URL、Key、Model 三件套都对,agent 的think()就有稳定的模型来源了。这一步是整个 OpenManus 跑通的地基,地基不稳,后面 tool 调用全是玄学问题。
3. 可复制的 settings 片段与 tool 注册到 agent 的完整配置
这一节把配置和代码放在一起,方便你直接抄。OpenManus 的工具注册发生在 agent 初始化时,以 Manus 为例,它会把 BrowserUseTool、PythonExecute、WebSearch 等工具塞进available_tools,然后 ToolCallAgent 在think()时把这些工具的 schema 一起发给 LLM。
先看完整的config/config.toml,这是最关键的 settings 片段:
[llm] model = "gpt-4o" base_url = "https://taotoken.net/api" api_key = "sk-你的TaoTokenKey" max_tokens = 4096 temperature = 0.0 [llm.vision] model = "gpt-4o" base_url = "https://taotoken.net/api" api_key = "sk-你的TaoTokenKey" max_tokens = 4096 temperature = 0.0 [browser] headless = false disable_security = true [search] engine = "Google"如果你用 Claude 系列模型做 agent 的推理,Model ID 换成对应的即可,Base URL 和 Key 不变。TaoToken 的模型对话入口在 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,你可以在里面先手动测一下目标模型是否可用,再去改 OpenManus 配置,能省不少来回。
然后是 tool 注册。OpenManus 里工具通过available_tools字典注入,键是工具名,值是工具实例。下面这段是精简后的注册逻辑,你可以对照自己的app/agent/manus.py:
from app.tool import BrowserUseTool, PythonExecute, WebSearch, Terminate class Manus(ToolCallAgent): name: str = "Manus" description: str = "A versatile agent that can solve various tasks using multiple tools" available_tools: ToolCollection = Field( default_factory=lambda: ToolCollection( PythonExecute(), BrowserUseTool(), WebSearch(), Terminate(), ) )ToolCollection会把每个工具的name、description、parameters转成 OpenAI function calling 格式。ToolCallAgent 的think()大致逻辑是:
async def think(self) -> bool: messages = self.memory.to_messages() response = await self.llm.ask_tool( messages=messages, tools=self.available_tools.to_params(), tool_choice="auto", ) self.memory.add_message(Message.assistant_message(response.content, tool_calls=response.tool_calls)) return bool(response.tool_calls)act()则遍历response.tool_calls,按function.name找到工具,json.loads(function.arguments)解析参数,调用execute():
async def act(self) -> str: for tool_call in self.tool_calls: tool = self.available_tools.get(tool_call.function.name) args = json.loads(tool_call.function.arguments) result = await tool.execute(**args) self.memory.add_message(Message.tool_message(result, tool_call.id)) return "Tools executed"看到这里,agent 和 tool 的协作闭环就完整了:LLM 决定调哪个 tool → agent 解析 tool_call → tool.execute 返回 observation → observation 写回 memory → 下一轮 think 带上新 observation。你要验证的,就是这个闭环里每一次 LLM 请求都经 TaoToken 正常返回。
4. 验证一次 tool 注册到 agent 调用的完整请求
配置改好后,跑一次真实调用。OpenManus 的入口是main.py,启动命令:
python main.py启动后它会进入交互模式,输入一个必须用工具才能完成的任务,比如:
帮我用 python_execute 计算 123 * 456,并打印结果预期行为是:agent 的think()通过 TaoToken 请求模型,模型返回一个python_execute的 tool_call,参数是{"code": "print(123 * 456)"};act()执行后 observation 是56088;下一轮think()把 observation 带回模型,模型判断任务完成,返回终止。
如果你想更直接地验证 tool 本身,可以单独跑python_execute.py里注释掉的测试代码。把文件末尾的注释解开,运行:
python app/tool/python_execute.py我实测的输出里,示例 2 算术运算返回成功: True 输出: 4,示例 3 语法错误返回成功: False 输出: '(' was never closed,示例 4 超时返回成功: False 输出: Execution timeout after 1 seconds。这些结果说明 tool 的execute()逻辑是自洽的,问题只会出在 agent 到 LLM 这一段。
再验证浏览器工具。browser_use_tool.py末尾也有注释掉的测试,解开后访问一个页面并提取内容:
async def go_to_bilibili(tool): result = await tool.execute( action="go_to_url", url="https://www.bilibili.com" ) print(result.output) result = await tool.execute( action="extract_content", url="https://www.bilibili.com", goal="get_content" ) print(result.output)这里有个我踩过的坑:extract_content在原始实现里必须调用 LLM 做内容抽取,而且必须先go_to_url让页面加载完成,否则context.get_current_page()拿不到有效页面。如果你直接调extract_content不先导航,会报页面相关错误。这不是 TaoToken 的问题,是工具本身的设计约束,等待上游修复。
验证成功的标志有三个:第一,check_llm.py能打印模型回复;第二,main.py里输入任务后 agent 能返回 tool_call 并执行;第三,终端日志里每次 LLM 请求都指向https://taotoken.net/api,没有出现连接超时或 401。三个都满足,说明 agent 与 tool 的协作链路已经跑通。
5. 本篇常见报错排查:401、local proxy failed、reading choices、OAuth
跑 OpenManus 时最容易撞上的几类报错,我按现象、原因、处理列出来,方便你对照。
401 Unauthorized。现象是check_llm.py直接抛openai.AuthenticationError: Error code: 401。原因通常是 Key 复制不完整、Key 前后有空格、或者config.toml和环境变量里的 Key 不一致。处理:重新去 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 复制一次,确认api_key = "sk-..."引号内没有多余字符,同时检查.env是否覆盖了 toml。
local proxy failed / Connection error。现象是openai.APIConnectionError: Connection error或日志里出现local proxy failed。原因多半是本地环境变量里残留了HTTP_PROXY、HTTPS_PROXY指向一个已经失效的地址,导致 openai SDK 走了一个不通的出口。处理:检查echo $HTTP_PROXY、echo $HTTPS_PROXY,如果指向本地端口且该端口没有服务,清掉这两个变量再跑。注意这里说的是清理本地无效代理配置,不是让你去搭什么通道。
reading choices 报错。现象是KeyError: 'choices'或TypeError: 'NoneType' object is not subscriptable,发生在解析 LLM 响应时。原因是模型返回体里没有choices字段,通常是 Base URL 写错,比如多写了/v1变成https://taotoken.net/api/v1/v1,或者请求打到了一个不返回 OpenAI 格式的端点。处理:确认base_url = "https://taotoken.net/api",末尾不加/v1,因为 SDK 会自己拼/chat/completions。
OAuth / token 过期类报错。现象是invalid_api_key或token expired。如果你用的是某些需要 OAuth 刷新的客户端,Key 可能有时效。处理:回到控制台重新生成 Key,更新到config.toml和.env。如果你在用 Claude Code 这类工具,它的配置在~/.claude/settings.json或项目级 settings 里,同样把 Base URL、Key、Model ID 三件套对齐即可,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。
tool_call 解析失败。现象是 agent 一直 think 但 act 不执行,日志里response.tool_calls为空。原因是模型没有按 function calling 格式返回,可能是 Model ID 不支持 tool use,或者tool_choice设置有问题。处理:换一个明确支持 function calling 的模型,确认tools参数正确传入。这一步排查完,agent 与 tool 的闭环基本就稳了。
6. 把统一通道用顺之后的下一步
OpenManus 的 agent 与 tool 模块,本质是一个 ReAct 循环加一层工具分发。你把config.toml里的base_url指向 https://taotoken.net/api ,把 Key 和 Model ID 对齐,agent 的think()就有了稳定输入,tool 的execute()结果也能可靠回传。我自己的经验是,先把check_llm.py跑通,再跑python_execute.py的单测,最后才启动main.py做端到端,这样出问题时能快速定位是通道问题还是工具问题。
如果你接下来要长期跑编码类或 Agent 类任务,可以了解下 Coding Plan,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,它更适合高频、长时间的模型调用场景。想先手动验证模型效果,模型对话入口在 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。接入过程中遇到配置问题,接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 里有各客户端的完整参数说明。
最后留一个实用技巧:改完配置后,在app/llm.py的请求处加一行日志,打印实际使用的base_url和model,这样每次 agent 发起 think 时你都能确认请求确实走了统一通道,而不是被某个残留环境变量劫持。这个习惯帮我省了很多「明明改了配置却没生效」的排查时间。