2026-02-28 08:21:35 +00:00
|
|
|
|
# ════════════════════════════════════════════════════════════════
|
2026-03-09 05:37:29 +00:00
|
|
|
|
# tools/file_reader.py
|
2026-02-28 08:21:35 +00:00
|
|
|
|
# ════════════════════════════════════════════════════════════════
|
2026-03-09 05:37:29 +00:00
|
|
|
|
"""文件读取工具(从配置读取 allowed_root / max_file_size_kb)"""
|
2026-02-28 08:21:35 +00:00
|
|
|
|
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
from tools.base_tool import BaseTool, ToolResult
|
2026-03-09 05:37:29 +00:00
|
|
|
|
from config.settings import settings
|
2026-02-28 08:21:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FileReaderTool(BaseTool):
|
|
|
|
|
|
name = "file_reader"
|
2026-03-09 05:37:29 +00:00
|
|
|
|
description = "读取本地文件内容,仅限配置的 allowed_root 目录"
|
2026-02-28 08:21:35 +00:00
|
|
|
|
parameters = {
|
2026-03-09 05:37:29 +00:00
|
|
|
|
"path": {"type": "string", "description": "文件路径(相对于 allowed_root)"},
|
|
|
|
|
|
"encoding": {"type": "string", "description": "文件编码,默认 utf-8"},
|
2026-02-28 08:21:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 05:37:29 +00:00
|
|
|
|
def __init__(self):
|
|
|
|
|
|
super().__init__()
|
|
|
|
|
|
cfg = settings.tools.file_reader
|
|
|
|
|
|
self._allowed_root = Path(cfg.allowed_root)
|
|
|
|
|
|
self._max_size_kb = cfg.max_file_size_kb
|
|
|
|
|
|
self.logger.debug(
|
|
|
|
|
|
f"⚙️ FileReader root={self._allowed_root}, "
|
|
|
|
|
|
f"max_size={self._max_size_kb}KB"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-02-28 08:21:35 +00:00
|
|
|
|
def execute(self, path: str, encoding: str = "utf-8", **_) -> ToolResult:
|
2026-03-09 05:37:29 +00:00
|
|
|
|
self._allowed_root.mkdir(parents=True, exist_ok=True)
|
2026-02-28 08:21:35 +00:00
|
|
|
|
|
2026-03-09 05:37:29 +00:00
|
|
|
|
target = (self._allowed_root / path).resolve()
|
|
|
|
|
|
if not str(target).startswith(str(self._allowed_root.resolve())):
|
|
|
|
|
|
return ToolResult(success=False, output="❌ 拒绝访问: 路径超出允许范围")
|
2026-02-28 08:21:35 +00:00
|
|
|
|
|
|
|
|
|
|
if not target.exists():
|
|
|
|
|
|
self._create_demo_file(target)
|
|
|
|
|
|
|
2026-03-09 05:37:29 +00:00
|
|
|
|
size_kb = target.stat().st_size / 1024
|
|
|
|
|
|
if size_kb > self._max_size_kb:
|
|
|
|
|
|
return ToolResult(
|
|
|
|
|
|
success=False,
|
|
|
|
|
|
output=f"❌ 文件过大: {size_kb:.1f}KB > 限制 {self._max_size_kb}KB",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-02-28 08:21:35 +00:00
|
|
|
|
try:
|
|
|
|
|
|
content = target.read_text(encoding=encoding)
|
|
|
|
|
|
return ToolResult(
|
|
|
|
|
|
success=True,
|
2026-03-09 05:37:29 +00:00
|
|
|
|
output=f"文件 [{path}] ({size_kb:.1f}KB):\n{content}",
|
|
|
|
|
|
metadata={"path": str(target), "size_kb": size_kb},
|
2026-02-28 08:21:35 +00:00
|
|
|
|
)
|
|
|
|
|
|
except OSError as exc:
|
|
|
|
|
|
return ToolResult(success=False, output=f"读取失败: {exc}")
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def _create_demo_file(path: Path) -> None:
|
|
|
|
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
path.write_text(
|
|
|
|
|
|
'{\n "app": "AgentDemo",\n "version": "1.0.0",\n'
|
2026-03-09 05:37:29 +00:00
|
|
|
|
' "llm": "claude-sonnet-4-6",\n "tools": ["calculator","web_search"]\n}\n',
|
2026-02-28 08:21:35 +00:00
|
|
|
|
encoding="utf-8",
|
2026-03-09 05:37:29 +00:00
|
|
|
|
)
|