@effect/ai-anthropic 客户端执行工具修复解析:Memory / Text Editor / Computer Use / Bash 的线上映射与 Schema 兼容
【免费下载链接】effectBuild production-ready applications in TypeScript项目地址: https://gitcode.com/GitHub_Trending/ef/effect
本篇文章基于
@effect/ai-anthropic包的一条 changeset 修复记录(.changeset/pre/fix-anthropic-memory-tool.md)展开。该修复解决了 Anthropic 提供商定义的客户端执行工具(Memory、Text Editor、Computer Use、Bash)在真实网络请求(on the wire)中不可用的问题,核心涉及三个技术点:provider wire 名称到自定义名称的映射、file_text必需字段的补齐、以及Schema.optionalKey与Schema.optional对 Anthropic codec 的兼容性。阅读本文后,你将理解 Effect AI SDK 中"提供商定义工具"的双名称机制,掌握客户端执行工具的 Schema 编写规范,并能定位与避免ToolNotFoundError与 "Unsupported AST Undefined" 两类典型故障。
背景:什么是客户端执行的提供商定义工具
在 Effect AI SDK 中,工具分为普通用户定义工具与"提供商定义工具"两类。后者由模型提供商(如 Anthropic、OpenAI)在协议层原生定义,开发者通过Tool.providerDefined将其接入请求。以 Anthropic 为例,这类工具包括:
- Bash(
bash):在沙箱中执行 shell 命令,需要computer-use-*beta header; - Computer Use(
computer/computer_use):控制屏幕的鼠标键盘操作,同样依赖 computer-use beta; - Memory(
memory):跨会话的持久化文件操作(create/view/insert/str_replace/rename/delete); - Text Editor(
str_replace_editor/str_replace_based_edit_tool):文件读写编辑; - 以及无需客户端处理的Web Search、Web Fetch、Tool Search、Code Execution等服务端执行工具。
所有定义集中在 packages/ai/anthropic/src/AnthropicTool.ts,并由 index.ts 以export * as AnthropicTool的方式对外暴露。
其中Memory、Text Editor、Computer Use、Bash都通过requiresHandler: true标记为客户端执行:模型的tool_use块会先被本地 Schema 解码,再交给开发者注册的 handler 执行,执行结果再编码回传给提供商。正因为多了一次"本地编解码 + 名称映射"的往返,这几类工具在 wire 上最容易出错——本次 changeset 修复的正是这条链路上的三个缺陷。
缺陷一:wire 名称无法映射回自定义名称,导致 ToolNotFoundError
双名称机制:customName 与 providerName
Tool.providerDefined要求同时提供两个名称,providerDefined 定义:
id:全局唯一标识,如"anthropic.memory_20250818";customName:Effect AI SDK 内部(Toolkit 键名)使用的名称,例如"AnthropicMemory"、"AnthropicBash"、"AnthropicTextEditor"、"AnthropicComputerUse";providerName:真正发送给 Anthropic 协议的名称,例如"memory"、"bash"、"str_replace_editor"、"computer"。
采用双名称的原因在 NameMapper 的文档注释中写得很清楚:一个 Toolkit 可能同时包含多个提供商的工具,它们会撞名(例如 OpenAI 和 Anthropic 都有web_search),因此 SDK 用"OpenAiWebSearch"/"AnthropicWebSearch"这类自定义名作为 Toolkit 内的唯一键,而把各自提供商的 wire 名保存在providerName中。
makeResponse 中的映射修复
当模型返回的 content 块带有type: "tool_use"时,makeResponse 处理逻辑 通过toolNameMapper.getCustomName(part.name)把 provider 返回的 wire 名(如"memory")映射回 Toolkit 使用的自定义名(如"AnthropicMemory"):
case "tool_use": { // ... const toolName = toolNameMapper.getCustomName(part.name) // 后续用 toolName 在 Toolkit 中查找 handler }NameMapper在构造时遍历 Toolkit 中的全部工具,为每个providerDefined工具建立customName ⇄ providerName双向映射(NameMapper 实现):
this.#customToProvider.set(tool.name, tool.providerName) this.#providerToCustom.set(tool.providerName, tool.name)getCustomName(providerName)返回映射后的自定义名;若某个 wire 名从未注册,transformToolCallParams会在 AnthropicLanguageModel.ts 第 3107-3118 行 抛出ToolNotFoundError,并把当前可用工具名列表一并放入错误信息,便于排查。
本次修复之前,正是这条反向映射在客户端执行工具上失效:模型返回name: "memory",但 SDK 未能把它还原为"AnthropicMemory",导致每次调用都触发ToolNotFoundError,工具在 wire 上完全不可用。修复后(含流式等价路径,见 第 2260 行 等处的同名调用)映射正确回归。
缺陷二:MemoryCreateCommand 丢失 file_text,create 命令写不出文件内容
Memory 工具的命令是一个可辨识联合(discriminated union),command字段决定载荷形态:Memory_20250818_Commands 由create / delete / insert / rename / str_replace / view六种命令 Schema 组合而成。
其中创建文件的命令此前缺少file_text字段。在 Anthropic 的 Memory 协议中,create命令必须携带文件正文内容;缺失该字段意味着模型发出的"创建文件"指令在解码后丢掉文件体,落盘时只能得到空文件(或直接失败)。修复后的 MemoryCreateCommand 完整定义如下:
export const MemoryCreateCommand = Schema.Struct({ command: Schema.Literal("create"), path: Schema.String, file_text: Schema.String // 修复:此前缺失,create 会丢失文件正文 })测试 AnthropicLanguageModel.test.ts 第 1079-1118 行 专门验证了这条链路:模拟提供商返回{ command: "create", path: "/memories/notes.txt", file_text: "hello world" },断言 handler 收到的参数完整包含file_text: "hello world",且工具结果以自定义名AnthropicMemory返回。
缺陷三:optionalKey 与 optional 的编解码差异,触发 "Unsupported AST Undefined"
两种可选字段的语义区别
Effect Schema 提供两种"可选"字段声明,二者编码侧语义不同(见 Schema.ts 中 optionalKey/optional 的说明):
Schema.optionalKey(S):字段可缺席(absent),编码为 JSON 时直接不输出该键;Schema.optional(S):等价于optionalKey(UndefinedOr(S)),字段可缺席,也可显式出现为undefined,编码侧类型是S | undefined。
关键在于:Schema.optional会在 AST 中引入Undefined字面量分支,而 Anthropic 结构化输出 codec 不支持Undefined这个 AST 节点,因此在把工具参数 Schema 转换为 Anthropic 可用的 codec 时会直接报错:
Unsupported AST Undefined本次涉及的具体字段
changeset 明确指出四个此前误用Schema.optional的字段,修复后全部改为Schema.optionalKey:
| 工具 | 字段 | 修复后定义位置 |
|---|---|---|
| Memory / Text Editor | view_range([start, end]行号区间) | MemoryViewCommand、TextEditorViewCommand |
| Computer Use | coordinate([x, y]屏幕坐标) | 如 ComputerUseLeftClickAction、DoubleClick、RightClick、Scroll、LeftMouseDown/Up等 |
| Bash | restart(是否重启 shell) | Bash_20241022 / Bash_20250124 |
以 Bash 为例,修复后的参数 Schema 为:
export const Bash_20241022 = Tool.providerDefined({ id: "anthropic.bash_20241022", customName: "AnthropicBash", providerName: "bash", requiresHandler: true, success: Schema.String, parameters: Schema.Struct({ command: Schema.String, restart: Schema.optionalKey(Schema.Boolean) }) })对view_range而言,其值本身是 1 起始、以-1表示"读到文件末尾"的行区间[start, end](ViewRange 定义),字段本身允许缺席,但显式undefined没有意义,因此optionalKey是语义上更准确的声明。
回归测试的验证方式
测试 client provider tool parameters compile with the Anthropic codec 遍历全部客户端执行工具(Bash 两个版本、Computer Use 三个版本、Memory、Text Editor 四个版本),对每个工具的parametersSchema调用AnthropicStructuredOutput.toCodecAnthropic(...),断言能成功编译出 codec——任何残留的Schema.optional字段都会让该测试失败,从而防止问题回归。
完整修复链路一览
把三个缺陷串起来,客户端执行工具在 wire 上的完整数据流是:
- 开发者用
Toolkit.make(AnthropicTool.Memory_20250818({}))注册工具,并通过toolkit.toLayer({ AnthropicMemory: handler })提供 handler; - 请求发出后,模型返回
content: [{ type: "tool_use", name: "memory", input: {...} }]; makeResponse用NameMapper.getCustomName("memory")还原为"AnthropicMemory"(修复一),否则抛ToolNotFoundError;- 按自定义名找到工具后,用其
parametersSchema(已通过optionalKey通过 codec 编译,修复三)解码input,例如{ command: "create", path: "...", file_text: "hello world" }(修复二保证file_text不丢失); - 解码后的参数交给
AnthropicMemoryhandler 执行,结果编码回传。
三处修复在 AnthropicLanguageModel.test.ts 第 1014-1146 行 有完整的回归覆盖,对应 changeset 提及的 issue #2615。
实践要点
- 为客户端执行工具编写参数 Schema 时,一律使用
Schema.optionalKey而非Schema.optional,避免Undefined进入 AST 触发 "Unsupported AST Undefined"。Schema.optional只适用于确实需要在编码侧表达undefined的场景。 - 不要假设模型返回的 tool name 等于 Toolkit 键名。wire 上永远是
providerName(如memory、bash),SDK 内部映射到customName;排查ToolNotFoundError时,先确认工具是否通过providerDefined注册、providerName是否与协议一致。 - 协议要求必填的字段(如 Memory
create的file_text)必须出现在 Schema 中,否则解码结果会静默丢字段,表现为"文件内容凭空消失"这类难以定位的问题。 - 涉及 Anthropic 相关功能改动时,可运行
packages/ai/anthropic/test/AnthropicLanguageModel.test.ts中的 Memory tool 与 codec 编译两组用例做快速验证。
上述行为在@effect/ai-anthropic的当前源码中均已生效,改动以patch级别随.changeset/pre/fix-anthropic-memory-tool.md发布。如需查看完整工具定义(含各版本 Computer Use 动作集、Text Editor 命令集、Web Search/Fetch 参数等),可直接阅读 packages/ai/anthropic/src/AnthropicTool.ts;工具双名称映射的通用机制则在 packages/effect/src/unstable/ai/Tool.ts 的providerDefined与NameMapper中。
【免费下载链接】effectBuild production-ready applications in TypeScript项目地址: https://gitcode.com/GitHub_Trending/ef/effect
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考