ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Sim 集成工具开发完全指南:从 API 文档到可用的 Tool 配置

Sim 集成工具开发完全指南:从 API 文档到可用的 Tool 配置 Sim 集成工具开发完全指南从 API 文档到可用的 Tool 配置【免费下载链接】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 集成开发者的实操手册系统讲解如何把一个第三方服务的 API 文档转化为 Sim 中规范、类型安全、可直接被工作流与 Agent 调用的工具Tool配置。你将掌握执行边界的选择原则进程内操作 vs 外部请求、参数与输出的 Schema 规范、密钥来源追踪Provenance边界以及把新工具注册进注册表、接入 Block 界面、再生成元数据与文档的完整闭环并了解仓库中对应的真实实现与验证机制。认识 Sim 的 Tools 体系在 Sim 中工具是工作流与 AI Agent 调用外部能力的统一抽象。每个工具由三部分构成参数 Schema这个工具接受什么输入、请求/操作定义如何真正执行是发 HTTP 请求还是调用进程内操作处理器、输出 Schema这个工具产生什么结构化结果。所有工具的公共契约都定义在 apps/sim/tools/types.ts 中。该文件声明了ToolConfig外部请求工具、InternalToolConfig进程内操作工具、OutputType、ParameterVisibility、ToolResponse等核心类型是开发任何新工具前必读的权威参考。工作流程总览为某个服务创建工具集的整体流程是使用 Context7 或 WebFetch 阅读目标服务的 API 文档在apps/sim/tools/{service}/下创建工具目录结构生成类型化、符合规范的工具配置文件将工具注册进 apps/sim/tools/registry.ts重新生成工具元数据与文档产物将新工具接入对应的 Block 定义使其在 UI 中可用。硬性规则绝不猜测响应 Schema这是整个 Skill 中优先级最高的一条规则如果 API 文档没有明确展示某个工具响应的 JSON 结构必须明确告诉用户哪些输出是未知的并停止猜测。不得凭空发明响应字段名不得从相邻端点推断嵌套路径不得猜测数组元素的结构不得针对未经验证的响应载荷编写transformResponse。当响应形状未知时只能选择以下替代方案请用户提供样例响应请用户提供测试凭据以便通过真实响应进行验证只实现那些输出有文档支撑的端点将该工具留空不实现并明确说明原因。这一规则与内容自检精神一致未知不等于动态Unknown is not the same as dynamic只有形状真正动态时才允许使用不带properties的裸type: json。目录结构每个服务的工具代码统一放在apps/sim/tools/{service}/下tools/{service}/ ├── index.ts # Barrel 导出导出所有工具与类型 ├── types.ts # 参数与响应类型定义 └── {action}.ts # 单个工具文件每个操作一个文件仓库中的真实范例可以参考 apps/sim/tools/airtable/外部请求型工具集含get_record.ts、create_records.ts、update_record.ts等 11 个操作与 apps/sim/tools/a2a/进程内操作型工具集含get_task.ts、send_message.ts、cancel_task.ts等。先选择执行边界二选一不可混用每个工具必须且只能使用以下两种执行边界之一进程内操作首选使用InternalToolConfig。适用于执行器与实现运行在同一个 Sim 进程/信任/运行时平面内的场景。需要将类型化的operation.input物化出来在apps/sim/lib/internal/{service}/execute-tool.ts下实现 handler并把每个工具 ID 注册到 apps/sim/lib/internal/tool-operations/registry.server.ts。外部 Provider 请求仅在 URL 是绝对的外部 HTTP(S) Provider 端点时才使用ToolConfig.request。禁止事项边界红线以下行为是被明确禁止的任何一项都会导致bun run check:tool-request-boundary检查失败把工具 URL 设置为/api/...构造指向 Sim 自身的绝对 URL声明request.internal添加directExecution属性为了规范化文件、鉴权或复用服务端代码而导入路由模块或新建 API 路由。真实的浏览器/API 路由可以作为薄适配层保留但路由与工具必须直接调用同一个操作。真正的跨进程/能力边界应使用显式的服务端客户端不能伪装成工具自跳self-hop。对于受保护的 Sim 资源内部 handler 应以可信执行上下文调用领域内已授权的应用用例application use case相关迁移工作可参考 .agents/skills/migrate-application-operation/SKILL.md。进程内操作的注册机制从 apps/sim/lib/internal/tool-operations/registry.server.ts 的源码可以看出注册机制通过registerFamily函数把一组工具 ID 批量绑定到一个延迟加载的 handler loaderfunction registerFamily( registry: Mapstring, InternalToolOperationHandlerLoader, toolIds: readonly string[], loader: InternalToolOperationHandlerLoader ): void { for (const toolId of toolIds) { if (registry.has(toolId)) { throw new Error(Duplicate internal tool execution registration: ${toolId}) } registry.set(toolId, loader) } } const handlerLoaders new Mapstring, InternalToolOperationHandlerLoader() registerFamily(handlerLoaders, STS_TOOL_IDS, async () { return (await import(/lib/internal/sts/execute-tool)).executeStsTool })这里有几个值得注意的实现细节工具 ID 使用常量数组集中声明重复注册会直接抛错从机制上杜绝了 ID 冲突handler 通过动态import()延迟加载避免在启动时就拉入所有服务的 SDK每个服务的入口统一命名为execute-tool.ts导出executeXxxTool函数。注册好的 handler 接收InternalToolOperationCall校验request.input仅使用可信的request.context作为授权依据转发request.signal取消信号并返回与工具执行器期望一致的受限Response契约。它没有URL、HTTP method、请求头、fetch 兜底或由调用方控制的_context授权。外部 Provider 请求工具的结构只有目标端点是绝对外部 Provider API 时才使用ToolConfig.request。标准结构如下import type { {ServiceName}{Action}Params } from /tools/{service}/types import type { ToolConfig } from /tools/types interface {ServiceName}{Action}Response { success: boolean output: { // 在此定义输出结构 } } export const {serviceName}{Action}Tool: ToolConfig {ServiceName}{Action}Params, {ServiceName}{Action}Response { id: {service}_{action}, // snake_case必须与工具名一致 name: {Service} {Action}, // 人类可读名称 description: Brief description, // 一句话描述 version: 1.0.0, // OAuth 配置若服务使用 OAuth oauth: { required: true, provider: {service}, // 必须匹配 OAuth provider ID }, params: { // 隐藏参数系统注入例如 OAuth accessToken accessToken: { type: string, required: true, visibility: hidden, description: OAuth access token, }, // 仅用户提供参数凭据、API key、用户必须提供的 ID someId: { type: string, required: true, visibility: user-only, description: The ID of the resource, }, // 用户或 LLM 参数其他一切参数用户提供或 LLM 计算均可 query: { type: string, required: false, // 可选参数用 false visibility: user-or-llm, description: Search query, }, }, request: { url: (params) https://api.service.com/v1/resource/${params.id}, method: POST, headers: (params) ({ Authorization: Bearer ${params.accessToken}, Content-Type: application/json, }), body: (params) ({ // 仅 POST/PUT/PATCH 需要请求体 // 对 ID 字段做 trim 以防止复制粘贴带来的空白字符错误 // userId: params.userId?.trim(), }), }, transformResponse: async (response: Response) { const data await response.json() return { success: true, output: { // 将 API 响应映射为输出 // 可空字段用 ?? null // 可选数组用 ?? [] }, } }, outputs: { // 定义每个输出字段 }, }真实范例Airtable Get Record仓库中 apps/sim/tools/airtable/get_record.ts 是外部请求工具的标准实现完整展示了 OAuth 配置、三类参数可见性、带trim()的 URL 构造、Bearer 鉴权头以及带嵌套properties的类型化输出export const airtableGetRecordTool: ToolConfigAirtableGetParams, AirtableGetResponse { id: airtable_get_record, name: Airtable Get Record, description: Retrieve a single record from an Airtable table by its ID, version: 1.0.0, oauth: { required: true, provider: airtable, }, params: { accessToken: { type: string, required: true, visibility: hidden, description: OAuth access token, }, baseId: { type: string, required: true, visibility: user-or-llm, description: Airtable base ID (starts with app, e.g., appXXXXXXXXXXXXXX), }, tableId: { type: string, required: true, visibility: user-or-llm, description: Table ID (starts with tbl) or table name, }, recordId: { type: string, required: true, visibility: user-or-llm, description: Record ID to retrieve (starts with rec, e.g., recXXXXXXXXXXXXXX), }, }, request: { url: (params) https://api.airtable.com/v0/${params.baseId?.trim()}/${params.tableId?.trim()}/${params.recordId?.trim()}, method: GET, headers: (params) ({ Authorization: Bearer ${params.accessToken}, Content-Type: application/json, }), }, // ...transformResponse 与 outputs }从 apps/sim/tools/types.ts 的类型定义可以看到ToolConfig.request还支持更多高级能力redirectPolicy重定向兼容与跨域凭据行为、stripAuthOnRedirect跟随重定向时丢弃Authorization头GitHub Actions 日志/工件下载即典型场景防止 API 凭据被发送到存储主机、retry重试策略、schemaEnrichment/toolEnrichment动态 Schema 增强以及hosting托管 API key等需要时可按需启用。进程内操作工具的结构import type { InternalToolConfig } from /tools/types export const {serviceName}{Action}Tool: InternalToolConfig {ServiceName}{Action}Params, {ServiceName}{Action}Response { id: {service}_{action}, name: {Service} {Action}, description: Brief description, version: 1.0.0, params: { // 与外部工具相同的规范元数据 }, operation: { input: (params) ({ // 将解析后的工具参数映射为类型化的语义操作输入 }), }, outputs: { // 定义每个输出字段 }, }真实范例A2A Get Taskapps/sim/tools/a2a/get_task.ts 展示了进程内操作的精髓——operation.input只负责物化类型化输入真正的执行逻辑在服务端 handler 中export const a2aGetTaskTool: InternalToolConfigA2AGetTaskParams, A2ATaskResponse { id: a2a_get_task, name: A2A Get Task, description: Retrieve the current state and result of an A2A task., version: 1.0.0, params: { agentUrl: { type: string, required: true, visibility: user-only, description: The A2A agent endpoint URL, }, taskId: { type: string, required: true, visibility: user-or-llm, description: The task ID to retrieve, }, historyLength: { type: number, required: false, visibility: user-or-llm, description: Maximum number of history messages to include, }, apiKey: { type: string, required: false, visibility: user-only, description: API key for authentication (if required), }, }, operation: { input: (params) { const body: Recordstring, unknown { agentUrl: params.agentUrl, taskId: params.taskId, } if (params.historyLength ! undefined) body.historyLength params.historyLength if (params.apiKey) body.apiKey params.apiKey return body }, }, transformResponse: async (response: Response) response.json(), outputs: A2A_TASK_OUTPUTS, }注意其中可选参数不进入输入的写法if (params.historyLength ! undefined)、if (params.apiKey)这是保持 handler 输入干净、避免向内部边界传播空值的推荐模式。参数关键规则可见性Visibility三选项hidden—— 系统注入OAuth 令牌、内部参数用户永远看不到user-only—— 用户必须提供凭据、API key、账号专属 IDuser-or-llm—— 用户提供或 LLM 可计算搜索查询、内容、过滤器等绝大多数参数属于此类。值得补充的是apps/sim/tools/types.ts 中实际定义了第四种可见性llm-only仅 LLM 提供即计算值需要时同样可用。参数类型string—— 文本值number—— 数值boolean—— 布尔值json—— 复杂对象注意是json不是objectfile—— 单个文件file[]—— 多个文件必填与可选必须显式设置required: true或required: false可选参数必须设置required: false。已解析密钥与来源追踪Provenance边界在实现任何工具之前需要对每个请求字段进行分类。需要强调的是这是可选项不是全量迁移。只有当服务官方文档或本地执行路径明确证明某个字段会被 AI 模型消费时才需要添加模型输入声明无法证明时保持现有工具行为、不加注解。三类输入的处理方式普通 Provider/API 输入保持不变。显式{{...}}引用会正常解析并按普通请求语义发送。URL、域名、资源 ID、控制字段或不透明载荷不会仅仅因为 Provider 是 AI 驱动的就视为模型可见。被 AI 模型消费的文本或结构化内容在外部请求工具上声明request.modelInput在进程内操作上声明operation.modelInput使用mode: project并只选择确切的模型可见字段。共享执行器会在请求格式化前把已激活的 Sim 密钥替换为规范的{{NAME}}标签。对嵌套或 JSON 字符串字段使用小型共享选择器加applyProjected并验证从重建参数中选择能精确复现投影结果。直接发送给外部 Provider 的序列化模型内容将序列化的顶层参数包含在request.modelInput中。在现有请求格式化器解析前投影私有副本当整值占位符在序列化语法中不合法时保持格式化器行为确定性。不要引入第二条硬拒绝路径。不透明模型输入与持久化来源进程内操作持有的不透明模型输入如内联音频、图片、视频或文档字节在mode: project的模型输入声明中追加privateInputPaths或当没有文本投影时使用mode: private-provenance加inputPaths详见 apps/sim/tools/types.ts 的modelInput联合类型。不得把存储键、路径、签名 URL 或普通远程 URL 选作字节来源拥有操作必须在模型出口处独立授权存储字节在下载或发送内容给模型前必须调用validateOpaqueModelInputProvenance读取持久化工作区文件前必须套用工作区文件来源保护。可进入工作流/模型的 Sim 持久化存储或内部执行交接表格单元格、Agent 记忆、知识文档/分块、工作区文件内容、子工作流输入通过operation.secretProvenance传输加密的字段级来源。操作校验精确选择与可信范围然后在所属边界持久化、导入或传播。对来源标记为NULL的行/文件保持共享遗留行为绝不发明工具本地迁移规则。来源追踪硬性规则绝不把密钥明文替换进源码绝不序列化明文来源绝不手写私有来源头/信封共享的executeTool边界负责传输并从功能结果中剥离私有元数据绝不把私有来源附加到外部 URL对经过证明的模型可见外部字段使用request.modelInput投影否则保持普通请求语义跨边界传输加密来源时必须使用已注册的进程内操作绝不净化任意第三方工具结果——投影只作用于 Sim 已解析密钥来源在该次执行/工具调用中激活的密钥不能仅仅因为值被持久化、被工具返回或出现在文件名中就添加来源。要求存在具体的 Sim{{...}}解析路径以及后续的模型/日志边界若某个不支持的字段能解析密钥但不值得持久化追踪例如file_write路径就在该入口处精确拒绝在诊断边界只投影携带执行作用域来源的值普通 Provider 响应、文件名、URL 和错误在 Sim 未向其解析密钥时保持原样。测试覆盖要求应添加聚焦测试覆盖命名投影、无来源的普通相同文本、嵌套与序列化形状处理、不变的普通外部输入、格式错误/不完整的私有元数据必须失败关闭failing closed、无头的遗留请求、公共工具结果中不存在私有元数据。对持久化接收端还需覆盖遗留NULL标记、精确空的新写入、被追踪的密钥写入、过期/缺失的 sidecar、作用域隔离。输出关键规则输出类型string、number、boolean—— 基本类型json—— 复杂对象使用这个不是objectarray—— 带items属性的数组object—— 带properties属性的对象可选输出对响应中可能不存在的字段添加optional: trueclosedAt: { type: string, description: When the issue was closed, optional: true, },类型化 JSON 输出当使用type: json且提前知道对象形状时必须用properties定义内部结构让下游消费者知道有哪些字段可用// BAD: 不透明的 json无法得知内部结构 metadata: { type: json, description: Response metadata, }, // GOOD: 定义已知属性 metadata: { type: json, description: Response metadata, properties: { id: { type: string, description: Unique ID }, status: { type: string, description: Current status }, count: { type: number, description: Total count }, }, },对象数组要定义元素结构items: { type: array, description: List of items, items: { type: object, properties: { id: { type: string, description: Item ID }, name: { type: string, description: Item name }, }, }, },只有当形状真正动态时才允许使用不带properties的裸type: json——未知不等于动态参见前文的硬性规则。transformResponse 关键规则处理可空字段对可能为 undefined 的字段必须使用?? nulltransformResponse: async (response: Response) { const data await response.json() return { success: true, output: { id: data.id, title: data.title, body: data.body ?? null, // 可能为 undefined assignee: data.assignee ?? null, // 可能为 undefined labels: data.labels ?? [], // 默认空数组 closedAt: data.closed_at ?? null, // 可能为 undefined }, } }绝不输出原始 JSON 转储反面示例禁止output: { data: data, // BAD - 原始 JSON 转储 }正确做法是提取有意义的字段output: { id: data.id, name: data.name, status: data.status, metadata: { createdAt: data.created_at, updatedAt: data.updated_at, }, }类型文件与 Barrel 导出types.ts 模式为所有参数与响应创建接口import type { ToolResponse } from /tools/types // 参数接口 export interface {Service}{Action}Params { accessToken: string requiredField: string optionalField?: string } // 响应接口继承 ToolResponse export interface {Service}{Action}Response extends ToolResponse { output: { field1: string field2: number optionalField?: string | null } }index.ts Barrel 导出模式// 导出所有工具 export { serviceTool1 } from ./{action1} export { serviceTool2 } from ./{action2} // 导出类型 export * from ./types注册工具与元数据产物注册到 registry.ts工具创建完成后在 apps/sim/tools/registry.ts 中导入工具按字母序以 snake_case 键加入tools对象import { serviceActionTool } from /tools/{service} export const tools { // ... 现有工具 ... {service}_{action}: serviceActionTool, }重新生成元数据产物bun run tool-metadata:generate客户端代码是从生成的元数据读取工具的params/outputs而不是直接导入注册表——因此新增、修改或删除工具后如果不重新生成UI 将看不到变化且 CI 会在产物过期时失败。必须提交生成的产物。关于该脚本的动机scripts/sync-tool-metadata.ts 的头部注释说明得很清楚apps/sim/tools/registry.ts是一个约 9000 行、导入 4300 工具的 barrel每个ToolConfig混合了纯数据params、outputs、name与闭包request.headers、transformResponse、postProcess而闭包及其触达的 SDK 客户端让整个 barrel 需要编译约 4700 个模块。没有任何客户端调用方需要闭包它们只需要outputsBlock 输出推断、params序列化与工具输入面板或 ID 存在性。因此该脚本将数据单独输出为三个产物tools/generated/tool-ids.ts # 所有已注册工具 ID tools/generated/tool-metadata.ts # id - { name, description, version, params, oauth } tools/generated/tool-outputs.ts # id - outputsID 单独成文件是因为仅做存在性检查的调用方只需键集合约 100 KB 而非约 4 MB每个产物在运行时以单个 JSON 字符串解析避免直接导入.json或对象字面量在大体量下的代价。--check模式对应tool-metadata:check脚本在产物过期时以退出码 1 失败。此外集成文档页是从每个工具的 description、params 和 outputs 渲染出来的因此还需要运行bun run scripts/generate-docs.tsCI 的bun run docs:check会在文档页过期时失败。相关脚本定义见 apps/sim/package.jsontool-metadata:generate、generate-docs、docs:check。更进一步的工具注册表边界约束可以参考 .agents/skills/tool-registry-boundary/SKILL.md。将工具接入 Block必做步骤在tools/registry.ts注册之后还必须更新 apps/sim/blocks/blocks/{service}.ts 中的 Block 定义。这一步不是可选的——工具只有接入 Block 才能从 UI 使用。1. 添加到 tools.accesstools: { access: [ // 现有工具... service_new_action, // 在此添加每个新工具 ID ], config: { ... } }2. 添加操作下拉选项如果 Block 使用操作下拉框为每个新工具添加选项{ id: operation, type: dropdown, options: [ // 现有选项... { label: New Action, id: new_action }, // id 映射到 tools.config.tool 的返回值 ], }3. 为新工具参数添加 subBlocks为每个新工具添加覆盖其全部必填参数以及有用的可选参数的 subBlocks。用condition让它们只在对应操作下显示必填参数用required标记// new_action 的必填参数 { id: someParam, title: Some Param, type: short-input, placeholder: e.g., value, condition: { field: operation, value: new_action }, required: { field: operation, value: new_action }, }, // 可选参数——放入高级模式 { id: optionalParam, title: Optional Param, type: short-input, condition: { field: operation, value: new_action }, mode: advanced, },4. 更新 tools.config.tool确保工具选择器对每个新操作返回正确的工具 ID。最简单的模式tool: (params) service_${params.operation}, // 若下拉 ID 与工具 ID 一致则无需修改如果下拉 ID 与工具 ID 不一致添加显式映射tool: (params) { const map: Recordstring, string { new_action: service_new_action, // ... } return map[params.operation] ?? service_${params.operation} },apps/sim/blocks/blocks/airtable.ts 中的tools.access与tools.config.tool展示了真实的 switch 映射写法操作 ID如get、create、updateMultiple与工具 ID如airtable_get_record、airtable_create_records并不一致因此需要用显式分支一一映射并在default分支抛出Invalid Airtable operation错误。5. 更新 tools.config.params添加新参数所需的类型强制转换在变量解析之后、执行时运行params: (params) { const result: Recordstring, unknown {} if (params.limit ! null params.limit ! ) result.limit Number(params.limit) if (params.newParamName) result.toolParamName params.newParamName // ID 不同时改名 return result },6. 添加新输出把新工具返回的新字段加入 Blockoutputsoutputs: { // 现有输出... newField: { type: string, description: Description of new field }, }7. 添加新输入把新 subBlock 参数 ID 加入 Blockinputsinputs: { // 现有输入... someParam: { type: string, description: Param description }, optionalParam: { type: string, description: Optional param description }, }Block 接线清单新工具 ID 已加入tools.access操作下拉框为每个新工具提供选项subBlocks 覆盖每个新工具的全部必填参数subBlocks 有正确的condition只在对应操作下显示可选/不常用参数设为mode: advancedtools.config.tool为每个新操作返回正确 IDtools.config.params处理 ID 重映射与类型强制转换新输出已加入 Blockoutputs新参数已加入 BlockinputsV2 工具模式如果创建 V2 工具API 对齐输出的新版本使用_v2后缀工具 ID{service}_{action}_v2变量名{action}V2Tool版本2.0.0输出扁平、与 API 对齐无 content/metadata 包装完成前自检清单所有工具 ID 使用 snake_case恰好选择一个边界已注册的InternalToolConfig.operation或绝对外部 HTTP(S)ToolConfig.request没有工具请求指向/api/...、构造指向 Sim 的 URL 或声明request.internal没有工具声明directExecution进程内工作使用已注册的操作所有参数显式设置required: true或required: false所有参数有合适的visibility所有可空响应字段使用?? null所有可选输出设置optional: true输出中没有原始 JSON 转储types.ts 包含所有接口index.ts 导出所有工具并重新导出类型export * from ./types工具已注册到tools/registry.ts已运行bun run tool-metadata:generate并提交重新生成的产物已运行bun run scripts/generate-docs.ts并提交刷新的文档Block 已接线tools.access、下拉选项、subBlocks、tools.config、outputs、inputs模型、持久化存储与内部执行边界仅在存在具体 Sim{{...}}解析路径时使用共享来源机制普通第三方输入/结果保持不变私有元数据永不离开 Sim最终验证对照 API 文档逐项核对完成前必须对照 API 文档验证每个工具文件重新通读创建的每个工具文件与 API 文档交叉核对所有必填参数标记required: true所有可选参数标记required: false参数类型与 API 匹配string、number、boolean、json外部工具请求 URL、method、headers、body 与 Provider API 规范一致内部工具operation.input与 handler schema 匹配handler 已注册且无 HTTP 兜底transformResponse从 API 响应中提取了正确字段所有输出字段与 API 实际返回一致API 提供的字段在输出中没有遗漏输出中没有定义 API 不返回的多余字段每个输出字段与 JSON 路径都有文档或实测样例支撑跨工具一致性验证types.ts中的共享类型与所有使用它们的工具匹配barrel 导出中的工具 ID 与工具文件定义一致错误处理一致错误检查、有意义的错误消息如果仍有未知响应 schema明确告知用户而不是猜测——这是本文反复强调的第一条硬性规则也是保证 Sim 工具生态可靠性的最后一道防线。【免费下载链接】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),仅供参考
返回列表