fix(bot): unified prompt 분류 -> 라우팅 흐름 구현
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user