☰
Sim 集成工具开发完全指南:从 API 文档到可用的 Tool 配置
2026/10/8 10:37:01 网站建设 项目流程

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等核心类型,是开发任何新工具前必读的权威参考。

工作流程总览

为某个服务创建工具集的整体流程是:

  1. 使用 Context7 或 WebFetch 阅读目标服务的 API 文档;
  2. 在apps/sim/tools/{service}/下创建工具目录结构;
  3. 生成类型化、符合规范的工具配置文件;
  4. 将工具注册进 apps/sim/tools/registry.ts;
  5. 重新生成工具元数据与文档产物;
  6. 将新工具接入对应的 Block 定义,使其在 UI 中可用。

硬性规则:绝不猜测响应 Schema

这是整个 Skill 中优先级最高的一条规则:如果 API 文档没有明确展示某个工具响应的 JSON 结构,必须明确告诉用户哪些输出是未知的,并停止猜测。

  • 不得凭空发明响应字段名;
  • 不得从相邻端点推断嵌套路径;
  • 不得猜测数组元素的结构;
  • 不得针对未经验证的响应载荷编写transformResponse。

当响应形状未知时,只能选择以下替代方案:

  1. 请用户提供样例响应;
  2. 请用户提供测试凭据,以便通过真实响应进行验证;
  3. 只实现那些输出有文档支撑的端点;
  4. 将该工具留空不实现,并明确说明原因。

这一规则与"内容自检"精神一致:未知不等于动态(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 模型消费时,才需要添加模型输入声明;无法证明时保持现有工具行为、不加注解。

三类输入的处理方式

  1. 普通 Provider/API 输入:保持不变。显式{{...}}引用会正常解析并按普通请求语义发送。URL、域名、资源 ID、控制字段或不透明载荷不会仅仅因为 Provider 是 AI 驱动的就视为模型可见。

  2. 被 AI 模型消费的文本或结构化内容:在外部请求工具上声明request.modelInput,在进程内操作上声明operation.modelInput,使用mode: 'project'并只选择确切的模型可见字段。共享执行器会在请求格式化前把已激活的 Sim 密钥替换为规范的{{NAME}}标签。对嵌套或 JSON 字符串字段,使用小型共享选择器加applyProjected,并验证从重建参数中选择能精确复现投影结果。

  3. 直接发送给外部 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

工具创建完成后:

  1. 在 apps/sim/tools/registry.ts 中导入工具;
  2. 按字母序以 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 -> outputs

ID 单独成文件是因为仅做存在性检查的调用方只需键集合(约 100 KB 而非约 4 MB);每个产物在运行时以单个 JSON 字符串解析,避免直接导入.json或对象字面量在大体量下的代价。--check模式(对应tool-metadata:check脚本)在产物过期时以退出码 1 失败。

此外,集成文档页是从每个工具的 description、params 和 outputs 渲染出来的,因此还需要运行:

bun run scripts/generate-docs.ts

CI 的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为每个新操作返回正确 ID
  • tools.config.params处理 ID 重映射与类型强制转换
  • 新输出已加入 Blockoutputs
  • 新参数已加入 Blockinputs

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 文档验证每个工具文件:

  1. 重新通读创建的每个工具文件;
  2. 与 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 路径都有文档或实测样例支撑
  3. 跨工具一致性验证:
    • types.ts中的共享类型与所有使用它们的工具匹配
    • barrel 导出中的工具 ID 与工具文件定义一致
    • 错误处理一致(错误检查、有意义的错误消息)
  4. 如果仍有未知响应 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询