diff --git a/api/discord_bot.py b/api/discord_bot.py index 05822c9..d1502e6 100644 --- a/api/discord_bot.py +++ b/api/discord_bot.py @@ -155,6 +155,31 @@ async def _agent_call(text: str, history: str, project_path: str) -> str: return response +def _parse_unified_response(raw: str) -> dict: + """Gemini unified prompt 응답에서 JSON 추출.""" + import re as _re + + # 1) ```json ... ``` 블록 + m = _re.search(r"```json\s*\n(.+?)```", raw, _re.DOTALL) + if m: + try: + return json.loads(m.group(1)) + except json.JSONDecodeError: + pass + + # 2) { ... } 직접 + m = _re.search(r"\{[\s\S]*\"mode\"[\s\S]*?\}", raw) + if m: + try: + return json.loads(m.group(0)) + except json.JSONDecodeError: + pass + + # 3) 파싱 실패 → chat 모드 폴백 + logger.warning(f"unified 응답 JSON 파싱 실패: {raw[:200]}") + return {"mode": "chat", "response": raw} + + # ────────────────────────────────────────────── # 이벤트 핸들러 # ────────────────────────────────────────────── @@ -330,30 +355,32 @@ async def on_message(message: discord.Message): await message.reply("실행 중인 작업이 없습니다.") return - # 에이전트 호출 (CLI 도구 자율 실행) + # 통합 분류 → 라우팅 (unified prompt → NC handler / chat / agent) channel_id = message.channel.id if channel_id in _running_tasks and not _running_tasks[channel_id].done(): await message.reply("⚠️ 이미 작업이 실행 중입니다. `취소` 후 다시 요청하세요.") return - async def _tracked_agent(): + async def _classify_and_route(): progress_msg = None try: - # 진행 표시 progress_msg = await message.channel.send( embed=discord.Embed( - title="🤖 에이전트 처리 중...", + title="🤖 처리 중...", description=f"```{user_text[:200]}```", color=0xF39C12, ) ) async with message.channel.typing(): - # 일반 → Gemini agent 모드 (도구 자동 호출) + # 1단계: unified prompt로 분류 + gemini = GeminiCaller() history = await _get_channel_history(message.channel, limit=10) - response = await _agent_call(user_text, history, ws.path) + classify_input = f"{history}## User Message\n{user_text}" + raw = await gemini.call("unified", classify_input, timeout=60) - logger.info(f"에이전트 응답: \"{user_text[:50]}\" -> {len(response)}자") + # JSON 파싱 + parsed = _parse_unified_response(raw) # 진행 메시지 삭제 if progress_msg: @@ -362,18 +389,59 @@ async def on_message(message: discord.Message): except Exception: pass - if not response: - await message.reply("응답을 생성하지 못했습니다.") - return + mode = parsed.get("mode", "chat") + logger.info(f"분류 결과: mode={mode} — \"{user_text[:50]}\"") + + # ── 라우팅 ── + + if mode == "nextcloud": + # NC 핸들러로 직접 라우팅 + await _nc_handler.handle(parsed, message.channel) + + elif mode == "chat": + # 즉시 응답 + response = parsed.get("response", "") + if response: + if len(response) <= 2000: + await message.reply(response) + else: + for i in range(0, len(response), 4000): + embed = discord.Embed(description=response[i:i + 4000], color=0x3498DB) + await message.channel.send(embed=embed) + else: + await message.reply("응답을 생성하지 못했습니다.") + + elif mode == "clarify": + question = parsed.get("question", "무엇을 도와드릴까요?") + await message.reply( + embed=discord.Embed( + title="🤔 확인이 필요합니다", + description=question, + color=0xF39C12, + ) + ) + + elif mode == "anime": + # 기존 anime 핸들러 호출 + from handlers.anime_handler import handle_anime_action + await handle_anime_action(parsed, message.channel) + + elif mode == "task": + # 에이전트 모드 (파일 작업 필요) + async with message.channel.typing(): + response = await _agent_call(user_text, history, ws.path) + if response: + if len(response) <= 2000: + await message.reply(response) + else: + for i in range(0, len(response), 4000): + embed = discord.Embed(description=response[i:i + 4000], color=0x3498DB) + await message.channel.send(embed=embed) + else: + await message.reply("응답을 생성하지 못했습니다.") - # 응답 전송 - if len(response) <= 2000: - await message.reply(response) else: - for i in range(0, len(response), 4000): - chunk = response[i:i + 4000] - embed = discord.Embed(description=chunk, color=0x3498DB) - await message.channel.send(embed=embed) + await message.reply(f"알 수 없는 모드: `{mode}`") except asyncio.CancelledError: await message.channel.send( @@ -386,12 +454,12 @@ async def on_message(message: discord.Message): except GeminiCallError as e: await message.reply(f"⚠️ AI 호출 오류: {str(e)[:200]}") except Exception as e: - logger.error(f"에이전트 호출 오류: {e}", exc_info=True) + logger.error(f"분류/라우팅 오류: {e}", exc_info=True) await message.reply(f"❌ 오류: {str(e)[:200]}") finally: _running_tasks.pop(channel_id, None) - task = asyncio.create_task(_tracked_agent()) + task = asyncio.create_task(_classify_and_route()) _running_tasks[channel_id] = task