微信扫码
添加专属顾问
我要投稿
复刻Manus智能体,探索前端后端交互与LLM工具调用的前沿技术。核心内容:1. Manus智能体执行任务的详细过程:从查询天气到绘制折线图2. 前端设计:双栏布局、实时通信与用户交互界面3. 后端架构:FastAPI处理请求、LLM决策模型及MCP服务的应用
先来看效果
输入需求:查询北京天气,并绘制为折线图。
智能体根据输入的需求,首先打开浏览器访问相关的网页,当网页无法访问时,还会自动切换网页,最后,智能体将会把浏览器中收集的数据保存整理为文件,并通过编程的方式,通过Python脚本绘制折线图。
任务规划:
浏览网页:
编写脚本:
Manus复刻思路
前端 (UI - HTML/CSS/JS): 用户交互的界面。我们设计了一个双栏布局:
l 左栏: 核心对话区域,展示用户与 Manus 的交流历史,以及 Manus 调用工具的摘要信息(可点击交互)。
l 右栏: 右栏是对Manus的电脑的模拟,可以展示Manus生成的文件、浏览网页等。
l 实时通信: 通过 WebSocket 与后端保持长连接,实现流式响应和状态更新。
后端 (API - FastAPI): 负责处理前端请求,管理 WebSocket 连接,并作为前端与智能体核心的桥梁。它使用异步框架以支持并发连接和流式处理。
LLM:使用DeepSeekV3作为核心的决策模型。并结合MCP实现工具的调用。
后端
MCP服务端
整个过程中一共需要两个MCP服务:
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest"]
},
"manus_server": {
"command": "python",
"args": ["manus_server.py"]
}
}
}
Playwright:
Playwright是一个由微软开发的现代化端到端测试框架,专为Web应用测试而设计。它支持多种浏览器(包括Chromium、Firefox和WebKit),提供跨浏览器的一致性测试能力,确保应用在不同环境下表现一致。其核心优势在于高可靠性和快速执行,通过直接与浏览器引擎交互,避免了传统测试工具的不稳定性问题。
Playwright 在 LLM(大语言模型)工具调用中的应用主要体现在通过其强大的浏览器自动化能力,为 LLM 提供了与网页交互的接口。Playwright MCP 是一个基于 Model Context Protocol(MCP)的服务器,它利用 Playwright 的浏览器自动化能力,使 LLM 能够直接控制浏览器,完成打开网页、点击元素、输入文字等操作。这种工具的核心优势在于快速、轻量,能生成结构化数据,无需依赖截图或视觉模型。
Playwright MCP 支持多种浏览器,如 Chromium、Firefox 和 WebKit,并且兼容多种编程语言,包括 TypeScript、JavaScript、Python、.NET 和 Java。它提供了两种模式:默认的快照模式(Snapshot Mode)和视觉模式(Vision Mode),其中快照模式更适合快速、高效的结构化数据交互。
manus_server:
由于Playwright中提供的工具并不能完全满足智能体的需求,因此需要构建一些额外的MCP服务,满足智能体的功能,因此,我额外的构建了一个名为manus_server的MCP服务,manus_server提供了下面几个工具:
google_search:搜索相关连接
make_todo_md:创建计划文件
write_to_file:创建文件
execute_command:执行指令
MCP客户端
客户端通过下面的函数实现核心的处理逻辑:
async def process_user_message(message: str, websocket: WebSocket):
"""
这是核心处理函数,你需要在这里集成你的 Agent 交互逻辑。
接收用户消息,调用 Agent,并将结果通过 WebSocket 发回前端。
"""
try:
manus_agent.add_message(role='user', content=message)
await SendStatus("Manus 正在处理...", websocket).send()
while True:
last_message_chunk = await generate_and_send_message_chunk(websocket, manus_agent.generate)
tool_executed = last_message_chunk.include_tool
if not tool_executed:
break
send_tool = SendTool(last_message_chunk, websocket)
await send_tool.send_tool()
result = await manus_agent.judge_and_execute(last_message_chunk)
await asyncio.sleep(0.5)
await send_tool.send_result()
manus_agent.add_message(role='system', content=result)
# --- 结束 ---
logging.info("Finished processing user message.")
except Exception as e:
logging.error(f"Error in agent logic: {e}", exc_info=True)
await websocket.send_text(json.dumps({"type": "error", "content": f"Agent 处理出错: {e}"}))
Manus电脑的模拟
Manus电脑的模拟主要通过docker实现。
我使用docker构建了一个docker容器,这个容器中包含了一些Python运行的环境,以便能运行智能体编写的Python代码。
docker容器中需要有一个路径与本机中的某个路径相绑定,两个路径中的文件是同步的,这样的设定能够方便后端获取智能体在docker中生成的文件,从而展示文件的内容。
def get_or_create_docker_container(
container_name: str = CONTAINER_NAME,
image_name: str = 'python:3.12.10',
local_workspace_dir=DEFAULT_TASK_DIRECTORY,
container_workspace_dir: str | None = CONTAINER_WORKSPACE_DIR,
docker_client: docker.DockerClient | None = client,
keep_alive_command: str = DEFAULT_KEEP_ALIVE_COMMAND,
auto_remove: bool = False # Set to True to automatically remove container on exit (useful for temp tasks)
):
client = docker_client
container = None
try:
# 1. Try to get the existing container
print(f"Checking for existing container '{container_name}'...")
container = client.containers.get(container_name)
print(f"Found existing container: {container.name} (ID: {container.short_id})")
# 2. Ensure the found container is running
if container.status != 'running':
print(f"Container '{container_name}' is not running (status: {container.status}). Starting...")
try:
container.start()
# Wait a moment for the container to fully start
time.sleep(2)
container.reload() # Refresh container state
if container.status != 'running':
# If start failed, raise an error
logs = container.logs(tail=10).decode('utf-8', errors='ignore')
raise RuntimeError(
f"Failed to start existing container '{container_name}'. "
f"Current status: {container.status}.\nLast logs:\n{logs}"
)
print(f"Container '{container_name}' started successfully.")
except Exception as e:
print(f"ERROR: Unexpected error while starting container '{container_name}': {e}")
raise
else:
print(f"Container '{container_name}' is already running.")
except NotFound:
# 3. If container Not Found, create it
print(f"Container '{container_name}' not found. Creating new container...")
# Prepare volumes if specified
volumes = {}
if local_workspace_dir and container_workspace_dir:
host_path = Path(local_workspace_dir).resolve() # Use absolute path
host_path.mkdir(parents=True, exist_ok=True) # Ensure host dir exists
print(f"Ensuring local directory exists: {host_path}")
volumes[str(host_path)] = {'bind': container_workspace_dir, 'mode': 'rw'}
print(f"Mapping local '{host_path}' to container '{container_workspace_dir}'")
# Check/Pull Image
print(f"Checking/pulling image: {image_name}...")
client.images.get(image_name)
# Create and start the container
print(f"Creating and starting container '{container_name}' from image '{image_name}'...")
container_config = {
"image": image_name,
"name": container_name,
"command": keep_alive_command,
"volumes": volumes if volumes else None,
"working_dir": container_workspace_dir if container_workspace_dir else None,
"detach": True,
"tty": True,
"stdin_open": True,
"auto_remove": auto_remove,
# Add restart policy if desired, e.g., restart_policy={"Name": "unless-stopped"}
}
# Remove None values from config
container_config = {k: v for k, v in container_config.items() if v is not None}
container = client.containers.run(**container_config)
# Wait a moment and verify
time.sleep(2)
container.reload()
if container.status == 'running':
print(f"Successfully created and started container: {container.name} (ID: {container.short_id})")
else:
logs = container.logs(tail=10).decode('utf-8', errors='ignore')
raise RuntimeError(
f"Created container '{container_name}' failed to start. "
f"Current status: {container.status}.\nLast logs:\n{logs}"
)
# Final verification (should be redundant if logic above is correct, but safe)
if not container or container.status != 'running':
# This path should ideally not be reached if errors were raised correctly above
raise RuntimeError(f"Failed to obtain a running container instance for '{container_name}'.")
print(f"Container '{container.name}' is ready and running.")
print("-" * 30)
return container
智能体可以直接通过命令行对docker容器进行操作,这段代码在execute_command工具的调用中实现。execute_command工具将直接对docker容器进行操作。
def execute_command(command: str) -> str:
"""执行命令并返回结果"""
workdir = CONTAINER_WORKSPACE_DIR
exit_code, output = container.exec_run(["/bin/sh", "-c", command], workdir=workdir, stream=False, demux=False)
output_str = output.decode('utf-8').strip() if output else ""
return f"{output_str}"
构建Manus这样一个智能体绝非易事。Prompt 的健壮性、复杂任务的规划能力、工具执行错误的处理、多工具协同、以及安全性都是需要持续投入的方向。
未来,我们设想 Manus 能集成更多类型的工具,拥有更强的长期记忆和规划能力,并在更复杂的场景中为用户提供端到端的解决方案。
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费场景POC验证,效果验证后签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2025-04-23
AI合同单据识别-自定义字段信息抽取-小帮手更新
2025-04-22
本地部署大模型实现扫描版 PDF 文件 OCR 识别,笔记本可跑
2025-04-21
谷歌的“MCP”
2025-04-20
Seedream 3.0 文生图模型技术报告发布
2025-04-18
一口气讲清楚:向量库、训练集、多模态
2025-04-17
豆包深度思考模型正式发布!和 o3 一样能「看图思考」,还有一个 Agent 大招
2025-04-17
刚刚,o4-mini发布!OpenAI史上最强、最智能模型
2025-04-17
刚刚,OpenAI重磅发布o3和o4-mini多模态推理能力爆炸式提升!!!
2024-09-12
2024-06-14
2024-08-06
2024-06-17
2024-08-30
2024-05-30
2024-10-07
2024-11-28
2024-10-16
2024-04-21
2025-04-08
2025-04-05
2025-03-30
2025-03-26
2025-03-05
2025-03-02
2025-01-08
2024-12-13