fix(anime): 파이프라인 5건 수정 — 에피소드 정규식(v2/S01E), 릴리스 그룹 필터, 자막 보호, 배치 다운로드, 타임아웃

This commit is contained in:
2026-03-15 08:27:08 +09:00
parent 63818999d9
commit 9f74812710
40 changed files with 2759 additions and 815 deletions

80
core/orchestrator.py Normal file
View File

@@ -0,0 +1,80 @@
"""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}