fix(bot): unified prompt 분류 -> 라우팅 흐름 구현

This commit is contained in:
2026-03-18 18:01:46 +09:00
parent d22493125c
commit 56787b1e4f

View File

@@ -155,6 +155,31 @@ async def _agent_call(text: str, history: str, project_path: str) -> str:
return response 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("실행 중인 작업이 없습니다.") await message.reply("실행 중인 작업이 없습니다.")
return return
# 에이전트 호출 (CLI 도구 자율 실행) # 통합 분류 → 라우팅 (unified prompt → NC handler / chat / agent)
channel_id = message.channel.id channel_id = message.channel.id
if channel_id in _running_tasks and not _running_tasks[channel_id].done(): if channel_id in _running_tasks and not _running_tasks[channel_id].done():
await message.reply("⚠️ 이미 작업이 실행 중입니다. `취소` 후 다시 요청하세요.") await message.reply("⚠️ 이미 작업이 실행 중입니다. `취소` 후 다시 요청하세요.")
return return
async def _tracked_agent(): async def _classify_and_route():
progress_msg = None progress_msg = None
try: try:
# 진행 표시
progress_msg = await message.channel.send( progress_msg = await message.channel.send(
embed=discord.Embed( embed=discord.Embed(
title="🤖 에이전트 처리 중...", title="🤖 처리 중...",
description=f"```{user_text[:200]}```", description=f"```{user_text[:200]}```",
color=0xF39C12, color=0xF39C12,
) )
) )
async with message.channel.typing(): async with message.channel.typing():
# 일반 → Gemini agent 모드 (도구 자동 호출) # 1단계: unified prompt로 분류
gemini = GeminiCaller()
history = await _get_channel_history(message.channel, limit=10) 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: if progress_msg:
@@ -362,18 +389,59 @@ async def on_message(message: discord.Message):
except Exception: except Exception:
pass pass
if not response: mode = parsed.get("mode", "chat")
await message.reply("응답을 생성하지 못했습니다.") logger.info(f"분류 결과: mode={mode}\"{user_text[:50]}\"")
return
# 응답 전송 # ── 라우팅 ──
if mode == "nextcloud":
# NC 핸들러로 직접 라우팅
await _nc_handler.handle(parsed, message.channel)
elif mode == "chat":
# 즉시 응답
response = parsed.get("response", "")
if response:
if len(response) <= 2000: if len(response) <= 2000:
await message.reply(response) await message.reply(response)
else: else:
for i in range(0, len(response), 4000): for i in range(0, len(response), 4000):
chunk = response[i:i + 4000] embed = discord.Embed(description=response[i:i + 4000], color=0x3498DB)
embed = discord.Embed(description=chunk, color=0x3498DB)
await message.channel.send(embed=embed) 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("응답을 생성하지 못했습니다.")
else:
await message.reply(f"알 수 없는 모드: `{mode}`")
except asyncio.CancelledError: except asyncio.CancelledError:
await message.channel.send( await message.channel.send(
@@ -386,12 +454,12 @@ async def on_message(message: discord.Message):
except GeminiCallError as e: except GeminiCallError as e:
await message.reply(f"⚠️ AI 호출 오류: {str(e)[:200]}") await message.reply(f"⚠️ AI 호출 오류: {str(e)[:200]}")
except Exception as e: 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]}") await message.reply(f"❌ 오류: {str(e)[:200]}")
finally: finally:
_running_tasks.pop(channel_id, None) _running_tasks.pop(channel_id, None)
task = asyncio.create_task(_tracked_agent()) task = asyncio.create_task(_classify_and_route())
_running_tasks[channel_id] = task _running_tasks[channel_id] = task