- 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>
101 lines
3.6 KiB
Python
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}
|