尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

AI Agent 之 OpenManus run_flow 源码 Debug 解读:从 PlanningFlow 断点看执行链路

AI Agent 之 OpenManus run_flow 源码 Debug 解读:从 PlanningFlow 断点看执行链路 1. 从 run_flow.py 断点看 OpenManus 执行链路PlanningFlow 调度到底卡在哪如果你正在本地复现 OpenManus 的 AI Agent 执行链路大概率会遇到一个很典型的现象run_flow.py跑起来了日志也打印了「Creating initial plan」但后面就停在某个步骤不动或者PlanningFlow的execute方法走到一半直接抛异常堆栈指向_get_current_step_info或者_execute_step。这类问题光看日志很难定位因为 OpenManus 的调度逻辑分散在flow/planning.py、agent/manus.py、tool/planning.py三个文件里中间还夹着一次 LLM 的ask_tool调用。这篇内容聚焦的就是这条链路从run_flow.py主入口下断点一路跟到PlanningFlow.execute再到_create_initial_plan、_get_current_step_info、_execute_step最后落到Manus.run。我会给出可复制的 VS Codelaunch.json调试配置、关键断点位置、以及每个断点处应该观察的变量。适合已经能把 OpenManus 跑起来、但想搞清楚「规划步骤是怎么被消费的」「为什么某个 step 一直 in_progress」的开发者。OpenManus 的run_flow.py本质上是一个多 Agent 编排入口。它先通过FlowFactory.create_flow()拿到PlanningFlow把任务交给规划流规划流内部调用 LLM 生成一个 plan包含若干 step然后逐个 step 创建Manus实例去执行。理解这条链路的关键是搞清楚「plan 的创建」和「step 的执行」是两个完全独立的阶段中间靠PlanningTool的状态字典self.plans串联。很多异常分支就出在这两个阶段的衔接处。我试过在_create_initial_plan里直接打印response.tool_calls发现 LLM 返回的arguments有时是字符串、有时是 dict如果解析失败会静默continue导致 plan 根本没创建后面_get_current_step_info拿到的step_info就是None。这类问题不打断点基本看不出来。2. TaoToken 前置准备给 PlanningFlow 的 LLM 调用配一个稳定入口在开始 Debug 之前得先保证self.llm.ask_tool这一层是通的。OpenManus 的llm.py默认走 OpenAI 兼容接口你需要一个能稳定返回tool_calls的模型服务。我本地调试时用的是 TaoToken 的 API 入口它的 Base URL 和 Key 配置方式和 OpenAI SDK 一致改config.toml就行不用动源码。先拿到 Key打开 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 创建一个 API Key。这个 Key 后面会写进 OpenManus 的配置文件。然后确认你要用的模型 ID。PlanningFlow 的_create_initial_plan依赖tool_choicerequired也就是模型必须支持 function calling。你可以在模型对话页 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 里先手动测一下发一条带 tools 参数的请求看返回里有没有tool_calls字段。如果模型不支持response.tool_calls会是空代码会走到logger.warning(Creating default plan)分支生成一个只有三步的兜底 plan这会让你的 Debug 结果和预期完全不一样。OpenManus 的配置文件通常在项目根目录的config/config.toml。你需要改的是[llm]段[llm] model 你的模型ID base_url https://taotoken.net/api api_key sk-你的Key max_tokens 4096 temperature 0.0注意base_url后面不要加/v1OpenManus 的llm.py内部会拼接路径。如果你加了/v1请求会变成/v1/v1/chat/completions直接 404。这个坑我在第一次配的时候踩过日志里只显示Connection error不打印具体 URL排查了半天。配好之后先别急着跑run_flow.py用一个小脚本单独验证 LLM 层import asyncio from app.llm import LLM async def main(): llm LLM() resp await llm.ask_tool( messages[{role: user, content: 创建一个三步计划}], tools[{ type: function, function: { name: planning, parameters: {type: object, properties: {}} } }], tool_choicerequired, ) print(resp.tool_calls) asyncio.run(main())如果这里能打印出tool_calls列表说明 LLM 层没问题可以进入源码 Debug。如果打印None或者空列表先换模型别往下走。3. 可复制调试配置launch.json 与 PlanningFlow 断点位置VS Code 调试 Python 异步代码关键是launch.json里的justMyCode和console配置。OpenManus 用了大量async/await如果justMyCode设成true你没法跟到asyncio内部的调度但设成false又会跳进一堆标准库。我的做法是设false然后用条件断点过滤。在项目根目录建.vscode/launch.json{ version: 0.2.0, configurations: [ { name: Debug run_flow, type: debugpy, request: launch, program: ${workspaceFolder}/run_flow.py, console: integratedTerminal, justMyCode: false, env: { PYTHONASYNCIODEBUG: 1, LOG_LEVEL: DEBUG }, args: [搜索薛之谦在百度上的信息] } ] }PYTHONASYNCIODEBUG1会让 asyncio 打印协程的创建和销毁对定位「协程没被 await」这类问题很有用。args里放你的测试任务建议用短任务比如「搜索 X 在百度上的信息」这样 plan 的步骤少断点不会跳太多次。断点位置按执行顺序排第一个断点在run_flow.py的flow FlowFactory.create_flow()之后观察flow的类型和flow.agents的内容。这里能看到agents_dict是怎么被塞进data[agents]的。第二个断点在app/flow/planning.py的_create_initial_plan方法里具体是response await self.llm.ask_tool(...)这一行之后。观察response.tool_calls的长度和tool_call.function.arguments的类型。如果arguments是字符串后面会走json.loads如果是 dict直接跳过解析。这个分支决定了 plan 能不能被正确创建。第三个断点在_create_initial_plan的result await self.planning_tool.execute(**args)之后。观察self.planning_tool.plans字典确认plan_id对应的 plan 是否被写入steps列表长度是多少step_statuses是不是全是not_started。第四个断点在PlanningFlow.execute的循环里self.current_step_index, step_info await self._get_current_step_info()之后。观察step_info是否为None。如果为None说明 plan 里没有not_started或in_progress的步骤循环会退出。第五个断点在step_result await self._execute_step(executor, step_info)之后。观察step_result的内容以及executor的类型是不是Manus。第六个断点在app/agent/manus.py的run方法入口。这里能看到step_prompt的完整内容确认它是不是当前步骤的 prompt。这些断点配合justMyCode: false基本能覆盖 PlanningFlow 调度的全链路。如果你只想看业务逻辑可以把justMyCode改回true但asyncio.wait_for的超时异常会看不到堆栈需要额外在except里打日志。4. 验证请求与成功结果从断点变量确认执行链路配好断点后按 F5 启动调试。第一个断点命中时flow应该是PlanningFlow实例flow.agents是一个 dict键是 agent 名字值是Manus实例。如果你传的是单个 agentagents_dict会自动包装成{default: agent}。继续到第二个断点response.tool_calls应该是一个列表长度通常为 1。tool_call.function.name是planningtool_call.function.arguments是一个 JSON 字符串内容类似{ command: create, title: Search Xuezhiqian on Baidu, steps: [ Open a web browser, Navigate to the Baidu website, Enter Xuezhiqian in the search bar, Press the search button, Review the search results ], plan_id: plan_1746675052 }注意plan_id在 LLM 返回时可能不存在代码里有一行args[plan_id] self.active_plan_id会强制覆盖。这个active_plan_id是在PlanningFlow.__init__里生成的格式是plan_加时间戳。到第三个断点self.planning_tool.plans里应该多了一个键为plan_1746675052的条目值是包含plan_id、title、steps、step_statuses、step_notes的字典。step_statuses初始全是not_started长度和steps一致。到第四个断点step_info应该是steps[0]的内容同时step_statuses[0]会从not_started变成in_progress。这个状态修改是在_get_current_step_info内部通过planning_tool.execute(commandmark_step)完成的。如果你在这里看到step_info是None回去检查第三个断点的plans字典大概率是 plan 没创建成功。到第五个断点executor是Manus实例step_result是Manus.run的返回值。如果Manus.run内部抛异常这里会直接跳到except分支你需要看step_result是不是None。到第六个断点step_prompt的内容应该是类似Current step: Open a web browser Step 1 of 5这个 prompt 是_execute_step内部拼的包含了当前步骤的描述和进度。Manus.run会拿这个 prompt 去调 LLM生成具体的工具调用。如果六个断点都能正常命中且变量符合预期说明 PlanningFlow 的调度链路是通的。接下来就是看Manus.run内部的工具调用是否成功那部分在上一篇文章里已经详细 Debug 过这里不展开。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth调试 PlanningFlow 时报错通常集中在 LLM 调用和工具执行两个环节。下面是我实际遇到过的几类对照你的日志看。401 Unauthorizedllm.py里抛出的说明 API Key 无效或者没被读到。先检查config.toml的api_key字段有没有写错再确认LLM类初始化时读的是哪个配置文件。OpenManus 支持环境变量覆盖如果你设了OPENAI_API_KEY它会优先用环境变量。排查方法是在llm.py的__init__里打断点看self.api_key的值。local proxy failed / Connection error这个报错通常不是代理问题而是base_url拼错了。OpenManus 的llm.py里有一行self.base_url base_url or os.getenv(OPENAI_BASE_URL)如果你在config.toml里写了base_url https://taotoken.net/api/v1实际请求会变成https://taotoken.net/api/v1/chat/completions但 SDK 内部可能又拼了一次/v1导致路径重复。正确写法是base_url https://taotoken.net/api不带/v1。reading choices 报错完整报错通常是KeyError: choices或者TypeError: NoneType object is not subscriptable发生在response.choices[0]这一行。原因是 LLM 返回的 JSON 里没有choices字段可能是模型不支持 function calling或者请求体格式不对。排查方法是在ask_tool里打印response的原始内容。如果返回的是{error: ...}看 error 信息。OAuth 相关报错如果你用的是 Claude Code 或者 Codex 的 OAuth 流程报错可能是OAuth token expired或者invalid_grant。这类问题不在 PlanningFlow 本身而在认证层。你需要重新走一遍授权流程拿到新的 token 再写回配置。OpenManus 本身不处理 OAuth它只认 API Key所以如果你混用了 OAuth token 和 API Key会直接 401。step_info 为 None这个不是报错是逻辑分支。_get_current_step_info返回None时execute循环会退出任务被标记为完成。如果你预期还有步骤没执行检查planning_tool.plans里的step_statuses看是不是所有步骤都变成了completed或者blocked。有时候 LLM 生成的 plan 里步骤描述重复mark_step会把多个步骤同时标记导致提前退出。Manus.run 超时asyncio.wait_for的超时时间默认是 300 秒如果某个步骤执行太久会抛TimeoutError。这个异常在execute里被捕获后会把当前步骤标记为blocked然后继续下一个步骤。如果你看到日志里某个步骤被跳过检查是不是超时了。可以在run_flow.py里把asyncio.wait_for的 timeout 参数调大或者在Manus.run内部加日志看卡在哪一步。排查这些错的时候建议把LOG_LEVEL设成DEBUGOpenManus 的 logger 会打印每次 LLM 请求的 URL 和响应摘要。如果日志不够直接在llm.py的ask_tool方法里加print(response)比断点快。6. 继续深入从 PlanningFlow 到 Manus 的衔接与调试建议PlanningFlow 的调度逻辑本身不复杂复杂的是它和 Manus 之间的状态传递。_execute_step拿到step_info后会拼一个step_prompt然后调executor.run(step_prompt)。Manus.run内部会再调一次 LLM这次是带工具列表的模型返回的tool_calls会被ToolCollection执行。执行结果会写回PlanningTool的step_notes同时step_statuses被标记为completed或blocked。如果你想继续 Debug 这条链路建议在Manus.run的tool_calls循环里加断点观察每个工具调用的name和arguments。常见的工具是bash、browser_use、file_saver、python_execute、terminate。terminate被调用时整个任务会提前结束PlanningFlow的循环也会退出。另一个值得关注的点是PlanningTool的mark_step命令。它接收plan_id、step_index、status、notes四个参数内部会校验step_index是否越界。如果你在_get_current_step_info里看到IndexError检查current_step_index是不是超过了steps的长度。这个索引是在PlanningTool内部维护的和PlanningFlow的循环变量不是同一个。最后给一个实用技巧在run_flow.py的asyncio.wait_for外面包一层try/except把TimeoutError和Exception分开捕获分别打印flow.planning_tool.plans的完整内容。这样即使任务失败你也能看到 plan 的最终状态知道卡在哪一步。这个日志比堆栈更有用因为堆栈只告诉你哪一行抛了异常不告诉你 plan 的状态。如果你想把这条链路跑得更稳可以在config.toml里把max_tokens调大避免 LLM 返回的 plan 被截断。截断的 plan 会导致json.loads失败代码走continueplan 创建失败后面全是None。这个坑很隐蔽因为日志里只有一行Failed to parse tool arguments不仔细看会以为是模型问题。调试 AI Agent 的执行链路核心是搞清楚「谁在什么时候改了哪个状态」。PlanningFlow 的状态在PlanningTool.plans里Manus 的状态在Memory里两者通过step_prompt和step_notes同步。把这两个状态字典在断点里看清楚大部分异常分支都能定位。
返回列表