跳到主要内容

Composio — 工具与执行主线

本章讲什么: tools.get(拿到框架能用的工具)和 tools.execute(真正调用一次工具)端到端怎么走,以及 before/after modifier 管线在哪两个位置插进去。读完你会知道 SDK 在一次工具调用里到底做了哪几件事。

1. 先看脊梁:两个方法

Composio 的工具世界只有两个核心动作:

动作方法干什么
取工具composio.tools.get(userId, filters)从后端拉工具 schema,套成 provider 格式,返回可直接喂给 agent 框架的工具
执行工具composio.tools.execute(slug, body)把工具 + 参数发到后端真正调用,返回结果

get 通常你只调一次(组装 agent 时);execute 在 agent 每次选用工具时被框架自动回调——不是你手动调的。这点很关键,见下一节。

2. tools.get:取工具 + 套格式 + 绑执行函数

它要解决的小问题

后端给的工具 schema 是「裸数据」(snake_case、跟某个框架无关)。agent 框架要的却是它自家格式的工具对象,而且工具被选中后还得知道「怎么真正执行」。get 把这两件事一次办了。

主线三步

tools.get(userId, {toolkits:['github']})

① getRawComposioTools ── HTTP ──▶ 后端 → 裸 schema(snake_case)
│ transformToolCases:转 camelCase + 严格 Zod 校验
│ applyDefaultSchemaModifiers:默认 schema 改写(如文件上传字段)

② wrapToolsForProvider
│ createExecuteToolFn(userId):造一个「绑死了 userId 的执行闭包」

③ provider.wrapTools(tools, executeFn)
│ 套成框架格式,把执行闭包塞进每个工具

返回:框架能直接用、且自带执行能力的工具集合

第 ② 步是精髓:createExecuteToolFnuserId 提前柯里化进闭包里,这样 agent 框架后面调工具时只需传「工具名 + 参数」,不必再关心是哪个用户(models/Tools.ts:833-848):

private createExecuteToolFn(userId: string, modifiers?: ExecuteToolModifiers): ExecuteToolFn {
const executeToolFn = async (toolSlug: string, input: Record<string, unknown>) => {
return await this.execute(toolSlug, {
userId, // ← 提前绑死,框架不用再传
arguments: input,
dangerouslySkipVersionCheck: true, // agentic 流程默认跳版本检查(见 §5)
}, modifiers);
};
return executeToolFn;
}

get 的两个重载

get 既能按 filter 批量取、也能按单个 slug 取(models/Tools.ts:690-769)。它在运行时用 typeof arg2 === 'string' 分流:字符串走「取单个工具」,对象走「按 filter 批量」。两条路最后都汇到 wrapToolsForProvider

一个容易忽略的细节:自动「important」过滤

当你只给 toolkits、没给 limit/search/tags 时,getRawComposioTools默认只取该 toolkit 里标了 important 的工具(models/Tools.ts:434-444)。原因:一个 toolkit 可能有上百个工具,全塞进上下文会爆 token,所以默认只给「主力工具」。想要全部就显式传 limitimportant: false

3. tools.execute:before → 调用 → after

怎么读这张图

这是一次执行的完整管线。注意 modifier 有两个插入点:发请求前(beforeExecute)和拿到结果后(afterExecute)。中间那步「真正调后端」才是唯一一次网络业务调用。

execute(slug, {userId, arguments, ...})

[校验] ToolExecuteParamsSchema.safeParse

[取 schema] getRawComposioToolBySlug(slug) ← 需要工具的 toolkit/版本信息

┌────▼─────────────── applyBeforeExecuteModifiers ──────────────┐
│ (a) 若开启自动上传:把本地文件 path 换成已上传的 s3 描述符 │
│ (b) 跑用户的 beforeExecute(params) → 改后的 params │
└────┬──────────────────────────────────────────────────────────┘

[执行] executeComposioTool ── HTTP ──▶ 后端真正调第三方 API

┌────▼─────────────── applyAfterExecuteModifiers ───────────────┐
│ (a) 若开启自动下载:把结果里的文件描述符下载到本地 │
│ (b) 跑用户的 afterExecute(result) → 改后的 result │
└────┬──────────────────────────────────────────────────────────┘

返回 ToolExecuteResponse { data, error, successful, logId }

真实主线就是这五步,顺序清清楚楚(models/Tools.ts:1016-1066):

async execute(slug, body, options?) {
const executeParams = ToolExecuteParamsSchema.safeParse(body); // 校验
const tool = await this.getRawComposioToolBySlug(slug, { version: body.version });
const params = await this.applyBeforeExecuteModifiers(tool, {...}, modifiers); // before
let result = await this.executeComposioTool(tool, params); // 真正执行
result = await this.applyAfterExecuteModifiers(tool, {...}, modifiers.afterExecute); // after
return result;
}

一个易踩的设计:after-modifier 不被 abort 短路

用户可以传 AbortSignal 取消请求。但 Composio 在 applyAfterExecuteModifiers故意不让晚到的取消短路掉 after 处理——因为工具此刻已经执行、可能已改了外部状态(比如已经发出了邮件)。如果这时跳过 after 管线,会留下「半成品」结果。注释把这个权衡讲得很清楚(models/Tools.ts:322-329):一旦远程调用提交,就把文件下载 + afterExecute 跑完;signal 仍会传给下载,让卡住的传输优雅降级,但绝不跳过 afterExecute。

4. Modifier 是什么(为什么重要)

Modifier 是 Composio 给你的「钩子」,让你在不改 SDK 的前提下拦截工具的输入输出。三种钩子:

钩子时机典型用途
modifySchemaget 时改工具 schema改描述、加约束、注入字段
beforeExecute发请求前改参数注入鉴权头、规范化输入、加默认值
afterExecute拿到结果后改结果裁剪冗余字段、脱敏、统一格式

源码里的类型定义带了很实用的例子(types/modifiers.types.ts:41-74),比如「给 GITHUB_SEARCH_REPOS 的 query 自动加上 language:typescript stars:>100 过滤」。

原理演示(简化)

下面这段演示 beforeExecute 的本质——它就是个「拿到 params、返回新 params」的纯函数:

// 示意,非源码:给所有 github 工具的调用自动加一个追踪头
const beforeExecute = ({ toolSlug, toolkitSlug, params }) => {
if (toolkitSlug !== 'github') return params; // 只处理 github
return {
...params,
arguments: { ...params.arguments, _trace: Date.now() }, // 注入字段
};
};

const result = await composio.tools.execute('GITHUB_CREATE_ISSUE',
{ userId, arguments: { owner: 'composio', repo: 'sdk', title: 'hi' } },
{ beforeExecute } // 钩子在这里挂进去
);

重点看:modifier 是你提供的函数,SDK 只负责在管线的固定位置 await 它。

5. 关键细节:工具版本(toolkit version)

Composio 的工具是版本化的(比如 20250909_00)。后端会随时间更新某个 toolkit 的工具 schema,版本号锁定行为。

这带来一个保护机制:手动 execute 默认拒绝「latest」。如果版本解析成 latest 且你没显式开 dangerouslySkipVersionCheck,会抛 ComposioToolVersionRequiredError(models/Tools.ts:896-898):

if (toolkitVersion === 'latest' && !body.dangerouslySkipVersionCheck) {
throw new ComposioToolVersionRequiredError();
}

用意:生产环境别让「latest」悄悄变。而 agentic 流程(通过 provider 执行)默认会跳过这个检查(见 §2 的 createExecuteFnForProviders,models/Tools.ts:777-785),因为 agent 通常是「先拉最新、紧接着执行」,中间不会变版本。

6. 边界与局限

  • SDK 不实现业务逻辑:真正调 GitHub/Gmail 的活在 Composio 云端后端;SDK 是个「取 schema + 转发执行 + 跑 modifier」的客户端。离线跑不了。
  • 手动 execute 的文件上传:关闭 dangerouslyAllowAutoUploadDownloadFiles 时,文件参数得你自己用 composio.files.upload() 先 stage,否则后端会拒;SDK 会发一次性警告提醒(models/Tools.ts:261-281)。
  • toolstoolkits filter 不能同时用,会直接抛 ValidationError(models/Tools.ts:421-425)。

7. 横向对比

  • 同 shelf 的 LangChain/LangGraph 等框架自己定义 tool 抽象,但不自带「上千个现成 toolkit + 托管鉴权」;Composio 的差异化正是「现成工具 + OAuth 托管」。
  • 与「直接写 function calling」相比,Composio 的额外价值是 modifier 管线 + 版本化 + provider 适配,代价是工具执行要过它的后端。

8. 代码地图(导航索引)

主题文件关键符号
取工具(批量)ts/packages/core/src/models/Tools.tsgetRawComposioToolstransformToolCases
取工具(套格式)ts/packages/core/src/models/Tools.tsgetwrapToolsForProvider
执行主线ts/packages/core/src/models/Tools.tsexecuteexecuteComposioTool
before/after 管线ts/packages/core/src/models/Tools.tsapplyBeforeExecuteModifiersapplyAfterExecuteModifiers
绑 userId 的执行闭包ts/packages/core/src/models/Tools.tscreateExecuteToolFncreateExecuteFnForProviders
版本检查ts/packages/core/src/models/Tools.tsComposioToolVersionRequiredError
modifier 类型ts/packages/core/src/types/modifiers.types.tsbeforeExecuteModifierafterExecuteModifier
Python 对应python/composio/core/models/tools.pyTools.getTools.execute