OpenManus框架解析(一)完整 Prompt 组装架构与全链路流程
在 AI Agent 开发中,Prompt 组装是决定 Agent 推理能力、工具调用准确性、场景适配性的核心底层逻辑。很多 Agent 项目看似功能复杂,其核心底层均依靠一套分层、动态、可扩展的 Prompt 组装机制。
本文将基于完整的项目源码逻辑,层层拆解AI Agent 三段式 Prompt 组装架构,涵盖模板定义、动态注入、LLM 格式化全流程,附源码片段、执行时序、数据流图与核心设计思想,帮你彻底吃透 Agent Prompt 运行原理。
一、核心整体架构:三层分层设计
项目将 Prompt 组装拆分为三层独立职责,各层各司其职、解耦协作,兼顾静态模板统一性与动态场景灵活性:
Layer 1:模板定义层(app/prompt/*.py)
静态字符串模板,为每一类 Agent 独立配置专属的系统提示词(SYSTEM_PROMPT)和下一步思考提示词(NEXT_STEP_PROMPT),是所有 Prompt 的基础模板。
Layer 2:Agent 注入层(think() 方法)
核心动态逻辑层,负责读取静态模板、填充动态占位符、将下一步提示词注入对话记忆(memory),实现每轮推理的动态引导。
Layer 3:LLM 格式化层(ask_tool / format_messages)
API 适配层,将项目自定义的 Message 对象转为 OpenAI API 兼容字典格式,专门处理多模态图片、Token 计算、消息格式标准化。
架构极简总结:静态模板定义 → 动态内容注入 → 标准化 API 格式化
二、模板定义层:各类型 Agent 专属 Prompt 模板
项目中不同功能的 Agent 均拥有独立的 Prompt 模板文件,区分系统定位(SYSTEM_PROMPT)和迭代思考引导(NEXT_STEP_PROMPT),部分模板支持动态占位符。所有 Agent 模板配置汇总如下:
文件 | Agent 类型 | SYSTEM_PROMPT(系统定位) | NEXT_STEP_PROMPT(下一步引导) |
|---|---|---|---|
toolcall.py | ToolCallAgent | "You are an agent that can execute tool calls" | "If you want to stop interaction, use terminate tool" |
manus.py | Manus | "You are OpenManus, an all-capable AI assistant..." 含 {directory} 动态占位符 | 主动选择工具、拆解任务、解释执行结果 |
browser.py | BrowserAgent | 详细浏览器自动化规则(JSON响应、元素交互、导航、错误处理) | 含 {url_placeholder} 等5个动态占位符 |
mcp.py | MCPAgent | MCP协议说明、动态工具适配、异常错误处理规则 | 思考并选择最优 MCP 工具执行下一步操作 |
swe.py | SWEAgent | 命令行编程环境专属定位,限制单次仅调用一个工具 | 空字符串(无需额外引导) |
planning.py | PlanningAgent | 结构化任务规划专家定位 | 校验计划完整性,执行规划的下一步任务 |
visualization.py | VisualizationAgent | 数据分析与可视化专属定位,含 {directory} 占位符 | 单次仅选一个工具,遇到报错自动修复重试 |
三、Agent 注入层:think() 核心双路径逻辑
注入层是 Prompt 动态组装的核心,通过think()方法实现两条核心路径:系统提示词独立前置、下一步提示词入库迭代,两者逻辑完全隔离。
3.1 System Prompt:永久前置,不参与 Memory 截断
系统提示词用于定义 Agent 核心身份与能力,设计上不存入对话记忆 memory,每次调用 LLM 时单独前置拼接,永久不受max_messages=100截断规则影响,保证 Agent 身份定位全程不变。
核心源码(toolcall.py 47-55行):
response = await self.llm.ask_tool( messages=self.messages, # 对话历史(不含 system prompt) system_msgs=( [Message.system_message(self.system_prompt)] if self.system_prompt else None ), tools=self.available_tools.to_params(), tool_choice=self.tool_choices, )LLM 内部拼接逻辑(llm.py 684-688行):
核心特性:System 消息永远位于消息列表最首位,全程保留,不会被对话历史截断。
3.2 Next Step Prompt:逐轮注入 Memory,引导迭代思考
下一步提示词是每轮推理的引导核心,会以伪用户消息的形式追加到 memory 末尾,强制引导 LLM 基于当前对话状态思考后续操作,实现任务迭代。
核心源码(toolcall.py 41-43行):
if self.next_step_prompt: user_msg = Message.user_message(self.next_step_prompt) self.messages += [user_msg] # 追加到 memory 对话历史末尾执行效果:每一轮 Agent 推理,都会新增一条用户侧引导消息,让 LLM 持续对齐任务目标、推进流程。
四、LLM 格式化层:统一 API 消息格式
format_messages()是底层适配核心,负责将项目自定义的 Pydantic Message 对象,转为标准 OpenAI API 字典格式,同时专门处理多模态图片资源,兼容图文混合推理。
核心源码(llm.py 267-352行):
@staticmethod def format_messages(messages, supports_images=False) -> List[dict]: formatted_messages = [] for message in messages: # Pydantic 对象转为字典 if isinstance(message, Message): message = message.to_dict() # 处理 base64 图片,适配 OpenAI 多模态格式 if supports_images and message.get("base64_image"): message["content"] = [ {"type": "text", "text": message["content"]}, {"type": "image_url", "image_url": { "url": f"data:image/jpeg;base64,{message['base64_image']}" }} ] del message["base64_image"] # 过滤有效消息 if "content" in message or "tool_calls" in message: formatted_messages.append(message) return formatted_messages核心能力:自动兼容纯文本/图文多模态场景,统一 API 入参格式,上层业务无需感知底层适配逻辑。
五、动态 Prompt 占位符机制:场景化实时适配
静态模板无法适配动态场景(如浏览器 URL、页面滚动位置、标签页数量),因此项目设计了动态占位符填充机制,以 BrowserAgent、Manus Agent 为典型代表。
5.1 BrowserAgent 动态 Prompt 逻辑
BrowserAgent 每次推理前,会实时获取浏览器状态,填充 5 个专属占位符,同时支持截图注入多模态消息。
think() 主流程源码(browser.py 120-125行):
async def think(self) -> bool: self.next_step_prompt = ( await self.browser_context_helper.format_next_step_prompt() ) return await super().think()占位符填充核心逻辑(browser.py 47-79行):
async def format_next_step_prompt(self) -> str: # 1. 获取浏览器实时状态 browser_state = await self.get_browser_state() # 2. 提取状态信息,组装占位符内容 url_info = f"URL: {browser_state.url}, Title: {browser_state.title}" tabs_info = f"{len(tabs)} tab(s) available" content_above_info = f"({pixels_above} pixels)" if pixels_above else "" content_below_info = f"({pixels_below} pixels)" if pixels_below else "" # 3. 截图注入:将浏览器截图转为多模态用户消息 if self._current_base64_image: image_message = Message.user_message( content="Current browser screenshot:", base64_image=self._current_base64_image, ) self.agent.memory.add_message(image_message) # 4. 批量替换模板占位符 return NEXT_STEP_PROMPT.format( url_placeholder=url_info, tabs_placeholder=tabs_info, content_above_placeholder=content_above_info, content_below_placeholder=content_below_info, results_placeholder=results_info, )5.2 Manus Agent 动态切换 Prompt
Manus 作为通用 Agent,支持场景自适应 Prompt 切换:检测到使用浏览器工具时,自动切换为浏览器专属动态 Prompt,执行完成后恢复默认模板。
核心源码(manus.py 140-165行):
async def think(self) -> bool: # 检测最近消息是否调用浏览器工具 browser_in_use = any( tc.function.name == BrowserUseTool().name for msg in recent_messages if msg.tool_calls for tc in msg.tool_calls ) # 浏览器场景动态替换Prompt if browser_in_use: self.next_step_prompt = ( await self.browser_context_helper.format_next_step_prompt() ) result = await super().think() # 执行后恢复默认Prompt self.next_step_prompt = original_prompt return result六、完整 Prompt 组装时序案例
以Manus Agent 执行「搜索 Python 最新版本」任务为例,完整还原两轮推理的 Prompt 组装、消息入库、API 拼接全流程。
初始状态
Memory.messages = []
Step 1:第一轮推理
1、think() 注入下一步引导 Prompt,memory 新增伪用户消息:
Memory.messages = [ Message(role=user, content="Based on user needs, proactively select...") ]2、ask_tool 完成最终消息拼接(系统消息前置):
# 前置系统消息 Message(role=system, content="You are OpenManus, an all-capable AI assistant...") # 后续对话历史 Message(role=user, content="Based on user needs..") # 附带全局工具列表 tools: [{name:"python_execute",...}, {name:"browser_use",...}, ...]3、LLM 返回工具调用结果,memory 存入助手消息:
Message(role=assistant, content="我来搜索一下", tool_calls=[{name:"web_search", args:{query:"Python latest version"}}])4、act() 执行工具,存入工具返回结果消息,第一轮结束:
Memory.messages = [ Message(role=user, content="Based on user needs..."), Message(role=assistant, content="我来搜索一下", tool_calls=[...]), Message(role=tool, content="Observed output of cmd `web_search`...", tool_call_id="call_1") ]Step 2:第二轮推理
1、再次执行 think(),注入新的下一步 Prompt:
Memory.messages = [ # 上一轮历史消息 Message(role=user, content="Based on user needs..."), Message(role=assistant, content="我来搜索一下", tool_calls=[...]), Message(role=tool, content="Observed output..."), # 本轮新增引导消息 Message(role=user, content="Based on user needs...") ]2、最终 API 消息结构:系统消息永久前置,完整拼接历史对话
七、全链路数据流总图
为方便整体理解,梳理从模板定义到 API 调用的全链路闭环流程:
①模板定义:app/prompt/ 下定义静态 SYSTEM、NEXT_STEP 模板,预留动态占位符
②Agent 初始化:实例化 Agent,填充固定占位符(如 workspace 目录),绑定 Prompt 模板
③每轮 think() 执行:动态替换场景占位符、注入下一步引导 Prompt、追加截图多模态消息
④LLM 格式化组装:系统消息前置、历史消息标准化、图片格式转换、Token 校验、挂载工具列表
⑤API 调用:组装完成的标准消息体、工具参数传入 OpenAI 接口,获取推理结果
八、核心设计决策与底层原理
这套 Prompt 架构的所有设计,均为解决 Agent 推理稳定性、场景适配性、可扩展性问题,核心设计决策汇总:
设计点 | 具体实现方式 | 核心设计原因 |
|---|---|---|
System Prompt 不入库 Memory | 单独传递 system_msgs,调用时前置拼接 | 不受 max_messages 截断影响,永久保留 Agent 身份定位 |
Next Step Prompt 逐轮入库 | 每轮以伪 user 消息追加至对话历史 | 贴合 LLM 对话交互逻辑,精准引导迭代思考 |
原生 str.format() 占位符 | 自定义 {key} 占位符,代码动态填充 | 无需引入 Jinja2 等模板引擎,轻量化、低依赖、可读性强 |
截图独立多模态传递 | base64 字段单独存储,格式化时转为标准图片格式 | 不污染文本对话历史,最大化利用多模态模型能力 |
工具列表动态生成 | 每次 think() 调用 to_params() 重新生成工具参数 | 适配 MCPAgent 动态增减工具的场景,保证工具列表实时准确 |
无复杂模板语法 | 纯字符串 + format 替换实现所有动态能力 | 降低项目复杂度,便于 Prompt 调试、修改、维护 |
九、Memory 最终消息形态(实战落地)
一次完整的 Manus 任务执行后,Memory 中真实存储的对话序列(无 System 消息,仅存交互历史):
索引 | 角色 | 内容说明 | 消息来源 |
|---|---|---|---|
0 | user | 帮我搜索 Python 最新版本 | 用户原始输入 |
1 | user | Based on user needs, proactively... | 第一轮 Next Step 注入 |
2 | assistant | 我来搜索 Python 最新版本 + 工具调用 | 第一轮 LLM 推理返回 |
3 | tool | 网页搜索结果数据 | 第一轮工具执行返回 |
4 | user | Based on user needs, proactively... | 第二轮 Next Step 注入 |
5 | assistant | 输出结果 + terminate 结束工具 | 第二轮 LLM 推理返回 |
6 | tool | 任务结束提示 | 第二轮工具执行返回 |
十、总结
这套三层 Prompt 组装架构,是 AI Agent 实现智能化、动态化、场景化推理的核心基石:
1、分层解耦:静态模板、动态注入、API 格式化三层分离,各司其职;
2、设计精巧:系统提示永久常驻、迭代提示逐轮更新,兼顾稳定性与灵活性;
3、场景适配:支持动态占位符、多模态图文、场景自适应 Prompt 切换;
4、轻量化可维护:无冗余模板依赖,逻辑清晰,便于迭代优化。
理解这套机制,就能彻底掌握 AI Agent 「如何思考、如何调用工具、如何迭代任务」的底层核心逻辑。
