尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

从零手写 ClaudeCode 实战笔记(2)Tool Use:用 dispatch map 搭出可复制的工具调用骨架

发布时间:2026/9/28 18:21:45

资讯中心
01
ARTICLE

从零手写 ClaudeCode 实战笔记(2)Tool Use:用 dispatch map 搭出可复制的工具调用骨架

从零手写 ClaudeCode 实战笔记(2)Tool Use:用 dispatch map 搭出可复制的工具调用骨架
1. 为什么单靠 bash 工具会让 Agent 越写越慌如果你正在跟着 learn-claude-code 这个项目从零手写一个 ClaudeCode 风格的 AI Agent走到第二章 Tool Use 时大概率会卡在同一个地方s01 里只有一个 bash 工具模型想读文件就cat想写文件就echo 想改内容就上sed。跑几个简单 prompt 看着挺顺一旦让它处理真实项目里的文件问题就全冒出来了。我实测下来最典型的三类翻车一是cat大文件时输出被截断模型拿到半截内容就开始瞎猜二是sed碰到路径里有空格、内容里有引号或正则元字符时直接报错甚至误改别的行三是所有操作都走 shell等于把整个工作目录甚至更上层路径都暴露给模型它一句rm或者cd ..就能跑出你划定的范围。这不是模型笨是工具设计的问题——你只给了它一把万能锤子它自然看什么都像钉子。Tool Use 这一章要解决的核心就是把「万能 bash」拆成一组职责明确的专用工具read_file、write_file、edit_file再配一个路径沙箱safe_path兜底。而把这些工具串起来的关键结构就是 dispatch map——一个{工具名: 处理函数}的字典。它的价值在于加一个新工具只需要加一个 handler 和一份 schemaAgent 主循环一行都不用动。这篇就围绕 learn-claude-code 里 s02 的落地角度把可复制的工具注册表和 dispatch map 骨架拆给你最后附一段验证动作确认新增工具真的能被路由和调用。2. 前置准备TaoToken 接入与项目环境在动手改 dispatch map 之前得先让 Agent 能稳定调到大模型。learn-claude-code 默认走 Anthropic 的 Messages API 协议Tool Use 的tools参数、tool_use/tool_result这些 block 结构都依赖这套协议。我这边用的是 TaoToken 做接入它兼容 Anthropic 的接口形态改一下 base_url 和 key 就能跑不用动业务代码。你需要准备两样东西一个可用的 API Key以及确认接入地址。API 端点是https://taotoken.net/api注意这个地址后面不要加多余的路径SDK 会自己拼/v1/messages。Key 在控制台的 API Keys 页面创建建议单独建一个给这个项目用方便后面排查调用量。环境侧确认三件事Python 3.10 以上safe_path里用到了Path.is_relative_to3.9 才有但 3.10 更稳、项目已经 clone 到本地、依赖装好。如果你还没拿到 key可以先到模型对话页面手动发一条带 tools 的请求确认账号和模型权限没问题再回到代码里接。git clone https://github.com/shareAI-lab/learn-claude-code.git cd learn-claude-code python -m venv .venv source .venv/bin/activate # Windows 用 .venv\Scripts\activate pip install -r requirements.txt然后把 key 写进环境变量别硬编码进源码export ANTHROPIC_API_KEY你的_taotoken_key export ANTHROPIC_BASE_URLhttps://taotoken.net/api注意不同 SDK 读取 base_url 的环境变量名可能不一样有的用ANTHROPIC_BASE_URL有的要在客户端初始化时显式传base_url。如果跑起来报 404 或连接错误先检查这一项而不是怀疑 key 失效。3. 可复制的 dispatch map 与工具注册表骨架这一节是全文的核心。s02 相对 s01 的改动可以浓缩成一句话循环不变工具变多靠 dispatch map 做路由。下面这份骨架你可以直接抄进agents/s02_tool_use.py我按「沙箱 → 处理函数 → 注册表 → 循环」四层拆开讲。3.1 路径沙箱 safe_path所有文件工具的第一道闸from pathlib import Path WORKDIR Path.cwd().resolve() def safe_path(p: str) - Path: # 拼成绝对路径同时消掉 .. 和 . path (WORKDIR / p).resolve() # 关键确认最终路径仍在工作目录内 if not path.is_relative_to(WORKDIR): raise ValueError(fPath escapes workspace: {p}) return path这里有个容易忽略的点resolve()必须在判断之前调用。因为../secret.txt这种路径只有解析成绝对路径后才能看出它逃出了 WORKDIR。如果你先判断字符串再 resolvea/../../b这类写法就能绕过检查。is_relative_to是 Python 3.9 引入的比手写str.startswith安全得多后者在/work和/work-evil这种前缀相似的路径上会误判。3.2 三个专用处理函数def run_read(path: str, limit: int None) - str: text safe_path(path).read_text(encodingutf-8) lines text.splitlines() if limit and limit len(lines): lines lines[:limit] return \n.join(lines)[:50000] def run_write(path: str, content: str) - str: target safe_path(path) target.parent.mkdir(parentsTrue, exist_okTrue) target.write_text(content, encodingutf-8) return fWrote {len(content)} chars to {path} def run_edit(path: str, old_text: str, new_text: str) - str: target safe_path(path) text target.read_text(encodingutf-8) if old_text not in text: return fEdit failed: old_text not found in {path} target.write_text(text.replace(old_text, new_text, 1), encodingutf-8) return fEdited {path}run_read里那个[:50000]是硬上限防止模型一次拉进超大文件把上下文撑爆。run_edit用replace(..., 1)只替换第一处避免模型给的 old_text 在文件里出现多次时被批量改掉——这是我在真实项目里踩过的坑一次误替换把配置文件里所有同名 key 都改了。3.3 dispatch map一行查表替代 if/elif 链TOOL_HANDLERS { bash: lambda **kw: run_bash(kw[command]), read_file: lambda **kw: run_read(kw[path], kw.get(limit)), write_file: lambda **kw: run_write(kw[path], kw[content]), edit_file: lambda **kw: run_edit(kw[path], kw[old_text], kw[new_text]), }这里的lambda **kw是个中间层作用是接住大模型发来的任意参数结构。模型返回的block.input是个 dict字段名和 schema 里定义的一致但不同工具的字段不同用**kw统一收口再在 lambda 里按名字取比给每个工具写一个if name ...干净得多。kw.get(limit)用 get 是因为 limit 是可选参数模型可能不传。3.4 循环体和 s01 完全一致for block in response.content: if block.type tool_use: handler TOOL_HANDLERS.get(block.name) output handler(**block.input) if handler \ else fUnknown tool: {block.name} results.append({ type: tool_result, tool_use_id: block.id, content: output, })注意TOOL_HANDLERS.get(block.name)而不是[]。模型偶尔会幻觉出一个不存在的工具名用[]会直接 KeyError 崩掉整个循环用get返回 None 后走Unknown tool分支把错误当成 tool_result 回传给模型它下一轮往往能自己纠正。这个容错设计在长对话里特别值。3.5 工具 schema 也要同步注册dispatch map 只管执行侧模型侧还得知道有哪些工具可用。schema 和 handler 是一一对应的加 handler 必须加 schemaTOOLS [ { name: read_file, description: Read a text file inside the workspace., input_schema: { type: object, properties: { path: {type: string}, limit: {type: integer}, }, required: [path], }, }, # write_file / edit_file / bash 同理 ]safe_path的沙箱只作用于文件类工具bash本身不受它约束——这是 s02 的一个已知边界。如果你要更严可以在run_bash里也做命令白名单但那超出本章范围先记住这个口子。4. 验证新增工具能否被正确路由与调用骨架搭完别急着跑复杂任务先用一个最小验证确认 dispatch map 真的在工作。我习惯分两步先离线测 handler再在线测路由。4.1 离线单测直接调 handler# 在项目根目录建一个临时文件 Path(requirements.txt).write_text(anthropic\nrich\n, encodingutf-8) print(run_read(requirements.txt)) # 预期输出anthropic\nrich print(run_read(../etc/passwd)) # 预期抛 ValueError: Path escapes workspace这一步能确认safe_path的沙箱生效。如果第二行没报错反而读出了内容说明你的resolve()或is_relative_to写错了先修这里再往下走。4.2 在线验证让模型自己选工具启动 Agent依次输入这几个 prompt观察它是否调用了正确的工具名python agents/s02_tool_use.pyRead the file requirements.txt Create a file called greet.py with a greet(name) function Edit greet.py to add a docstring to the function Read greet.py to verify the edit worked判断成功的标准不是「任务完成了」而是日志里出现的 tool_use block 的 name 字段第一条应该是read_file第二条write_file第三条edit_file第四条又是read_file。如果它还在用bash加cat说明你的 schema description 没写清楚模型没意识到有专用工具可用——把 description 写得更具体比如「Use this instead of bash cat for reading files」。4.3 加一个新工具验证「循环不变」这是最能说明 dispatch map 价值的验证。给注册表加一个list_dirdef run_list_dir(path: str .) - str: target safe_path(path) return \n.join(sorted(p.name for p in target.iterdir())) TOOL_HANDLERS[list_dir] lambda **kw: run_list_dir(kw.get(path, .))再往TOOLS里补一份对应 schema然后重启 Agent 输入List the files in the current directory。如果它调用了list_dir并返回了文件列表而你的主循环一行没改就证明这套骨架是可扩展的。加工具 加 handler 加 schema这个等式成立Tool Use 这章就算真正落地了。5. 本篇常见报错排查报错一ValueError: Path escapes workspace。先确认你传的 path 是不是真的越界了。如果传的是绝对路径比如/tmp/x.txtWORKDIR / p在 pathlib 里遇到绝对路径会直接丢弃 WORKDIR结果就是越界。正确做法是让模型只传相对路径schema 的 description 里明确写「relative to workspace root」。报错二KeyError: command或KeyError: path。模型返回的 input 字段名和你的 lambda 取值对不上。常见原因是 schema 里写的是file_pathlambda 里取的是kw[path]。两边必须严格一致改完 schema 记得同步改 handler。报错三Unknown tool: xxx反复出现。要么是工具名拼写不一致schema 里叫read_file注册表里写成read要么是模型幻觉。前者改一致即可后者可以接受因为容错分支已经把它变成 tool_result 回传模型下一轮通常会改用正确工具。报错四调用返回 401 或 404。先查 base_url 是不是写成了https://taotoken.net/api/v1/messages这种带路径的形式SDK 会重复拼接。再查 key 有没有多余空格。这两项排除后再看模型名是否在账号权限范围内。报错五edit_file返回old_text not found。模型给的 old_text 和文件实际内容有细微差异比如缩进、换行符、全角半角。可以在返回信息里附上文件前若干行帮模型下一轮对齐。别直接抛异常让它有机会自我修正。6. 继续往下走从 Tool Use 到可长期运行的 Agent把 dispatch map 跑通之后你会发现 Tool Use 这层其实很薄——真正让 Agent 变复杂的是工具变多之后的上下文管理、错误恢复和多轮编排。learn-claude-code 后面的章节会陆续处理这些但前提是你现在这套注册表骨架是干净的。如果你打算把这个 Agent 长期跑在本地做编码辅助建议直接上 Coding Plan按量计费比单次调用更适合高频的 tool_use 往返日常调试和验证模型行为用模型对话页面手动发带 tools 的请求最快接入细节和参数说明都在接入文档里遇到 401、404、字段不匹配这类问题先翻它。API Key 统一在 API Keys 页面管理给项目单独建一个出问题好定位。最后留一个我自己的习惯每加一个新工具先写离线单测确认 handler 和沙箱再写一句 prompt 确认路由两步都过了才接进主流程。这样出问题时你能立刻判断是工具本身错了还是模型选错了工具——这两类问题的修法完全不同。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。