- 👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家
- 📕系列专栏:Spring原理、JUC原理、Kafka原理、分布式技术原理、数据库技术、JVM原理、AI应用
- 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
- 🍂博主正在努力完成2025计划中:逆水行舟,不进则退
- 📝联系方式:nhs19990716,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬👀
文章目录
OpenManus 是一个开源的单智能体(Single-Agent)系统,旨在复现多智能体系统 Manus 的核心功能,但通过工程化整合和简化设计降低使用门槛。以下是其实现原理、关键代码逻辑及具体操作步骤的详细分析:
一、OpenManus 的核心架构
- 系统定位
OpenManus 被设计为“单智能体系统”,通过整合大语言模型(LLM)作为认知核心,结合预定义的工具库(如浏览器操作、Python 执行、文件保存等)完成任务规划与执行。其核心能力包括规划、执行、记忆,但工具扩展需手动实现 基类 - 技术栈
- LLM 驱动:依赖大语言模型(如 GPT-4o、Qwen 等)生成任务计划和工具调用指令。
- 工具库:内置\等工具,通过 JSON 参数配置调用1。
- 流程控制:通过多轮迭代的“规划-执行-反馈”循环完成任务,LLM 在每个步骤中动态调整策略
二、关键代码逻辑
1. 工具扩展机制
所有工具需继承 `基类并实现 方法。以下是一个自定义工具的示例:
public abstract class BaseTool {
// 工具名称和描述,用于LLM识别功能
public abstract String getName();
public abstract String getDescription();
// 执行逻辑,输入为LLM生成的JSON参数
public abstract String execute(Map<String, Object> params);
}
// 示例:浏览器操作工具
public class BrowserUseTool extends BaseTool {
@Override
public String getName() { return "browser_use"; }
@Override
public String getDescription() { return "Execute browser actions like page navigation or JS execution."; }
@Override
public String execute(Map<String, Object> params) {
String action = (String) params.get("action");
String script = (String) params.get("script");
// 实际调用浏览器驱动(如Selenium)
return WebDriver.executeJavaScript(script);
}
}
2. LLM 的规划与调度
LLM 根据用户指令生成工具调用链,格式如下(JSON 示例):
{
"tool_calls": [
{
"name": "browser_use",
"arguments": {
"action": "execute_js",
"script": "function extractData() { ... }"
}
},
{
"name": "python_execute",
"arguments": {
"code": "data = process(extracted_data)"
}
}
]
}
3. 主流程代码(简化版)
# main.py 核心逻辑
class OpenManus:
def __init__(self, llm_config):
self.llm = LLMClient(**llm_config)
self.tools = self._load_tools() # 加载所有注册的工具
def run(self, user_input):
plan = self.llm.generate_plan(user_input) # LLM生成初始计划
context = {}
for step in plan['steps']:
tool = self._select_tool(step['tool_name'])
result = tool.execute(step['parameters'])
context.update({step['output_key']: result})
# 动态调整后续步骤(LLM重新规划)
if self.llm.evaluate(result) == "NEED_RETRY":
plan = self.llm.replan(context)
return self.llm.generate_final_output(context)
# 示例调用
manus = OpenManus(config)
manus.run("访问知乎,分析Manus相关帖子并生成小红书风格报告")
三、具体操作步骤
环境准备
git clone https://github.com/mannaandpoem/OpenManus.git
cd OpenManus
pip install -r requirements.txt
cp config/config.example.toml config/config.toml
# 修改config.toml中的LLM API配置(如qwen-max或GPT-4o)
工具调用示例
假设需新增一个
# tools/pdf_generator.py
class PDFGeneratorTool(BaseTool):
def execute(self, params):
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
filename = params.get("filename", "output.pdf")
content = params.get("content", "")
c = canvas.Canvas(filename, pagesize=letter)
c.drawString(100, 750, content)
c.save()
return {"status": "success", "path": filename}
任务执行
输入指令(终端或API):
python main.py --task "分析知乎热搜并生成PDF报告"
- **LLM 规划阶段**生成工具链(如抓取数据 →处理数据 →生成报告)。
- 执行阶段:依次调用工具并整合结果。
四、基于MCP Client 和 Server原理还原OpenManus实现流程呢?
基于上面的三步,我们其实能够知道,所谓manus其实核心流程是:
1、分析用户输入生成工具链
2、依次调用工具
3、整合所有结果。
那么结合我们之前提高的MCP-Client是否也能实现一个 简易版本的Manus呢?我们将之称为 openManus-mini。
说是迟那时快,搞点代码看看趴。
4.1、注册环境
class DeepSeekMCPClient:
"""基于MCP协议的智能代理客户端,实现任务分解→执行→整合的三阶段处理流程"""
def __init__(self):
self.task_pipeline = {
'decompose': self._decompose_tasks, # 任务分解方法
'execute': self._execute_tasks, # 任务执行方法
'synthesize': self._synthesize_results # 结果整合方法
}
self.execution_state = { # 执行状态跟踪器
'phase': None,
'tasks': [],
'results': [],
'errors': [],
'context': {}
}
4.2、链路编排
async def process_query(self, query: str) -> str:
"""重构后的三阶段处理流程"""
try:
# 阶段1:任务分解
self.execution_state['phase'] = 'decompose'
tasks = await self._decompose_tasks(query)
if not tasks:
return "未检测到可执行任务"
self.execution_state['tasks'] = tasks
self.execution_state['context']['original_query'] = query
# 阶段2:任务执行
self.execution_state['phase'] = 'execute'
results = await self._execute_tasks(tasks)
# 阶段3:结果整合
self.execution_state['phase'] = 'synthesize'
return await self._synthesize_results(results)
except Exception as e:
self.execution_state['errors'].append(str(e))
return f"处理中断: {str(e)}"
4.3、任务分解阶段
async def _decompose_tasks(self, query: str) -> list:
"""任务分解阶段"""
messages = [
{"role": "system", "content": "请将复杂任务分解为有序的子任务列表"},
{"role": "user", "content": query}
]
response = await self.llm_client.chat.completions.create(
model=f"{self.model}-task-decomposer",
messages=messages,
temperature=0.3,
max_tokens=2048
)
tasks = response.choices[0].message.content
self.execution_state['context']['decomposition'] = tasks
return json.loads(tasks)
4.4、任务执行
任务执行阶段其实可以理解 为 将MCP-Client里之前的逻辑循环的调用整合即可,比如说第一个步是将一个大任务分解成了多个小任务, 那么这种就可以复用之前的逻辑,把每个小任务都当做一个需求去调用即可。当然还有一种就是,大任务分解的时候不仅分解出来小任务,还把具体小任务要执行那些tools也分解出来,这样的话,之前的逻辑就可以去掉 拿到小任务去调用llm,结合function call去分析出具体需要调用那些tools了。
当然这个流程其实还好,因为可能不同的llm模型其实在不同的阶段做的事情不一样,可能分解任务阶段的模型如果用来function call解析意图的话可能效果不一定好。。。
async def _execute_tasks(self, tasks: list) -> list:
"""任务执行阶段"""
final_results = []
for idx, task in enumerate(tasks):
self.execution_state['current_task'] = task
print(f"\n正在执行任务 {idx+1}/{len(tasks)}: {task['description']}")
# 检查工具可用性
tool_available = any(t['name'] == task['tool'] for t in self.tools)
if not tool_available:
raise Exception(f"工具 {task['tool']} 未注册")
# 执行工具调用
try:
result = await self._execute_tool_with_retry(task)
final_results.append({
'status': 'success',
'task': task,
'result': result
})
self.execution_state['results'].append(result)
except Exception as e:
final_results.append({
'status': 'failed',
'task': task,
'error': str(e)
})
self.execution_state['errors'].append(str(e))
print(f"任务 {idx+1} 执行失败: {str(e)}")
return final_results
async def _execute_tool_with_retry(self, task, max_retries=3):
"""带重试机制的工具执行"""
for attempt in range(max_retries):
try:
return await self.session.call_tool(task['tool'], task['parameters'])
except Exception as e:
if attempt == max_retries - 1:
raise
await asyncio.sleep(2 ** attempt) # 指数退避
4.5、结果整合
async def _synthesize_results(self, results: list) -> str:
"""结果整合阶段"""
messages = [
{"role": "system", "content": "请根据任务执行结果生成最终回复"},
{"role": "user", "content": "整合以下执行结果:"},
{"role": "assistant", "content": json.dumps(results, indent=2)}
]
response = await self.llm_client.chat.completions.create(
model=self.model,
messages=messages,
temperature=0.2,
max_tokens=4096
)
self.execution_state['final_response'] = response.choices[0].message.content
return response.choices[0].message.content
五、通过计划分解出来的任务,使用多个不同的模型去执行 擅长的功能
基于第四点其实我们是伪造出来了一个简单的 任务分解版本的 manus-mini的,但是还有个问题,我们在前面提到过一嘴,就是通过任务分解出来的使用的llm 和 最后每个子任务去调用llm 执行function call意图好像都使用的是同一个llm。。。
那么假如说有一个更适合干image解析意图的llm 以及一个更适合干意图分解的llm,还有更适合干翻译相关、数学题相关的llm的话,就没办法充分发挥其优势。那么一个改造版本由此而生。
5.1、注册环境
class DeepSeekMCPClient:
def __init__(self):
# 新增LLM管理器
self.llm_managers = {
'default': OpenAI(
api_key=os.getenv("DEFAULT_API_KEY"),
base_url=os.getenv("DEFAULT_BASE_URL")
),
'analytics': OpenAI(
api_key=os.getenv("ANALYTICS_API_KEY"),
base_url=os.getenv("ANALYTICS_BASE_URL")
),
'data_processing': OpenAI(
api_key=os.getenv("DATA_API_KEY"),
base_url=os.getenv("DATA_BASE_URL")
)
}
# 新增任务-LLM映射配置
self.task_llm_mapping = {
'data_analysis': 'analytics',
'report_generation': 'analytics',
'database_query': 'data_processing',
'image_processing': 'media_processor' # 示例扩展
}
# 其他原有初始化保持不变
5.2、选择具体的llm
async def _select_llm(self, task: dict) -> OpenAI:
"""根据任务类型选择最优LLM"""
# 获取任务类型标识(可通过任务描述NLP分析或预定义类型)
task_type = self._get_task_type(task)
# 选择策略:优先使用映射表中的专用模型,其次默认模型
selected_llm = self.llm_managers.get(
self.task_llm_mapping.get(task_type, 'default'),
self.llm_managers['default']
)
print(f"任务 {task['id']} 选用模型: {selected_llm.model_name}")
return selected_llm
# 示例:实际上可以 走一次llm获取对应的大模型映射
def _get_task_type(self, task: dict) -> str:
"""任务类型识别(可通过LLM分析或规则匹配)"""
# 示例:基于任务描述的简单规则匹配
description = task.get('description', '').lower()
if 'analytics' in description:
return 'analytics'
elif 'database' in description:
return 'data_processing'
return 'general'
5.3、任务分解阶段
async def _decompose_tasks(self, query: str) -> list:
"""增强版任务分解(支持多模型协同)"""
# 使用元LLM进行任务分解
messages = [
{"role": "system", "content": SYSTEM_PROMPT_DECOMPOSITION},
{"role": "user", "content": query}
]
# 动态选择分解专用模型
decomposer_llm = self.llm_managers.get(
self.task_llm_mapping.get('decomposition', 'default'),
self.llm_managers['default']
)
response = await decomposer_llm.chat.completions.create(
model=f"{decomposer_llm.model_name}-task-decomposer",
messages=messages,
temperature=0.3,
max_tokens=2048
)
return json.loads(response.choices[0].message.content)
5.4、任务执行
async def _execute_tool_with_retry(self, task, max_retries=3):
"""增强版工具执行(动态模型选择)"""
# 为每个任务选择最优LLM
current_llm = await self._select_llm(task)
for attempt in range(max_retries):
try:
# 注入当前LLM上下文
with self._create_llm_context(current_llm) as llm_ctx:
return await self.session.call_tool(
task['tool'],
task['parameters'],
llm_ctx=llm_ctx # 传递LLM上下文
)
except Exception as e:
# 记录模型特定错误
self._log_tool_error(task, current_llm, e)
# 错误恢复策略
if attempt < max_retries - 1:
await self._retry_strategy(task, current_llm)
else:
raise
def _create_llm_context(self, llm: OpenAI):
"""创建LLM上下文管理器"""
return LLMContextManager(
llm=llm,
tools=self.tools,
system_prompt=self._get_system_prompt()
)
def _get_system_prompt(self) -> str:
"""动态生成系统提示(根据当前任务类型)"""
current_task = self.execution_state.get('current_task', {})
task_type = self._get_task_type(current_task)
return SYSTEM_PROMPTS[task_type] if task_type in SYSTEM_PROMPTS else DEFAULT_PROMPT
# 新增辅助类
class LLMContextManager:
"""LLM上下文管理器,支持工具调用时的参数注入"""
def __init__(self, llm: OpenAI, tools: list, system_prompt: str):
self.llm = llm
self.tools = tools
self.system_prompt = system_prompt
async def __aenter__(self):
# 注入工具描述到系统提示
self.llm.chat.completions.create(
model=self.llm.model_name,
messages=[{"role": "system", "content": self.system_prompt}]
)
return self.llm
async def __aexit__(self, *exc_info):
# 清理上下文(可选)
pass
总结:核心就是为了不同的任务类型使用擅长的llm去执行对应的任务。具体的解法是在任务执行前 单独过一遍llm的判断分析出具体执行那个映射然后匹配返回,或者在任务分解时,调整提示词,使其在任务分解的那个过程中,同时将具体任务需要用到的llm也一并返回即可。
六、如何在MCP-Client端实现 workFlow类似的工作流编排呢?
以上的方式其实我们就实现了基本的MCP,但是如果我们想定制化流程,完全按照我们自定义的工作流去执行任务的话,我们应该怎么办呢?
6.1、注册环境
"""
DeepSeek MCP 工作流编排客户端
基于模型上下文协议实现AI与外部系统的标准化交互
"""
import asyncio
import json
import yaml
import sys
from typing import Dict, List, Optional
from contextlib import AsyncExitStack
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from openai import OpenAI
from load_dotenv
import os
load_dotenv()
class WorkflowEngine:
"""工作流状态管理引擎"""
def __init__(self):
"""初始化工作流引擎状态
"""
self.workflow_state = {
'current_step': None,
'completed_steps': [],
'context_data': {},
'error': None
}
def reset_workflow(self):
"""重置工作流状态到初始值
用于重新启动新的工作流执行,清空历史记录和错误状态
"""
self.workflow_state = {
'current_step': None,
'completed_steps': [],
'context_data': {},
'error': None
}
class DeepSeekMCPClient:
"""DeepSeek MCP协议客户端实现"""
def __init__(self):
"""初始化MCP客户端组件
组件包括 - MCP会话管理
- OpenAI API客户端
- 工作流引擎
"""
self.session: Optional[ClientSession] = None
self.exit_stack = AsyncExitStack()
self.llm_client = OpenAI(
api_key=os.getenv("API_KEY"),
base_url=os.getenv("BASE_URL")
)
self.model = os.getenv("MODEL")
self.workflow_engine = WorkflowEngine()
async def connect_to_server(self, server_script_path: str):
"""建立与MCP服务器的连接
参数:
server_script_path: MCP服务脚本路径(支持.py/.js)
实现流程:
1. 检测脚本类型
2. 启动对应语言的解释器
3. 建立stdio通信通道
4. 初始化MCP会话
"""
# ... (保持原有连接代码不变) ...
6.2、执行工作流
6.2.1、工作流编排
async def execute_workflow(self, workflow_name: str):
"""执行指定名称的工作流
实现工作流编排的核心逻辑:
1. 加载YAML配置
2. 状态机驱动执行
3. 工具调用与结果处理
4. 异常捕获与重试
参数:
workflow_name:流配置文件名(不含扩展名)
返回:
格式化的工作流执行报告
"""
try:
workflow = await self._load_workflow(workflow_name)
self.workflow_engine.reset_workflow()
while True:
current_step = self._get_next_step(workflow)
if not current_step:
break
await self._execute_workflow_step(current_step)
return self._generate_workflow_report(workflow)
except Exception as e:
self.workflow_engine.error = str(e)
raise
6.2.2、获取工作流配置
async def _load_workflow(self, workflow_name: str):
"""加载YAML格式的工作流配置
参数:
workflow_name: 工作流配置文件名
返回:
解析后的工作流定义字典
异常:
FileNotFoundError: 配置文件不存在
yaml.parser.ParserError: YAML解析错误
"""
file_path = f"workflows/{workflow_name}.yaml"
with open(file_path) as f:
return yaml.safe_load(f)['workflow']
6.2.3、执行下一步
def _get_next_step(self, workflow: List[Dict]):
"""获取下一个待执行步骤
实现工作流的状态机逻辑:
- 按顺序查找未执行的步骤
- 支持依赖关系检查
参数:
workflow: 工作流定义列表
返回:
下一步骤的字典描述或None
"""
for step in workflow:
if step['name'] not in self.workflow_engine.completed_steps:
return step
return None
6.2.4、执行单工作流
async def _execute_workflow_step(self, step: Dict):
"""执行单个工作流步骤
实现步骤的完整生命周期:
1. 条件判断
2. 工具调用
3. 结果处理
4. 上下文更新
参数:
step: 步骤定义字典
"""
try:
# 处理条件判断
if 'condition' in step:
condition = step['condition']
context = self.workflow_engine.context_data
if not self._evaluate_condition(condition, context):
print(f"条件不满足跳过步骤: {step['name']}")
return
# 执行工具调用
tool_result = await self._execute_tool_with_retry(step)
# 更新上下文数据
self.workflow_engine.context_data.update(tool_result)
self.workflow_engine.completed_steps.append(step['name'])
print(f"步骤完成: {step['name']}")
except Exception as e:
self.workflow_engine.error = str(e)
raise
async def _execute_tool_with_retry(self, step: Dict, max_retries=3):
"""带重试机制的工具执行
实现工具调用的容错机制:
- 最多重试3次
- 指数退避策略
参数:
step: 步骤定义
max_retries: 最大重试次数
返回:
工具执行结果
异常:
重试次数耗尽后抛出异常
"""
for attempt in range(max_retries):
try:
return await self._execute_tool(step)
except Exception as e:
if attempt == max_retries - 1:
raise
print(f"工具执行失败重试 {attempt+1}/{max_retries}: {str(e)}")
await asyncio.sleep(2 ** attempt) # 指数退避
6.2.5、调用工具
async def _execute_tool(self, step: Dict):
"""执行单个工具调用
实现工具调用的核心逻辑:
1. 参数解析
2. 占位符替换
3. 模板响应处理
参数:
step: 步骤定义
返回:
工具执行结果字典
"""
tool_name = step['tool']
tool_args = self._resolve_placeholders(step.get('args', {}))
print(f"执行工具: {tool_name} 参数: {tool_args}")
result = await self.session.call_tool(tool_name, tool_args)
# 处理模板响应
return self._handle_template_response(tool_name, result)
def _resolve_placeholders(self, args: Dict) -> Dict:
"""解析参数中的占位符
实现动态参数替换:
- 支持{{variable}}格式的上下文变量
- 未定义变量保留原值
参数:
args: 原始参数字典
返回:
解析后的参数字典
"""
resolved = {}
for k, v in args.items():
if isinstance(v, str) and v.startswith("{{") and v.endswith"):
key = v[2:-2].strip()
resolved[k] = self.workflow_engine.context_data.get(key, "")
else:
resolved[k] = v
return resolved
def _handle_template_response(self, tool_name: str, result: Dict):
"""处理MCP模板响应
实现模板流程的嵌套执行:
1. 检测模板响应结构
2. 解析模板参数
3. 递归执行模板工作流
参数:
tool_name: 工具名称
result: 工具执行结果
返回:
处理后的结果字典
"""
if 'prompt_template' in result:
template_name = result['prompt_template']
template_args = result['template_args']
return self._execute_template(template_name, template_args)
return result
async def _execute_template(self, template_name: str, args: Dict):
"""执行MCP模板流程
实现模板化工作流的执行:
1. 查找模板对应的工具链
2. 递归调用工作流执行方法
参数:
template_name: 模板名称
args: 模板参数
返回:
模板执行结果
"""
print(f"执行模板流程: {template_name}")
response = await self.session.list_tools()
tools = response.tools
# 查找模板对应的工具链
template_workflow = next(
(t for t in tools if t.name == template_name),
None
)
if not template_workflow:
raise ValueError(f"未找到模板: {template_name}")
return await self._execute_workflow(template_workflow.name)
6.2.6、生成工具流执行报告
def _generate_workflow_report(self, workflow: List[Dict]) -> str:
"""生成工作流执行报告
生成结构化报告:
- 步骤执行状态
- 错误信息
参数:
workflow: 工作流定义
返回:
格式化的报告字符串
"""
report = []
for step in workflow:
status = "完成" if step['name'] in self.workflow_engine.completed_steps else "未执行"
report.append(f"{step['name']}: {status}")
if self.workflow_engine.error:
report.append(f"错误: {self.workflow_engine.error}")
return "\n".join(report)
6.3、聊天触发
async def chat_loop(self):
"""增强版聊天循环
支持的工作流命令:
1. workflow <名称> - 执行指定工作流
2. report - 查看执行报告
3. quit - 退出系统
实现交互式命令处理:
- 输入解析
- 命令路由
- 异常处理
"""
print("\n=== MCP 工作流模式 ===")
print("支持以下命令:")
print("1. 执行工作流: workflow <名称>")
print("2. 查看报告: report")
print("3. 退出: quit")
while True:
try:
command = input("\n命令: ").strip().split()
if not command:
continue
if command[0] == 'workflow' and len(command) > 1:
await self.execute_workflow(command[1])
elif command[0] == 'report':
print(await self._generate_workflow_report(self.workflow_engine.workflow_state))
elif command[0] == 'quit':
break
else:
print("未知命令")
except Exception as e:
print(f"\n错误: {str(e)}")
# 使用示例
async def main():
"""主函数入口
实现:
1. MCP客户端初始化
2. 服务器连接
3. 交互式会话管理
"""
client = DeepSeekMCPClient()
try:
await client.connect_to_server("workflow_server.py")
await client.chat_loop()
finally:
await client.cleanup()
if __name__ == "__main__":
asyncio.run(main())
6.4、工作流配置
workflow:
- name: "data_collection"
tool: "query_database"
args:
table: "sales"
condition: "{{ start_date }} >= '2024-01-01'"
- name: "data_cleaning"
tool: "clean_data"
dependencies: ["data_collection"]
- name: "analysis"
tool: "perform_analysis"
dependencies: ["data_cleaning"]
condition: "{{ cleaned_data_count > 100 }}"
- name: "report_generation"
tool: "generate_report"
dependencies: ["analysis"]