ARTICLE DETAIL

建站实战干货

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

Effect AI SDK 修复实录:Anthropic 客户端执行工具(Memory / Text Editor / Computer Use / Bash)线上不可用的三项根因与修复

2026/9/14 22:18:51 拓冰建站 浏览量
Effect AI SDK 修复实录:Anthropic 客户端执行工具(Memory / Text Editor / Computer Use / Bash)线上不可用的三项根因与修复 Effect AI SDK 修复实录Anthropic 客户端执行工具Memory / Text Editor / Computer Use / Bash线上不可用的三项根因与修复【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code本篇文章以effect/ai-anthropic的一则 patch 级 changeset.repos/effect-smol/.changeset/pre/fix-anthropic-memory-tool.md为主线完整还原 Anthropic 提供商工具在传输wire层面不可用的三个根因及其修复方案。读完本文你将理解 Effect AI SDK 中provider 定义工具provider-defined tool在请求/响应两端的名称映射机制、命令载荷 Schema 的字段要求以及Schema.optional与Schema.optionalKey在 Anthropic 编解码器中的关键差异并能在自己的工具开发中直接复用这些修复经验。一、问题背景为什么客户端执行工具在线上不可用该 changeset 针对effect/ai-anthropic包发布了一个patch级修复改动点概括如下Fix client-executed provider tools (Memory, Text Editor, Computer Use, Bash) which were unusable on the wire.所谓客户端执行工具client-executed provider tools是指由 Anthropic 服务端预定义、但由客户端代码实际执行requiresHandler: true的工具包括工具提供方 wire 名称SDK 自定义名称用途MemorymemoryAnthropicMemory跨会话的持久化文件操作创建、查看、编辑、重命名、删除Text Editortext_editorAnthropicTextEditor文件内容读写与目录列举Computer UsecomputerAnthropicComputerUse鼠标/键盘等屏幕操作BashbashAnthropicBash沙箱内执行 shell 命令以 Memory 工具为例其在 AnthropicTool.ts 中的定义如下export const Memory_20250818 Tool.providerDefined({ id: anthropic.memory_20250818, customName: AnthropicMemory, providerName: memory, requiresHandler: true, parameters: Memory_20250818_Commands, success: Schema.String })注意customNameAnthropicMemory与providerNamememory并不相同。在 Effect AI SDK 中自定义名称用于解决同一套 toolkit 中包含来自多个 provider 的同名工具的命名冲突例如多个 provider 都有web_search因此providerDefined工具会同时维护两个名字。这恰恰是本轮修复第一项问题的导火索。二、根因一wire 名称与自定义名称映射断裂导致ToolNotFoundError2.1 问题现象当 Anthropic 在响应中返回一个客户端执行工具的调用时其tool_use内容块里的name字段是服务端 wire 名称如memory而不是 SDK 层 toolkit 所注册的自定义名称如AnthropicMemory。在修复之前makeResponse以及流式场景对应的makeStreamResponse在处理tool_use时会直接拿 wire 名称去 toolkit 中查找工具结果自然找不到——因为 toolkit 的键是自定义名称——最终抛出ToolNotFoundError导致 Memory、Text Editor、Computer Use、Bash 这四个客户端执行工具在真实链路上全部不可用。2.2 修复方案引入toolNameMapper双向映射修复后的makeResponse在tool_use分支中先通过toolNameMapper.getCustomName(part.name)把 wire 名称反解回自定义名称再以该名称执行参数转换与回调分发见 AnthropicLanguageModel.tscase tool_use: { // ... // Map the provider wire name (e.g. memory) back to the tools // custom name (e.g. AnthropicMemory) that the toolkit is keyed by const toolName toolNameMapper.getCustomName(part.name) const params yield* transformToolCallParams(options.tools, toolName, part.input) parts.push({ type: tool-call, id: part.id, name: toolName, params, // ... }) break }toolNameMapper是在请求入口处基于用户传入的工具列表构造的AnthropicLanguageModel.tsconst toolNameMapper new Tool.NameMapper(options.tools) const request yield* makeRequest({ config, options, toolNameMapper }) return yield* makeResponse({ options, rawResponse, response, toolNameMapper })2.3NameMapper的底层实现NameMapper定义在核心库 Tool.ts内部维护两张互逆的映射表export class NameMapperTools extends ReadonlyArrayAny { readonly #customToProvider: Mapstring, string new Map() readonly #providerToCustom: Mapstring, string new Map() constructor(tools: Tools) { for (const tool of tools) { if (isProviderDefined(tool)) { this.#customToProvider.set(tool.name, tool.providerName) this.#providerToCustom.set(tool.providerName, tool.name) } } } getCustomName(providerName: string): string { return this.#providerToCustom.get(providerName) ?? providerName } getProviderName(customName: string): string { return this.#customToProvider.get(customName) ?? customName } }getCustomName(providerName)wire 名称 → 自定义名称响应方向makeResponse使用getProviderName(customName)自定义名称 → wire 名称请求方向makeRequest在组装工具定义与消息时使用见 AnthropicLanguageModel.ts 与 L1137两个 getter 均为查不到就原样返回的兜底策略保证非 provider 定义工具的兼容性。getCustomName的映射在流式路径同样生效makeStreamResponse中所有tool_use、server_tool_use、web_fetch、web_search、code_execution等分支均通过toolNameMapper.getCustomName(...)归一化工具名AnthropicLanguageModel.ts、L2247 等这正是 changeset 中and the streaming equivalents所指的改动。2.4 修复后的错误路径仍然存在值得注意的是映射修复后ToolNotFoundError并未从代码库中消失而是退回到真正的异常兜底职责在 transformToolCallParams 中若映射后仍未在用户工具列表中找到对应工具仍会抛出带availableTools清单的ToolNotFoundError随后使用Schema.decodeEffect对tool_use载荷做参数校验校验失败则包装为ToolParameterValidationError。这说明修复目标是可用的工具不再被误判为未找到而不是吞掉真实的错误。三、根因二MemoryCreateCommand缺失file_text创建文件时正文被丢弃3.1 问题现象Memory 工具的create命令用于在模型的内存空间创建新文件。修复前MemoryCreateCommand的 Schema 中file_text被声明为可选file_text: Schema.optional(Schema.NullOr(Schema.String)) // 修复前效果是当模型想创建文件并写入正文时payload 中的file_text会被当作可选字段处理实际发送给客户端执行器的create命令丢掉了文件正文导致创建出的文件内容为空。3.2 修复方案file_text升级为必填字段修复后MemoryCreateCommand 将file_text改为必填的Schema.Stringexport const MemoryCreateCommand Schema.Struct({ command: Schema.Literal(create), /** * The path to the file that should be created. */ path: Schema.String, /** * The content to write to the file. */ file_text: Schema.String })command用Schema.Literal(create)作为判别字段与delete、insert、rename、str_replace、view等命令通过Schema.Union组合成Memory_20250818_CommandsAnthropicTool.ts。字段从可选变为必填后create命令的载荷在构造、传输与解码三个环节都能保证file_text存在文件正文不再丢失。这一改动同时印证了 Anthropic 服务端对该命令的协议要求create语义上创建即写内容正文属于命令的组成部分而非可选项。四、根因三Schema.optional与Schema.optionalKey的编解码差异——Unsupported AST Undefined4.1 问题现象changeset 明确记录了一行异常信息which the Anthropic codec rejected with Unsupported AST Undefined客户端执行工具中有一批可选参数包括工具可选参数类型Memory / Text Editorview_range[start, end]行号元组1 起始-1表示读到文件末尾Computer Usecoordinate[x, y]像素坐标缺省时使用当前鼠标位置Bashrestartboolean修复前它们使用Schema.optional(...)声明例如 Bash 工具AnthropicTool.tsparameters: Schema.Struct({ command: Schema.String, restart: Schema.optional(Schema.Boolean) // 修复前Anthropic codec 拒绝 })当 SDK 把工具定义编码为 Anthropic 的 JSON Schemawire format时Schema.optional会生成包含Undefined的 ASTAnthropic 编解码器无法理解该 AST 节点直接报出Unsupported AST Undefined使得带可选参数的工具整体无法注册/使用。4.2 修复方案统一改用Schema.optionalKey修复后所有受影响的可选参数统一改用Schema.optionalKeyMemory 的MemoryViewCommandAnthropicTool.tsexport const MemoryViewCommand Schema.Struct({ command: Schema.Literal(view), path: Schema.String, view_range: Schema.optionalKey(ViewRange) })Text Editor 的TextEditorViewCommandAnthropicTool.tsview_range: Schema.optionalKey(ViewRange)Bash 的restartAnthropicTool.tsrestart: Schema.optionalKey(Schema.Boolean)Computer Use 各动作中的coordinate如左键点击 AnthropicTool.ts、双击 L832、拖拽 L958、滚轮 L1102 等也全部为Schema.optionalKey(Coordinate)。两者的差异在于Schema.optional允许字段值本身为undefined在 AST 中引入Undefined节点而Schema.optionalKey表示键可缺省缺省时整个字段从对象中移除从而在编码为 Anthropic JSON Schema 时不产生UndefinedAST规避了编解码器的拒绝行为。这是使用外部 LLM provider 时非常典型的SDK 内部 Schema 语义与服务端协议之间的适配问题。五、修复的完整落地与验证本轮修复的三项改动共同作用于四个工具最终由effect/ai-anthropic以patch版本发布对应 issue #2615。相关的回归保障体现在单测覆盖AnthropicLanguageModel.test.ts 与 AnthropicClient.test.ts 覆盖了makeResponse对tool_use的解析、toolNameMapper映射以及参数编解码路径同源修复同样的NameMapper机制在 OpenAiLanguageModel.ts 与 OpenAiLanguageModel.ts (openai-compat) 中被一致使用说明wire 名称 ↔ 自定义名称双向映射是 Effect AI SDK 各 provider 适配器的通用设计。在 t3code 仓库中effect-smol 作为受控第三方仓库被归档在.repos/effect-smol下根工作区通过 patches/effect4.0.0-rc.112.patch 对 Effect 生态进行补丁管理。因此当你升级依赖时可以重点关注effect/ai-anthropic的 patch 版本变更记录CHANGELOG.md确认是否包含本 changeset 描述的修复。六、对工具开发者的实践启示provider 定义工具的命名是双轨制customName是 SDK/toolkit 层的键providerName是服务端 wire 名称。凡是处理工具调用回包的地方都应通过NameMapper或等价的getCustomName映射归一化名称切勿直接假定 wire 名称与本地工具名一致。命令类工具的载荷字段宁可必填create类命令一旦语义上要求携带正文就应当把正文字段声明为必填避免可选字段在编码/传输环节被静默丢弃。面向外部编解码器的可选字段优先用optionalKey当 Schema 会被编码为第三方如 Anthropic的 JSON Schema 时Schema.optional产生的UndefinedAST 可能不被外部编解码器接受Schema.optionalKey通过键缺省表达可选性兼容性更好。这也是本次Unsupported AST Undefined报错给出的最直接经验。changeset 是问题定位的浓缩档案一条 patch changeset 往往包含问题现象、根因、修复位置与关联 issue是理解开源 SDK 演进脉络的高性价比入口——本次三个根因恰好对应名称映射、字段必填、Schema 语义三类高频 bug 类别。【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考