news 2026/9/12 14:17:26

Composio Mastra Provider 测试指南:从 wrapTool 到 Schema 兼容性的完整测试体系

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Composio Mastra Provider 测试指南:从 wrapTool 到 Schema 兼容性的完整测试体系

Composio Mastra Provider 测试指南:从 wrapTool 到 Schema 兼容性的完整测试体系

【免费下载链接】composioComposio powers 1000+ toolkits, tool search, context management, authentication, and a sandboxed workbench to help you build AI agents that turn intent into action.项目地址: https://gitcode.com/GitHub_Trending/co/composio

Composio 的@composio/mastra包将 1000+ Composio 工具转换为 Mastra AI 框架的原生createTool格式,并内置执行能力。本文基于仓库中 ts/packages/providers/mastra/test/README.md 展开,逐层讲解该 provider 的单元测试覆盖范围、测试结构与 Mock 策略,并结合 src/index.ts 与各测试文件的源码实现,剖析 wrapTool/wrapTools/executeTool 的底层原理、strict 模式、输出 Schema 放宽(relaxation)以及$ref悬空引用的容错机制。读完本文,你将掌握如何运行与扩展这套测试,并理解 Mastra 与 Composio 集成时的类型转换、Schema 映射与错误处理全貌。

测试目录概览:为什么需要一个专属测试 README

@composio/mastra的测试位于 ts/packages/providers/mastra/test,该目录包含 5 个测试文件,覆盖 provider 从"身份声明"到"真实 Schema 编译"的完整契约:

  • mastra.test.ts:主测试套件,覆盖 provider 属性、工具包装、工具集合、执行、MCP 转换、strict 模式与类型安全;
  • mastra-ref.test.ts:针对真实@mastra/schema-compat$ref回归测试;
  • mastra-dangling-defs.test.ts:针对悬空$ref(声明了引用却未定义$defs)的容错回归测试;
  • output-validation.integration.test.ts:验证输出 Schema 放宽后真实第三方 API 响应可通过校验的集成测试;
  • relax-output-schema.test.ts:对relaxOutputSchema纯函数的逐条规则测试。

这套测试的价值在于:它不只验证"函数不抛错",还验证"与 Mastra 的集成契约是否正确"——包括 Schema 是否经createTool正确传递、工具结果能否通过 Mastra 的validateToolOutput校验、类型推断是否保持安全。

Provider 属性测试:身份与能力的断言

测试套件首先验证 provider 的基础属性(见 mastra.test.ts):

  • Name verification:断言provider.name === 'mastra',对应 src/index.ts 中readonly name = 'mastra'的实现;
  • Agentic nature:断言provider._isAgentic === true,确认该 provider 支持工具执行,是"可行动的"(agentic)provider,而非纯被动数据源。

这两条断言为整个集成定下基调:MastraProvider继承自BaseAgenticProvider<MastraToolCollection, MastraTool, MastraUrlMap>,是 Composio 统一 Provider 抽象中的"Mastra 方言"实现。

wrapTool:Composio 工具到 Mastra createTool 的转换

wrapTool是整套集成的核心。测试覆盖以下场景(mastra.test.ts):

  • Basic wrapping:验证createTool以正确的参数被调用,返回的对象具有iddescriptioninputSchemaoutputSchemaexecute五个关键字段;
  • Schema handling:验证输入/输出参数通过 Schema 转换器正确映射;
  • Edge cases:工具缺失description(回退为空字符串'')、缺失inputParameters(回退为空对象{})、缺失outputParameters(同理)时均能优雅处理;
  • Execution context:测试execute闭包在空参数{}、缺失参数undefined、完整参数三种上下文下的行为——缺失参数会被规范化为{}而不是原样透传undefined(对应 issue #2406)。

底层调用链:从 Tool 到 MastraTool

对照 src/index.ts,wrapTool的实际流程是:

  1. 读取tool.inputParameters作为输入 Schema;
  2. strict 模式:若构造 provider 时传入strict: true,调用toStrictJsonSchema将输入 Schema 规范化为 OpenAI structured outputs 契约(所有属性进required、对象闭合、可选属性放宽为可接受null);若 Schema 无法表达(如接受任意键的对象、allOfprefixItems、未解析的$ref),则保留原 Schema 并输出一条logger.warn
  3. 解引用$ref:调用dereferenceJsonSchema,以onUnresolved: 'sentinel'模式处理内部$ref指针,将悬空引用替换为宽松的对象 Schema({ type: 'object', additionalProperties: true });
  4. 兼容层转换applyCompatLayer({ schema, compatLayers: [], mode: 'jsonSchema' })将 JSON Schema 交给@mastra/schema-compat处理,供 Mastra 内部编译为 Zod;
  5. 输出 Schema 放宽:对tool.outputParameters先解引用、再经relaxOutputSchema放宽(详见下文);
  6. 创建 Mastra 工具createTool({ id: tool.slug, description, inputSchema, outputSchema, execute })
  7. 执行包装execute闭包内部先normalizeToolArguments规范化参数(兼容模型把工具输入输出成 JSON 字符串的情况,issue #2406),再在 strict 模式下用omitNullToolArguments剔除工具自身 Schema 不接受null的参数,最后调用全局executeTool函数。

测试通过 MockcreateTool后取出.mock.calls[0][0].executegetCreatedToolExecute辅助函数)来直接驱动execute闭包,从而验证参数规范化与版本透传行为——包括工具带version: '20250101_01'与不带版本两种情形,执行函数均以(tool.slug, inputData)形式调用全局执行器。

wrapTools:批量包装与集合键映射

wrapTools将工具数组归约为以工具 slug 为键的键值集合(mastra.test.ts):

  • Multiple tools:多个工具逐个经wrapTool包装,createTool被调用的次数与工具数一致;
  • Empty arrays:空数组返回空对象{},且不会触发createTool
  • Key mapping:集合键严格使用tool.slug(如first-toolsecond-toolthird-tool);
  • Duplicate handling:重复 slug 时后者覆盖前者,集合仅保留一个键。

实现见 src/index.ts:tools.reduce((acc, tool) => { acc[tool.slug] = this.wrapTool(tool, executeTool); return acc; }, {})。该返回值直接可传给 MastraAgenttools字段。

executeTool:全局执行、Modifiers 与错误传递

executeTool是执行层入口,测试验证(mastra.test.ts):

  • Global execution:调用provider.executeTool(slug, params)会转发到_setExecuteToolFn注入的全局执行函数,第三个参数为undefined
  • Modifiers supportbeforeExecute/afterExecute修饰器会原样作为第三个参数传给全局执行函数;
  • Error handling:当执行函数返回{ data: null, error: { message: 'Tool execution failed' }, successful: false }时,executeTool将其原样透传,不抛异常、不吞错误。

Mastra 集成与类型安全测试

集成层测试(mastra.test.ts)重点验证三件事:

  • Compatibility:包装结果具备iddescriptioninputSchemaoutputSchemaexecute属性,其中execute必须是函数,且id与工具 slug 一致——这是 MastracreateTool的硬性契约;
  • Type safetyMastraToolMastraToolCollection的类型定义(见 src/index.ts)在测试中通过类型断言验证;MastraToolCollection是字符串键对象而非数组;
  • Minimal tools:仅含 slug/name/description/tags 的最小工具也能包装成功,缺失的 Schema 全部回退为空对象。

错误处理:执行失败与畸形 Schema

错误路径测试(mastra.test.ts)确保鲁棒性:

  • Execution failuresexecute闭包内执行函数rejects时,异常向上抛出,测试用rejects.toThrow('Execution failed')断言;
  • Malformed schemasinputParametersnulloutputParametersundefined的工具调用wrapTool不会抛异常(Schema 回退为空对象)。

strict 模式测试:OpenAI structured outputs 契约

strict 模式是MastraProvider的可选能力(new MastraProvider({ strict: true })),测试覆盖(mastra.test.ts):

  • 默认关闭:不传参构造时strict === false
  • required-nullable 语义:开启后,可选属性optional_field: { type: 'string' }被改写为{ type: ['string', 'null'] }并加入required,对象追加additionalProperties: false——即"所有属性必填,但可选属性放宽为接受 null";
  • 不可表达的工具保留原 Schema:如headers: { type: 'object', additionalProperties: { type: 'string' } }(接受任意键的 map)无法表达为闭合对象,则整体保留原 Schema;
  • null 参数剔除:strict 模式下,工具自身 Schema 不接受null的参数(如cfg.note: null)在执行前被剔除,而 Schema 本身声明可空(type: ['string', 'null'])的参数(如clearable: null)保留;
  • 非对象/缺失参数inputParameters为字符串 Schema 或undefined时原样/回退处理,不因 strict 模式崩溃;
  • wrapTools 联动:strict 模式下批量包装仍以 slug 为键,且每个工具的 Schema 都应用 required-nullable 改写。

输出 Schema 放宽:让真实第三方 API 响应通过校验

这是整个 provider 最具价值的设计之一。Mastra 会通过validateToolOutputoutputSchema校验每个工具结果,不匹配就丢弃数据并替换为错误。而 Composio API 下发的输出 Schema 是严格的:可选字段被声明为非空原始类型、对象带additionalProperties: false。真实第三方 API(Linear、Notion、Jira、Slack 等)常对未设置的字段返回null,偶尔还返回多余键,导致原本合法的响应被拒、模型看到的工具输出被截断。

解决方案是 relax-output-schema.ts 中的relaxOutputSchema纯函数,对输出 Schema 做四项"只放宽、不收紧"的改写:

  1. 所有类型节点可空化type: 'string'['string', 'null'];已含null的类型数组不重复追加;
  2. additionalProperties: false(或未设置)的对象允许额外键:改为true;若additionalProperties本身是 Schema,则递归放宽;
  3. enum/const放宽为接受nullconst: 'fixed'变为enum: ['fixed', null]enum: ['open', 'closed']变为enum: ['open', 'closed', null]
  4. 删除required:真实 API 对未设置的字段是直接省略而非返回null,强求字段存在会拒掉合法输出。

该函数递归遍历itemsanyOfoneOfallOfproperties$defs等所有子 Schema 位置,并刻意处理not关键字——not是否定语义,放宽其内层 Schema 反而会收窄父级可接受的值域,违背"只放宽"的不变量(relax-output-schema.test.ts 专门回归验证)。同时函数不修改输入对象(不可变),null/undefined原样透传。

output-validation.integration.test.ts 用真实@mastra/schema-compat跑完整链路relaxOutputSchema → applyCompatLayer → convertSchemaToZod → parse,证明:严格 Schema 拒绝含null可选字段的真实响应(复现 bug),放宽后同一响应success === truenull与额外键完整保留、无数据截断;enum放宽后仍拒绝非法值('nope');not关键字下此前合法的值(null、数字)依旧合法。

$ref回归测试:解引用与悬空引用容错

Schema 中的$ref是另一个真实痛点,对应两个回归测试文件:

  • mastra-ref.test.ts 使用真实@mastra/schema-compat(不 Mock),验证dereferenceJsonSchema后:$defsUser.id的类型信息被保留(type: 'string',而非被降级为宽容的原始类型anyOf);输出侧definitions(Draft-7 写法)同样被保留(放宽后为['string', 'null']);包装后的 Schema 中不再残留任何$ref字符串;
  • mastra-dangling-defs.test.ts 针对 issue #3307:Composio API 下发的部分工具(如GMAIL_FETCH_EMAILS)声明了$ref: '#/$defs/...'却从未定义$defs,严格解引用会抛异常并让tools.get直接崩溃。Provider 通过onUnresolved: 'sentinel'将悬空分支替换为宽松对象 Schema,并保证:
    • 包装不抛异常、输出 Schema 中无残留$ref
    • 每个(toolSlug, ref)对只输出一条logger.warnwarnedDanglingRefs集合去重),提示降级为宽松校验并指向 issue 跟踪;
    • 不同 slug 携带相同 ref 时分别告警;
    • 告警内容中的用户可控片段(slug、toolkit、ref)经JSON.stringify转义,中和换行/ANSI 转义/控制字节,防止日志伪造(CWE-117 回归测试);
    • 同时向 telemetry 发送composio.mastra.wrapTool.danglingRef聚合事件(每对一次,可被COMPOSIO_DISABLE_TELEMETRY=true关闭),可解析的$ref不触发任何告警与遥测。

测试结构:Vitest 约定与 Mock 策略

按 test/README.md 的说明,测试遵循 Vitest 约定:

  • Comprehensive mocking:对@mastra/core(具体是createTool)与@composio/corejsonSchemaToModel,即 Schema 转换)使用vi.mock()隔离外部依赖;
  • Setup and teardownbeforeEach中重新构造MastraProvider、重置 Mock 工具与执行函数、vi.clearAllMocks(),保证每个用例干净隔离;
  • Detailed assertions:成功与失败场景均有详尽断言(expect(createTool).toHaveBeenCalledWith({...})全量比对调用参数);
  • Type-safe implementations:测试内定义MockedMastraToolCreateToolMockConfig等接口,保持类型安全。

值得注意的是 Vitest 的 Mock 作用域按文件隔离:mastra.test.ts顶层vi.mock('@mastra/schema-compat')不会泄漏到mastra-ref.test.tsmastra-dangling-defs.test.ts,因此这两个文件能对真实@mastra/schema-compat(基于 AJV 的编译链路)做端到端验证。此外,由于@mastra/corecreateTool(≥1.43)会把 JSON Schema 包装进JsonSchemaWrapper,测试用getSchema()辅助函数穿透包装层,检查真正编译产物的 JSON Schema。

运行测试

按 test/README.md 与 package.json 的脚本配置:

# 在 mastra provider 目录内运行(等价于 vitest run) npm test # 从工作区根目录运行,仅过滤 @composio/mastra 包 pnpm test --filter=@composio/mastra

package.jsontest: "vitest run"typecheck: "tsc --noEmit --skipLibCheck"可配合使用。注意该包要求node >= 22.22.3,peer 依赖为@composio/core >= 0.10.0@mastra/core ^1.46.0zod ^3.25 || ^4,运行前需确保依赖满足。

扩展测试的建议

结合上述源码,若需为 provider 新增测试,可参考以下切入点:

  1. 新的 Schema 关键字放宽规则:在relaxOutputSchema中增加规则时,先在 relax-output-schema.test.ts 补纯函数用例,再在 output-validation.integration.test.ts 补真实编译链路用例,保证"只放宽"不变量;
  2. 新的执行上下文形态:在execute闭包新增参数规范化逻辑时,参照 issue #2406 的测试模式,同时断言对象输入与 JSON 字符串输入两条路径;
  3. strict 模式边界:凡新增"strict 模式无法表达"的 Schema 形态,应断言其保留原 Schema 且只产生一次告警,避免日志刷屏;
  4. $ref场景:仿照mastra-dangling-defs.test.ts构造悬空/可解析/恶意 ref 三类 fixture,同时验证告警去重、CWE-117 转义与 telemetry 事件计数。

小结

test/README.md 用一份精炼的清单勾勒了@composio/mastra的测试全貌:provider 属性、工具包装、集合映射、执行链路、Mastra 集成契约、错误处理与类型安全。而测试文件本身则沉淀了四个真实生产问题的回归保障——模型输出 JSON 字符串参数(#2406)、严格输出 Schema 截断第三方响应(#3047)、悬空$ref导致tools.get崩溃(#3307)与日志注入(CWE-117)。理解这套测试,等于同时理解了 Mastra Provider 的 Schema 转换管线与容错哲学:对输入做结构化约束(strict 模式),对输出做宽容校验(relaxation),对上游数据缺陷做降级不崩溃。

【免费下载链接】composioComposio powers 1000+ toolkits, tool search, context management, authentication, and a sandboxed workbench to help you build AI agents that turn intent into action.项目地址: https://gitcode.com/GitHub_Trending/co/composio

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 14:14:27

ESLint max-classes-per-file 规则详解:限制单文件中的类数量

ESLint max-classes-per-file 规则详解&#xff1a;限制单文件中的类数量 【免费下载链接】eslint Find and fix problems in your JavaScript code. 项目地址: https://gitcode.com/GitHub_Trending/es/eslint 导读 max-classes-per-file 是 ESLint 内置的一条代码风格…

作者头像 李华
网站建设 2026/9/12 14:12:45

医疗AI如何用自然语言处理提升患者病历理解

1. 医疗健康领域的技术革新背景医疗健康行业正经历着前所未有的数字化转型浪潮。根据美国医学信息协会(AMIA)的统计&#xff0c;2022年全球医疗数据总量已达到40ZB&#xff0c;其中非结构化数据占比超过80%。这些数据中&#xff0c;病历记录作为核心医疗文档&#xff0c;其复杂…

作者头像 李华