81 lines
2.4 KiB
Python
81 lines
2.4 KiB
Python
"""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}
|