ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

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

2026/9/10 13:37:26 拓冰建站 浏览量
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。这意味着工具的参数、请求、响应结构必须以服务官方 API 为唯一事实来源任何对既有工具的修改也应回到 API 文档核对字段含义与取值范围工具是 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.ts、list_channels.ts、schedule_message.ts、message.ts等几十个 action 文件每个文件只负责一个工具定义index.ts 则统一export出slackMessageTool、slackGetMessageTool、slackListChannelsTool等全部工具并同时按名称导出供 registry.ts 引用。2.2 为什么这样拆分从工具定义的类型结构见 types.ts 中的ToolConfig可以推断每个工具定义体量不小包含id/name/description/version、完整的params参数 schema、request请求构造、transformResponse响应转换、outputs输出 schema以及可选的oauth、hosting、postProcess等配置。一动作一文件的好处是每个文件的职责单一便于审阅与测试仓库中大量存在exa.test.ts、operations.test.ts等与工具文件一一对应的测试增加新 API 操作时不触碰既有工具降低回归风险registry 的导入关系清晰可追踪。三、工具 ID 规范snake_case 与注册表精确对齐3.1 硬性要求AGENTS.md 明确两条 ID 规则工具 ID 必须使用snake_case如slack_get_message、exa_search工具 ID 必须与 registry 中的键精确匹配match registry keys exactly。3.2 ID 解析机制tool-ids.ts 提供了 ID 解析的底层实现getToolIds()返回全部已注册 ID从 generated/tool-ids 生成的静态列表导入避免加载整个可执行注册表注释说明这样可将体积从约 4 MB 降到约 100 KBresolveToolId(toolName)支持版本化 ID 解析当传入不带版本后缀的名称时会通过getLatestByBaseName()映射到最新版本——例如notion_search会被解析为notion_search_v2hasToolId(toolId)用于判断某 ID 是否为内置工具。因此在新增工具时ID 的选择不仅影响展示还直接参与版本解析逻辑。带_v{n}后缀的 ID 会被识别为版本化工具且同名基础 ID 只会保留版本号最高者。3.3 registry.ts 中的对齐registry.ts11434 行是该规范落地的集中体现文件按服务分组通过import { xxxTool } from /tools/{service}引入每个工具再统一放入 registry 对象。例如 Airtable 的 9 个工具、Ashby 的 40 个工具都以airtableCreateRecordsTool、ashbySearchCandidatesTool的形式成组注册。新增工具时必须同时完成导出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的典型用法——同文件中的query、numResults、type、includeDomains等操作参数用户和 LLM 都有权提供是绝大多数业务参数的标准选择。4.3 为什么这样设计可见性分级直接关系到安全与可用性边界。在 tools/index.ts 的执行逻辑中可以看到其深层作用只有visibility: user-only的参数才允许进行环境变量引用解析resolveToolEnvReferences且严格限定为整值恰好是一条引用{{NAME}}从而保证 LLM 可写的 URL、header、body 等参数永远无法被用来提取密钥hidden参数如 accessToken不会进入模型上下文避免敏感凭据泄漏到提示词或日志。五、托管 API KeyHosting配置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的文档注释明确了一套编号约定设置{envKeyPrefix}_COUNT声明可用 key 的数量依次提供{envKeyPrefix}_1、{envKeyPrefix}_2…{envKeyPrefix}_N。例如envKeyPrefix: EXA_API_KEY且配置 5 个 key 时EXA_API_KEY_COUNT5 EXA_API_KEY_1sk-... EXA_API_KEY_2sk-... EXA_API_KEY_3sk-... EXA_API_KEY_4sk-... EXA_API_KEY_5sk-...单 key 部署时未配置_COUNT的情况下也支持直接使用{envKeyPrefix}本身。扩容只需更新 COUNT 并新增环境变量无需改代码。5.2 运行时注入流程从 tools/index.ts 的injectHostedKeyIfNeeded可以看到完整决策链工具未配置hosting或非托管环境 → 不注入tool.hosting.enabled谓词不满足 → 不注入用户已提供apiKeyParam→ 不注入尊重自带 key配置了byokProviderId且工作区存在 BYOK key → 优先使用工作区自带 key不计费否则通过getHostedKeyRateLimiter().acquireKey()从环境变量池中轮询分配一个托管 key并标记__usingHostedKey若工作区被限流则抛HostedKeyRateLimitedError无 key 可用则抛HostedKeyUnavailableError。此外还有配套的成本核算calculateToolCost支持per_request固定每次调用费用与custom根据参数与响应动态计算两种定价模型assertBillableCost会拒绝NaN/Infinity/负数避免污染计费账本。__costDollars这类以双下划线开头的内部字段会在返回用户前由stripInternalFields剥离postProcessToolOutput。六、transformResponse提炼有意义字段而非转储原始 JSON6.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数组的每个属性id、title、url、publishedDate、score等都写了类型与描述Slack 工具则复用MESSAGE_OUTPUT_PROPERTIES常量定义于 types.ts保持多处输出定义一致。这样 LLM 与下游才能可靠地消费工具结果。七、OAuth 与凭据处理7.1 oauth 配置需要用户授权的服务在工具定义中声明oauth块结构见 types.ts 的OAuthConfigoauth: { 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 的实现完成一个工具需要串起以下环节阅读服务 API 文档确定操作、参数与响应契约在tools/{service}/下新建 action 文件如search.ts实现完整的ToolConfigidsnake_case、name、description、version、params含 visibility、request、transformResponse、outputs在tools/{service}/types.ts中补充XxxParams/XxxResponse类型在tools/{service}/index.ts中导出工具在 tools/registry.ts 中注册并确保注册键与导出的工具 ID 完全一致可选为该工具补充单元测试仓库中已有大量先例如 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.tstypes.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.tsSDK/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),仅供参考