머니페니 76e3220e77
All checks were successful
Deploy to Production / deploy (push) Successful in 3m10s
feat: 에이전트 기능 추가 (LLM 서비스, 에이전트 API, 테스트)
2026-05-06 20:56:45 +09:00

66 lines
2.0 KiB
Python

from __future__ import annotations
import asyncio
from functools import partial
from pathlib import Path
from typing import Any
from app.agents.tools.types import RegisteredTool, ToolResult
PROJECT_ROOT = Path(__file__).resolve().parents[5]
def _validate_path(raw_path: str) -> Path:
"""경로를 검증하고 절대 경로로 변환합니다.
프로젝트 루트 외부 접근 및 경로 순회 공격을 차단합니다.
"""
resolved = (PROJECT_ROOT / raw_path).resolve()
if not resolved.is_relative_to(PROJECT_ROOT):
raise ValueError(f"프로젝트 외부 경로 접근 불가: {raw_path}")
return resolved
def _read_sync(path: Path, offset: int, limit: int) -> str:
text = path.read_text(encoding="utf-8")
lines = text.splitlines(keepends=True)
selected = lines[offset : offset + limit]
return "".join(selected)
def create_read_file_tool() -> RegisteredTool:
"""파일 읽기 도구를 생성합니다."""
async def execute(params: dict[str, Any]) -> ToolResult:
raw_path: str = params["path"]
offset: int = params.get("offset", 0)
limit: int = params.get("limit", 2000)
try:
path = _validate_path(raw_path)
except ValueError as e:
return ToolResult(data=f"오류: {e}")
if not path.exists():
return ToolResult(data=f"파일 없음: {raw_path}")
if not path.is_file():
return ToolResult(data=f"파일이 아님: {raw_path}")
loop = asyncio.get_running_loop()
try:
content = await loop.run_in_executor(
None, partial(_read_sync, path, offset, limit)
)
except Exception as e:
return ToolResult(data=f"파일 읽기 실패: {e}")
return ToolResult(data=content)
return RegisteredTool(
name="read_file",
description="파일 내용을 읽습니다. 프로젝트 내 파일만 접근 가능합니다.",
compact_description="파일 읽기",
concurrency_safe=True,
execute=execute,
)