Files
variet-agent/api/discord_bot.py

182 lines
5.7 KiB
Python

"""Discord Bot 어댑터.
사용자 명령을 받아 FastAPI API를 호출하고 결과를 디스코드로 보고합니다.
"""
import asyncio
import logging
import discord
from discord.ext import commands
import config
logger = logging.getLogger("variet.discord")
# Bot 설정
intents = discord.Intents.default()
intents.message_content = True # MESSAGE CONTENT INTENT 필요
bot = commands.Bot(
command_prefix=config.DISCORD_COMMAND_PREFIX,
intents=intents,
help_command=commands.DefaultHelpCommand(no_category="명령어"),
)
# In-memory: Discord 채널 ↔ Task 매핑
_channel_tasks: dict[int, list[str]] = {}
@bot.event
async def on_ready():
"""봇 접속 완료."""
logger.info(f"Discord Bot 접속 완료: {bot.user} (ID: {bot.user.id})")
logger.info(f"서버 {len(bot.guilds)}개 연결됨")
await bot.change_presence(
activity=discord.Activity(
type=discord.ActivityType.listening,
name=f"{config.DISCORD_COMMAND_PREFIX}agent",
)
)
@bot.command(name="agent", help="AI Agent에게 작업 요청\n예: !agent README에 설치 방법 추가해줘")
async def agent_command(ctx: commands.Context, *, request: str):
"""작업 요청 → Pipeline 실행 → 결과 보고."""
# 1. 접수 메시지
embed = discord.Embed(
title="📋 작업 접수",
description=f"```{request[:200]}```",
color=0x3498DB,
)
embed.set_footer(text="분석 중...")
status_msg = await ctx.send(embed=embed)
try:
# 2. Pipeline 직접 실행 (같은 프로세스)
from core.task_pipeline import TaskPipeline
import uuid
task_id = uuid.uuid4().hex[:8]
# Planning
embed.color = 0xF39C12
embed.set_footer(text=f"🔍 작업 분해 중... (ID: {task_id})")
await status_msg.edit(embed=embed)
pipeline = TaskPipeline(
project_path=str(config.PROJECT_ROOT),
)
pipeline.setup()
# Plan 단계
plan = await pipeline.plan(request)
tasks = plan.get("tasks", [])
plan_text = plan.get("summary", str(plan))[:500]
plan_embed = discord.Embed(
title="📝 작업 계획",
description=f"```{plan_text}```",
color=0x2ECC71,
)
if tasks:
task_list = "\n".join(
f"{t.get('title', t.get('description', '?'))}"
for t in tasks[:10]
)
plan_embed.add_field(
name=f"태스크 ({len(tasks)}개)",
value=task_list[:1000],
inline=False,
)
plan_embed.set_footer(text=f"ID: {task_id}")
await ctx.send(embed=plan_embed)
# Code + Review 단계
if tasks:
for i, task in enumerate(tasks, 1):
progress_embed = discord.Embed(
title=f"⚙️ 실행 중 ({i}/{len(tasks)})",
description=task.get("title", task.get("description", ""))[:200],
color=0xE67E22,
)
await ctx.send(embed=progress_embed)
code_output = await pipeline.code(task)
review = await pipeline.review(task, code_output)
passed = review.get("passed", True)
review_emoji = "" if passed else "⚠️"
review_embed = discord.Embed(
title=f"{review_emoji} 리뷰 결과 ({i}/{len(tasks)})",
description=review.get("summary", str(review))[:500],
color=0x2ECC71 if passed else 0xE74C3C,
)
await ctx.send(embed=review_embed)
# 완료
done_embed = discord.Embed(
title="✅ 작업 완료",
description=f"{len(tasks)}개 태스크 처리 완료",
color=0x2ECC71,
)
done_embed.set_footer(text=f"ID: {task_id}")
await ctx.send(embed=done_embed)
# 채널 태스크 기록
_channel_tasks.setdefault(ctx.channel.id, []).append(task_id)
except Exception as e:
logger.error(f"작업 실행 오류: {e}", exc_info=True)
error_embed = discord.Embed(
title="❌ 오류 발생",
description=f"```{str(e)[:500]}```",
color=0xE74C3C,
)
await ctx.send(embed=error_embed)
@bot.command(name="ping", help="봇 응답 테스트")
async def ping_command(ctx: commands.Context):
"""연결 상태 확인."""
latency = round(bot.latency * 1000)
await ctx.send(f"🏓 Pong! ({latency}ms)")
@bot.command(name="info", help="시스템 정보")
async def info_command(ctx: commands.Context):
"""시스템 정보 표시."""
embed = discord.Embed(
title="🤖 Variet Agent",
description="AI Agent Team — Gemini CLI 기반 자동화 개발 에이전트",
color=0x9B59B6,
)
embed.add_field(name="프로젝트", value=str(config.PROJECT_ROOT), inline=False)
embed.add_field(name="명령어 접두사", value=config.DISCORD_COMMAND_PREFIX, inline=True)
embed.add_field(name="서버 수", value=str(len(bot.guilds)), inline=True)
await ctx.send(embed=embed)
async def start_bot():
"""Discord Bot 시작 (async)."""
token = config.DISCORD_BOT_TOKEN
if not token:
logger.error("DISCORD_BOT_TOKEN이 설정되지 않았습니다. .env 파일을 확인하세요.")
return
logger.info("Discord Bot 시작 중...")
try:
await bot.start(token)
except discord.LoginFailure:
logger.error("Discord 로그인 실패 — 토큰을 확인하세요.")
except Exception as e:
logger.error(f"Discord Bot 오류: {e}")
async def stop_bot():
"""Discord Bot 정지."""
if not bot.is_closed():
await bot.close()