乐于分享
好东西不私藏

工具系统 — 给 AI 装上手脚

工具系统 — 给 AI 装上手脚

没有工具的 Agent = 只有嘴的程序员

想象一个只会说不会做的程序员:

PM:帮我把这个 Bug 修了          程序员:好的,你需要打开 src/utils.py,找到第 42 行,          把 return None 改成 return [],然后保存...          PM:你为什么不自己改?          程序员:我没有手。

这就是没有工具的 LLM。它能分析问题、给出方案,但无法执行任何操作

工具系统就是给 Agent 装上"手脚"。

Function Calling:LLM 与工具的桥梁

LLM 本身不能调用函数,但它可以输出一个结构化的请求,告诉外部系统"我想调用这个函数"。

LLM 输出:          {            "name": "read_file",            "arguments": {"file_path": "/src/main.py"}          }          外部系统:          1. 解析这个请求          2. 执行 read_file("/src/main.py")          3. 把结果返回给 LLM

这就是 OpenAI 提出的 Function Calling 机制,现在已成为行业标准。

工具 Schema 定义

每个工具需要定义一个 JSON Schema,告诉 LLM 这个工具能做什么、需要什么参数:

{  "name": "edit_file",  "description": "Edit a file by replacing a specific text section",  "parameters": {    "type": "object",    "properties": {      "file_path": {        "type": "string",        "description": "Absolute path to the file"      },      "old_str": {        "type": "string",        "description": "The text to search for and replace"      },      "new_str": {        "type": "string",        "description": "The text to replace with"      }    },    "required": ["file_path", "old_str", "new_str"]  }}

Schema 设计的黄金法则

1.description 要精确:LLM 靠描述决定何时调用工具

2.参数要有类型和描述:减少 LLM 传错参数的概率

3.required 要合理:必填参数越少,调用成功率越高

4.不要太多工具:10-15 个工具是甜蜜点,太多 LLM 会选错

工具分类与设计

一个完整的 AI Coding Agent 需要哪些工具?

文件操作类(核心)

工具

功能

风险等级

read_file

读取文件内容

🟢 安全

write_file

创建/覆盖文件

🟡 中等

edit_file

精确替换文件内容

🟡 中等

delete_file

删除文件

🔴 危险

设计要点

# read_file:支持 offset + limit,避免一次读取超大文件def read_file(file_path: str, offset: int = 1, limit: int = 2000) -> str:    """读取文件指定行范围"""    with open(file_path, 'r'as f:        lines = f.readlines()    return ''.join(lines[offset-1:offset-1+limit])# edit_file:基于搜索替换,比"覆盖整个文件"更安全def edit_file(file_path: str, old_str: str, new_str: str) -> str:    """精确替换文件中的文本片段"""    content = Path(file_path).read_text()    if old_str not in content:        return "Error: old_str not found in file"    new_content = content.replace(old_str, new_str, 1)  # 只替换第一个匹配    Path(file_path).write_text(new_content)    return "File edited successfully"

为什么用 edit_file 而不是 write_file?

write_file 的风险:          Agent 需要输出整个文件内容 → 容易丢失代码 → 一次失误全毁          edit_file 的优势:          Agent 只需输出要修改的部分 → 改动最小化 → 出错范围可控

代码搜索类

工具

功能

使用场景

grep

正则搜索文件内容

查找函数定义、变量引用

glob

按文件名模式搜索

查找特定类型的文件

list_directory

列出目录内容

了解项目结构

命令执行类

工具

功能

风险等级

run_command

执行终端命令

🔴 危险

这是最强大也最危险的工具。必须加安全限制

工具路由器:统一调度中心

所有工具通过路由器统一管理:

Agent 请求调用工具       ↓  ToolRouter       ↓  ┌────┼────┐  ↓    ↓    ↓文件工具 搜索工具 命令工具

路由器的核心职责:

class ToolRouter:    def register(self, tool: BaseTool) -> None:        """注册工具"""    def get_tool(self, name: str) -> BaseTool | None:        """按名称查找工具"""    def get_all_schemas(self) -> list[dict]:        """获取所有工具的 Schema(给 LLM 用)"""    async def execute(self, name: str, arguments: dict) -> ToolResult:        """执行工具"""

为什么需要路由器?

5.统一接口:所有工具通过同一个入口调用

6.Schema 聚合:一次性获取所有工具描述,传给 LLM

7.安全拦截:在执行前做权限检查

8.结果标准化:所有工具返回统一的 ToolResult

安全机制:给手脚戴上手套

Agent 能执行命令、修改文件,如果被恶意引导后果严重。

1. 危险操作确认

class BaseTool:    def requires_confirmation(self) -> bool:        """是否需要用户确认"""        return False  # 默认不需要class DeleteFileTool(BaseTool):    def requires_confirmation(self) -> bool:        return True  # 删除文件需要确认class RunCommandTool(BaseTool):    def requires_confirmation(self) -> bool:        return True  # 执行命令需要确认

执行流程:

Agent 请求 delete_file("/important/config.yaml")       ↓  requires_confirmation? → Yes       ↓  弹出确认框:Agent 想要删除 config.yaml,是否允许?       ↓  用户确认 → 执行  用户拒绝 → 返回 "User declined this operation"

2. 命令黑名单

BLOCKED_COMMANDS = [    "rm -rf /",       # 删除整个系统    "format C:",      # 格式化磁盘    "shutdown",        # 关机    "curl | bash",    # 远程执行脚本    "chmod 777",      # 危险权限]

3. 路径限制

def validate_path(file_path: str, allowed_dirs: list[str]) -> bool:    """确保文件操作只在允许的目录内"""    abs_path = os.path.abspath(file_path)    return any(abs_path.startswith(d) for d in allowed_dirs)

工具设计的实战经验

经验 1:工具粒度要适中

太粗:code_editor(action="modify"file="a.py", content="...")  → LLM 不知道有哪些 action,容易用错太细:insert_line(), delete_line(), replace_line(), append_line()  → 工具太多,LLM 选择困难适中:edit_file(old_str, new_str)  → 一个工具覆盖所有修改场景,参数语义清晰

经验 2:工具结果要精简

# 不好:返回整个文件内容def grep(pattern, path):    # 返回所有匹配行 + 前后 10 行上下文    # 一个 grep 可能返回 5000 行# 好:限制返回行数def grep(pattern, path, max_results=50):    # 最多返回 50 行匹配结果

原因:工具结果会进入对话历史,太长会快速消耗上下文窗口。

经验 3:错误信息要有指导性

# 不好return "Error: file not found"# 好return "Error: file '/src/main.py' not found. Use list_directory('/src') to see available files."

LLM 看到有指导性的错误信息,会自动采取正确的下一步操作。

经验 4:工具描述要教 LLM 何时使用

# 不好description = "Read a file"# 好description = """Read file content. Use this when you need to:- Understand existing code before modifying it- Check current file state- Find specific code patternsAlways read a file BEFORE editing it."""

工具系统的未来

当前的工具系统是"被动"的——LLM 请求什么就执行什么。未来的方向:

9.智能工具推荐:根据任务上下文,主动推荐合适的工具

10.工具组合:自动编排多个工具完成复杂操作(如"重构"= read + grep + edit × N)

11.工具学习:Agent 根据执行结果,学习哪些工具组合更有效

12.MCP 协议:Model Context Protocol,标准化的工具接入协议