跳到主要内容

Composio — Provider 适配器

本章讲什么: Composio 最优雅的设计——怎么用「一个 wrapTool 方法」把同一份工具数据适配到 OpenAI、LangChain、Vercel 等任意框架,而核心 SDK 完全不认识这些框架。以及为什么 provider 分「agentic / non-agentic」两类。

1. 它要解决的小问题

每个 agent 框架的工具格式都不一样:

框架工具长什么样
OpenAI{ type:'function', function:{ name, description, parameters } }
LangChainnew DynamicStructuredTool({ name, schema, func })(schema 是 zod)
Vercel AI SDKtool({ parameters, execute })

如果核心 SDK 直接产某一种格式,换框架就得重写。Composio 的解法:核心只产「中立工具数据」,把「翻译成框架格式」这件事外包给 Provider

2. 思路 / 直觉:适配器模式

类比: Provider 就是「插头转换器」。Composio 后端是「220V 电源」(中立工具数据),OpenAI / LangChain 是不同国家的「插座标准」。你出国不用换电器,换个转换头就行。

核心 SDK 只跟一个抽象接口打交道:「给你一批工具,你 wrap 成你的格式」。具体怎么 wrap,各 provider 自己实现。

Composio 中立工具数据 (Tool[])

┌───────────┼───────────┐
▼ ▼ ▼
OpenAIProvider LangchainP. VercelP.
.wrapTool() .wrapTool() .wrapTool()
│ │ │
▼ ▼ ▼
OpenAI 格式 LangChain 格式 Vercel 格式

3. 两类 Provider:agentic vs non-agentic

这是理解 provider 的关键分叉。差别在于「谁负责真正执行工具」。

non-agenticagentic
代表OpenAI(裸 chat completions)LangChain、Vercel、OpenAI Agents
框架会自己跑工具吗不会,只产 tool schema 给模型,框架内部有 agent loop
wrapTool 签名wrapTool(tool)wrapTool(tool, executeTool)
执行谁管你手动调 provider.handleToolCalls()框架回调 executeTool 自动执行

这个分叉直接刻在基类里(provider/BaseProvider.ts:82-129):

export abstract class BaseNonAgenticProvider<...> extends BaseProvider<...> {
override readonly _isAgentic = false;
abstract wrapTool(tool: Tool): TTool; // ← 不收执行函数
abstract wrapTools(tools: Tool[]): TToolCollection;
}

export abstract class BaseAgenticProvider<...> extends BaseProvider<...> {
override readonly _isAgentic = true;
abstract wrapTool(tool: Tool, executeTool: ExecuteToolFn): TTool; // ← 收执行函数
abstract wrapTools(tools: Tool[], executeTool: ExecuteToolFn): TToolCollection;
}

注意 wrapTool 签名差一个参数 executeTool:agentic provider 在 wrap 时就把执行函数缝进工具里,这样框架的 agent loop 选中工具后能直接调;non-agentic 不需要,因为执行由你显式触发。

4. non-agentic 实例:OpenAIProvider(默认)

OpenAIProvider 是 SDK 自带的默认 provider(composio.ts:317)。它的 wrapTool 极简——纯格式转换,不碰执行(provider/OpenAIProvider.ts:95-105):

override wrapTool = (tool: Tool): OpenAiTool => {
return {
type: 'function',
function: {
name: tool.slug,
description: tool.description,
parameters: tool.inputParameters, // 直接用 Composio 的 JSON schema
},
};
};

因为 OpenAI 裸 API 不自己跑工具,执行得你手动触发。Provider 为此提供 handleToolCalls:给它一个 chat completion 响应,它解析出工具调用、逐个执行、返回 tool 消息(provider/OpenAIProvider.ts:242-265)。执行时它调的是基类的 executeTool,后者最终走回 Tools.execute

执行函数从哪来?

Tools 构造时把全局执行函数注入给 provider(models/Tools.ts:142):

this.provider._setExecuteToolFn(this.createExecuteFnForProviders());

基类的 executeTool 就是这个注入函数的薄封装(provider/BaseProvider.ts:50-61)——这样 provider 不直接依赖 Tools,核心与 provider 解耦。

5. agentic 实例:LangchainProvider

LangChain 自己有 agent executor,会主动跑工具。所以它的 wrapTool 收第二个参数 executeTool,并把它包成 LangChain 工具的 func(ts/packages/providers/langchain/src/index.ts:124-150):

wrapTool(tool: Tool, executeTool: ExecuteToolFn): DynamicStructuredTool {
const func = async (...args: unknown[]): Promise<unknown> => {
// 模型偶尔把入参发成 JSON 字符串而非对象,normalize 容错(issue #2406)
const result = await executeTool(tool.slug, normalizeToolArguments(args[0], tool.slug));
return JSON.stringify(result);
};
const parameters = jsonSchemaToZodSchema(tool.inputParameters); // JSON schema → zod
return new DynamicStructuredTool({ name: tool.slug, description, schema: parameters, func });
}

两个看点:

  1. executeTool 缝进 func:LangChain 一旦选中工具就调 func,自动打回 Composio 执行。
  2. jsonSchemaToZodSchema:LangChain 要 zod schema,而 Composio 给的是 JSON schema,所以要转一道(这个转换器本身在 ts/packages/core/src/utils/jsonSchema.ts:329)。

6. 原理演示:写一个最小 provider

下面这段演示「实现一个自定义 non-agentic provider 要写什么」——核心就是 wrapTool/wrapTools:

// 示意,非源码:把工具转成「给某个假想框架」的格式
import { BaseNonAgenticProvider, Tool } from '@composio/core';

class MyProvider extends BaseNonAgenticProvider<MyTool[], MyTool> {
readonly name = 'my-framework';

wrapTool(tool: Tool): MyTool {
return { id: tool.slug, schema: tool.inputParameters }; // 翻译成你框架的格式
}
wrapTools(tools: Tool[]): MyTool[] {
return tools.map(t => this.wrapTool(t)); // 几乎总是 map 一遍
}
}
// 执行时:在拿到模型的工具调用后,调 this.executeTool(slug, {userId, arguments})

重点看:你只需关心「格式翻译」,执行能力由基类的 executeTool 免费给你。

7. 巧妙之处

  • 核心零框架依赖:@composio/core 不 import 任何 agent 框架;框架 SDK 反过来 import core。所以加新框架支持 = 新写一个 provider 包,核心一行不动。
  • agentic 标志驱动遥测与执行策略:_isAgentic 一个布尔位,既告诉遥测「这是 agentic 流程」(composio.ts:384),又决定 wrapTool 是否注入执行函数。一个标志贯穿两处行为。
  • 执行函数注入而非继承:provider 通过 _setExecuteToolFn 被动接收执行能力,而不是继承 Tools。这让 provider 可以单独测试、单独发包。

8. 边界与局限

  • non-agentic provider 的工具不会自动执行——你必须自己调 handleToolCalls 之类的 helper,忘了就只有 schema、没有动作。
  • wrapTool 是同步纯转换,不能在这里做异步取数据;要异步预处理得用 modifySchema(见 01)。

9. 横向对比

  • 这套「适配器 + 注入执行函数」与 LangChain 的 tool 抽象思路相通,但 Composio 把它抬到「跨框架」层级:同一份工具数据可同时供 N 个框架。
  • MCP(Model Context Protocol) 相比:MCP 是「协议层」标准,Composio provider 是「SDK 层」适配;两者不冲突,Composio 也能把会话暴露成 MCP endpoint(见 04)。

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

主题文件关键符号
Provider 基类ts/packages/core/src/provider/BaseProvider.tsBaseNonAgenticProviderBaseAgenticProviderexecuteTool_setExecuteToolFn
默认 providerts/packages/core/src/provider/OpenAIProvider.tsOpenAIProvider.wrapToolhandleToolCallsexecuteToolCall
agentic 范例ts/packages/providers/langchain/src/index.tsLangchainProvider.wrapTool
JSON→zod 转换ts/packages/core/src/utils/jsonSchema.ts:329jsonSchemaToZodSchema
Python provider 基类python/composio/core/provider/base.pyBaseProvider(Python)