基于 LangGraph 的 Multi-Agent 仓库感知代码助手,通过 MCP 协议获得文件读写/测试/lint能力, 支持"检索代码 → 生成/修改代码 → 自我复查 → 不通过则重写"的完整闭环。
用户请求先进入 Orchestrator(Planner),判断意图后分发:
用户输入
│
▼
Orchestrator (意图路由: chat / explain / generate / refactor)
│
├─ chat ──────────────────────────────► 直接对话回复
│
└─ explain / generate / refactor
│
▼
Repo Search (tree-sitter AST切分 + BM25/向量双检索 + RRF融合)
│
├─ explain ──────────────────────► Explain节点,基于检索结果回答
│
└─ generate / refactor
│
▼
Coder (Prompt Chaining: 出计划 → 生成新文件内容 → MCP写入)
│
▼
Reviewer (MCP跑pytest/ruff + LLM Self-Reflection)
│
┌──────┴──────┐
│ │
不通过(≤2次) 通过/达重试上限
│ │
└──► 回Coder └──► 结束,输出结果
短期记忆通过 LangGraph 的 InMemorySaver checkpointer 实现,同一 thread_id 下多轮对话共享上下文。
| 子系统 | 说明 |
|---|---|
| Orchestrator-Workers编排 | LangGraph StateGraph + 条件边,中枢Planner路由到专职节点 |
| Repo Search | tree-sitter按函数/类边界做AST级分块,BM25+向量双路检索,RRF融合排序 |
| Coder | Prompt Chaining模式:先出JSON格式修改计划,再逐文件生成完整新内容 |
| Reviewer | Evaluator-Optimizer模式:MCP跑pytest/ruff拿客观信号,LLM做Self-Reflection判定,不通过打回重写(上限2次) |
| MCP协议 | 官方mcpSDK + langchain-mcp-adapters,文件读写/测试/lint均通过标准协议调用 |
| 安全沙箱 | 所有文件操作限制在workspace/内,双重路径校验(绝对路径拦截+祖先路径校验) |
| 模块 | 技术选型 |
|---|---|
| Agent编排 | LangGraph (StateGraph, conditional edges, InMemorySaver) |
| MCP协议 | 官方 mcp SDK (mcp.server.fastmcp.FastMCP) + langchain-mcp-adapters |
| 大模型 | 智谱 GLM-5.2 (生成) / embedding-3 (向量化),走 OpenAI 兼容接口 |
| 代码解析 | tree-sitter + tree-sitter-python,AST级函数/类边界分块 |
| 检索 | rank-bm25 (关键词) + ChromaDB (向量) + RRF融合 |
| Lint/测试 | ruff + pytest,通过MCP工具调用 |
| 演示目标 | 一个故意留了3个bug的小型FastAPI待办事项App (sample_repo/) |
# 1. 创建环境
conda create -n coding-agent python=3.11 -y
conda activate coding-agent
# 2. 安装依赖
pip install -r requirements.txt
# 3. 配置API密钥
# 新建 .env 文件,写入: ZHIPU_API_KEY=你的密钥
# 4. 重置沙箱工作区(从sample_repo拷贝一份干净副本)
python reset_workspace.py
# 5a. 单次任务模式
python cli.py "请加一个DELETE /todos/{todo_id}接口,删除成功返回被删除的todo"
# 5b. 交互式多轮对话模式
python cli.py
# 6. 跑评测基准
python -m eval.run_eval
# 7. (可选) 单独验证MCP工具服务器
python mcp_server/code_tools_server.pyeval/tasks.jsonl 5个任务(3个bug修复 + 1个代码库问答 + 1个隐藏测试的新功能开发),
每个任务用独立于Agent自评的客观标准打分(目标pytest用例是否通过 / 关键词是否命中),
连续跑了2轮:
| 轮次 | 通过率 | 平均retry次数 |
|---|---|---|
| 第1轮 | 5/5 (100%) | 0 |
| 第2轮 | 5/5 (100%) | 0 |
add_count_endpoint任务用的是隐藏测试(Agent做任务时看不到这个测试文件,跑完后才由eval脚本
复制进去打分),且这个任务专门踩了FastAPI"静态路由要放在动态路由前面,否则/todos/count会被
/todos/{todo_id}吃掉"这个经典坑——两轮都正确处理。
- 演示代码库(
sample_repo/)规模很小,检索/分块效果在大型仓库上未验证 - Coder采用"整文件重写"策略,大文件场景下token消耗会明显上升,需要换成更精细的函数级替换
- Reviewer的"是否和本次需求相关"判断依赖LLM自身理解,复杂多任务耦合场景下可能误判
- 不支持长期记忆(跨session的经验沉淀),目前只有单session内的短期记忆
- pathlib拼接路径时,若第二段是绝对路径(如
/etc/passwd或C:/xxx),会直接覆盖/丢弃第一段—— MCP工具的沙箱校验必须先用Path.is_absolute()拦截,再对.resolve()结果做祖先路径校验,双保险 - MCP server走stdio传输时,工具内部
subprocess.run()调子进程若不显式传stdin=subprocess.DEVNULL, 子进程会尝试读取MCP协议正在用的那根stdin管道,直接卡死到超时 - conda在Windows上的openssl激活脚本会把
SSL_CERT_FILE指向<env>/ssl/cacert.pem,但很多环境根本没有 这个文件,需要用certifi.where()强制覆盖 - 本机开代理软件(如Clash)时,httpx默认信任系统代理,国内大模型API经常因此握手失败
(
SSL: UNEXPECTED_EOF_WHILE_READING),需要传httpx.Client(trust_env=False)绕开 OpenAIEmbeddings默认用tiktoken按OpenAI自家模型规则做token计数,遇到非OpenAI模型名(如embedding-3) 需要显式传check_embedding_ctx_length=False- tree-sitter-python里被装饰器修饰的函数/类,节点类型是外层的
decorated_definition,不是直接的function_definition——只看function_definition会漏掉装饰器所在的整个上下文 - MCP协议里,server函数
return的Python对象不是客户端拿到的原样对象,而是被包装成content block列表, 需要自己从{"type": "text", "text": ...}里把文本拆出来 - LangGraph里一旦有节点是
async def,整张图必须用await app.ainvoke()而不是app.invoke()来跑 - 意图路由不能简单分成"闲聊"和"代码任务"两类——纯问答(explain)不该走进会真正改代码的Coder节点, 否则会话式提问也可能触发不必要的文件写入