从零构建 AI Agent 工具系统:bash、文件操作与网络请求完全指南
如果你已经看过各种 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 -la、pip 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 能自主操作计算机的完整能力集。核心启示有三点:
- 工具调用不是黑盒——本质就是 LLM 输出 JSON 格式的函数调用请求,由宿主程序执行并返回结果
- 7 种基础工具构成最小完备集——bash(通用执行)、文件读写(持久化)、glob+grep(发现与搜索)、edit(精确修改)、webfetch(外部信息)
- 安全是下一步的关键——bash 工具的
shell=True意味着 Agent 可以做任何事情。生产环境中应当加入沙箱(Docker、nsjail)或权限控制
这个系列后续还将覆盖 MCP 协议集成、安全沙箱、任务规划和记忆持久化等进阶话题。理解了这套基础工具机制后,无论你用的是 Claude Code、Cursor 还是自建 Agent,都能更清晰地理解它们背后的运作原理。
如果你想立即上手,完整的代码可以在作者的 GitHub 仓库 中找到。