"""Orchestrator — 인터페이스 무관 작업 오케스트레이터. 사용자 입력을 받아 NLU 분류 → 모드 분기. Discord, API, CLI 등 어떤 인터페이스에서든 동일한 로직을 실행합니다. 도구 실행은 Gemini CLI agent가 SKILL.md를 참조하여 직접 수행합니다. """ import json import logging import re from core.gemini_caller import GeminiCaller, GeminiCallError logger = logging.getLogger("variet.orchestrator") class Orchestrator: """인터페이스-무관 오케스트레이터. 흐름: 1. 사용자 입력 + 히스토리 → NLU 통합 분류 2. mode에 따라 분기: - chat → 즉답 반환 - clarify → 질문 반환 - anime → AnimePipeline 직접 호출 - task → TaskPipeline 실행 (핸들러에서 처리) """ def __init__(self): pass async def classify( self, user_input: str, history: str = "", project_path: str = "", ) -> dict: """통합 프롬프트로 의도 분류. Returns: 분류 결과 dict: {mode, response?, action?, title?, ...} """ gemini = GeminiCaller(project_path) from core.docs_manager import DocsManager docs = DocsManager(project_path) if project_path else None docs_index = docs.get_docs_index() if docs else "" context = ( f"{history}" f"## Workspace\nPath: {project_path}\n\n" f"## Project Docs\n{docs_index}\n\n" f"## User Message\n{user_input}" ) raw = await gemini.call("unified", context, timeout=120) # JSON 추출 try: match = re.search(r'```json\s*\n(.*?)\n\s*```', raw, re.DOTALL) if match: return json.loads(match.group(1)) brace_depth = 0 start = -1 for i, ch in enumerate(raw): if ch == '{': if brace_depth == 0: start = i brace_depth += 1 elif ch == '}': brace_depth -= 1 if brace_depth == 0 and start >= 0: return json.loads(raw[start:i + 1]) except (json.JSONDecodeError, AttributeError): pass logger.warning(f"통합 프롬프트 JSON 파싱 실패: {raw[:100]}") return {"mode": "chat", "response": raw}