微信扫码
添加专属顾问
我要投稿
复刻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容器进行操作。
@mcp.tool()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-30
Qwen能吞下整本扫描版PDF,直接转Word了,这波操作太赞了!
2025-04-28
3D 小白亲测:用 Trae + Blender MCP 从零开始 AI 建模(附踩坑指南)
2025-04-27
行业落地分享:作业帮问答检索系统实践
2025-04-27
大模型赋能CAD图纸智能识别与集成实战指南
2025-04-25
英伟达推出 Describe Anything 3B AI 模型了
2025-04-24
OpenAI 图像生成 API 开放!开发者也能“一键出图”了
2025-04-24
OpenAI终于放出图像生成模型 API ,Midjourney危!
2025-04-24
多模态RAG:解读检索、重排、精炼三大关键技术
2024-09-12
2024-06-14
2024-06-17
2024-08-06
2024-08-30
2024-05-30
2024-11-28
2024-10-07
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