趋近智
为语言模型配置与文件系统交互的能力,拓展了广泛的用途,从保存生成的代码和研究笔记,到读取配置文件或数据集,都涵盖在内。本节将详细说明如何构建工具,以使大型语言模型安全地执行文件操作。主要的难点和关注点是安全性:无限制的文件访问是主要的安全隐患。因此,我们将着重强调细致的沙盒隔离和路径处理。
为了让大型语言模型代理能够有效管理文件,它通常需要一些基本能力。这些能力可以转化为不同的工具:
在为大型语言模型构建文件系统操作工具时,通常建议将每项功能实现为独立的、细致的工具(例如 read_file 工具、write_file 工具),而不是一个带有子命令的单一复杂工具。这样做可以简化对大型语言模型的工具描述,减少歧义,并允许为每项操作制定更有针对性的安全策略。
每个文件操作工具都需要明确定义输入(参数)和输出(结果)。下面看一些示例:
read_file 工具:
file_path (字符串):文件的路径,必须是相对于指定的沙盒目录。encoding (字符串,可选):文件的字符编码(例如 'utf-8')。默认为 'utf-8'。content(字符串)数据或说明任何问题的 error(字符串)。write_file 工具:
file_path (字符串):文件应在沙盒中创建或覆盖的相对路径。content (字符串):要写入文件的实际数据或文本。mode (字符串,可选):指定写入模式,通常 'w' 表示覆盖,'a' 表示追加。默认为 'w'。status(字符串)消息(例如“文件 'example.txt' 写入成功。”),失败时包含 error(字符串)。list_directory 工具:
directory_path (字符串,可选):沙盒内目录的相对路径。如果省略,则默认为沙盒的根目录。items(字符串列表,每个字符串都是文件或目录名称)或 error(字符串)。构建文件系统工具最重要的方面是确保它们安全运行。大型语言模型,根据其特点,可能会生成或被提示文件路径,这些路径如果未经检查,可能导致对敏感系统文件的未经授权的访问或修改。
沙盒隔离是一种技术,其中大型语言模型代理的文件操作被严格限制在特定目录或一组目录(即“沙盒”)内。代理不应感知或访问此预定义区域之外的文件系统任何部分。大型语言模型指定的所有文件路径都必须被解释为相对于此沙盒的根目录。
此图显示文件系统工具作为访问中介。来自大型语言模型的请求会经过验证;指定沙盒内的操作被允许,而尝试访问沙盒外敏感区域的请求则被阻止。
即使在沙盒内,大型语言模型提供(或从外部输入派生)的路径也需要严格的验证:
/srv/app/agent_files/user123/)。此路径不应由大型语言模型更改。reports/report.txt)与沙盒根目录合并。生成的路径随后应进行规范化,以处理如 .(当前目录)和 ..(父目录)等结构。Python 的 os.path.join() 和 os.path.normpath() 在此很有用。pathlib.Path.resolve() 在此表现出色,因为它还能处理符号链接(尽管您可能希望禁止对指向沙盒外部的符号链接进行操作)。resolved_path.is_relative_to(sandbox_root_path)(Python 3.9+)为 false,或等效检查失败,则必须拒绝该操作。这可以防止路径遍历攻击(例如 ../../../../../etc/passwd)。以下是 FileSystemTools 类在 Python 中的实现概述,侧重于安全方面:
import os
import pathlib
# 这应安全配置,可能按代理或按会话。
# 例如,来自环境变量或安全配置服务。
# 确保此目录存在并具有适当的权限。
SANDBOX_ROOT_DIR = pathlib.Path(os.getenv("AGENT_SANDBOX_ROOT", "./default_agent_sandbox")).resolve()
SANDBOX_ROOT_DIR.mkdir(parents=True, exist_ok=True) # 如果不存在则创建
def _get_sandboxed_path(relative_path_str: str) -> pathlib.Path | None:
"""
将相对路径字符串安全解析为沙盒内的绝对路径。
如果路径无效或在沙盒外部,则返回 None。
"""
if not relative_path_str or not isinstance(relative_path_str, str):
# print("安全:无效的路径输入类型或空路径。")
return None
# 不允许绝对路径或尝试在开头使用反斜杠/斜杠
# 这可能会绕过简单的连接。
if os.path.isabs(relative_path_str) or relative_path_str.startswith(('/', '\\')):
# print(f"安全:绝对路径尝试被拒绝:{relative_path_str}")
return None
# 通过将沙盒根目录与相对路径连接来创建完整路径。
# 在解析和检查之前,这仍然可能不安全。
candidate_path = SANDBOX_ROOT_DIR / relative_path_str
try:
# 将路径解析为其规范的绝对形式。
# 这会处理 '..' 和符号链接。
# strict=False 允许解析不存在的文件的路径以进行“写入”操作。
# 对于“读取”或“列出”操作,如果路径必须存在,您可能希望使用 strict=True。
resolved_path = candidate_path.resolve(strict=False)
except FileNotFoundError:
# 如果 strict=True 且路径不存在(例如,对于读取操作)
# print(f"安全/信息:路径不存在无法解析(严格模式):{candidate_path}")
return None # 或专门作为“文件未找到”处理
except Exception as e:
# 其他解析错误(例如,某些操作系统上的符号链接循环、无效字符)
# print(f"安全:路径 '{candidate_path}' 解析错误:{e}")
return None
# 最重要的安全检查:解析后的路径是否仍在沙盒内?
# 适用于 Python 3.9+
if hasattr(resolved_path, 'is_relative_to'):
if resolved_path.is_relative_to(SANDBOX_ROOT_DIR):
return resolved_path
else: # 旧版 Python 的回退方案
# 确保 resolved_path 以 SANDBOX_ROOT_DIR 字符串开头,
# 注意尾部斜杠以进行准确比较。
# 或者检查 SANDBOX_ROOT_DIR 是否是 resolved_path 的父目录之一。
if SANDBOX_ROOT_DIR == resolved_path or SANDBOX_ROOT_DIR in resolved_path.parents:
return resolved_path
# print(f"安全:路径遍历尝试。解析后的路径 '{resolved_path}' 在沙盒 '{SANDBOX_ROOT_DIR}' 外部。")
return None
class FileSystemTools:
MAX_FILE_SIZE = 10 * 1024 * 1024 # 示例:10MB 限制
def read_file(self, file_path: str, encoding: str = 'utf-8') -> dict:
"""从代理的沙盒中读取文件。"""
resolved_path = _get_sandboxed_path(file_path)
if not resolved_path:
return {"error": f"访问被拒绝或路径无效: {file_path}"}
if not resolved_path.is_file():
return {"error": f"文件未找到或不是常规文件: {file_path}"}
try:
# 在读取之前检查文件大小以防止大型文件导致内存问题
if resolved_path.stat().st_size > self.MAX_FILE_SIZE:
return {"error": f"文件超出允许的最大大小: {file_path}"}
content = resolved_path.read_text(encoding=encoding)
return {"content": content}
except Exception as e:
return {"error": f"无法读取文件 '{file_path}': {str(e)}"}
def write_file(self, file_path: str, content: str, mode: str = 'w') -> dict:
"""将内容写入代理沙盒中的文件。"""
resolved_path = _get_sandboxed_path(file_path)
if not resolved_path:
return {"error": f"访问被拒绝或写入路径无效: {file_path}"}
if mode not in ['w', 'a']:
return {"error": "无效的写入模式。请使用 'w'(覆盖)或 'a'(追加)。"}
# 在写入之前检查内容大小
if len(content.encode(errors='ignore')) > self.MAX_FILE_SIZE:
return {"error": "内容超出允许的最大文件大小。"}
try:
# 确保父目录存在
resolved_path.parent.mkdir(parents=True, exist_ok=True)
with open(resolved_path, mode, encoding='utf-8') as f:
f.write(content)
return {"status": f"文件 '{file_path}' {'写入' if mode == 'w' else '追加'}成功。"}
except Exception as e:
return {"error": f"无法写入文件 '{file_path}': {str(e)}"}
def list_directory(self, directory_path: str = ".") -> dict:
"""列出代理沙盒中目录的内容。"""
# 使用 "." 作为默认值意味着列出沙盒的根目录
resolved_path = _get_sandboxed_path(directory_path)
if not resolved_path:
return {"error": f"访问被拒绝或目录路径无效: {directory_path}"}
if not resolved_path.is_dir():
return {"error": f"路径不是目录或不存在: {directory_path}"}
try:
items = [item.name for item in resolved_path.iterdir()]
return {"items": items}
except Exception as e:
return {"error": f"无法列出目录 '{directory_path}': {str(e)}"}
在 _get_sandboxed_path 函数中,candidate_path.resolve(strict=False) 通常是合适的,因为对于 write_file 操作,文件本身可能尚不存在。strict=False 参数允许解析路径直到最后一个不存在的组件。随后使用 is_relative_to(或旧版 Python 的父目录检查)进行的检查是最终的守门员。在示例中添加安全事件的打印语句(已注释掉)有助于开发和调试,但在生产环境中应替换为适当的日志记录。
大型语言模型代理将通过指定工具名称及其参数来调用这些工具。例如,读取文件:
大型语言模型请求:{"tool_name": "read_file", "parameters": {"file_path": "project_notes/alpha_phase.md"}}
工具的响应应结构化且提供信息:
{"content": "alpha_phase.md 的内容..."}{"error": "文件未找到或不是常规文件:project_notes/alpha_phase.md"}{"error": "访问被拒绝或路径无效:../../sensitive_config.ini"}清晰、明确的错误消息有助于大型语言模型(和开发人员)理解结果。安全相关的错误应明确说明访问被拒绝,而不是仅仅说“文件未找到”,因为这可以指导大型语言模型后续的操作或提示澄清。
MAX_FILE_SIZE)、代理可创建的文件数量以及可能分配给代理沙盒的总磁盘空间实施限制。通过以安全为首要考虑来细致设计文件系统工具,您可以显著增强大型语言模型代理执行有用任务的能力,这些任务涉及数据持久化和与本地环境的交互。务必严格测试这些工具,特别是它们在各种极端情况下的安全强制机制。
这部分内容有帮助吗?
© 2026 ApX Machine Learning用心打造