2026年6月6日 4 分钟阅读

从零构建 AI Agent 工具系统:bash、文件操作与网络请求完全指南

tinyash 0 条评论

如果你已经看过各种 AI Agent 框架的教程,可能会觉得 Agent 的工具调用(Tool Calling)是个黑盒——LLM 怎么知道该调用哪个函数?为什么现代 Agent 能自主运行 bash 命令、读写文件、抓取网页?其实,这套机制远没有想象中复杂。

本文将带你从零实现一个完整的 AI Agent 工具系统,包含 7 个核心工具:bash 执行、文件读写、glob 搜索、grep 检索、编辑替换和网页抓取。读完你将不仅理解工具调用的原理,还能自己动手打造一个可运行的 Agent 工具集。

Agent 工具的本质

所谓工具(Tool),就是暴露给 LLM 让它自主调用的程序或函数。它可以简单到一行 Python 函数,也可以复杂到 MCP(Model Context Protocol)服务器——通过 HTTP 请求访问远程 API。

早期 Agent 的实现依赖文本解析:让 LLM 输出类似 Action: web_fetch 的文本,再由 harness 解析并执行对应函数。这种方法不太可靠,模型经常不按预期的格式输出。现代 LLM 内置了原生工具调用能力——模型经过微调,可以直接输出结构化的 JSON 工具请求,内置校验机制大幅减少了幻觉。

工具一:Bash 执行

这是最强大的工具,也是最有风险的工具。允许 Agent 在宿主机上执行任意 bash 命令,意味着它能做任何事——从安装软件包到修改系统配置。

import subprocess

def run_bash(command: str) -> str:
    """Run a bash command and return its output."""
    result = subprocess.run(
        command, shell=True, text=True, capture_output=True
    )
    output = result.stdout
    if result.stderr:
        output += f"\nSTDERR:\n{result.stderr}"
    return output or "(no output)"

shell=True 让 Agent 可以直接运行 ls -lapip install 甚至 git clone。这种灵活性既是优势也是安全隐患——未来你需要为 Agent 加上沙箱隔离。

工具二:文件读取

有了 bash,Agent 还需要读取文件的能力。这个工具支持偏移量和行数限制,避免一次性读取超大文件塞满上下文:

from pathlib import Path

def read_file(path: str, offset: int = 1, limit: int = 200) -> str:
    """Read lines from a file, with optional offset and limit."""
    p = Path(path)
    if not p.exists():
        return f"Error: file not found: {path}"
    lines = p.read_text(errors="replace").splitlines()
    selected = lines[offset - 1 : offset - 1 + limit]
    return "\n".join(
        f"{offset + i}:{line}"
        for i, line in enumerate(selected)
    )

对于编程类 Agent,这是最常用的工具之一——读取代码文件、配置文件、日志文件。

工具三:Glob 文件搜索

Agent 需要先知道有哪些文件,然后才能读取它们。glob 工具解决了”发现文件”的问题:

import glob as glob_module

def glob_files(pattern: str, path: str = ".") -> str:
    """Find files matching a glob pattern inside a directory."""
    matches = glob_module.glob(f"{path}/**/{pattern}", recursive=True)
    matches += glob_module.glob(f"{path}/{pattern}")
    unique = sorted(set(matches))
    return "\n".join(unique) if unique else "(no matches)"

结合 **/*.py 这样的模式,Agent 可以快速定位项目中的所有 Python 文件。

工具四:Grep 内容检索

知道文件存在之后,Agent 还需要在文件内容中搜索特定模式。grep 工具结合了文件遍历和正则匹配:

import re

def grep(pattern: str, path: str = ".", include: str = "*") -> str:
    """Search file contents for a regex pattern."""
    results = []
    for filepath in glob_module.glob(f"{path}/**/{include}", recursive=True):
        fp = Path(filepath)
        if not fp.is_file():
            continue
        try:
            for i, line in enumerate(fp.read_text(errors="replace").splitlines(), 1):
                if re.search(pattern, line):
                    results.append(f"{filepath}:{i}:{line}")
        except OSError:
            pass
    return "\n".join(results) if results else "(no matches)"

include 参数允许按文件名过滤——比如只搜索 .py 文件,跳过二进制内容。

工具五:文件写入

Agent 不仅要读,还要写。write_file 自动创建父目录:

def write_file(path: str, content: str) -> str:
    """Write content to a file, creating it if it does not exist."""
    p = Path(path)
    p.parent.mkdir(parents=True, exist_ok=True)
    p.write_text(content)
    return f"Wrote {len(content)} bytes to {path}"

这对于代码生成类 Agent 至关重要——写好文件后,Agent 可以继续调用 bash 运行测试。

工具六:编辑文件

write_file 的问题是它会覆盖整个文件。当 Agent 只需要修改某几行时,edit_file 更加安全:

def edit_file(path: str, old_string: str, new_string: str) -> str:
    """Replace the first occurrence of old_string with new_string."""
    p = Path(path)
    if not p.exists():
        return f"Error: file not found: {path}"
    original = p.read_text()
    if old_string not in original:
        return f"Error: string not found in {path}"
    p.write_text(original.replace(old_string, new_string, 1))
    return f"Edited {path}"

这是编程 Agent 最常用的工具——找到 bug 所在行,直接替换修复,而不是重写整个文件。

工具七:网页抓取

最后,Agent 还需要访问外部信息的能力:

from urllib.parse import urlparse
import urllib.request
from bs4 import BeautifulSoup

def webfetch(url: str) -> str:
    """Fetch a URL and return its plain-text content."""
    parsed = urlparse(url)
    if parsed.scheme not in ("http", "https"):
        return f"Error: unsupported scheme '{parsed.scheme}'."
    req = urllib.request.Request(
        url, headers={"User-Agent": "agent/1.0"}
    )
    with urllib.request.urlopen(req, timeout=15) as resp:
        raw = resp.read().decode(resp.headers.get_content_charset() or "utf-8", errors="replace")
    soup = BeautifulSoup(raw, "html.parser")
    text = soup.get_text(separator="\n", strip=True)
    return re.sub(r"\n{3,}", "\n\n", text).strip()

使用 BeautifulSoup 剥离 HTML,只返回纯文本。支持 http/https 协议,限制 2MB 响应大小——这些限制保护了 Agent 的上下文窗口不被巨大页面撑爆。

工具注册与模式定义

有了工具函数,还需要让 LLM 知道它们的存在。每个工具需要一个 JSON Schema 来描述它的名称、功能和参数:

def get_tool_schemas():
    return [
        {
            "type": "function",
            "function": {
                "name": "run_bash",
                "description": "Run a bash command on the user's machine and return the output.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "command": {"type": "string", "description": "The bash command to execute."}
                    },
                    "required": ["command"],
                },
            },
        },
        # ... 其他工具的 schema
    ]

然后需要一个注册表把函数名映射到实际函数,以及一个工具调用处理器:

TOOL_REGISTRY = {
    "run_bash": run_bash,
    "read_file": read_file,
    "glob_files": glob_files,
    "grep": grep,
    "write_file": write_file,
    "edit_file": edit_file,
    "webfetch": webfetch,
}

集成到 Agent 循环

最后,把工具调用集成到 Agent 主循环中:

def handle_tool_calls(tool_calls, messages):
    for tool_call in tool_calls:
        name = tool_call.function.name
        args = json.loads(tool_call.function.arguments)
        if name not in TOOL_REGISTRY:
            result = f"Error: unknown tool '{name}'"
        else:
            result = TOOL_REGISTRY[name](**args)
        messages.append({
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": result,
        })

Agent 循环的逻辑变成了:用户输入 → LLM 响应(可能包含工具调用)→ 执行工具 → 结果追加到消息 → LLM 再次响应 → … → 最终回复用户。

实战测试

启动 Agent 后,你可以让它做各种事情。比如抓取网页后保存到文件:

You: 访问 ruxu.dev 首页,把文章列表保存到 ruxu.md
[tool] webfetch({'url': 'https://ruxu.dev'})
[tool result] Blog | Roger Oriol ...
[tool] write_file({'path': 'ruxu.md', 'content': '# Articles...'})
[tool result] Wrote 375 bytes to ruxu.md
Assistant: 已完成 — 已将 ruxu.dev 的文章列表写入 ruxu.md。

7 个工具的配合让 Agent 具备了基本的”自主工作”能力:发现文件 → 读取内容 → 搜索模式 → 编辑修改 → 运行验证 → 保存结果。

总结与扩展方向

本文实现的 7 个工具构成了 AI Agent 能自主操作计算机的完整能力集。核心启示有三点:

  1. 工具调用不是黑盒——本质就是 LLM 输出 JSON 格式的函数调用请求,由宿主程序执行并返回结果
  2. 7 种基础工具构成最小完备集——bash(通用执行)、文件读写(持久化)、glob+grep(发现与搜索)、edit(精确修改)、webfetch(外部信息)
  3. 安全是下一步的关键——bash 工具的 shell=True 意味着 Agent 可以做任何事情。生产环境中应当加入沙箱(Docker、nsjail)或权限控制

这个系列后续还将覆盖 MCP 协议集成、安全沙箱、任务规划和记忆持久化等进阶话题。理解了这套基础工具机制后,无论你用的是 Claude Code、Cursor 还是自建 Agent,都能更清晰地理解它们背后的运作原理。

如果你想立即上手,完整的代码可以在作者的 GitHub 仓库 中找到。

发表评论

你的邮箱地址不会被公开,带 * 的为必填项。