☰
Skybridge Evals 实战:如何在 CI 中用真实模型断言你 MCP App 的工具调用
2026/10/8 13:08:39 网站建设 项目流程

Skybridge Evals 实战:如何在 CI 中用真实模型断言你 MCP App 的工具调用

【免费下载链接】skybridgeSkybridge is a full-stack TypeScript framework for MCP Apps and ChatGPT Apps. Type-safe. React-powered. Platform-agnostic.项目地址: https://gitcode.com/gh_mirrors/skybr/skybridge

🧪Skybridge Evals是 Skybridge(一款面向 MCP Apps 与 ChatGPT Apps 的全栈 TypeScript 框架)内置的测试能力:它让一个真实模型在你的 App 上发起对话,把模型实际做出的工具调用交给你断言——不需要端口、不需要 fixture,跑在 Vitest 里,天然适合放进 CI。

为什么需要 Evals:工具能跑 ≠ 模型会调用

对 MCP App 来说,最隐蔽的 bug 不是"工具本身报错",而是"工具明明可用,模型却不调用它"——比如改了工具名、描述或参数 schema 之后,模型开始绕过它,或传错参数。

Skybridge 的测试工具箱按"保真度 vs 成本"分了层(见 docs/test/index.mdx):

想验证什么用什么真实模型适合 CI
手动调工具、看视图DevTools❌❌
用真实模型先手动对话Playground✅❌
在 CI 中断言模型的工具调用Evals✅✅
提交前整体合规检查Audit✅部分

推荐节奏:先用 Playground 手动确认"模型大致会用我的工具",再用 Evals 把这个行为固化成可重复运行的测试,之后每次改动由 CI 替你盯住。

一键接入:3 步开启 Evals

第 1 步:安装测试运行时

pnpm add -D @skybridge/test vitest@^4 ai @ai-sdk/anthropic

第 2 步:在 Vite 插件里打开evals开关

// vite.config.ts export default defineConfig({ plugins: [ skybridge({ evals: {} }), // 注册 expect.chat 匹配器,收集 evals/**/*.eval.ts react(), ], });

evals: {}会同时做四件事:注册断言匹配器、自动收集evals/目录下的场景、把单场景超时提到 2 分钟、加载.env让模型 API Key 可用。

第 3 步:加一个运行脚本

{ "evals": "vitest run evals" }

写第一个场景:自然语言提问 + 工具调用断言

场景的结构非常简单:start()在进程内把你的 App 服务起来(App 只需从src/server.ts导出,导入不会启动任何服务),发一条消息,然后断言:

// evals/search.eval.ts const chat = await start({ app, model: anthropic("claude-sonnet-4-5") }); await chat.send("Find me running shoes under 100 dollars"); expect.chat(chat).toHaveCalledToolWith("search-products", { query: "running shoes", });

关键点:expect.chat(chat)的类型直接来自你的 App——工具名有自动补全,参数按各工具的 input schema 做类型检查。改错工具名,编译器就会先于模型发现问题。

完整匹配器清单:断言模型的每一步行为

匹配器通过条件
toHaveCalledToolOnce(name, args?)恰好一次成功的调用,可匹配参数
toHaveCalledToolWith(name, args)某次成功调用匹配了参数
toNeverHaveCalledTool(name)该工具从未被尝试
toHaveFailedToolCall(name)某次调用被拒绝或抛错
toHaveSaid(text)助手回合包含文本(支持正则)
toHaveCalledToolsInOrder(...names)按相对顺序调用了这些工具
toHaveCalledNoTools()完全没调用任何工具
toPassJudgment(criteria)裁判模型按标准评分通过

所有匹配器都支持.not、覆盖整段对话(不只是最后一次send),失败信息会列出模型实际做过的每一次调用及参数。需要自定义断言时,chat.toolCalls和chat.assistantTurns也全部可用。

裁判模型:断言"语气得体"这类软性标准

有些行为没法用确定性匹配器表达,比如"回答是否礼貌、是否基于工具结果编造价格"。toPassJudgment会把整段对话交给一个裁判模型打分,失败的输出里直接带上裁判理由:

judge: FAIL The plan totals 640 euros. The assistant quoted hotel prices that do not appear in the search-hotels result.

裁判默认使用对话自身的模型,可传model换成更便宜的模型,也可以传judge换成任意打分服务(官方示例里用了一个返回概率的评估模型,缺 Key 时自动跳过自己,其余场景照常跑)。

💡 提示:裁判断言是实时模型调用——费钱且不完全可复现,只在其他匹配器都表达不了时再上,并把标准写得足够窄。

CI 稳定性的关键:用 stubs 钉住会漂移的数据

依赖"今天日期"或线上商品目录的场景,今天绿、下月红。stubs让你在场景里直接回答某个工具,而不经过你的 handler:

const chat = await start({ app, model, stubs: { "search-flights": ({ to }) => (to === "LIS" ? lisbonFixture : undefined), }, });

返回undefined就回落到真实工具——这样既能钉住一组参数、又让其余调用保持"真实"。被 stub 的调用同样计入chat.toolCalls,所有匹配器照常工作。

🔐测试带鉴权的工具:传authInfo即可为会话声明身份。注意它只跳过 token 验证,每个工具自己的鉴权方案和 scope 检查都会真实执行;不传则走匿名路径(含鉴权挑战)。

CI 实践:何时跑、怎么省

Evals 是真实模型调用,会消耗 token,建议这样安排:

  1. 触发时机——在改动工具的名字、描述或 input schema时运行(这正是最容易让模型"不会调用"的改动),或按夜间任务跑
  2. 成本控制——保持temperature: 0(默认值),断言落在工具调用而非措辞上;toHaveSaid尽量用宽松正则;裁判断言少而精
  3. 密钥注入——CI 中通过环境变量注入ANTHROPIC_API_KEY(.env会被插件自动加载)
  4. 数据稳定——会漂移的数据一律走stubs,别依赖线上接口
  5. 调参——skybridge({ evals: { systemPrompt, maxSteps, timeout } })设置全局默认,单个场景可在start中覆盖

相关代码与文档路径

  • 官方文档:docs/test/evals.mdx(完整参数与默认值)
  • 测试运行时源码:packages/test/,入口 packages/test/src/index.ts、会话与start()实现 packages/test/src/session-registry.ts
  • 匹配器实现:packages/test/src/matchers/index.ts
  • Vite 插件中 Evals 的接线逻辑:packages/vite-plugin/src/plugin.ts
  • 完整参考示例(一个专为测试而生的滑雪商店 App,4 个工具、每个匹配器都有对应场景):examples/evals/,场景如 examples/evals/evals/search.eval.ts

小结

✅ Skybridge Evals 让你用三行代码回答一个关键问题:真实模型到底会不会正确地调用我的 MCP App 工具?进程内运行、类型安全断言、stub 钉住易变数据,再配上 CI 触发策略,你的工具改动从此有一道自动守门员。

【免费下载链接】skybridgeSkybridge is a full-stack TypeScript framework for MCP Apps and ChatGPT Apps. Type-safe. React-powered. Platform-agnostic.项目地址: https://gitcode.com/gh_mirrors/skybr/skybridge

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询