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 loader:
function registerFamily( registry: Map<string, 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 Map<string, 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: ToolConfig<AirtableGetParams, 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 Task
apps/sim/tools/a2a/get_task.ts 展示了进程内操作的精髓——operation.input只负责物化类型化输入,真正的执行逻辑在服务端 handler 中:
export const a2aGetTaskTool: InternalToolConfig<A2AGetTaskParams, 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: Record<string, 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、账号专属 ID);'user-or-llm'—— 用户提供或 LLM 可计算(搜索查询、内容、过滤器等,绝大多数参数属于此类)。
值得补充的是,apps/sim/tools/types.ts 中实际定义了第四种可见性'llm-only'(仅 LLM 提供,即计算值),需要时同样可用。
参数类型
'string'—— 文本值'number'—— 数值'boolean'—— 布尔值'json'—— 复杂对象(注意:是'json',不是'object')'file'—— 单个文件'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'—— 复杂对象(使用这个,不是'object')'array'—— 带items属性的数组'object'—— 带properties属性的对象
可选输出
对响应中可能不存在的字段,添加optional: true:
closedAt: { 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 的字段,必须使用?? null:
transformResponse: 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 个模块。没有任何客户端调用方需要闭包,它们只需要outputs(Block 输出推断)、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.json(tool-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.access
tools: { 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: Record<string, 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: Record<string, unknown> = {} if (params.limit != null && params.limit !== '') result.limit = Number(params.limit) if (params.newParamName) result.toolParamName = params.newParamName // ID 不同时改名 return result },6. 添加新输出
把新工具返回的新字段加入 Blockoutputs:
outputs: { // 现有输出... newField: { type: 'string', description: 'Description of new field' }, }7. 添加新输入
把新 subBlock 参数 ID 加入 Blockinputs:
inputs: { // 现有输入... someParam: { type: 'string', description: 'Param description' }, optionalParam: { type: 'string', description: 'Optional param description' }, }Block 接线清单
- 新工具 ID 已加入
tools.access - 操作下拉框为每个新工具提供选项
- subBlocks 覆盖每个新工具的全部必填参数
- subBlocks 有正确的
condition(只在对应操作下显示) - 可选/不常用参数设为
mode: 'advanced' tools.config.tool为每个新操作返回正确 IDtools.config.params处理 ID 重映射与类型强制转换- 新输出已加入 Block
outputs - 新参数已加入 Block
inputs
V2 工具模式
如果创建 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),仅供参考