第 20 章 第三部分:完整案例

完整 Case Study:从工具定义到 Agent 执行的端到端流程

用一个具体的「智能代码审查」场景,走完 Tool 定义、MCP 接入、Agent 配置、Task 编排、LLM 消息流的完整生命周期

Nexus Case Study Tool MCP Agent 执行流

为什么需要这个 Case Study

前面章节分别讲了 Registry、Runtime、Agent、Tool、Protocol。但把它们串起来的是一个真实的执行流——从 YAML 配置到 LLM 拿到 tool schema,再到工具在 Runtime 里执行,最后返回给 Task 评分。

本章用一个完整的可运行案例走完这条链路。


场景设定:智能代码审查 Task

目标:给定一个 PR 的 diff,让 Agent 自动审查代码质量,输出审查意见。

需要的工具:

  1. get_pr_diff — 获取 PR 的代码变更(自定义 Python 工具)
  2. run_linter — 在沙箱里跑 pylint/flake8(自定义 Python 工具)
  3. git_analyze — 通过 MCP Server 做 deeper 的 git 分析(MCP 工具)

Agent:main_agent(通用 tool-calling) 评分:用 LLM Judge 评估审查意见的质量


第一层:Tool 定义

自定义工具(Python,通过 @tool_registry 注册)

# extensions/tools/code_review_tools.py

from nexus.core import tool_registry
from nexus.core.tool_protocol import ToolContext, ToolResponse, ChatCompletionContentPartParam

@tool_registry.register(
    "get_pr_diff",
    meta={
        "description": "获取指定 PR 的 diff 内容",
        "tool_type": "runtime",   # 在沙箱里执行
        "parameters": {
            "pr_number": {"type": "integer", "description": "PR 编号"},
            "repo_path": {"type": "string", "description": "仓库路径,默认 /workspace/repo"}
        }
    }
)
async def get_pr_diff(params: dict, context: ToolContext) -> ToolResponse:
    pr_number = params["pr_number"]
    repo_path = params.get("repo_path", "/workspace/repo")
    
    # 在 Runtime 沙箱里执行 git 命令
    result = await context.runtime.run_command(
        f"cd {repo_path} && git diff origin/main...HEAD --unified=3",
        timeout=30
    )
    
    return ToolResponse(
        output=[{"type": "text", "text": result.stdout}],
        meta={"exit_code": result.exit_code}
    )

@tool_registry.register(
    "run_linter",
    meta={
        "description": "在代码上运行 linter(pylint/flake8)",
        "tool_type": "runtime",
        "parameters": {
            "file_path": {"type": "string", "description": "要检查的文件路径"},
            "linter": {"type": "string", "enum": ["pylint", "flake8"], "default": "flake8"}
        }
    }
)
async def run_linter(params: dict, context: ToolContext) -> ToolResponse:
    file_path = params["file_path"]
    linter = params.get("linter", "flake8")
    
    result = await context.runtime.run_command(
        f"{linter} {file_path}",
        timeout=60
    )
    
    return ToolResponse(
        output=[{"type": "text", "text": result.stdout or "No issues found."}],
        meta={"exit_code": result.exit_code}
    )

关键点:

  • tool_type: "runtime" → 这个工具在 Runtime 沙箱里执行(不是 Host)
  • context.runtime → 工具通过 ToolContext 拿到 Runtime 引用,可以调用 run_command、upload_file 等
  • 返回 ToolResponse → 统一格式,Agent 拿到的是 output 里的 content parts

MCP 工具(通过 MCP Server 接入)

假设我们有一个 git 分析的 MCP Server,提供 git_analyze 工具。

在 YAML 里配置 MCP Server:

# code_review.yaml
tools:
  - "get_pr_diff"      # 自定义 Python 工具
  - "run_linter"        # 自定义 Python 工具
  - "mcp::git_analysis::git_analyze"  # MCP 工具(格式:mcp::server_name::tool_name)
  - "mcp::git_analysis::get_blame"     # MCP 工具

mcp_servers:
  git_analysis:
    # 2026-10-08 更正:@modelcontextprotocol/server-git 已从 npm registry 下架
    # (2026-10-08 探测 registry.npmjs.org/@modelcontextprotocol/server-git 返回 404,
    #  同族对照 server-github / server-filesystem 均返回 200,说明是特定包下架)
    # 官方 Git 参考实现现为 PyPI 的 mcp-server-git(2026-10-08 一手:PyPI 最新 2026.8.18)
    command: "uvx"
    args: ["mcp-server-git", "--repository", "/workspace/repo"]
    # 只读审查场景:不要暴露 git_add / git_commit / git_checkout 等写工具

⚠️ 三件事必须知道(2026-10-08 补,一手探测):

  1. 旧写法已失效。本书此前写的 npx -y @modelcontextprotocol/server-git 在 npm registry 已返回 404(2026-10-08 探测;同族的 server-github 与 server-filesystem 均返回 200,可确认是特定包下架而非 registry 不可达)。照抄会以 npx 报 404 的形式失败,而不是语义错误,读者很难定位到「书里的包名过期了」。官方 Git 参考实现现为 PyPI 的 mcp-server-git,推荐启动方式 uvx mcp-server-git --repository PATH。
  2. 包名抄错一个字符会中招。npm 上无作用域的 mcp-server-git 是一个被 npm 安全团队下架的恶意包,占位页仍在(原文:该包含恶意代码,已被移除并发布占位包以防后续用户受影响)。装包时必须走 PyPI 源或带 @modelcontextprotocol/ 作用域,抄漏作用域前缀会命中同名恶意包。
  3. 这个 server 有 12 个工具,其中含写操作。除 status / diff / log / show 等只读项外,还包含 git_add、git_commit、git_checkout、git_reset、git_branch。本章是只读审查场景,应显式裁剪工具面,不要整包注册——这与本节末「Copilot Code Review 版」刚立的「生产级审查应默认采用只读 MCP 作用域」原则一致。

MCP 工具的注册流程(框架自动处理):

YAML 解析阶段
  → 发现 mcp::git_analysis::git_analyze
  → 连接 MCP Server(stdio 或 HTTP)
  → 调用 MCP list_tools() 获取工具 schema
  → 自动注册到 tool_registry(名称:mcp::git_analysis::git_analyze)
  → 元数据里标记 tool_type: "mcp"

Agent 调用 MCP 工具时(框架自动处理):

Agent 决定调用 git_analyze
  → 框架把 tool_call 转发给 MCP Server(JSON-RPC)
  → MCP Server 执行,返回结果
  → 框架把结果包装成 ToolResponse
  → 传给 Agent(跟自定义工具无差别)

第二层:Agent 配置

# code_review.yaml(续)
agent_name: "main_agent"
agent_config:
  system_prompt: |
    You are a senior code reviewer. 
    First get the PR diff, then run linter on changed files, 
    then use git_analyze to understand commit patterns.
    Output a structured review with severity levels.
  max_iterations: 15
  tools:
    - "get_pr_diff"
    - "run_linter"
    - "mcp::git_analysis::git_analyze"
    - "mcp::git_analysis::get_blame"
  parallel_tool_calls: false
  tool_output_truncation: 8000

关键点:

  • tools 列表决定 LLM 能看到的 function schema
  • MCP 工具和自定义工具在 tools 列表里平级——Agent 不知道工具是 Python 实现的还是 MCP Server 提供的
  • system_prompt 里可以引导 Agent 使用工具的顺序(先拿 diff → 再跑 linter → 再做 git 分析)

第三层:Task 定义

# extensions/tasks/code_review/task.py

from nexus.core import task_registry
from nexus.core.task_protocol import TaskRunInput, TaskRunOutput, WithAgentRunner, WithScore
from pydantic import BaseModel

class CodeReviewInput(TaskRunInput, WithAgentRunner, WithScore):
    pr_number: int
    repo_url: str
    expected_issues: list[str]  # 预期发现的问题(用于评分)

class CodeReviewOutput(TaskRunOutput):
    review_text: str
    issues_found: list[str]

@task_registry.register("code_review_task")
async def run_code_review(task_input: CodeReviewInput) -> CodeReviewOutput:
    
    # 1. 准备 Runtime 环境
    runtime = await task_input.get_runtime("main")
    await runtime.run_command(f"git clone {task_input.repo_url} /workspace/repo")
    await runtime.run_command("cd /workspace/repo && git checkout pr-{pr_number}-branch")
    
    # 2. 调用 Agent(通过注入的 run_agent)
    agent_output = await task_input.run_agent(
        user_prompt=f"Please review PR #{task_input.pr_number}. Use the tools to analyze the code.",
        working_dir="/workspace/repo"
    )
    
    # 3. 提取 Agent 输出
    review_text = agent_output.final_answer
    
    # 4. 评分
    # 4.1 用 LLM Judge 评估审查质量
    judge_score = await judge_review_quality(
        review=review_text,
        expected_issues=task_input.expected_issues
    )
    
    # 4.2 检查是否用了预期的工具
    tools_used = [call["function"]["name"] for call in agent_output.tool_calls]
    tool_coverage_score = 1.0 if "run_linter" in tools_used else 0.5
    
    final_score = 0.7 * judge_score + 0.3 * tool_coverage_score
    
    return CodeReviewOutput(
        review_text=review_text,
        issues_found=extract_issues(review_text),
        score=final_score
    )

关键点:

  • WithAgentRunner Mixin → 注入 run_agent() 方法
  • Task 不关心 Agent 的具体实现——只管传 prompt,拿回结果
  • get_runtime("main") → 从 YAML 的 runtime_configs.main 获取 Runtime 实例

第四层:YAML 完整配置

# code_review.yaml(完整版)

task_name: "code_review_task"
task_config:
  pr_number: 42
  repo_url: "https://github.com/example/project.git"
  expected_issues: ["missing error handling", "unused import"]

runtime_provider_name: "docker"
runtime_configs:
  main:
    image: "nexus/swe-bench:latest"
    worker_venv: false

agent_name: "main_agent"
agent_config:
  system_prompt: |
    You are a senior code reviewer.
  max_iterations: 15
  tools:
    - "get_pr_diff"
    - "run_linter"
    - "mcp::git_analysis::git_analyze"

mcp_servers:
  git_analysis:
    # 与第二节同步更正(2026-10-08):npm 版已 404,改用 PyPI 的 mcp-server-git + uvx
    command: "uvx"
    args: ["mcp-server-git", "--repository", "/workspace/repo"]

openai_clients:
  main:
    name: "openai"
    config:
      client_args:
        api_key: "${oc.env:OPENAI_API_KEY}"
      request_args:
        model: "gpt-4o"

第五层:执行全流程走读

这是最核心的部分。从 nexus run code_review.yaml 到结果返回,完整走一遍。

Step 1:框架启动,加载配置

nexus run code_review.yaml
  ↓
YAML 解析
  → task_name: "code_review_task"       → task_registry.get("code_review_task")
  → agent_name: "main_agent"            → agent_registry.get("main_agent")
  → runtime_provider_name: "docker"     → runtime_provider_registry.get("docker")
  → openai_clients.main                → openai_client_registry.get("openai")

关键点:所有 _registry.get() 都是懒加载——只有用到的对象才会被 import。

Step 2:准备 Runtime 环境

RuntimeProvider("docker").acquire("main")
  ↓
启动 Docker 容器(镜像:nexus/swe-bench:latest)
  ↓
容器里启动 Nexus Server(监听端口,等待 tool call)
  ↓
返回 RemoteRuntime 实例(Host 侧的客户端代理)
  ↓
存入 ExecutionContext(后续所有调用共享这个 Runtime)

Step 3:启动 Agent 推理循环

Task 函数被调用:run_code_review(task_input)
  ↓
task_input.get_runtime("main")  →  拿到 RemoteRuntime 实例
  ↓
task_input.run_agent(user_prompt=...)  →  注入的 AgentRunner 开始工作
  ↓
───────────────── Agent 推理循环开始 ─────────────────

Iteration 1:LLM 第一次调用

框架构造 LLM 消息:

{
  "model": "gpt-4o",
  "messages": [
    {
      "role": "system",
      "content": "You are a senior code reviewer. First get the PR diff, then run linter..."
    },
    {
      "role": "user",
      "content": "Please review PR #42. Use the tools to analyze the code."
    }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_pr_diff",
        "description": "获取指定 PR 的 diff 内容",
        "parameters": {
          "type": "object",
          "properties": {
            "pr_number": {"type": "integer"},
            "repo_path": {"type": "string"}
          },
          "required": ["pr_number"]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "run_linter",
        "description": "在代码上运行 linter(pylint/flake8)",
        "parameters": { ... }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "mcp::git_analysis::git_analyze",
        "description": "Analyze git commit patterns and code churn",
        "parameters": { ... }
      }
    }
  ]
}

关键点:

  • tools 列表来自 YAML 的 agent_config.tools
  • MCP 工具的 schema 是框架启动时从 MCP Server 动态获取的
  • LLM 看不到 Task 的其他细节(pr_number、expected_issues 等)——这些只有 Task 函数知道

LLM 返回:

{
  "choices": [{
    "message": {
      "role": "assistant",
      "content": null,
      "tool_calls": [
        {
          "id": "call_abc123",
          "type": "function",
          "function": {
            "name": "get_pr_diff",
            "arguments": "{\"pr_number\": 42, \"repo_path\": \"/workspace/repo\"}"
          }
        }
      ]
    }
  }]
}

Iteration 2:执行工具调用

框架收到 tool_call,开始执行:

1. 解析 tool_name: "get_pr_diff"
   ↓
2. tool_registry.get("get_pr_diff")
   → 拿到之前注册的函数(code_review_tools.py 里的 get_pr_diff)
   ↓
3. 检查 tool_type: "runtime"
   → 需要通过 Runtime 执行(因为操作文件系统)
   ↓
4. 序列化参数,通过 RemoteRuntime.invoke() 发送到沙箱
   ↓
5. 沙箱里的 ToolExecutor 收到调用
   → 反序列化参数
   → 调用 get_pr_diff(params, context)
   → 执行:runtime.run_command("git diff ...")
   ↓
6. 返回 ToolResponse
   → 通过 RPC 传回 Host
   ↓
7. 框架把工具结果包装成 LLM 消息

发送给 LLM 的下一轮消息:

{
  "messages": [
    {"role": "system", "content": "..."},
    {"role": "user", "content": "..."},
    {
      "role": "assistant",
      "content": null,
      "tool_calls": [{"function": {"name": "get_pr_diff", ...}}]
    },
    {
      "role": "tool",
      "tool_call_id": "call_abc123",
      "content": "diff --git a/src/main.py b/src/main.py\n--- a/src/main.py\n+++ b/src/main.py\n@@ -10,6 +10,7 @@ def process_data():\n     data = load_data()\n+    # TODO: add error handling\n+    process(data)\n     return result"
    }
  ],
  "tools": [...]
}

Iteration 3:LLM 继续推理

LLM 返回:

{
  "choices": [{
    "message": {
      "role": "assistant",
      "content": null,
      "tool_calls": [
        {
          "function": {
            "name": "run_linter",
            "arguments": "{\"file_path\": \"/workspace/repo/src/main.py\", \"linter\": \"flake8\"}"
          }
        }
      ]
    }
  }]
}

执行流程同上,这次调用 run_linter → Runtime 里跑 flake8 /workspace/repo/src/main.py → 返回 linter 输出。

Iteration 4:LLM 调用 MCP 工具

LLM 返回:

{
  "choices": [{
    "message": {
      "role": "assistant",
      "content": null,
      "tool_calls": [
        {
          "function": {
            "name": "mcp::git_analysis::git_analyze",
            "arguments": "{\"file_path\": \"/workspace/repo/src/main.py\"}"
          }
        }
      ]
    }
  }]
}

MCP 工具的执行路径略有不同:

1. 解析 tool_name: "mcp::git_analysis::git_analyze"
   ↓
2. 识别为 MCP 工具(前缀 mcp::)
   → 查找 mcp_servers.git_analysis 的配置
   ↓
3. 通过 MCP 协议发送 tool_call(JSON-RPC)
   格式:
   {
     "jsonrpc": "2.0",
     "method": "tools/call",
     "params": {
       "name": "git_analyze",
       "arguments": {"file_path": "/workspace/repo/src/main.py"}
     }
   }
   ↓
4. MCP Server(`mcp-server-git`,PyPI 包,2026-10-08 更正)执行
   → 跑 git blame / git log 分析
   ↓
5. 返回 MCP 格式的结果
   {
     "content": [{"type": "text", "text": "File has 15 commits in last week..."}]
   }
   ↓
6. 框架转换成 ToolResponse
   → 传回 Agent(跟自定义工具无差别)

关键点:Agent 不知道 git_analyze 是 MCP 工具还是 Python 工具——对 Agent 来说,它们都是 tools 列表里的一个 function。

Iteration 5:LLM 生成最终答案

经过 3-4 轮工具调用后,LLM 收集了足够信息,生成最终审查意见:

{
  "choices": [{
    "message": {
      "role": "assistant",
      "content": "## Code Review for PR #42\n\n### Issues Found:\n1. **Missing error handling** in `process_data()` (line 13) - TODO comment suggests intended but not implemented\n2. **Unused import**: `import sys` not used in `src/main.py`\n3. **Linter warning**: flake8 reports E501 (line too long) at line 25\n\n### Recommendations:\n- Add try/except block around data processing\n- Remove unused import\n- Break line 25 to <79 characters"
    }
  }]
}

Step 4:Task 评分

Agent 推理循环结束,返回 AgentOutput(含 final_answer)
  ↓
Task 函数继续执行
  → 提取 review_text
  → 调用 judge_review_quality()(另一个 LLM 调用)
  → 计算 tool_coverage_score
  → 返回 TaskRunOutput(score=0.82)
  ↓
框架记录结果到 SQLite / 输出到终端

第六层:消息流可视化

┌───────────┐
  Host 进程                                   │
  │                                           │
  │  YAML 配置                                │
  │    ↓                                     │
  │  task_registry.get("code_review_task")    │
  │    ↓                                     │
  │  agent_registry.get("main_agent")        │
  │    ↓                                     │
  │  openai_client 构造 LLM 请求              │
  │    ↓                                     │
  │  ► LLM API Call ← 关键网络请求            │
  │    ↓                                     │
  │  收到 tool_calls                          │
  │    ↓                                     │
  │  tool_registry.get("get_pr_diff")        │
  │    ↓                                     │
  │  runtime.invoke("get_pr_diff", params)   │
  └───────────┘
                   ↓ RPC(HTTP or Socket.IO)
┌───────────┐
  Runtime 沙箱(Docker 容器)                 │
  │                                           │
  │  Nexus Server 收到 invoke 请求             │
  │    ↓                                     │
  │  ToolExecutor 查找工具实现                 │
  │    ↓                                     │
  │  执行 get_pr_diff()                       │
  │    → runtime.run_command("git diff ...")  │
  │    ↓                                     │
  │  返回 ToolResponse                        │
  └───────────┘
                   ↓ RPC 返回
  Host 进程
  │
  │  工具结果包装成 LLM message
  │    ↓
  │  ► LLM API Call(带 tool result)
  │    ↓
  │  下一轮 tool_call or final answer
  │    ↓
  │  所有 iteration 完成
  │    ↓
  │  Task 函数评分
  │    ↓
  │  返回最终结果

关键设计点总结

设计点实现方式好处
Tool 注册解耦@tool_registry.register() + 懒加载Task/Agent 不 import 工具代码
MCP 工具平级接入框架自动把 MCP list_tools() 注册进 tool_registryAgent 无感使用 MCP 工具
Agent 不知道工具实现只看 tools 列表里的 function schema换工具实现不影响 Agent
Task 不知道 Agent 实现run_agent() 是注入的工厂函数换 Agent 只需要改 YAML
Runtime 隔离Docker 容器 + Nexus Server工具执行不影响 Host
ToolContext 不可变ImmutableDict并行工具调用无竞态

MCP 选型的行业收敛:2026-07-28 Microsoft .NET Agent Framework 宣布 MCP Skills Discovery,2026-07-29 GitHub Copilot Code Review GA——两个独立团队在同周选择同一协议(MCP)作为 skill 传输与发现的中间层,标志 MCP 从「周边协议」变为「通用中间件」,佐证本章将 MCP 作为代码审查等场景通用接入层的选型。(该结论目前来自单一二手来源,建议交叉验证。)

协议收敛 ≠ 参考实现可用于生产(2026-10-08 补):上面论证的是协议的采用度,与实现的成熟度是两件事。官方 modelcontextprotocol/servers README 明确写:参考 server 是「intended as reference implementations to demonstrate MCP features and SDK usage」,「not as production-ready solutions」;官方把想找目录的人指向 MCP Registry。当前仍在维护的只有 7 个(everything / fetch / filesystem / git / memory / sequentialthinking / time),另有 14 个已迁到 servers-archived,归档 README 以大写警示「NO SECURITY GUARANTEES ARE PROVIDED FOR THESE ARCHIVED SERVERS」;归档包普遍 pin 在旧版 SDK、协议协商上限停留在 2024-11-05,无法使用 structured tool output / elicitation / tool annotations。 → 本章用参考 server 讲解机制是可以的;读者若要上生产,应走向 MCP Registry 里的实现,而不是照抄书里的示例。 来源纪律:官方 README 的「not as production-ready solutions」原文由第三方页面转引,本书未直读仓库 README;7 / 14 拆分名单与 SDK pin 细节亦来自二手技术博客,标注为二手。

另一条不同性质的证据线(2026-10-08 补):MCP-Bench(Accenture,ICLR 2026)把 28 个生产级 MCP server、250 个工具、11 个领域接进基准,且每个任务额外挂 10 个干扰服务,agent 实际面对 100+ 冗余工具;评估采用「规则检查 + rubric 驱动的 LLM 评委」双层。这与「两个团队同周选同一协议」性质不同:前者是采用度,后者是规模化之后是否仍可用。另需与 Ch21 的基准表联动——Ch21 的 MCP-Bench 行缺「11 领域」与「干扰服务」两项,且未说明它用 LLM 评委(与 Ch21 主张的「拒绝 LLM-as-a-judge」形成张力)。来源:评分机制与干扰服务细节来自 ICLR 2026 论文笔记站转述(二手);仓库 511 stars / 最后 push 2025-10-07 为 GitHub API 一手(2026-10-08)。

跨章一致性提示:MCP 2026-07-28 规范已 GA(非 RC)。全书凡涉及该规范状态的章节(Ch07 / Ch18 / 本章)表述须与此保持一致——Ch07 仍有若干处称其为 RC,若只改其中一章会制造新的书内矛盾。(2026-10-08 复测:modelcontextprotocol/modelcontextprotocol 最新 release 仍为 tag 2026-07-28(published 2026-07-28T16:47:49Z),前一条为 2026-07-28-RC(2026-05-29),无更新版本,本提示继续成立。)


对比:如果不用这套架构

传统做法(把工具调用写死在 Agent 里):

# 紧耦合,难扩展
def my_agent(prompt):
    diff = subprocess.run(["git", "diff", ...])  # 硬编码
    linter_result = subprocess.run(["flake8", ...])  # 硬编码
    # 想加新工具?改 Agent 代码
    # 想换 LLM?改 Agent 代码

Nexus 做法(解耦,配置驱动):

# 想加新工具?改 YAML 的 tools 列表
tools:
  - "get_pr_diff"
  - "run_linter"
  - "new_tool"   # ← 加这一行,Agent 自动能用

一张图总结全流程

YAML 配置
  │
  ├── task_name ─────→ task_registry ─────→ Task 函数
  │                                          │
  ├── agent_name ────→ agent_registry ────→ Agent 实现
  │                                          │
  ├── tools ────────→ tool_registry ─────→ 工具执行
  │            │                          │
  │            ├── 自定义工具(Python)    │
  │            └── MCP 工具(JSON-RPC)  │
  │                                          │
  └── runtime ──────→ RuntimeProvider ──→ Docker 沙箱
                                                         │
  用户 prompt ──→ Agent 推理循环 ←──→ 工具执行 ←──┘
                                              │
                                          Task 评分
                                              │
                                          返回结果

从教学到生产:代码审查的三种规模

本章的代码审查案例是教学级简化版(3 个工具 + main_agent + LLM Judge 评分)。以下是三种规模的对比:

维度Nexus 教学版(本章)GitLab MCP 版Cloudflare 企业版Cursor BugbotCode-Review-Graph 版CodeBuddy MCP 管线版
工具数37-8 API endpoint182+(13 MCP Server)多维度审查工具集30 个 MCP 工具 + 5 个 workflow prompt3 个自定义 MCP Server(安全+规范+性能)
Agentmain_agentreview skill + MCPClaude Code + Workers AICursor Agent任意 MCP 兼容 AgentCodeBuddy IDE Agent + RAG 知识库
评分LLM Judge成本降 30x质量门 + 自动审查PR comment 反馈82x token 缩减(中位数)审查覆盖率 60→95%,漏洞发现 3x
规模单 PRMR 自动审查所有 MR 自动审查(8700+/周)所有 PR 自动审查2900 文件项目 <2s 索引20 人团队,15-20 PR/天
触发方式手动命令行命令行手动触发API 集成触发CI/CD 自动触发MCP 请求(实时)IDE 集成 + 自动 PR comment
基础设施Docker 沙箱GitLab MCP ServerAI Gateway(2413.7 亿 token/月)CI/CD PipelineTree-sitter + 本地 Knowledge Graph腾讯云 CodeBuddy + MCP Protocol

GitLab MCP 版:用 7-8 个 GitLab API endpoint + Jira 集成构建 MCP Server,配合 review skill 实现 MR 自动审查。成本比 Claude Code Review 低 30x。单文件 MCP server + skill 文件的结构简洁可复现。

Cloudflare 企业版:历时 11 个月构建内部 AI 工程基础设施,93% R&D 采用率。13 个 MCP Server(182+ 工具)、~3900 仓库生成 AGENTS.md、所有 MR 自动 AI 代码审查。MR 数量近翻倍(5600→8700+/周)。零信任认证 + 零数据保留策略。

Cursor Bugbot:CI/CD 级别的自动化代码审查——PR 创建时自动启动 Agent 代码审查(无需手动调用),覆盖 bug 检测、安全漏洞、架构问题、性能优化建议等多维度,理解 PR 的完整 diff 和代码历史,审查结果以 PR comment 形式反馈。代表代码审查从”on-demand”到”automated”的范式转变。

Code-Review-Graph 版(仓库 tirth8205/code-review-graph;31,960 stars / 2,927 forks,最后 push 2026-10-06,2026-10-08 一手;PyPI 最新 2.3.9,2026-09-18 发布):用 Tree-sitter 将代码库解析为持久化结构化知识图谱,图谱存于本地 SQLite(.code-review-graph/),无外部数据库、无云端。官方 Benchmarks 段(2026-08-02 重测,crg 2.3.7)给出:每问 token 缩减中位数 ~65x,区间 36x–376x,其中 376x 是 fastapi 这一最大语料的最佳个例而非典型值;样本为 6 个真实开源仓库、13 个 commit。

⚠️ 本章原写「中位数 82x、最佳场景 528x」,该组数字已退役。 官方 README 明确写道:现测值「低于它们所取代的 2026-05-25 那次采集」——原因是节点嵌入文本变丰富导致 graph 响应变大。82x/528x 正对应那次已退役的旧采集。引用时请以官方 README 的 Benchmarks 段为准并带采集日期。

版本绑定与倍数口径(2026-10-08 补):上引 65x(36x–376x)是 crg 2.3.7 于 2026-08-02 的采集;当前 PyPI 已到 2.3.9(2026-09-18),该组数字是否在 2.3.9 之后重测,本章未核——引用前请回官方 README 的 Benchmarks 段确认采集版本与日期。

另有一条可写的事实:本项目在第三方渠道上同时流传三套互不相同的数字——82x(本章已标注退役的 2026-05-25 旧采集)、65x(现行官方值)、6.8x–49x / 约 65x(另两家聚合站各自口径)。凡引用本项目的倍数,先问「哪个口径、哪次采集」。

与前五种规模的核心区别仍然成立:不是”直接分析原始代码”,而是”查询预构建的结构化索引”——token 节省来自架构级的上下文精确化。但这个判断需要再往前推一步:它已经不是一个接口,而是一层可替换的「上下文供给层」——30 个 MCP 工具覆盖 blast-radius 查询、语义/混合检索(FTS5 关键词 + 向量)、架构总览、社区发现、风险评分、执行流追踪、重构预览、wiki 生成与跨仓库检索;另有 5 个 workflow prompt(review / architecture / debug / onboard / pre-merge)把审查动作本身模板化。当工具集大到这个程度,「图谱层」不再是 Nexus 的一个工具,而是一个可整体替换的子系统。(30 工具 / 5 prompt 为官方 README 一手。)

CodeBuddy MCP 管线版(腾讯云 CodeBuddy,2026-07-20):审查逻辑完全在 MCP Server 层实现——Agent 不直接分析代码,只协调 MCP Server 之间的调用。与 Nexus 教学案例(Agent 自己调用 git diff/linter)的本质区别:审查能力不再是 Agent 的推理能力,而是 MCP Server 的工具能力。审查覆盖率从 60% 提升至 95%,安全漏洞发现率提升 3 倍。(该案例为单团队实践,普适性待验证。)

Copilot Code Review 版(GitHub,2026-07-29 GA):GitHub 将 Copilot code review 的 Agent skills 与 MCP server 连接从 public preview 转为 GA(覆盖 Pro/Pro+/Business/Enterprise 所有付费档位)。关键设计:审查期间所有 MCP 工具调用均为只读(read-only,无 write-back)——审查 Agent 可拉取 issue tracker/文档/service catalog 上下文但不能写回,印证本章「MCP 工具接入代码审查」的架构价值,并提示生产级审查应默认采用只读 MCP 作用域。

更进一步,GitHub 在 2026-07-17 的 changelog 中把 Copilot Code Review 的 Instructions、Setup Workflow、Firewall、Runner 与 Cloud Agent 网络策略一次性推进,组合成「可编程 Agent Runtime」——审查结果由仓库控制文件(copilot-instructions.md / AGENTS.md / Agent Skills)、运行时依赖、工具、网络策略与 Runner 共同决定,是比「固定规则 Reviewer」更进一步的形态。

关键洞察:Nexus 架构可以自然扩展到后两种规模——@tool_registry.register() 支持无限工具注册,MCP 接入支持外部 Server 扩展,run_agent() 的注入模式支持从教学到生产的无缝过渡。

评分策略选择

本章使用 LLM Judge 评估审查质量 + 工具覆盖度评分。但在生产场景中,存在更工程化的评分方式:

三种评分策略

  1. LLM Judge(当前方案):适合开放式评估——审查意见的深度和洞察力。灵活但成本高、不稳定。
  2. 质量门(SonarQube/GitLab CI):适合结构化评估——是否有安全漏洞、是否通过 lint、测试覆盖是否足够。确定性强但覆盖面窄。
  3. 混合方案:质量门做硬性通过/失败判断 + LLM Judge 做软性质量评分。

SonarQube + Claude Code 闭环实践

Claude Opus 4.6 通过 SonarQube MCP Server 实现「写代码 → 扫描 → 自修复 → 验证质量门」的闭环:

  • CLAUDE.md 强制 Agent 先扫描再提交
  • --max-turns 和 hooks 防止失控
  • Agent 发现 S3 bucket ownership 安全漏洞并自主修复

这种闭环模式可以在 Nexus 的 Task 评分中实现:质量门做硬性通过/失败,LLM Judge 评估审查意见的深度,两者加权得到最终分数。

三层审查架构

第 0 层:图谱预索引层(Pre-Indexing)——在 Agent 审查启动前,通过 Tree-sitter AST 解析将代码库预构建为结构化知识图谱。审查时 Agent 不再需要”搜索代码”再”理解上下文”,而是直接查询图谱获取依赖关系、调用链、影响半径。这层解决了三层架构中的上下文效率瓶颈:静态分析和 AI 审查都需要精确定位代码上下文,图谱预索引将上下文从”每次查询重新构建”变为”预计算+按需读取”。对 Nexus 的启示:在 Step 3(Agent 推理循环)中,Agent 获取代码信息的方式可以从”工具调用执行 git diff”演进为”查询 Knowledge Graph 索引”。

第 0 层不只是给 AI 供上下文,它自己也能做硬门禁。 Code-Review-Graph 已提供 composite GitHub Action:每次 push 在 PR 上发一条 sticky、就地更新的评论,含风险评分函数、受影响执行流与测试缺口;图谱构建与查询全部在 CI runner 内完成,源码不出内网;并可通过可选的 fail-on-risk 输入把审查结果直接升级为合并门禁(merge gate)。

这条路径跳过了 LLM——风险分来自图的结构性影响(blast radius / 耦合度 / 测试覆盖),是确定性的。于是本章的四层架构出现一条旁路:

图谱预索引 ──┬─→ 静态分析 → AI 审查 → 人工审批
             └─→ 结构性风险评分 → fail-on-risk 合并门禁(无 LLM)

对 Nexus 的启示:Task 的评分不必一定是 LLM Judge。可以把「结构性风险分」作为一个独立、零 LLM 开销、完全确定的评分维度——与 Ch21 主张的「regex/程序化校验优先于 LLM-as-judge」是同一个取向。这也补上了本章四层架构中缺失的一环:图谱层此前只服务 AI 审查层,现在它自己就能当质量门。

⚠️ 第 0 层的适用边界(2026-10-08 补):上面这段是单向收益论述,真实边界有三条,读者照抄前必须先过这三关:

  1. 开销会反超。基线对照中 express 仓库的压缩比为 0.7×——低于 1,即小改动场景下走图谱比直接读文件更贵。图谱层的收益条件是「大仓 + 多文件改动」,不是无条件成立。
  2. 质量有上限。关键词检索 MRR 仅 0.35(部分 module-pattern 查询零命中);执行流检测整体召回 33%,且只在 Python 仓库可靠,JS / Go 需改进;blast-radius 平均精度 0.38——用「零漏报」换「多误报」。→ 把第 0 层当 fail-on-risk 硬门禁时,误报率是设计取舍而不是 bug,上文「结构性风险分是完全确定的」应当理解为「确定地偏保守」,不是「精确」。
  3. 对本章教学案例的反向启示:get_pr_diff 那个案例(3 个工具、单个 PR)恰好落在图谱不回本的区间。只有当工具集与仓库规模涨到 Code-Review-Graph 量级(27k+ 文件 monorepo),第 0 层才值得引入——这给本章「三层 → 四层」的升级加了一个触发条件。

来源纪律:MRR 0.35 / 召回 33% / 精度 0.38 / express 0.7× 四项均来自单一第三方聚合站(ossaihub,2026-10-06 读取),其中 0.7× 与「graph metadata overhead can exceed naive file reads」在另一家站点有独立表述(两家一致),但四项都未在项目官方 README 上逐条复核。

SonarQube MCP、code-review-mcp 和 Bifrost+MCP 的出现,使”图谱预索引 → 静态分析 → AI 审查 → 人工审批”四层架构成为可工程化的标准模式:

  1. 静态分析层(SonarQube MCP):将静态分析结果通过 MCP 协议暴露给 AI Agent,查询代码质量指标、安全漏洞列表、技术债务等
  2. AI 审查层(code-review-mcp):开源 MCP Server,提供标准化的代码审查接口(diff 分析、评审意见提交、审批流程),支持 GitHub/GitLab 集成
  3. 人工审批层(Bifrost+MCP 或直接 PR review):企业级代码审查平台通过 MCP 协议接入 AI Agent,实现人机协同审查工作流

三层架构的关键优势:每层聚焦不同维度(确定性/语义性/业务性),误报逐层过滤——静态分析的误报由 AI 审查过滤,AI 审查的不确定判断由人工审批兜底。

评分策略覆盖范围确定性一致性(重复运行是否稳定)集成复杂度误报控制
LLM Judge广(语义深度)低低(同一轨迹多次评判可能翻转)低弱
质量门窄(结构化规则)高高(规则确定)中强(规则明确)
混合方案中中中(LLM 部分仍抖动)中中
三层架构最广分层递增中高最强(逐层过滤)
四层+图谱最广(上下文精确)中(图谱质量决定)中高(需 Tree-sitter + 图谱维护)强(上下文精准减少误判)

「确定性」与「一致性」是两个不同的维度:前者问「评分规则本身会不会变」,后者问「同一个 Agent 重复跑同一个任务,得分是否稳定」。本章此前的讨论只覆盖了前者,而行业基准已经开始把后者作为正式字段——MCPMark(ICLR 2026,127 个任务、5 类真实 MCP 环境)实测:最强模型 gpt-5-medium 的 pass@1 = 52.56%,pass@4 = 68.50%,而 pass^4 只有 33.86%;claude-sonnet-4 则是 pass@1 28.15% → pass^4 12.60%。重复尝试能拉高 pass@k,但「连续 k 次都成功」的 pass^k 会断崖式下跌。

对 Nexus 尤其重要:本章把 final_score 回传给 RL 训练,而 final_score = 0.7 * judge_score + 0.3 * tool_coverage_score 是单次采样。若只做单次采样,高方差策略会被误判为可用策略,污染 RL 训练样本。建议在关键决策点上重复采样 k 次,用 pass^k 而非 pass@1 定 reward。(与 Ch19 推荐的 τ 系列 pass^k、Ch21 的 RL 样本导出需同步处理。)

第四种必须并行的维度:安全(2026-10-08 补)。上表的表头是 覆盖范围 / 确定性 / 一致性 / 集成复杂度 / 误报控制——五个字段全部只统计「该做的做到了多少」,对「不该做的做了什么」不敏感。ICLR 2026 同期出现两个本章未收录的安全向基准:MCP Security Bench(MSB)(12 类攻击、2000 个真实攻击实例,通过真实工具调用评测 MCP 环境下 LLM Agent 的安全性)与 MCP-SafetyBench(基于真实 MCP server 的多轮多域安全评测)。它们不是「另一种评分法」,而是与评分并列的独立门禁:一个 Agent 可以完成度 100%、效率优秀、一致性达标,同时在工具调用链上泄漏凭据或执行了越权写操作。

→ 落到本章两个场景(水费查询与代码审查)的判据:凡评分只看目标状态达成与否的体系,必须另配一条「副作用 / 越权」检查线,因为「完成度」对「多做的坏事」是不敏感的。这与本节刚改的 MCP 示例联动:Git server 默认带 git_add / git_commit,一个「完成度满分」的审查 Agent 完全可能顺手提交一笔。来源纪律:MSB / MCP-SafetyBench 的规模与定位来自 ICLR 2026 论文列表聚合站(二手),未逐条回 arXiv abs 页核验编号、标题与数字。