Files
gpu-services/nanoclaude/tools/registry.py
Hyungi Ahn 6e24da56a4 feat: 이드 도구 확장 — 캘린더/메일/문서 연동 (read-only + 캘린더 생성 확인)
- tools/calendar_tool.py: CalDAV search/today/create_draft/create_confirmed
- tools/email_tool.py: IMAP search/read (전송 비활성화)
- tools/document_tool.py: Document Server search/read (read-only)
- tools/registry.py: 도구 디스패처 + WRITE_OPS 안전장치 + 에러 표준화
- 분류기: "tools" 액션 추가, 도구 목록/파라미터 스키마/규칙 명시
- Worker: tools 분기 + tool timeout 10초 + payload 2000자 제한
- conversation: pending_draft (TTL 5분) + create 확인 플로우
- 현재 시간을 분류기에 전달 (날짜 질문 대응)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 13:39:15 +09:00

101 lines
3.6 KiB
Python

"""Tool Registry — 도구 실행 디스패처 + 안전장치."""
from __future__ import annotations
import logging
from tools import calendar_tool, document_tool, email_tool
logger = logging.getLogger(__name__)
# 에러 메시지 표준화 (내부 에러 노출 안 함)
ERROR_MESSAGES = {
"calendar": "⚠️ 캘린더 서비스를 사용할 수 없습니다. 잠시 후 다시 시도해주세요.",
"email": "⚠️ 메일 서비스를 사용할 수 없습니다. 잠시 후 다시 시도해주세요.",
"document": "⚠️ 문서 서비스를 사용할 수 없습니다. 잠시 후 다시 시도해주세요.",
}
# 허용된 operations
ALLOWED_OPS = {
"calendar": {"today", "search", "create_draft", "create_confirmed"},
"email": {"search", "read"},
"document": {"search", "read"},
}
# payload hard limit
MAX_TOOL_PAYLOAD = 2000
async def execute_tool(tool_name: str, operation: str, params: dict) -> dict:
"""도구 실행 디스패처."""
# 도구 존재 확인
if tool_name not in ALLOWED_OPS:
return _error(tool_name, operation, f"알 수 없는 도구: {tool_name}")
# operation 허용 확인
if operation not in ALLOWED_OPS[tool_name]:
return _error(tool_name, operation, f"허용되지 않은 작업: {tool_name}.{operation}")
try:
if tool_name == "calendar":
result = await _exec_calendar(operation, params)
elif tool_name == "email":
result = await _exec_email(operation, params)
elif tool_name == "document":
result = await _exec_document(operation, params)
else:
result = _error(tool_name, operation, "미구현")
if not result["ok"]:
logger.warning("Tool %s.%s failed: %s", tool_name, operation, result.get("error"))
result["error"] = ERROR_MESSAGES.get(tool_name, "⚠️ 서비스를 사용할 수 없습니다.")
return result
except Exception:
logger.exception("Tool %s.%s exception", tool_name, operation)
return _error(tool_name, operation, ERROR_MESSAGES.get(tool_name, "⚠️ 서비스 오류"))
async def _exec_calendar(operation: str, params: dict) -> dict:
if operation == "today":
return await calendar_tool.today()
elif operation == "search":
return await calendar_tool.search(
params.get("date_from", ""),
params.get("date_to", ""),
)
elif operation == "create_draft":
return await calendar_tool.create_draft(
params.get("title", ""),
params.get("date", ""),
params.get("time", "00:00"),
params.get("description", ""),
)
elif operation == "create_confirmed":
return await calendar_tool.create_confirmed(params)
return _error("calendar", operation, "미구현")
async def _exec_email(operation: str, params: dict) -> dict:
if operation == "search":
return await email_tool.search(
params.get("query", ""),
params.get("days", 7),
)
elif operation == "read":
return await email_tool.read(params.get("uid", ""))
return _error("email", operation, "미구현")
async def _exec_document(operation: str, params: dict) -> dict:
if operation == "search":
return await document_tool.search(params.get("query", ""))
elif operation == "read":
return await document_tool.read(params.get("doc_id", ""))
return _error("document", operation, "미구현")
def _error(tool: str, operation: str, msg: str) -> dict:
return {"ok": False, "tool": tool, "operation": operation, "data": [], "summary": "", "error": msg}