基于MCP规范自动合成智能体评测的完整方案与代码实现 📅 发布时间:2026/9/2 20:03:20 👁 浏览次数: 最近在折腾智能体Agent项目时最让我头疼的并不是模型怎么调用、工具怎么接而是评测怎么做。代码写完了功能跑通了但一问“你的智能体到底靠不靠谱”往往只能用“我试了几个例子感觉还行”来回答。这种状态在开发阶段可以凑合一旦要上生产环境或交给业务方验收就完全站不住脚。后来我接触到一个思路与其人工设计评测用例不如从 MCPModel Context Protocol规范中自动合成评测任务。顺着这个方向我整理了一套基于“MCP 规范自动合成智能体评测”的完整方案并用代码落地了核心流程。本文就把这套方法拆开讲清楚内容包括核心概念、工作流程、代码示例、常见坑点以及工程建议适合正在做智能体开发、AI 应用集成或评测体系建设的开发者阅读。1. 背景与核心概念先聊一个基础问题为什么智能体评测这么难传统软件测试有明确的输入、输出和断言但智能体的行为链路很长。它要理解用户指令规划任务调用工具处理工具返回结果再决定下一步动作。任何一个环节出错最终表现都可能是失败的。更麻烦的是智能体的“正确回答”往往不唯一你很难用一条 SQL 或者一个 assertEquals 来判定它是否成功。这时候MCP 规范的出现给了评测一个很好的抓手。1.1 MCP 到底是什么MCP 是 Model Context Protocol 的缩写中文常翻译为“模型上下文协议”。它定义了一套标准化接口让 AI 模型或智能体能够与外部工具、数据源、服务进行交互。你可以把它理解成“AI 世界的 USB 接口”只要工具方实现了 MCP 协议任何支持 MCP 的智能体客户端就能直接使用这个工具不用再为每家工具单独写适配代码。MCP 协议中有几个核心概念MCP Server提供工具能力的服务端比如一个能查询天气、操作数据库、调用浏览器的服务。ToolMCP Server 暴露给模型的具体能力每个 Tool 都有名称、描述、输入参数 schema。MCP Client智能体或应用侧的角色负责连接 Server发现 Tools并在需要时调用 Tools。Resource可读取的数据资源比如文件内容、API 返回结果。Prompt可复用的提示词模板方便客户端规范化调用。对于一个智能体来说MCP 规范定义了“它能操作什么”的边界而智能体评测的核心恰恰就是验证“它是否正确地操作了这些能力”。1.2 Agent Seer 是什么Agent Seer 这个名字我理解为一套“面向智能体的评测方法论和工具链”核心动作是“从 MCP 规范中自动合成评测集并执行评测”。它解决的痛点很直接人工写评测用例慢且覆盖不全。智能体接入了大量 MCP 工具每个工具都要测人力跟不上。手工用例很难跟上 Agent 行为的多变性容易漏掉边界场景。评测结果主观性强缺少可量化的指标。而如果 MCP 规范已经写清楚了每个工具的名称、描述和参数 schema理论上我们就可以基于这份规范自动生成评测任务让智能体去完成“调用该工具完成某件事”的目标再检查它的工具选择、参数填充和结果处理是否正确。1.3 Agent Skill 与 MCP 的区别在很多文章里会看到“Agent Skill”和“MCP”两个词。My understandingMCP 偏向“工具连接层”负责定义并打通模型与外部能力的通道。Agent Skill 偏向“能力封装层”通常是一个包含提示词、工具调用逻辑、执行流程的完整技能单元。你可以这样理解MCP Server 提供了“能力接口”Agent Skill 则是“如何用好这些接口的方法论”。两者有交集但不能直接画等号。在评测时MCP 更贴近协议层容易做自动化断言Skill 更贴近行为层需要结合具体场景设计评测。1.4 为什么“从规范自动合成评测”是可行的因为 MCP 工具定义里已经包含了大量可结构化解析的信息{ name: get_weather, description: 查询指定城市的实时天气信息, inputSchema: { type: object, properties: { city: { type: string, description: 城市名称例如 北京、上海 }, unit: { type: string, enum: [celsius, fahrenheit], default: celsius } }, required: [city] } }这份 schema 本身就提供了工具调用意图用于生成用户自然语言请求。参数约束用于构造合法参数、边界参数、缺失参数等测试场景。工具依赖关系如果多个工具协同工作可以合成多步任务。预期输出结构为结果断言提供参考。所以说MCP 规范不只是给智能体看的接口文档它同时是一份“高密度的评测需求说明书”。2. 环境准备与版本说明在开始写代码之前先明确一下本文的环境。由于 Agent Seer 目前还没有一个统一官方的标准发行版不同团队落地方式也不同。本文以我习惯的 Python 技术栈为例演示如何实现“MCP 规范解析 评测任务合成 评测执行与打分”的闭环。版本方面需要说明的是MCP 协议本身还在快速演进不同语言的 SDK 版本差异较大本文示例不绑定特定 SDK 版本重点演示协议层面的思路。你可以根据自己的项目实际情况调整如果遇到接口变化优先查阅你当前安装 SDK 的官方文档。我本次实验使用的环境如下组件说明操作系统Windows 11 / Ubuntu 22.04 均可Python3.10 及以上MCP SDK示例以 mcp Python SDK 为例不写死版本大模型 API以 OpenAI 兼容接口为例实际可替换IDEVS Code 或 PyCharm为便于复现我创建了一个项目目录结构agent-seer-demo/ ├── specs/ # 存放 MCP 工具定义文件 │ └── weather_server.json ├── generator/ # 评测任务生成器 │ ├── __init__.py │ └── task_synthesizer.py ├── executor/ # 评测执行器 │ ├── __init__.py │ └── evaluator.py ├── reports/ # 评测报告输出目录 └── main.py # 主流程入口这只是我的个人组织方式你可以按照团队规范调整。关键在于把“规范读取”“任务生成”“评测执行”“结果输出”四个环节解耦。3. 核心原理与整体工作流这套评测方案的全流程可以拆成五个阶段解析阶段读取 MCP Server 暴露的工具定义提取工具名、描述、参数 schema。合成阶段根据工具定义自动生成用户请求、预期工具调用序列、预期参数约束。执行阶段将生成的自然语言请求发送给被测智能体记录智能体的工具调用轨迹和最终回答。判定阶段将智能体实际行为与预期行为对比计算准确率、工具调用正确率、参数合规率等指标。报告阶段汇总评测结果定位失败场景输出结构化报告。3.1 评测任务合成的基本策略这是整套流程中最关键的环节。大致有三类策略第一类单工具直接调用根据单个工具的输入 schema生成一条自然语言指令期望智能体调用且只调用该工具并正确填写参数。例如工具get_weather参数city必填unit可填。生成请求“北京现在多少度请帮我查一下。”预期行为调用get_weather参数city北京。这种策略适合验证基础工具调用能力是评测集里的地基。第二类多工具协同任务从工具集中挑选多个有关联的工具组合成一条复杂任务。例如一个工具负责查询地址另一个工具负责查询天气评测任务就是“先查到杭州市西湖区的地址再查一下那里的天气”。这种策略能验证智能体的任务拆解和多步规划能力。第三类边界与异常场景基于参数 schema 的约束生成异常输入缺少必填参数。参数类型错误。枚举值超出范围。语义模糊需要澄清。多个参数组合冲突。这类用例的目的是测试智能体的容错能力和兜底策略。3.2 评测指标设计评测不能只看“最终回答是否成功”还需要关注过程指标。我常用的指标如下指标名称计算方式说明工具选择正确率正确工具调用数 / 总评测任务数智能体是否选对了工具参数填充完整率必填参数填完整的任务数 / 总任务数是否漏填参数参数值合规率参数值符合 schema 的任务数 / 总任务数参数类型、枚举是否合法任务完成率最终结果正确的任务数 / 总任务数是否真正完成任务平均工具调用轮数工具调用次数总和 / 总任务数反映执行效率在实现时最关键的是“判定逻辑”要分两层硬判定程序自动比对比如工具名称是否匹配、必填参数是否存在。软判定语义相似度判断比如最终回答是否准确这部分建议用 LLM 作为 Judge或者借助相似度计算。4. 完整实战实现一个最小可用的 Agent Seer下面进入实操环节。我会从零实现一个简化版但逻辑完整的流程让你可以复制到本地运行。4.1 准备 MCP 工具定义我们在specs/weather_server.json中准备一份简化的 MCP 工具定义。注意这里为了演示而手动构造了一个 JSON 文件实际项目中你可以通过 MCP Client 的list_tools接口动态获取。{ tools: [ { name: get_weather, description: 查询指定城市的实时天气信息, inputSchema: { type: object, properties: { city: { type: string, description: 城市名称例如 北京、上海 }, unit: { type: string, enum: [celsius, fahrenheit], default: celsius } }, required: [city] } }, { name: get_city_code, description: 根据城市名称查询城市代码, inputSchema: { type: object, properties: { city: { type: string, description: 城市中文名称 } }, required: [city] } } ] }这里我故意放了一个“先查询城市代码再查询天气”的潜在协同场景方便后面演示多工具任务。4.2 编写评测任务合成器创建generator/task_synthesizer.py。这个模块的核心目标是根据工具 schema自动生成评测用例。# 文件路径generator/task_synthesizer.py import json from typing import Any, Dict, List class TaskSynthesizer: 根据 MCP 工具定义自动合成评测任务。 def __init__(self, spec_path: str): with open(spec_path, r, encodingutf-8) as f: self.spec json.load(f) self.tools self.spec[tools] def _generate_single_tool_tasks(self) - List[Dict[str, Any]]: 基于单工具生成基础调用任务。 tasks [] for tool in self.tools: name tool[name] desc tool[description] schema tool[inputSchema] required schema.get(required, []) # 从 properties 里取第一个必填参数作为示例值来源 if not required: continue first_prop required[0] prop_schema schema[properties][first_prop] # 这里用简单的规则构造示例值实际可以接 LLM 生成更丰富的描述 sample_value 北京 if prop_schema.get(type) string else 1 user_request f{desc}请查询 {sample_value} 的信息 task { task_id: fsingle_{name}, user_request: user_request, expected_tool_calls: [ { tool_name: name, arguments: {first_prop: sample_value}, } ], scenario: single_tool, } tasks.append(task) return tasks def _generate_multi_tool_tasks(self) - List[Dict[str, Any]]: 基于工具组合生成多步协同任务。 tasks [] # 示例将 get_city_code 和 get_weather 串起来 city_tool None weather_tool None for tool in self.tools: if tool[name] get_city_code: city_tool tool if tool[name] get_weather: weather_tool tool if city_tool and weather_tool: tasks.append( { task_id: multi_city_weather, user_request: 我想知道上海的天气但调用天气接口前需要先根据城市名称查到城市代码请帮我完成整个流程。, expected_tool_calls: [ {tool_name: get_city_code, arguments: {city: 上海}}, {tool_name: get_weather, arguments: {city: 上海}}, ], scenario: multi_tool, } ) return tasks def synthesize(self) - List[Dict[str, Any]]: 合成所有评测任务。 tasks [] tasks.extend(self._generate_single_tool_tasks()) tasks.extend(self._generate_multi_tool_tasks()) return tasks这段代码的思路是先读取工具定义。对每个工具生成一个基础调用任务。再根据工具之间的潜在依赖生成一个多工具协同任务。实际工程中这里应该接入 LLM根据工具描述生成更自然、更多样的用户请求而不仅是“请查询 XX”。但作为最小示例这套规则已经能跑通流程。4.3 编写评测执行器创建executor/evaluator.py。这里的核心职责是输入一个评测任务模拟智能体执行过程并输出判定结果。为了让示例不依赖真实大模型和 MCP 网络服务我们先实现一个“模拟智能体”。这个模拟器中我们写死一个工具调用逻辑如果用户请求里包含“北京”或“上海”就调用get_weather。这样做的目的是快速验证评测框架本身真实环境中你可以换成对大模型 Agent 的调用。# 文件路径executor/evaluator.py import json from typing import Any, Dict, List class MockAgent: 一个用于演示的模拟智能体负责模拟工具调用行为。 def __init__(self, tool_specs: Dict[str, Any]): self.tool_specs tool_specs def run(self, user_request: str) - List[Dict[str, Any]]: 根据用户请求模拟返回工具调用轨迹。 tool_calls [] if 城市代码 in user_request or 查城市代码 in user_request: tool_calls.append( { tool_name: get_city_code, arguments: {city: 上海}, } ) if 天气 in user_request or 温度 in user_request: tool_calls.append( { tool_name: get_weather, arguments: {city: 北京}, } ) return tool_calls class Evaluator: 评测执行器比较智能体实际行为与预期行为。 def __init__(self, agent: MockAgent): self.agent agent def _check_tool_call( self, actual_calls: List[Dict[str, Any]], expected_calls: List[Dict[str, Any]] ) - Dict[str, Any]: 对比工具调用轨迹。 actual_names [call[tool_name] for call in actual_calls] expected_names [call[tool_name] for call in expected_calls] tool_correct actual_names expected_names # 检查参数 param_all_ok True for expected in expected_calls: matched [ call for call in actual_calls if call[tool_name] expected[tool_name] ] if not matched: param_all_ok False break for key, value in expected[arguments].items(): if matched[0][arguments].get(key) ! value: param_all_ok False break return { tool_correct: tool_correct, param_correct: param_all_ok, } def evaluate(self, task: Dict[str, Any]) - Dict[str, Any]: 执行单个评测任务。 user_request task[user_request] expected_calls task[expected_tool_calls] actual_calls self.agent.run(user_request) check_result self._check_tool_call(actual_calls, expected_calls) return { task_id: task[task_id], scenario: task.get(scenario, ), user_request: user_request, expected_calls: expected_calls, actual_calls: actual_calls, **check_result, } def generate_report(results: List[Dict[str, Any]]) - Dict[str, Any]: 汇总评测结果并生成报告。 total len(results) tool_correct_num 0 param_correct_num 0 for result in results: if result[tool_correct]: tool_correct_num 1 if result[param_correct]: param_correct_num 1 return { total: total, tool_correct_rate: round(tool_correct_num / total, 4) if total else 0, param_correct_rate: round(param_correct_num / total, 4) if total else 0, details: results, }这里需要注意我的MockAgent存在一个明显问题当请求“上海的天气”时它会同时调用get_city_code和get_weather但get_weather的参数会被错误地写死为“北京”。这正是评测框架的价值它能自动发现模拟智能体的工具参数错误。4.4 编写主流程入口创建main.py把合成器和执行器串联起来。# 文件路径main.py import json from generator.task_synthesizer import TaskSynthesizer from executor.evaluator import Evaluator, MockAgent, generate_report SPEC_PATH specs/weather_server.json def main(): # 1. 根据 MCP 规范合成评测任务 synthesizer TaskSynthesizer(SPEC_PATH) tasks synthesizer.synthesize() print(f共合成评测任务 {len(tasks)} 个) for task in tasks: print(f - {task[task_id]}: {task[user_request]}) # 2. 构造模拟智能体 with open(SPEC_PATH, r, encodingutf-8) as f: spec json.load(f) agent MockAgent(spec[tools]) # 3. 执行评测 evaluator Evaluator(agent) results [evaluator.evaluate(task) for task in tasks] # 4. 生成报告 report generate_report(results) print(\n 评测报告 ) print(f工具选择正确率: {report[tool_correct_rate]:.2%}) print(f参数填充正确率: {report[param_correct_rate]:.2%}) # 输出详细结果 for detail in report[details]: print(\n----------------------------) print(f任务ID: {detail[task_id]}) print(f用户请求: {detail[user_request]}) print(f预期工具: {[c[tool_name] for c in detail[expected_calls]]}) print(f实际工具: {[c[tool_name] for c in detail[actual_calls]]}) print(f工具选择是否正确: {detail[tool_correct]}) print(f参数是否正确: {detail[param_correct]}) if __name__ __main__: main()4.5 运行与预期结果在项目根目录执行python main.py预期输出大致如下共合成评测任务 3 个 - single_get_weather: 查询指定城市的实时天气信息请查询 北京 的信息 - single_get_city_code: 根据城市名称查询城市代码请查询 北京 的信息 - multi_city_weather: 我想知道上海的天气但调用天气接口前需要先根据城市名称查到城市代码请帮我完成整个流程。 评测报告 工具选择正确率: 66.67% 参数填充正确率: 33.33% ---------------------------- 任务ID: single_get_weather ...从结果可以看到模拟智能体在single_get_weather中参数填错了把北京写成了默认在multi_city_weather中工具顺序和参数也有问题。这个最小示例证明了评测框架的有效性即使是一个很粗糙的 Agent也能通过这套机制快速暴露问题。5. 进阶改造接入真实 MCP Server 与 LLM Agent上面的示例是本地模拟实际落地时你需要替换两个核心模块真实 Agent接入大模型并启用 MCP Client。真实工具执行可调用 MCP Server 执行工具或使用 Mock Server。下面给出一个“接入 OpenAPI 兼容大模型作为 Agent 推理核心”的代码草图。# 文件路径executor/llm_agent.py from openai import OpenAI class LLMAgent: 通过大模型 API 驱动智能体使用 OpenAI 兼容格式。 def __init__(self, base_url: str, api_key: str, model: str, tools: list): self.client OpenAI(base_urlbase_url, api_keyapi_key) self.model model # tools 是 MCP 中定义的 tools转换为 OpenAI function calling 格式 self.tools tools def run(self, user_request: str) - list: messages [{role: user, content: user_request}] response self.client.chat.completions.create( modelself.model, messagesmessages, toolsself.tools, ) tool_calls response.choices[0].message.tool_calls or [] parsed_calls [] for call in tool_calls: args_str call.function.arguments parsed_calls.append( { tool_name: call.function.name, arguments: json.loads(args_str), } ) return parsed_calls这段代码只是一个核心片段需要放入你自己的项目文件中并安装openaiPython SDK。注意OpenAI 的function calling格式与 MCP 的 Tool 格式有差异你需要做一次格式转换。转换并不复杂主要是将 MCP 的inputSchema映射到 OpenAI 的parameters字段。如果你的模型不支持 function calling也可以通过“文本提示 结构化输出”的方式让模型输出 JSON 格式的工具调用指令然后解析 JSON。这种方式更通用但对提示词要求较高。6. 常见问题与排查思路在实现这套评测方案时我遇到了一些典型问题这里以表格形式分享排查思路。问题现象常见原因解决思路MCP 工具列表获取为空MCP Server 未启动或鉴权失败检查 Server 地址、Token先用 Postman 或 curl 验证工具接口可访问工具注册不上工具 schema 格式与客户端期望不一致检查 MCP 规范版本确认 client 与 server 的 SDK 版本兼容生成的自然语言请求太僵硬规则模板过于简单接入 LLM根据工具描述生成多样化的用户意图并保留预期结果字段智能体调用了多个工具但顺序不对评测任务拆解不明确在合成任务时加入步骤序号和依赖关系字段判定时要求顺序匹配参数类型频繁出错模型对 schema 理解不足在工具描述中增加参数示例并在 System Prompt 中强调参数格式评测结果不稳定大模型输出有随机性设 temperature0 或较低值多次运行取统计结果而不是依赖单次输出多工具任务无法验证中间步骤只检查最终回答需要 Agent 支持记录工具调用日志从日志中解析真实调用轨迹另一个非常常见的坑是在 Codex 或 VS Code Copilot 这类工具里使用 Figma MCP 时工具注册不稳定。通常原因是 MCP Server 需要 WebSocket 连接网络代理或权限配置不对。排查顺序是先确认 MCP Server 能在独立客户端中正常工作再去排查 IDE 插件的连接配置。7. 最佳实践与工程建议到了这个环节我结合自己的落地经验给出一些工程层面的建议。7.1 MCP 规范本身要提前规范化既然评测依赖 MCP 规范规范的完整性就直接影响评测质量。工具定义里下面几点必须写清楚工具名称语义明确不要用func1这种无意义命名。description写清楚工具能力、适用场景、限制条件。参数说明每个参数都要有 description、类型、枚举值、默认值。必填约束务必准确required列表不要漏项。错误返回如果工具会返回错误码在 schema 或描述中补充说明。如果你的团队有 Git 提交规范或代码规范建议同样将 MCP 规范文件的命名、格式、目录纳入版本管理方便回溯评测集的变化。7.2 评测集与规范版本强绑定每次 MCP 服务端更新评测集都应重新生成并回归执行。建议在 CI 流程中增加一个自动化任务当specs/目录下的文件变更时自动触发评测。这样可以第一时间发现工具升级对智能体的影响。7.3 合成样例 人工审核结合完全自动合成的评测用例可能会出现“自然语言表达奇怪”或“预期结果与真实业务不符”的问题。我的建议是自动合成一批候选用例。由测试工程师或业务方抽查并标注其中一部分。将人工修正后的用例加入回归集逐步形成“自动生成 人工沉淀”的混合测试集。这种方法既保证了覆盖率也不会让评测集完全脱离人控。7.4 安全与权限边界在评测真实 MCP Server 时要特别注意安全边界尽量使用 Mock Server 或隔离的测试环境不要直接评测生产环境工具。对会修改数据的工具如写数据库、发送消息要在用例设计阶段避免真实副作用。如果评测中涉及密钥或 Token使用环境变量注入不要写死在代码或报告里。最小权限原则智能体评测的“工具执行账号”应只拥有测试环境的最小权限。7.5 指标要区分“过程”和“结果”只盯着“任务完成率”很容易掩盖过程问题。比如智能体最终答对了但中间误调用了多个无关工具这在实际生产中是高成本的。因此建议指标体系中同时保留工具选择正确率。参数合规率。平均调用轮数。整体任务成功率。这样既能发现“能不能完成”也能发现“完成得好不好”。8. 总结与下一步从 MCP 规范自动合成智能体评测并不是一个遥远的概念而是可以落地到日常开发流程中的工程方法。通过解析工具定义我们能够批量生成覆盖单工具调用、多工具协同、边界异常等场景的评测任务再结合模拟或真实智能体得到量化的评测结果。本文用一套最小 Python 实现走通了核心链路你可以在此基础上继续做三件事把 MockAgent 替换成真实 LLM Agent 和 MCP Client连接实际的大模型产品。把评测结果输出为 JSON/HTML 报告并接入 CI。引入 LLM-as-a-Judge 机制对最终回答的质量做语义层面的评分。智能体的评测体系本质上决定了一个 Agent 项目能不能从“能跑”走向“可信”。如果你正准备做智能体平台或 MCP 工具链建议尽早把“评测”当成一等公民纳入设计。如果这篇文章对你有帮助欢迎收藏备查。后续我还会继续分享 Agent 评测集构建、多智能体评测以及 MCP 工具链路压测方面的实战内容。