Manus Agent实现原理:多Agent协作、工具调用与断点恢复
发布时间:2026/9/19 20:04:35来源:尧图网络
简介Manus 实现原理解析.pdf 是一份深入剖析 Manus 智能代理系统工作机制的技术文档面向人工智能学习者、智能体开发者以及希望借助自动化完成复杂任务的工程技术人员帮助读者理解该系统的核心运行逻辑。文件以规划机制、代码使用策略和交互方式为切入点详细展开动态任务规划、失败处理与调整、分层规划架构中的战略层、战术层、执行层和监控评估层并延伸讨论规划的时间维度、代码驱动的问题解决全流程包括自动化工具设计、算法选择、代码实现与优化以及 Python 在数据处理、科学计算、文件操作和系统交互中的具体用法。资源为单个 PDF 文件大小约 1.09MB内容集中精炼便于直接阅读、标记和反复查阅。目前已有 117 人学习读者可从中获得智能体任务拆解、异常恢复策略、分层系统设计以及多语言编程选型等兼具原理性和实操价值的参考思路。1. 从“能聊天”到“能自己把事做完”Manus 到底解决的是什么Manus 走红的那场演示里真正让工程师停下刷手机的不是对话质量而是它真的在一台云电脑上打开浏览器、翻页面、下文件、把结果整理成表格。这个动作背后没有魔法只有一条很朴素的链路把一个大模型拆成规划者和执行者再为执行者配一套能操纵操作系统和浏览器的工具。下面要讲的是这类 Agent 的功能架构落到工程上之后的分寸多 Agent 怎么分工、工具怎么暴露、上下文怎么续、任务断了怎么恢复。如果你正在做 Agent 平台、RPA 或内部自动化并且对“能自主完成任务”的系统抱有怀疑这套判断框架可以直接拿去用。2. Manus 实现原理的第一层Planner 与 Executor 的分工Manus 官方一直强调“Multiple Agents”但多 Agent 不是噱头而是对长尾任务失控率的一种工程妥协。与其让一个模型从头到尾背负所有决策不如把“做什么”和“怎么做”切成两层每层只维护一个很小的职责面。这一章先拆开这层分工再给出消息结构的参考实现。2.1 单 Agent 在长任务里最典型的三个失控点我早期做 Agent 时也试过单模型 ReAct 循环每次迭代让模型看历史、想下一步、调工具。短任务效果尚可任务一旦超过十步失控基本是必然的原因集中在三处。第一是规划漂移。模型在第五步之后开始忘记最初的约束比如用户要“筛选 Java 简历”它却跑去统计所有简历的平均工作年限。第二是工具迷失模型在“想”和“做”之间反复横跳生成了大量计划文本却迟迟不触发工具调用。第三是错误复滚某一步产生的脏数据没有清理后续判断全被带偏模型不去修错误反而围绕错误结论绕出更诡异的路径。把规划和执行拆成两个角色后上述问题会被限制在小范围内。规划层只输出子任务和验收条件执行层只负责把单个子任务做透。这样即使某个子任务反复失败也只是局部的重试不会把整条对话历史拖入混乱。2.2 Planner 的任务拆解把“筛选简历”变成可执行子任务一个常见做法是让 Planner 先输出结构化任务清单而不是自然语言段落。以“筛选简历”为例规划结果大致长这样{ goal: 从 download/ 目录中筛选出符合 Java 后端岗位的简历, subtasks: [ { id: s1, action: list_files, target: download/, acceptance: 能列出目录下所有 PDF 文件 }, { id: s2, action: parse_pdf, target: download/*.pdf, acceptance: 抽取每个文件的关键字段姓名、年限、技能 }, { id: s3, action: match_criteria, target: parsed_resumes.json, acceptance: 输出 3 年以上 Java 经验、有 Spring 项目的候选人列表 } ], fallback: 如果 PDF 无法解析打印文件名并跳过 }这段规划里最值得关注的是acceptance字段它比action更重要。执行层每完成一个子任务都要拿 acceptance 去校验校验失败就回到该子任务重试而不是继续往下走。这正是 Agent 系统与普通函数调用链的差别每个子任务都有完成定义而不是“代码不报错就算完”。另外fallback也不能省。很多实现只规划成功路径一旦遇到缺文件、权限不足、页面结构变化执行层会当场卡死。把“失败时做什么”写进规划后续稳定性会高很多。你还可以让 Planner 为每个子任务附带预估时间方便执行层做超时控制。2.3 代理间通信一条消息里该带哪些字段Planner 和 Executor 之间不是同步函数调用而是异步消息。你可以把它理解成一个极简的消息队列每条消息至少要有五类信息dataclass class AgentMessage: msg_id: str # 消息唯一 ID靠它做幂等防止重复处理 task_id: str # 子任务 ID对应 planner 输出的 subtasks.id agent: str # 发送方planner / executor / verifier intent: str # plan_update / action_request / result_report payload: dict # 具体内容任务描述、执行结果或错误上下文payload的 key 应尽量固定。比如action_request的 payload 固定为{tool: ..., args: ...}result_report固定为{status: ok|retry|failed, data: ..., error: ...}。固定的 schema 能带来两个实际好处一是可以对每个子任务做超时控制二是能把执行轨迹落成结构化日志方便事后回放。这里我给各角色做了一个职责对照方便你规划自己的 Agent 系统角色输入输出关键约束Planner用户目标子任务列表只规划不执行Executor单个子任务执行结果只做当前任务不越级Verifier子任务与验收条件pass / retry / failed失败时返回错误上下文我建议在实现里加一个 verifier 角色即使由同一个 Executor 模型兼任也可以每完成一个子任务先自问“验收条件满足了吗”再决定是否上报 Planner。一个 Manus case 里最值得照搬的设计就是这个它把质量检查点放在每一个子任务之后而不是留到任务最后统一验收。后面章节会展开工具层因为只有消息结构但没有手和眼Planner 拆得再细也落不了地。3. Manus 的“手和眼睛”工具调用与浏览器操作的实现细节Manus 之所以看起来像“真人操作电脑”是因为它运行在云端虚拟机里模型的每次决策都会转成一个真实动作点哪个按钮、往输入框填什么、滚动到什么位置、跑哪条命令。这一章讲动作空间怎么设计、工具定义里哪些参数最影响效果以及浏览器页面状态怎么被模型“看见”。3.1 动作空间Action Space大模型输出如何映射成电脑操作浏览器自动化里最容易犯的错是让模型直接输出像素坐标。真实页面在不同分辨率、不同缩放比例下坐标全变而且模型对坐标的感知极其不稳定。更稳的做法是让模型操作一棵“页面语义树”把动作空间收敛成有限集合。比如维持下面这张动作表基本能覆盖绝大多数网页任务动作参数说明gotourl打开指定页面clickselector点击某个可交互元素inputselector, text向输入框填入文本presskey发送键盘按键如 Enter、Escapescrolldirection, distance按方向滚动页面extractselector提取元素文本或属性screenshot无获取当前视口截图shellcommand在云端 VM 里执行命令动作空间越窄模型越容易收敛。不要把自动化测试框架里的全部 API 都暴露给模型只保留与任务强相关的 8 到 10 个动作即可。动作背后的实现可以复用 Playwright 或 CDP但暴露给模型的接口一定要按模型易学的方式重写。当模型需要执行shell动作时常见做法是把 command 交给白名单解释器校验可执行路径并设置超时。Manus 能在演示里装依赖、跑脚本靠的就是这个动作而不是模型直接拥有操作系统的 Root 权限。3.2 暴露给大模型的工具定义必调的 3 个参数大模型侧的 tools 定义要按函数式 schema 写。假设你走 OpenAI 兼容接口一个点击动作的定义如下tools [ { type: function, function: { name: browser_click, description: 根据 CSS selector 点击页面元素仅用于可见且可交互的元素, parameters: { type: object, properties: { selector: { type: string, description: CSS selector例如 #submit } }, required: [selector] } } } ]必调的 3 个参数是description的边界描述、required的最小化、以及取值约束。description决定模型会不会选错工具所以不要只写“点击元素”要写清楚适用条件和失败场景。required要尽量最小化多余必填项会让模型为了凑参数而编造值。取值约束则是用enum或format限制合法输入不让模型把格式传错。工具响应里同样要讲究除了返回“动作是否成功”还要返回“动作后的页面状态”。很多实现只返回执行结果模型看不到界面变化下一轮就很容易重复点击同一个按钮。3.3 页面状态采集截图、DOM 摘要与 JS 注入取数浏览器自动化里最大的坑是不可观测页面渲染了但模型不知道。类 Manus 系统通用做法是分三步。第一步先截图让多模态模型看整体布局如果截图不足以定位再抓 DOM 摘要对于表格这类结构化数据直接用 JS 注入把数据抽成 JSON。DOM 摘要是把原始 HTML 压缩成模型友好的文本去掉 script、style、svg只保留含文本的元素和可交互属性。核心是控制 token 长度。常见做法是只保留前 200 个可交互节点超出部分按节点深度丢弃而不是按出现顺序丢弃这样能保住关键表单区域。JS 注入取数则更可控// 在目标页面执行把表格数据转成 JSON const rows Array.from(document.querySelectorAll(table tr)).map(tr Array.from(tr.querySelectorAll(td,th)).map(cell cell.innerText.trim()) ); return JSON.stringify({ rowCount: rows.length, rows: rows.slice(0, 50) });这段注入脚本的价值在于把“模型看图猜数据”变成“程序精确取数”。JS 注入的实现原理是把脚本通过 CDP 的Runtime.evaluate送到页面上下文执行同时设置returnByValue: true才能拿回可序列化的 JSON而不是一个远程对象引用。执行后要验证rowCount与rows.length的关系。如果rowCount远大于 50说明数据被截断需要提示模型翻页或调整提取范围而不是默默接受不完整结果。3.4 最小可跑的浏览器操作循环在本地复现一个最小闭环可以用 Playwright 把“动作 - 观察 - 再动作”的循环写出来。下面代码不依赖任何 Agent 框架只为了让你看清循环骨架import json from playwright.sync_api import sync_playwright ACTIONS [ {action: goto, args: {url: https://example.com}}, {action: extract, args: {selector: h1}}, ] def run_agent_loop(actions): with sync_playwright() as p: browser p.chromium.launch(headlessTrue) page browser.new_page() for step in actions: if step[action] goto: page.goto(step[args][url], timeout15000) elif step[action] extract: text page.inner_text(step[args][selector]) # 实际系统里这里会把观察结果发回给模型 print(json.dumps({step: step, observation: text})) browser.close() run_agent_loop(ACTIONS)这个循环最关键的不是 Playwright API而是“观察结果又被作为下一条消息喂回模型”。print(json.dumps(...))的位置在实际系统里是一个消息总线接口。当观察结果与预期不符模型会生成修正动作而不是按原计划硬跑。这也是类 Manus 系统排错时最先要看的地方动作、观察、推理三段信息是否都进入了下一轮上下文。4. Manus 不“失忆”的关键上下文管理与断点恢复Agent 做长任务时最大的成本往往不是工具调用而是上下文越来越长。Manus 这类云端异步任务的精巧之处在于它没有试图把全部历史都塞进窗口而是把相当一部分状态外置到了文件系统里。这一章讲工作区怎么组织、上下文不够用时怎么压缩、以及任务中断后怎么恢复。4.1 工作区落盘把 Agent 状态当作文件系统问题Manus 在云端 VM 里建立独立的工作区中间产物以文件形式存在。这个设计的工程收益远大于“临时存点东西”文件系统天然可继承、可审计、可恢复比把中间结果全部塞进上下文窗口靠谱得多。我通常会按下面这个结构组织工作区workspace/ ├── tasks/ │ └── task_20250301_001/ │ ├── plan.json │ ├── checkpoint.json │ ├── artifacts/ │ ├── logs/ │ └── memory/ │ └── summary.md每个任务一个目录plan.json 记录 Planner 的子任务checkpoint.json 记录当前执行位置artifacts 保存下载文件和生成文件memory 放跨步骤摘要。这里的关键是执行器每完成一个子任务先写 checkpoint再上报结果上报失败没关系文件已经落盘。各文件的读写关系可以这样划分文件或目录写入方读取方用途plan.jsonPlannerExecutor、恢复逻辑任务分解与验收条件checkpoint.jsonExecutor恢复逻辑、前端展示断点与进度artifacts/Executor用户、报告生成下载与生成的中间产物logs/所有角色调试、审计动作与观察记录checkpoint.json 的最小结构如下{ task_id: task_20250301_001, current_subtask: s3, completed: [s1, s2], pending: [s4], last_error: null, updated_at: 2025-03-01T12:34:56Z }注意这里只记录子任务编号和完成状态不保存模型内部消息。这样恢复时不需要回放一整段对话只要把 plan 和 checkpoint 重新读进上下文就能继续执行。Manus 能支持长时间运行的异步任务靠的就是这种把“记忆”外置到文件系统的方式而不是无限扩大模型窗口。4.2 摘要压缩窗口不够用时的降级策略当任务超过上下文窗口直接裁剪历史会丢失关键约束。常见做法是分级压缩保留最近 N 步原始消息把更早的消息按主题合并成摘要再把摘要作为上下文的一部分继续喂给模型。摘要的生成本身也是一次 LLM 调用要控制它的 token 预算。async def summarize_events(events: list[dict], max_chars: int 1200) - str: prompt ( 下面是一段 Agent 执行日志。请压缩成一段中文摘要。 保留当前目标、已完成的关键操作、生成过的文件路径、未解决的错误。 丢弃重复的点击、滚动、等待动作。 f控制在 {max_chars} 字以内。\n\n json.dumps(events, ensure_asciiFalse) ) # 实际系统里这里调用 LLM 并返回摘要文本 return await call_llm(prompt)压缩参数里最值得调的是max_chars。设太小模型会丢掉关键约束设太大上下文又装不下。我一般按原内容的 15% 到 20% 定初始值之后用任务完成率往回标定。另一个细节是压缩边界的划分不要把一条正在执行的子任务劈成两半摘要在子任务边界处生成语义才完整。4.3 断点续跑checkpoint 与人工接管的边界任务运行到一半失败是 Agent 系统最被人诟病的地方。类 Manus 系统的设计哲学是把不可预期的异常暴露给用户确认而不是让模型自己硬猜。恢复流程可以抽象成三步。第一步读取 checkpoint找出current_subtask。第二步对失败子任务执行重试最多 N 次。第三步重试仍失败就把 plan、checkpoint、最近日志打包成报告交给用户让用户决定是改需求、补文件还是直接停。人工接管的界面也可以做得很简单展示当前子任务、验收条件和失败原因再给“重试 / 跳过 / 终止”三个按钮。我在本地验证时常用一个很小的脚本模拟恢复流程checkpoint_fileworkspace/tasks/task_20250301_001/checkpoint.json if [ -f $checkpoint_file ]; then current$(python3 -c import json;print(json.load(open($checkpoint_file))[current_subtask])) echo 从子任务 $current 恢复 else echo 无 checkpoint从第一个子任务开始 fi这段脚本的关键是current_subtask字段落在文件里而不是进程里所以即使云端 VM 重启也能恢复。5. 复现类 Manus 系统时值得先做的验证在你真的去调 Prompt 或换模型之前先建立一套可量化的验证方式。下面是我常用来给 Agent 系统做“体检”的四个指标和排查技巧。5.1 四个可量化指标先跑通指标采集方式参考阈值任务完成率端到端人工验收高于 80%工具调用成功率按工具类型分别统计浏览器类高于 70%平均步数每个任务的 action 计数小于 12 步峰值 token每轮输入 token 取最大低于窗口上限的 70%这四个指标里平均步数最容易被忽略但它能直接暴露“模型在原地打转”的问题。如果模型反复点击同一个按钮工具调用成功率可能还是 100%但平均步数已经出卖了规划层。峰值 token 则决定了系统能否稳定跑完长任务超过窗口上限 70% 时就应该触发摘要压缩或主动要求用户确认目标。5.2 一个排查“卡死”的调试习惯我在本地调试类 Manus 系统时坚持在每个动作后打印三角形的三段信息action模型想做什么、observation页面或工具返回了什么、reason模型为什么这样做。只要把这三段落盘任务卡死时直接翻日志tail -n 50 logs/action_trace.jsonl | jq {action, observation, reason}这个习惯能解决大多数“看起来像模型不行其实是上一轮观察没进上下文”的问题。如果 observation 为空就优先查页面采集链路而不是先调模型提示词。每次改完架构后在 CI 里跑几个代表任务对比上面四个指标几秒内就能知道多 Agent 协作有没有回退这个信号比任何人的代码评审都来得直接。本文还有配套的精品资源点击获取
网站建设高端定制企业官网