Learn Claude Code Agent 开发 | 2、插拔式工具系统:扩展功能不修改核心循环
Learn Claude Code Agent 开发 | 2、插拔式工具系统:扩展功能不修改核心循环
整体概述
多工具分发核心实现是基础智能体循环的直接扩展,核心思想就是:
“加一个工具, 只加一个 handler”– 循环不用动, 新工具注册进 dispatch map 就行。
这是智能体工具分发机制的核心实现,体现了 Harness 层最重要的设计原则:扩展工具不修改核心循环。
解决的核心问题
单bash工具存在很多缺陷:
cat截断不可预测,sed遇到特殊字符容易崩溃- 每次 bash 调用都是不受约束的安全面,容易产生越权操作
- 扩展工具需要修改核心循环代码,违反开闭原则
核心设计思想
+--------+ +-------+ +------------------+ | User | ---> | LLM | ---> | Tool Dispatch | | prompt | | | | { | +--------+ +---+---+ | bash: run_bash | ^ | read: run_read | | | write: run_wr | +-----------+ edit: run_edit | tool_result | } | +------------------+核心创新:用**分发字典(dispatch map)**替代硬编码的工具调用,实现工具的插拔式扩展。
逐段解析
1. 环境初始化
WORKDIR=Path.cwd()client=Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))MODEL=os.environ["MODEL_ID"]SYSTEM=f"You are a coding agent at{WORKDIR}. Use tools to solve tasks. Act, don't explain."- 新增
WORKDIR常量作为工作区根目录,是路径沙箱的基础 - 系统提示从 “Use bash” 改成 “Use tools”,明确支持多工具
2. 路径安全沙箱 ⭐ 核心安全机制
defsafe_path(p:str)->Path:path=(WORKDIR/p).resolve()ifnotpath.is_relative_to(WORKDIR):raiseValueError(f"Path escapes workspace:{p}")returnpath功能:防止智能体访问工作目录之外的文件
- 将输入路径解析为绝对路径
- 校验路径是否在
WORKDIR之下 - 如果路径逃逸(比如
../etc/passwd)直接抛出错误 - 所有文件操作工具都会先调用这个函数做安全校验
3. 工具实现
提供了4个工具,每个工具都是独立的处理函数:
(1)bash 工具
和基础实现基本一致,只是将cwd固定为WORKDIR,提升安全性。
(2)read_file 工具
defrun_read(path:str,limit:int=None)->str:try:text=safe_path(path).read_text()lines=text.splitlines()iflimitandlimit<len(lines):lines=lines[:limit]+[f"... ({len(lines)-limit}more lines)"]return"\n".join(lines)[:50000]exceptExceptionase:returnf"Error:{e}"功能:安全读取文件内容
- 支持
limit参数限制读取行数,避免大文件撑爆上下文 - 超过行数限制时会提示剩余行数,更友好
- 统一异常处理,错误信息直接返回给模型
(3)write_file 工具
defrun_write(path:str,content:str)->str:try:fp=safe_path(path)fp.parent.mkdir(parents=True,exist_ok=True)fp.write_text(content)returnf"Wrote{len(content)}bytes to{path}"exceptExceptionase:returnf"Error:{e}"功能:写入文件
- 自动创建不存在的父目录
- 返回写入结果给模型确认
(4)edit_file 工具
defrun_edit(path:str,old_text:str,new_text:str)->str:try:fp=safe_path(path)content=fp.read_text()ifold_textnotincontent:returnf"Error: Text not found in{path}"fp.write_text(content.replace(old_text,new_text,1))returnf"Edited{path}"exceptExceptionase:returnf"Error:{e}"功能:精确替换文件内容
- 只替换第一次出现的匹配文本,避免误修改
- 匹配不到内容时明确返回错误,模型可以调整匹配文本重试
4. 工具分发字典 ⭐ 核心扩展机制
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 统一参数传递,适配不同工具的参数差异
- 加新工具只需要在这里加一行映射,不需要修改其他代码
5. 工具定义数组
TOOLS=[{"name":"bash","description":"Run a shell command.","input_schema":{"type":"object","properties":{"command":{"type":"string"}},"required":["command"]}},{"name":"read_file","description":"Read file contents.","input_schema":{"type":"object","properties":{"path":{"type":"string"},"limit":{"type":"integer"}},"required":["path"]}},{"name":"write_file","description":"Write content to file.","input_schema":{"type":"object","properties":{"path":{"type":"string"},"content":{"type":"string"}},"required":["path","content"]}},{"name":"edit_file","description":"Replace exact text in file.","input_schema":{"type":"object","properties":{"path":{"type":"string"},"old_text":{"type":"string"},"new_text":{"type":"string"}},"required":["path","old_text","new_text"]}},]- 给 LLM 提供的工具描述,遵循 Anthropic 函数调用格式
- 每个工具包含名称、功能描述、参数结构(JSON Schema)
- LLM 根据这些描述决定调用什么工具、传递什么参数
6. 核心智能体循环
和基础智能体循环几乎完全一致,只有工具调用部分做了修改:
defagent_loop(messages:list):whileTrue:response=client.messages.create(model=MODEL,system=SYSTEM,messages=messages,tools=TOOLS,max_tokens=8000,)messages.append({"role":"assistant","content":response.content})ifresponse.stop_reason!="tool_use":returnresults=[]forblockinresponse.content:ifblock.type=="tool_use":# 从分发字典取处理函数,替代硬编码调用handler=TOOL_HANDLERS.get(block.name)output=handler(**block.input)ifhandlerelsef"Unknown tool:{block.name}"print(f">{block.name}:{output[:200]}")results.append({"type":"tool_result","tool_use_id":block.id,"content":output})messages.append({"role":"user","content":results})关键变化:
- 去掉了硬编码的
run_bash调用 - 改为从
TOOL_HANDLERS字典中根据工具名查找处理函数 - 支持未知工具的错误提示,鲁棒性更强
- 核心循环逻辑完全不变,实现了对扩展开放、对修改关闭的开闭原则
7. 交互式主循环
和基础实现完全一致,仅提示符做了修改。
基础版对比
| 组件 | 基础版(s01) | 多工具版(s02) |
|---|---|---|
| 工具数量 | 1个(仅 bash) | 4个(bash, read, write, edit) |
| 工具调用方式 | 硬编码 bash 调用 | TOOL_HANDLERS分发字典 |
| 路径安全 | 无 | safe_path()沙箱防护 |
| Agent 循环 | 固定 | 完全不变 |
设计优势
扩展性极强:加新工具只需要两步:
- 写工具处理函数
- 在
TOOL_HANDLERS和TOOLS中注册 - 核心循环不需要动一行代码
安全性提升:
- 路径沙箱防止文件越权访问
- 专用工具比 bash 更可控,减少安全风险
稳定性更高:
- 核心循环经过验证不需要修改,减少出问题的概率
- 工具之间独立,修改一个工具不影响其他功能
使用示例
启动后可以测试这些指令:
python agents/s02_tool_use.py s02>>Read thefilerequirements.txt>read_file:[文件内容]s02>>Create afilecalled greet.py with a greet(name)function>write_file: Wrote50bytes to greet.py s02>>Edit greet.py toadda docstring to thefunction>edit_file: Edited greet.py s02>>Read greet.py to verify the edit worked>read_file:[修改后的文件内容]核心收获
这个版本建立了 Harness 层最核心的设计范式:
循环永远不变,变化的只有工具和分发规则
后续的所有功能扩展都是在这个基础上扩展更多工具和机制,核心循环始终保持稳定。这种设计思想也贯穿了整个 Claude Code 的架构。
本内容参考开源项目 learn-claude-code
