从 MCP 工具定义自动生成 Agent 评测集:把可靠性验证接入 CI

一、为什么评测集应该从工具契约开始

Agent 评测最容易失真的地方,是用例和真实能力脱节。MCP 工具定义同时描述名称、参数和返回约束,天然是一份机器可读契约。把它作为生成种子,评测就能随接口变化同步更新。每次提交都能留下可追踪差异。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。

很多团队仍靠人工维护 JSON 用例,工具增加到 15 个后,遗漏率会明显上升。定义驱动的方法先读取输入 Schema,再把必填项、枚举值和边界值转成测试维度。这样测试覆盖的是能力边界,而不是作者记忆。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。

工具描述里的 description 不能只写营销话术。它应说明业务前置条件、权限要求和失败含义,评测生成器才能构造有效任务。一个字段缺少范围说明,往往会让验证器无法区分拒绝与异常。契约质量决定数据质量。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。

生成评测集时必须保留来源指针,包括工具名、Schema 路径和规则版本。报告发现失败时,工程师可以直接回到定义定位原因。没有来源绑定的分数只能展示趋势,不能支撑回归决策。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。

MCP 的 Tool、Resource 与 Prompt 应分别建模。Tool 适合动作测试,Resource 适合读取一致性,Prompt 适合流程组合。把三类对象混成一张表,会导致期望结果和实际副作用难以对应。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。

第一轮生成不应追求大量样本。针对每个工具生成 2 类正常输入、3 类边界输入和 1 类拒绝输入,通常已经能暴露 Schema 误差。小而稳定的种子比随机扩张更利于审查。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。

评测样本需要区分静态断言和语义断言。状态码、字段类型、数组长度属于静态断言,答案是否包含授权范围则属于语义断言。两者混在同一函数里,会让失败原因失去可读性。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。

版本变化是评测集的核心输入。工具从 v2 升到 v3 时,生成器应输出新增、删除和收紧约束的清单。只有明确变化影响,CI 才能判断是预期升级还是兼容性回退。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。

二、把 JSON Schema 编译成可执行用例

Schema 到用例不是简单遍历字段,而是把类型、必填关系和条件约束转换成任务意图。字符串字段可以生成空值、超长值和合法值,枚举字段则覆盖每个分支。组合规则必须进入同一个样本。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。

边界数据必须来自 Schema,而不是拍脑袋。整数范围若规定为 1 到 100,生成器至少应覆盖 1、100 和越界值。浮点字段还要测试精度截断,否则生产中的金额误差不会被发现。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。

对象嵌套会放大组合数量。一个包含 4 个字段的对象,如果每个字段取 3 种状态,理论组合达到 81 种。工程上应使用正交采样和风险权重,先保留高影响路径。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。

条件 Schema 需要显式记录触发条件。字段 mode 为 batch 时才允许 batch_size,评测器就应生成成对样本验证条件开启和关闭。只测默认路径,会把分支错误隐藏到上线之后。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。

名称和描述也参与生成质量。工具名表达动作,描述表达约束,二者共同决定用户任务的自然语言。生成器应拒绝空描述,并把低质量定义标记为契约警告。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。

参数顺序不应决定测试结果。调用层必须按键名构造对象,再由 Schema 校验器统一排序。这样客户端换语言或 SDK 后,评测集仍能复用,不会出现位置参数漂移。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。

错误用例要有期望分类。缺字段属于 validation_error,权限不足属于 authorization_error,后端故障属于 execution_error。分类越稳定,CI 越能按责任边界分派告警。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。

评测数据要避免真实隐私。生成器可以用固定租户、虚拟订单和合成邮箱,保持字段形状但不触碰生产数据。脱敏不是附加步骤,而是评测样本进入 Git 前的强制门。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。

三、受控 Runner 让评测结果可重复

评测 Runner 不应该直接连接生产工具。它应运行在隔离环境,使用 Mock Server、临时数据库和可回滚文件系统。这样一次失败不会改变真实状态,重复执行也能得到相同前置条件。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。

Runner 的输入是用户任务和工具清单,输出应包含调用序列、参数快照、返回结果和耗时。四类证据缺一不可。只保存最终答案,无法判断模型选错工具还是工具返回错误。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。

时间、随机数和网络响应会破坏重复性。测试环境需要固定时钟、种子和响应夹具,并把外部访问默认设为拒绝。任何未声明的网络调用都应被记录为隔离违规。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。

工具副作用必须采用事务边界。写入操作先落到临时仓库,验证通过后再提交快照,失败则回滚。这个设计既能测试写操作,也能避免测试数据污染后续样本。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。

多工具任务要记录因果链。一次查询可能先调用身份工具,再读取资源,最后执行动作。Runner 应为每步分配序号和父步骤,报告才能解释哪一步改变了决策。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。

超时策略要分层设置。单工具可以限制为 10 秒,整项任务可以限制为 30 秒,流水线总时长则应有独立上限。没有分层超时,慢请求会拖住全部并发任务。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。

并发执行要隔离租户和文件目录。即使两个样本调用同一个工具,也不能共享可变缓存。隔离键至少包含任务编号和数据版本,避免顺序差异造成偶发失败。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。

Runner 的日志要面向审计而非调试堆栈。日志应隐藏令牌和个人数据,只保留哈希、类型、时间与决策结果。安全日志可读,评测结果才适合长期保存。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。

四、验证器如何判断 Agent 真正完成任务

验证器首先检查工具调用是否符合预期,但不能停在工具被调用。用户目标可能要求结果过滤、权限收敛和状态更新,必须用业务断言检查最终状态。调用成功不等于任务成功。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。

字段级验证适合确定性接口。对返回对象逐项检查类型、必填键和数值范围,再把差异路径写进报告。路径化错误比一段长字符串更容易定位,也便于在 CI 中聚合。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。

顺序验证用于有依赖的流程。身份确认必须发生在数据读取之前,写操作必须发生在预览之后。验证器可以把调用序列转换成图,再检查前置节点是否存在。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。

语义验证需要控制尺度。可以检查答案是否提到租户、时间范围和风险提示,但不应把自由表达锁成唯一文案。过严的文本匹配会把合理答案误报成失败。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。

拒绝能力同样是质量指标。面对缺少权限或危险参数,Agent 应拒绝调用并给出可行动解释。评测集要统计安全拒绝率,而不是只统计完成率。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。

验证器应返回结构化结果。每个断言包含 id、状态、证据和修复建议,多个断言失败时仍要继续收集。结构化结果可直接转换成 Markdown、JSON 和评论机器人消息。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。

分数必须按风险加权。只读查询通过并不能抵消越权写入,安全断言应拥有更高权重。权重表放进版本控制,改动时要求评审,避免人为调分。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。

失败样本要支持最小复现。报告保留脱敏任务、工具版本和验证器版本,工程师可以用同一输入重跑。没有最小复现,修复只能依赖猜测,回归价值会迅速下降。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。

五、接入 CI 的门禁与回归策略

评测集进入 CI 后,触发条件应覆盖工具 Schema、提示模板和 Agent 策略变化。只在模型代码变更时运行,会漏掉契约更新带来的兼容性风险。变更检测应比较规范化后的 JSON。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。

快速门和完整门需要分开。快速门运行高风险的 6 个样本,控制反馈时间;完整门在合并队列执行全部数据,保证覆盖率。两层门禁能兼顾开发速度和发布可信度。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。

CI 输出不能只有一个总分。应展示通过率、拒绝率、工具选择准确率、平均耗时和 P95 耗时。多个指标共同变化时,团队才能判断是质量下降还是性能退化。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。

阈值应采用基线加容差。若历史通过率为 90%,可以设置 2 个百分点的回退警戒,而不是要求每次完全相同。随机模型仍有波动,门禁要阻止趋势性恶化。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。

模型升级必须保留对照组。新模型和当前模型使用同一份种子、夹具和验证器,差异报告才有意义。若只跑新模型,任何变化都无法归因。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。

失败重试不能掩盖不稳定。每个样本最多重试 2 次,并同时记录首轮结果和最终结果。通过率高但重试率上升,说明系统存在潜在抖动。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。

CI 缓存只缓存不可变输入。工具定义哈希、夹具版本和模型标识应构成缓存键,运行结果不能跨版本复用。错误缓存会让红灯变绿,是比慢更危险的问题。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。

评测报告要作为构建产物保存 30 天。保留周期足够覆盖一次发布窗口和一次事故复盘,同时避免无限堆积。报告链接写入合并请求,审查者无需进入运行节点查日志。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。

复制代码
import json
import hashlib
import sys
from dataclasses import dataclass
from typing import Any, Callable

@dataclass
class Case:
    name: str
    tool: str
    arguments: dict[str, Any]
    expected: dict[str, Any]

@dataclass
class CheckResult:
    case: str
    passed: bool
    details: list[str]

def stable_hash(value: Any) -> str:
    raw = json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
    return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:16]

def make_cases(tool_def: dict[str, Any]) -> list[Case]:
    schema = tool_def.get("inputSchema", {})
    props = schema.get("properties", {})
    required = set(schema.get("required", []))
    normal = {}
    for name, spec in props.items():
        if name in required:
            kind = spec.get("type")
            if kind == "string": normal[name] = "demo"
            elif kind == "integer": normal[name] = spec.get("minimum", 1)
            elif kind == "boolean": normal[name] = True
            else: normal[name] = {}
    cases = [Case("normal", tool_def["name"], normal, {"kind": "success"})]
    for name, spec in props.items():
        if name in required:
            missing = dict(normal); missing.pop(name, None)
            cases.append(Case("missing_" + name, tool_def["name"], missing, {"kind": "validation_error"}))
        if spec.get("type") == "integer" and "minimum" in spec:
            bad = dict(normal); bad[name] = spec["minimum"] - 1
            cases.append(Case("boundary_" + name, tool_def["name"], bad, {"kind": "validation_error"}))
    return cases

def run_case(case: Case, executor: Callable[[str, dict[str, Any]], dict[str, Any]]) -> CheckResult:
    details = ["input=" + stable_hash(case.arguments)]
    try:
        result = executor(case.tool, case.arguments)
    except Exception as exc:
        result = {"kind": "execution_error", "error": type(exc).__name__}
    actual = result.get("kind")
    expected = case.expected.get("kind")
    passed = actual == expected
    details.append("expected=" + str(expected))
    details.append("actual=" + str(actual))
    return CheckResult(case.name, passed, details)

def fake_executor(tool: str, args: dict[str, Any]) -> dict[str, Any]:
    if tool != "search_orders": return {"kind": "validation_error"}
    if "user_id" not in args or "limit" not in args: return {"kind": "validation_error"}
    if not isinstance(args["limit"], int) or args["limit"] < 1: return {"kind": "validation_error"}
    return {"kind": "success", "items": []}

def main() -> int:
    definition = {"name": "search_orders", "inputSchema": {"type": "object", "required": ["user_id", "limit"], "properties": {"user_id": {"type": "string"}, "limit": {"type": "integer", "minimum": 1}}}}
    cases = make_cases(definition)
    results = [run_case(case, fake_executor) for case in cases]
    report = {"tool": definition["name"], "case_count": len(results), "passed": sum(r.passed for r in results), "results": [r.__dict__ for r in results]}
    print(json.dumps(report, ensure_ascii=False, indent=2))
    return 0 if all(r.passed for r in results) else 1

if __name__ == "__main__":
    sys.exit(main())

六、生产安全与持续演进

安全评测从身份开始。每个工具调用都应绑定租户、主体和授权范围,验证器检查实际参数是否越过边界。模型输出的 user_id 不能覆盖认证上下文中的真实主体。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。

敏感工具要采用最小权限。数据库查询优先使用只读账号,写操作拆成预览和提交两步,删除动作要求人工确认。评测集必须覆盖越权、重复提交和重放请求。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。

提示注入应作为攻击样本进入数据集。资源内容里放置诱导指令,观察 Agent 是否把数据当命令执行。验证器重点检查工具选择、权限边界和最终解释,而非只看文字。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。

MCP Server 的描述内容也是供应链输入。接入前应校验来源、版本和哈希,变更自动触发评测。未经审查的工具定义进入上下文,可能扩大攻击面和数据外泄范围。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。

密钥不能写入样本和日志。Runner 使用短期凭证,报告保存脱敏标识,失败堆栈经过字段过滤后才上传。评测平台本身拥有高权限,更需要默认拒绝和分层审计。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。这一步让失败可定位。

长期维护要关注样本老化。业务规则变化、工具弃用和模型更新都会让旧样本失去代表性。每个样本应标注创建版本、最近执行时间和失效原因,便于周期清理。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。边界证据必须保留。

当工具数量超过 20 个时,不应把所有定义一次塞进上下文。可以先检索相关工具,再执行少量候选,降低 token 消耗和误选概率。评测集也要测工具发现阶段。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。结果需要进入版本库。

真正成熟的评测体系不是一份静态文件,而是一条契约到数据、执行、验证和发布的流水线。它让每次变更都留下证据,让可靠性从口号变成可审查的工程属性。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。工程判断要能复现。

复制代码
from jsonschema import validate
schema={"type":"object","required":["user_id"],"properties":{"user_id":{"type":"string"}}}
validate({"user_id":"demo"}, schema)
print("schema ok")
相关推荐
萧鼎2 小时前
2026新库实测:sbxloop 1.5.24 让 AI Agent 在 Docker 沙箱中安全自治,告别环境混乱
人工智能·python·开源·开发工具·ai agent
Blockbuater_drug4 小时前
MCP Server 接入实战: 9种平台配置差异与凭证安全
claude·cursor·mcp·openclaw·hermes agent·dsh·agent 配置
Geek-Chow6 小时前
MCP 模型上下文协议:八、深入传输层 · stdio 与 Streamable HTTP
人工智能·mcp
deepseek236 小时前
Agent Control Plane 同构拆解:Forrester 三平面首登分类,KPMG 27.6 万 Agent 部署背书
ai agent·forrester·agent control plane·企业级治理
asaotomo20 小时前
从抓包插件到浏览器安全 Agent:Hx0 鹰眼 v1.0.6,正式接入 MCP
安全·渗透测试·agent·浏览器插件·ai工具·mcp
guwentian1 天前
手撕 MCP:用 TypeScript 从零写一个能跑的最小客户端(附可运行 demo)
开发语言·nodejs·mcp
deepseek231 天前
Google Gemini Agentic Video 上线:88% 少 token 的主动取样如何改写长视频理解
python·多模态·ai agent·gemini·视频理解
deepseek231 天前
Anthropic开源Commerce Agents:购物与商户智能体如何把审批写进工具链
人工智能·ai agent·mcp
lunzi_08261 天前
【无标题】
ai·金融·开源·供应链安全·ai agent·银行开源治理