news 2026/9/10 13:37:20

Sim 集成工具开发规范:从服务 API 到注册表全流程实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Sim 集成工具开发规范:从服务 API 到注册表全流程实战指南

Sim 集成工具开发规范:从服务 API 到注册表全流程实战指南

【免费下载链接】simSim is the collaborative workspace to build, deploy, and monitor AI agents and workflows. Used by 100,000+ builders.项目地址: https://gitcode.com/GitHub_Trending/sim16/sim

导读

本文以 Sim 仓库中 apps/sim/tools/AGENTS.md 为骨架,系统讲解在 Sim 中为外部服务(Slack、Exa、Airtable 等数百个集成)开发、维护工具定义(Tool Definition)的完整规范:目录结构、工具 ID 命名、参数可见性分级、响应转换与注册表对齐。读完本文,你将掌握"以服务 API 文档为起点 → 按目录约定建模 → 定义参数可见性 → 转换响应 → 注册工具"的标准开发链路,并能对照仓库中的真实工具实现(如 Exa Search、Slack 消息读取)落地自己的集成工具。


一、适用范围与核心原则

apps/sim/tools/AGENTS.md开篇即划定边界:这些规则适用于apps/sim/tools/**下的所有集成工具定义(integration tool definitions)。所谓集成工具,指的是 Sim 工作流与 Copilot 中可被 LLM 调用的、面向外部 SaaS 服务的能力单元——从仓库 apps/sim/tools 的顶层目录可以看到,它涵盖了 a2a、affinity、airtable、github、gmail、slack、snowflake、stripe 等数百个服务,每个服务一个目录。

整套规范的核心原则可以概括为一句话:先读服务 API 文档,再动手写工具("Start from the service API docs before adding or changing a tool")。这意味着:

  1. 工具的参数、请求、响应结构必须以服务官方 API 为唯一事实来源;
  2. 任何对既有工具的修改,也应回到 API 文档核对字段含义与取值范围;
  3. 工具是 Sim 对外部世界的"翻译层",不是 API 的直通代理——它要负责把原始响应提炼成 LLM 与用户可消费的结构化输出。

这一原则在 Exa Search 工具 中体现得很典型:url指向https://api.exa.ai/searchbody构造严格遵循 Exa API 的请求契约(如/search下内容选项嵌套在contents字段中,与/contents的顶层字段不同),注释明确写明了这一差异。

二、目录结构约定:一服务一目录,一动作一文件

2.1 标准文件布局

AGENTS.md 规定每个服务必须位于tools/{service}/下,并包含三个必备要素:

  • index.ts:作为该服务的统一导出入口,聚合该服务下的所有工具;
  • types.ts:定义该服务的参数类型(XxxParams)与响应类型(XxxResponse),并承载可复用的输出属性常量;
  • 每个 action 一个文件:一个工具(一次 API 操作)对应一个独立文件。

以 Slack 服务 为例,其目录下有get_message.tslist_channels.tsschedule_message.tsmessage.ts等几十个 action 文件,每个文件只负责一个工具定义;index.ts 则统一exportslackMessageToolslackGetMessageToolslackListChannelsTool等全部工具,并同时按名称导出,供 registry.ts 引用。

2.2 为什么这样拆分

从工具定义的类型结构(见 types.ts 中的ToolConfig)可以推断,每个工具定义体量不小:包含id/name/description/version、完整的params参数 schema、request请求构造、transformResponse响应转换、outputs输出 schema,以及可选的oauthhostingpostProcess等配置。一动作一文件的好处是:

  • 每个文件的职责单一,便于审阅与测试(仓库中大量存在exa.test.tsoperations.test.ts等与工具文件一一对应的测试);
  • 增加新 API 操作时不触碰既有工具,降低回归风险;
  • registry 的导入关系清晰可追踪。

三、工具 ID 规范:snake_case 与注册表精确对齐

3.1 硬性要求

AGENTS.md 明确两条 ID 规则:

  • 工具 ID 必须使用snake_case(如slack_get_messageexa_search);
  • 工具 ID 必须与 registry 中的键精确匹配("match registry keys exactly")。

3.2 ID 解析机制

tool-ids.ts 提供了 ID 解析的底层实现:

  • getToolIds()返回全部已注册 ID(从 generated/tool-ids 生成的静态列表导入,避免加载整个可执行注册表,注释说明这样可将体积从约 4 MB 降到约 100 KB);
  • resolveToolId(toolName)支持版本化 ID 解析:当传入不带版本后缀的名称时,会通过getLatestByBaseName()映射到最新版本——例如notion_search会被解析为notion_search_v2
  • hasToolId(toolId)用于判断某 ID 是否为内置工具。

因此,在新增工具时,ID 的选择不仅影响展示,还直接参与版本解析逻辑。带_v{n}后缀的 ID 会被识别为版本化工具,且同名基础 ID 只会保留版本号最高者。

3.3 registry.ts 中的对齐

registry.ts(11434 行)是该规范落地的集中体现:文件按服务分组,通过import { xxxTool } from '@/tools/{service}'引入每个工具,再统一放入 registry 对象。例如 Airtable 的 9 个工具、Ashby 的 40+ 个工具都以airtableCreateRecordsToolashbySearchCandidatesTool的形式成组注册。新增工具时必须同时完成"导出(index.ts)→ 注册(registry.ts)"两步,且 ID 完全一致,否则工具将无法被运行时解析。

四、参数可见性分级:三种 visibility 的语义与使用场景

4.1 类型定义

AGENTS.md 规定的三条 visibility 规则,其完整枚举定义在 types.ts 中:

export type ParameterVisibility = | 'user-or-llm' // User can provide OR LLM must generate | 'user-only' // Only user can provide (required/optional determined by required field) | 'llm-only' // Only LLM provides (computed values) | 'hidden' // Not shown to user or LLM

对应 AGENTS.md 的三条指令:

visibility适用场景说明
hidden系统注入的参数典型如 OAuth access token——由授权流程自动注入,用户与 LLM 均不可见、不可填写
user-only凭据与账户特定值API key、bot token、账户相关参数,用户必须自行提供
user-or-llm普通操作参数查询词、数量、日期等业务参数,用户可填、LLM 也可生成

llm-only属于"仅 LLM 提供"的计算值,用于更细的边界控制。)

4.2 源码实例印证

hidden的典型用法——Slack Get Message 工具:

accessToken: { type: 'string', required: false, visibility: 'hidden', description: 'OAuth access token or bot token for Slack API', },

该工具的oauth配置声明provider: 'slack',授权完成后 token 由系统注入到accessToken参数,并在请求头中作为 Bearer 使用:Authorization: \Bearer ${params.accessToken || params.botToken}``。用户与模型都看不到也不该触碰这个字段。

user-only的典型用法——Exa Search 工具:

apiKey: { type: 'string', required: true, visibility: 'user-only', description: 'Exa AI API Key', },

apiKey只允许用户提供;同时 Exa 工具还声明了hosting配置(envKeyPrefix: 'EXA_API_KEY'byokProviderId: 'exa'),意味着当用户未自带 key 时,Sim 可以注入平台托管的 API key(详见下文第五节)。

user-or-llm的典型用法——同文件中的querynumResultstypeincludeDomains等操作参数,用户和 LLM 都有权提供,是绝大多数业务参数的标准选择。

4.3 为什么这样设计

可见性分级直接关系到安全与可用性边界。在 tools/index.ts 的执行逻辑中可以看到其深层作用:

  • 只有visibility: 'user-only'的参数才允许进行环境变量引用解析(resolveToolEnvReferences),且严格限定为"整值恰好是一条引用"({{NAME}}),从而保证 LLM 可写的 URL、header、body 等参数永远无法被用来提取密钥;
  • hidden参数(如 accessToken)不会进入模型上下文,避免敏感凭据泄漏到提示词或日志。

五、托管 API Key(Hosting)配置

AGENTS.md 虽未展开,但仓库中大量工具(如 Exa、Serper、Firecrawl 等)都配置了hosting字段,这是理解"用户不带 key 也能用工具"的关键。配置结构定义在 types.ts 的ToolHostingConfig中:

hosting: { envKeyPrefix: 'EXA_API_KEY', // 环境变量前缀 apiKeyParam: 'apiKey', // 接收 key 的参数名 byokProviderId: 'exa', // BYOK provider ID pricing: { type: 'custom', getCost: ... }, // 计费模型 rateLimit: { mode: 'per_request', requestsPerMinute: 60 }, // 限流 }

5.1 环境变量约定

ToolHostingConfig的文档注释明确了一套编号约定:

  1. 设置{envKeyPrefix}_COUNT声明可用 key 的数量;
  2. 依次提供{envKeyPrefix}_1{envKeyPrefix}_2{envKeyPrefix}_N

例如envKeyPrefix: 'EXA_API_KEY'且配置 5 个 key 时:

EXA_API_KEY_COUNT=5 EXA_API_KEY_1=sk-... EXA_API_KEY_2=sk-... EXA_API_KEY_3=sk-... EXA_API_KEY_4=sk-... EXA_API_KEY_5=sk-...

单 key 部署时,未配置_COUNT的情况下也支持直接使用{envKeyPrefix}本身。扩容只需更新 COUNT 并新增环境变量,无需改代码

5.2 运行时注入流程

从 tools/index.ts 的injectHostedKeyIfNeeded可以看到完整决策链:

  1. 工具未配置hosting或非托管环境 → 不注入;
  2. tool.hosting.enabled谓词不满足 → 不注入;
  3. 用户已提供apiKeyParam→ 不注入(尊重自带 key);
  4. 配置了byokProviderId且工作区存在 BYOK key → 优先使用工作区自带 key(不计费);
  5. 否则通过getHostedKeyRateLimiter().acquireKey()从环境变量池中轮询分配一个托管 key,并标记__usingHostedKey
  6. 若工作区被限流则抛HostedKeyRateLimitedError,无 key 可用则抛HostedKeyUnavailableError

此外还有配套的成本核算calculateToolCost支持per_request(固定每次调用费用)与custom(根据参数与响应动态计算)两种定价模型;assertBillableCost会拒绝NaN/Infinity/负数,避免污染计费账本。__costDollars这类以双下划线开头的内部字段会在返回用户前由stripInternalFields剥离(postProcessToolOutput)。

六、transformResponse:提炼有意义字段,而非转储原始 JSON

6.1 规范要求

AGENTS.md 的两条响应处理规则:

  • transformResponse中提取有意义字段,而不是把原始 JSON 原样丢给调用方;
  • 可空字段用?? null,可选数组用?? []兜底。

6.2 实例一:Exa Search 的结构化提炼

Exa Search 的transformResponse将上游返回的results数组逐字段映射为稳定的、有语义的输出对象:

transformResponse: async (response: Response) => { const data = await response.json() return { success: true, output: { results: (data.results ?? []).map((result: any) => ({ id: result.id, title: result.title || '', url: result.url, publishedDate: result.publishedDate, author: result.author, summary: result.summary, favicon: result.favicon, image: result.image, text: result.text, highlights: result.highlights, highlightScores: result.highlightScores, subpages: result.subpages, entities: result.entities, extras: result.extras, score: result.score, })), requestId: data.requestId, structuredOutput: data.output?.content, grounding: data.output?.grounding, __costDollars: data.costDollars, }, } }

值得注意的细节:data.results ?? []正是规范中"可选数组用?? []"的落地;而__costDollars作为内部计费字段,会在输出前被剥离(见上文 5.2)。

6.3 实例二:Slack Get Message 的字段归一化与错误翻译

Slack Get Message 的transformResponse展示了更丰富的处理手法:

错误码翻译——把 Slack 的 API 错误码转换为可诊断的中文/人话信息:

if (!data.ok) { if (data.error === 'missing_scope') { throw new Error('Missing required permissions. Please reconnect your Slack account with the necessary scopes (channels:history, groups:history, im:history, mpim:history).') } if (data.error === 'invalid_auth') { throw new Error('Invalid authentication. Please check your Slack credentials.') } ... }

?? null兜底——响应对象中的每个可空字段都显式兜底:

const message = { type: msg.type ?? 'message', ts: msg.ts, text: msg.text ?? '', user: msg.user ?? null, bot_id: msg.bot_id ?? null, username: msg.username ?? null, ... reactions: msg.reactions ?? [], is_starred: msg.is_starred ?? false, pinned_to: msg.pinned_to ?? [], files: (msg.files ?? []).map(...), }

同时它还做了一致性校验:请求指定timestamp后,若返回消息的ts与请求不符则抛错(Message not found at timestamp ...),防止 API 返回了相邻线程消息却被误当作目标消息。

6.4 outputs 声明

除了transformResponse,规范还要求用outputs字段声明结构化输出 schema(见 types.ts 的ToolOutputProperty)。Exa 工具为results数组的每个属性(idtitleurlpublishedDatescore等)都写了类型与描述,Slack 工具则复用MESSAGE_OUTPUT_PROPERTIES常量(定义于 types.ts)保持多处输出定义一致。这样 LLM 与下游才能可靠地消费工具结果。

七、OAuth 与凭据处理

7.1 oauth 配置

需要用户授权的服务在工具定义中声明oauth块(结构见 types.ts 的OAuthConfig):

oauth: { required: true, // 该工具是否必须 OAuth 授权 provider: 'slack', // 授权服务 requiredScopes?: string[], // 需要的具体 scope(粒度校验) credentialKind?: 'oauth' | 'service-account', // 限定凭据类型 authoritativeParams?: [...], // 授权响应中必须覆盖同名调用参数的字段 }

Slack 工具中required: true即意味着:未授权时工具不可用,运行时必须先完成 Slack 账号连接。

7.2 凭据选择的强制校验

从 tools/index.ts 的enforceCopilotCredentialSelection可以看出,Copilot 场景下工具执行有额外的凭据选择强制逻辑:若工具oauth.required为 true 且调用未显式传入credentialId/oauthCredential/credential,执行会直接报错,提示模型从environment/credentials.json读取确切的credentialId。这保证了"由谁提供凭据"始终是显式的。

八、注册与一致性:让工具真正可用

最后一步是保证工具进入运行时。依据 AGENTS.md 与 tool-ids.ts、registry.ts 的实现,完成一个工具需要串起以下环节:

  1. 阅读服务 API 文档,确定操作、参数与响应契约;
  2. tools/{service}/下新建 action 文件(如search.ts),实现完整的ToolConfigid(snake_case)、namedescriptionversionparams(含 visibility)、requesttransformResponseoutputs
  3. tools/{service}/types.ts中补充XxxParams/XxxResponse类型
  4. tools/{service}/index.ts中导出工具
  5. 在 tools/registry.ts 中注册,并确保注册键与导出的工具 ID 完全一致;
  6. 可选:为该工具补充单元测试(仓库中已有大量先例,如 exa.test.ts、list_channels.test.ts、operations.test.ts)。

从仓库现状看,这套规范已经支撑起了数百个服务的数千个工具定义(registry.ts 单文件即超过一万行),generated/tool-ids与 metadata.ts 等生成/元数据模块也依赖同一套 ID 体系——ID 一致性是整个工具系统的地基

九、总结

apps/sim/tools/AGENTS.md虽然只有 13 行,却浓缩了 Sim 集成工具开发的核心纪律:

  • 流程上:先 API 文档、后代码,工具是 API 的语义化翻译层;
  • 结构上:一服务一目录、一动作一文件、index.ts+types.ts兜底;
  • 命名上:snake_case、版本化 ID、与 registry 精确对齐;
  • 边界上hidden/user-only/user-or-llm三级可见性 + OAuth/托管 key 凭据体系;
  • 输出上:提炼字段、?? null/?? []兜底、显式 outputs schema。

对想要为 Sim 添加新集成的开发者而言,最稳妥的路径是:先找一个与目标服务形态最接近的既有工具(如 HTTP 类参考 Exa、OAuth 类参考 Slack),照着它的结构与 AGENTS.md 的规则逐步补齐,最后在 registry 中注册并通过测试验证。

延伸阅读:工具执行期逻辑可继续阅读 tools/index.ts(凭据注入、限流重试、成本核算)与 tools/utils.ts(参数合并与校验);参数解析细节可参考 params-resolver.ts 与 operation-input.ts;SDK/API 层调用方可在 lib/copilot 与 lib/internal/tool-operations 中继续追踪。

【免费下载链接】simSim is the collaborative workspace to build, deploy, and monitor AI agents and workflows. Used by 100,000+ builders.项目地址: https://gitcode.com/GitHub_Trending/sim16/sim

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

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

CANN/ge变量查询接口

GetVariables 【免费下载链接】ge GE(Graph Engine)是面向昇腾的图编译器和执行器,提供了计算图优化、多流并行、内存复用和模型下沉等技术手段,加速模型执行效率,减少模型内存占用。 GE 提供对 PyTorch、TensorFlow 前…

作者头像 李华
网站建设 2026/9/10 13:32:53

2026材料信息学MI落地指南:破解新材料开发试错低效、成本超高难题

新材料开发的核心困境并非研发人员技术能力不足,而是传统试错式研发模式存在结构性缺陷。依托材料信息学(MI)结合AI基础模型,可彻底革新传统研发逻辑,大幅压缩研发周期、削减巨额试错成本,同时突破人工经验…

作者头像 李华
网站建设 2026/9/10 13:32:16

SerenityOS `w` 命令完全指南:查看当前登录用户与终端活动状态

SerenityOS w 命令完全指南:查看当前登录用户与终端活动状态 【免费下载链接】serenity The Serenity Operating System 🐞 项目地址: https://gitcode.com/GitHub_Trending/se/serenity 导读 w 是 SerenityOS 系统中用于查看当前已登录用户及其…

作者头像 李华