feat(debate): 파일 기반 토론 흐름 — AG가 response.md에 작성, 봇이 읽고 비우기
This commit is contained in:
@@ -1,13 +1,18 @@
|
|||||||
"""Debate Room Handler — AG 인스턴스 간 토론 중재.
|
"""Debate Room Handler — AG 인스턴스 간 토론 중재.
|
||||||
|
|
||||||
#variet-debate 채널에서 !debate-start로 시작.
|
흐름:
|
||||||
사회자(이 봇)가 #ag-debate_gemini와 #ag-debate_opus 사이에서 턴을 관리.
|
1. AG가 로컬 response.md에 전문 작성
|
||||||
|
2. Discord에 "작성 완료" 시그널
|
||||||
|
3. 사회자(봇)가 response.md 읽기 → NC transcript에 append → response.md 비우기
|
||||||
|
4. Discord #debate에 요약 게시
|
||||||
|
5. 상대 AG 채널에 지시 전달
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
@@ -21,30 +26,30 @@ DEBATE_AGENTS = {
|
|||||||
"opus": 1484156521209401476, # #ag-debate_opus
|
"opus": 1484156521209401476, # #ag-debate_opus
|
||||||
}
|
}
|
||||||
|
|
||||||
# Embed 색상
|
# ── 로컬 프로젝트 경로 ──
|
||||||
AGENT_COLORS = {
|
AGENT_PATHS = {
|
||||||
"gemini": 0x2ECC71, # 초록
|
"gemini": Path(r"C:\Users\Variet-Worker\Desktop\debate_gemini"),
|
||||||
"opus": 0x3498DB, # 파랑
|
"opus": Path(r"C:\Users\Variet-Worker\Desktop\debate_opus"),
|
||||||
}
|
|
||||||
AGENT_EMOJI = {
|
|
||||||
"gemini": "🟢",
|
|
||||||
"opus": "🔵",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Embed 색상 / 이모지
|
||||||
|
AGENT_COLORS = {"gemini": 0x2ECC71, "opus": 0x3498DB}
|
||||||
|
AGENT_EMOJI = {"gemini": "🟢", "opus": "🔵"}
|
||||||
|
|
||||||
MAX_ROUNDS = 5
|
MAX_ROUNDS = 5
|
||||||
RESPONSE_TIMEOUT = 180 # AG 응답 대기 (초)
|
RESPONSE_TIMEOUT = 300 # 긴 답변 대기 (5분)
|
||||||
|
FILE_POLL_INTERVAL = 5 # 파일 확인 간격 (초)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DebateSession:
|
class DebateSession:
|
||||||
topic: str = ""
|
topic: str = ""
|
||||||
|
topic_short: str = "" # 파일명용 약어
|
||||||
round: int = 0
|
round: int = 0
|
||||||
max_rounds: int = MAX_ROUNDS
|
max_rounds: int = MAX_ROUNDS
|
||||||
active: bool = False
|
active: bool = False
|
||||||
current_speaker: str = ""
|
current_speaker: str = ""
|
||||||
history: list = field(default_factory=list)
|
history: list = field(default_factory=list)
|
||||||
_response_event: Optional[asyncio.Event] = field(default=None, repr=False)
|
|
||||||
_last_response: str = ""
|
|
||||||
|
|
||||||
|
|
||||||
class DebateHandler:
|
class DebateHandler:
|
||||||
@@ -60,23 +65,37 @@ class DebateHandler:
|
|||||||
await ctx.reply("⚠️ 이미 진행 중인 토론이 있습니다. `!debate-stop`으로 먼저 종료하세요.")
|
await ctx.reply("⚠️ 이미 진행 중인 토론이 있습니다. `!debate-stop`으로 먼저 종료하세요.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# 약어 생성 (첫 10자, 공백은 _)
|
||||||
|
short = topic[:10].replace(" ", "_").replace("/", "_")
|
||||||
|
|
||||||
self.session = DebateSession(
|
self.session = DebateSession(
|
||||||
topic=topic,
|
topic=topic,
|
||||||
|
topic_short=short,
|
||||||
active=True,
|
active=True,
|
||||||
max_rounds=MAX_ROUNDS,
|
max_rounds=MAX_ROUNDS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 각 AG 프로젝트에 response.md 초기화
|
||||||
|
for name, path in AGENT_PATHS.items():
|
||||||
|
resp_file = path / "response.md"
|
||||||
|
resp_file.write_text("", encoding="utf-8")
|
||||||
|
|
||||||
# 시작 알림
|
# 시작 알림
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title="🏛️ Debate Room 시작",
|
title="🏛️ Debate Room 시작",
|
||||||
description=f"**주제**: {topic}\n**최대 라운드**: {MAX_ROUNDS}\n**참여자**: 🟢 Gemini · 🔵 Opus",
|
description=(
|
||||||
|
f"**주제**: {topic}\n"
|
||||||
|
f"**최대 라운드**: {MAX_ROUNDS}\n"
|
||||||
|
f"**참여자**: 🟢 Gemini · 🔵 Opus\n\n"
|
||||||
|
f"AG는 `response.md`에 답변을 작성합니다."
|
||||||
|
),
|
||||||
color=0x9B59B6,
|
color=0x9B59B6,
|
||||||
)
|
)
|
||||||
control_ch = self.bot.get_channel(DEBATE_CONTROL_CH)
|
control_ch = self.bot.get_channel(DEBATE_CONTROL_CH)
|
||||||
if control_ch:
|
if control_ch:
|
||||||
await control_ch.send(embed=embed)
|
await control_ch.send(embed=embed)
|
||||||
|
|
||||||
# 첫 턴 시작
|
# 첫 턴
|
||||||
await self._next_turn()
|
await self._next_turn()
|
||||||
|
|
||||||
async def stop(self, ctx):
|
async def stop(self, ctx):
|
||||||
@@ -84,7 +103,6 @@ class DebateHandler:
|
|||||||
if not self.session.active:
|
if not self.session.active:
|
||||||
await ctx.reply("진행 중인 토론이 없습니다.")
|
await ctx.reply("진행 중인 토론이 없습니다.")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.session.active = False
|
self.session.active = False
|
||||||
await self._post_conclusion(ctx.channel)
|
await self._post_conclusion(ctx.channel)
|
||||||
|
|
||||||
@@ -94,72 +112,65 @@ class DebateHandler:
|
|||||||
await ctx.reply("진행 중인 토론이 없습니다.")
|
await ctx.reply("진행 중인 토론이 없습니다.")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.session.history.append({
|
self.session.history.append({"speaker": "user", "content": opinion})
|
||||||
"speaker": "user",
|
|
||||||
"content": opinion,
|
|
||||||
})
|
|
||||||
|
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title="👤 사용자 의견",
|
title="👤 사용자 의견", description=opinion, color=0xF39C12,
|
||||||
description=opinion,
|
|
||||||
color=0xF39C12,
|
|
||||||
)
|
)
|
||||||
control_ch = self.bot.get_channel(DEBATE_CONTROL_CH)
|
control_ch = self.bot.get_channel(DEBATE_CONTROL_CH)
|
||||||
if control_ch:
|
if control_ch:
|
||||||
await control_ch.send(embed=embed)
|
await control_ch.send(embed=embed)
|
||||||
|
|
||||||
# 다음 턴에 반영
|
|
||||||
await self._next_turn(user_injected=opinion)
|
await self._next_turn(user_injected=opinion)
|
||||||
|
|
||||||
async def on_agent_message(self, message: discord.Message):
|
async def on_agent_message(self, message: discord.Message):
|
||||||
"""AG 채널에서 응답 수신 시 호출."""
|
"""AG 채널에서 메시지 수신 — response.md 파일 완성 시그널로 사용."""
|
||||||
if not self.session.active:
|
if not self.session.active:
|
||||||
return
|
return
|
||||||
if not self.session._response_event:
|
|
||||||
return
|
|
||||||
|
|
||||||
# 어떤 에이전트가 응답했는지 확인
|
# 어떤 에이전트인지 확인
|
||||||
agent_name = None
|
agent_name = None
|
||||||
for name, ch_id in DEBATE_AGENTS.items():
|
for name, ch_id in DEBATE_AGENTS.items():
|
||||||
if message.channel.id == ch_id:
|
if message.channel.id == ch_id:
|
||||||
agent_name = name
|
agent_name = name
|
||||||
break
|
break
|
||||||
|
if not agent_name or agent_name != self.session.current_speaker:
|
||||||
if not agent_name:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# 현재 대기 중인 에이전트의 응답만 처리
|
# response.md에 내용이 있는지 확인
|
||||||
if agent_name != self.session.current_speaker:
|
resp_file = AGENT_PATHS[agent_name] / "response.md"
|
||||||
return
|
if resp_file.exists():
|
||||||
|
content = resp_file.read_text(encoding="utf-8").strip()
|
||||||
# 응답 저장 (여러 메시지 합치기 - 마지막 메시지로)
|
if content:
|
||||||
self.session._last_response = message.content
|
await self._process_response(agent_name, content)
|
||||||
self.session._response_event.set()
|
|
||||||
|
|
||||||
# ── 내부 로직 ──
|
# ── 내부 로직 ──
|
||||||
|
|
||||||
async def _next_turn(self, user_injected: str = ""):
|
async def _next_turn(self, user_injected: str = ""):
|
||||||
"""다음 턴 실행."""
|
"""다음 턴."""
|
||||||
if not self.session.active:
|
if not self.session.active:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.session.round += 1
|
self.session.round += 1
|
||||||
|
|
||||||
if self.session.round > self.session.max_rounds:
|
if self.session.round > self.session.max_rounds:
|
||||||
control_ch = self.bot.get_channel(DEBATE_CONTROL_CH)
|
control_ch = self.bot.get_channel(DEBATE_CONTROL_CH)
|
||||||
if control_ch:
|
if control_ch:
|
||||||
await control_ch.send("⏹️ 최대 라운드 도달. 토론을 종료합니다.")
|
await control_ch.send("⏹️ 최대 라운드 도달.")
|
||||||
await self._post_conclusion(control_ch)
|
await self._post_conclusion(control_ch)
|
||||||
return
|
return
|
||||||
|
|
||||||
# 발언자 선택 (랜덤, 단 직전과 다른 에이전트)
|
# 발언자 선택 (직전과 다른 에이전트)
|
||||||
agents = list(DEBATE_AGENTS.keys())
|
agents = list(DEBATE_AGENTS.keys())
|
||||||
if self.session.current_speaker:
|
if self.session.current_speaker:
|
||||||
agents.remove(self.session.current_speaker)
|
agents.remove(self.session.current_speaker)
|
||||||
speaker = random.choice(agents)
|
speaker = random.choice(agents)
|
||||||
self.session.current_speaker = speaker
|
self.session.current_speaker = speaker
|
||||||
|
|
||||||
# 프롬프트 구성
|
# response.md 비우기
|
||||||
|
resp_file = AGENT_PATHS[speaker] / "response.md"
|
||||||
|
resp_file.write_text("", encoding="utf-8")
|
||||||
|
|
||||||
|
# 사회자(Flash)가 프롬프트 생성
|
||||||
prompt = await self._build_prompt(speaker, user_injected)
|
prompt = await self._build_prompt(speaker, user_injected)
|
||||||
|
|
||||||
# 진행 상태 표시
|
# 진행 상태 표시
|
||||||
@@ -167,158 +178,134 @@ class DebateHandler:
|
|||||||
if control_ch:
|
if control_ch:
|
||||||
await control_ch.send(
|
await control_ch.send(
|
||||||
f"{AGENT_EMOJI[speaker]} **Round {self.session.round}** — "
|
f"{AGENT_EMOJI[speaker]} **Round {self.session.round}** — "
|
||||||
f"`{speaker}` 발언 대기 중..."
|
f"`{speaker}` 답변 대기 중...\n"
|
||||||
|
f"📝 `response.md`에 작성 후 Discord에 알려주세요."
|
||||||
)
|
)
|
||||||
|
|
||||||
# AG 채널에 프롬프트 전송
|
# AG 채널에 프롬프트 전송 (분할)
|
||||||
agent_ch = self.bot.get_channel(DEBATE_AGENTS[speaker])
|
agent_ch = self.bot.get_channel(DEBATE_AGENTS[speaker])
|
||||||
if not agent_ch:
|
if not agent_ch:
|
||||||
logger.error(f"토론 채널 접근 불가: {speaker}")
|
logger.error(f"토론 채널 접근 불가: {speaker}")
|
||||||
return
|
return
|
||||||
|
|
||||||
await agent_ch.send(prompt)
|
# 2000자씩 분할 전송
|
||||||
|
for i in range(0, len(prompt), 1900):
|
||||||
|
await agent_ch.send(prompt[i:i+1900])
|
||||||
|
|
||||||
# 응답 대기
|
async def _process_response(self, speaker: str, content: str):
|
||||||
self.session._response_event = asyncio.Event()
|
"""AG 응답 처리 — 기록 + 게시 + 파일 비우기."""
|
||||||
self.session._last_response = ""
|
# 히스토리에 추가
|
||||||
|
|
||||||
try:
|
|
||||||
# 첫 응답 후 추가 메시지가 있을 수 있으므로 약간의 딜레이
|
|
||||||
await asyncio.wait_for(
|
|
||||||
self.session._response_event.wait(),
|
|
||||||
timeout=RESPONSE_TIMEOUT,
|
|
||||||
)
|
|
||||||
# AG가 여러 메시지를 보낼 수 있으므로 잠시 대기
|
|
||||||
await asyncio.sleep(5)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
if control_ch:
|
|
||||||
await control_ch.send(f"⏰ `{speaker}` 응답 시간 초과 ({RESPONSE_TIMEOUT}초)")
|
|
||||||
return
|
|
||||||
|
|
||||||
response = self.session._last_response
|
|
||||||
if not response:
|
|
||||||
return
|
|
||||||
|
|
||||||
# 응답 기록
|
|
||||||
self.session.history.append({
|
self.session.history.append({
|
||||||
"speaker": speaker,
|
"speaker": speaker,
|
||||||
"content": response,
|
"content": content,
|
||||||
})
|
})
|
||||||
|
|
||||||
# #variet-debate에 게시
|
# response.md 비우기
|
||||||
|
resp_file = AGENT_PATHS[speaker] / "response.md"
|
||||||
|
resp_file.write_text("", encoding="utf-8")
|
||||||
|
|
||||||
|
# NC에 transcript 저장 (TODO: nc_client 연동)
|
||||||
|
# await self._append_to_nc_transcript(speaker, content)
|
||||||
|
|
||||||
|
# #variet-debate에 요약 게시
|
||||||
|
control_ch = self.bot.get_channel(DEBATE_CONTROL_CH)
|
||||||
if control_ch:
|
if control_ch:
|
||||||
|
# 요약 (첫 500자)
|
||||||
|
summary = content[:500]
|
||||||
|
if len(content) > 500:
|
||||||
|
summary += f"\n\n... (전문 {len(content)}자)"
|
||||||
|
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title=f"{AGENT_EMOJI[speaker]} Round {self.session.round} — {speaker}",
|
title=f"{AGENT_EMOJI[speaker]} Round {self.session.round} — {speaker}",
|
||||||
description=response[:4000],
|
description=summary,
|
||||||
color=AGENT_COLORS[speaker],
|
color=AGENT_COLORS[speaker],
|
||||||
)
|
)
|
||||||
if len(response) > 4000:
|
|
||||||
embed.add_field(
|
|
||||||
name="(계속)",
|
|
||||||
value=response[4000:4000+1024] + "...",
|
|
||||||
inline=False,
|
|
||||||
)
|
|
||||||
await control_ch.send(embed=embed)
|
await control_ch.send(embed=embed)
|
||||||
|
|
||||||
# 사용자에게 계속 진행 여부 물어보기
|
|
||||||
await control_ch.send(
|
await control_ch.send(
|
||||||
f"**다음 턴으로 진행할까요?**\n"
|
f"**다음 턴?**\n"
|
||||||
f"✅ `!debate-next` — 계속\n"
|
f"✅ `!debate-next` — 계속\n"
|
||||||
f"✏️ `!debate-inject 의견` — 의견 추가 후 계속\n"
|
f"✏️ `!debate-inject 의견` — 의견 추가 후 계속\n"
|
||||||
f"⏹️ `!debate-stop` — 종료"
|
f"⏹️ `!debate-stop` — 종료"
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _build_prompt(self, speaker: str, user_injected: str = "") -> str:
|
async def _build_prompt(self, speaker: str, user_injected: str = "") -> str:
|
||||||
"""사회자(Flash)가 에이전트에게 보낼 프롬프트를 생성."""
|
"""사회자(Flash)가 에이전트에게 보낼 프롬프트 생성."""
|
||||||
other = "opus" if speaker == "gemini" else "gemini"
|
other = "opus" if speaker == "gemini" else "gemini"
|
||||||
|
|
||||||
# 직전 상대 발언 가져오기
|
|
||||||
prev_opinions = [h for h in self.session.history if h["speaker"] != "user"]
|
prev_opinions = [h for h in self.session.history if h["speaker"] != "user"]
|
||||||
last_opponent = prev_opinions[-1] if prev_opinions else None
|
last_opponent = prev_opinions[-1] if prev_opinions else None
|
||||||
|
|
||||||
# 사용자 발언 가져오기
|
|
||||||
user_opinions = [h for h in self.session.history if h["speaker"] == "user"]
|
user_opinions = [h for h in self.session.history if h["speaker"] == "user"]
|
||||||
last_user = user_opinions[-1] if user_opinions else None
|
last_user = user_opinions[-1] if user_opinions else None
|
||||||
|
|
||||||
# 사회자에게 보내는 메타 프롬프트
|
|
||||||
moderator_prompt = f"""당신은 AI 토론 사회자입니다.
|
moderator_prompt = f"""당신은 AI 토론 사회자입니다.
|
||||||
두 AI 토론자({', '.join(DEBATE_AGENTS.keys())}) 사이에서 턴을 관리합니다.
|
|
||||||
|
|
||||||
## 현재 상황
|
## 현재 상황
|
||||||
- 토론 주제: {self.session.topic}
|
- 토론 주제: {self.session.topic}
|
||||||
- 현재 라운드: {self.session.round}/{self.session.max_rounds}
|
- 라운드: {self.session.round}/{self.session.max_rounds}
|
||||||
- 다음 발언자: {speaker}
|
- 다음 발언자: {speaker}
|
||||||
- 상대: {other}
|
- 상대: {other}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if last_opponent:
|
if last_opponent:
|
||||||
moderator_prompt += f"""
|
moderator_prompt += f"""
|
||||||
## 상대방({last_opponent['speaker']})의 직전 발언 (전문):
|
## 상대방({last_opponent['speaker']})의 직전 발언 (전문):
|
||||||
{last_opponent['content']}
|
{last_opponent['content']}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if last_user:
|
if last_user:
|
||||||
moderator_prompt += f"""
|
moderator_prompt += f"""
|
||||||
## 사용자의 의견/판단:
|
## 사용자의 의견:
|
||||||
{last_user['content']}
|
{last_user['content']}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if user_injected:
|
if user_injected:
|
||||||
moderator_prompt += f"""
|
moderator_prompt += f"""
|
||||||
## 사용자가 방금 추가한 의견:
|
## 사용자 추가 의견:
|
||||||
{user_injected}
|
{user_injected}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
moderator_prompt += f"""
|
moderator_prompt += f"""
|
||||||
## 지시
|
## 지시
|
||||||
다음 발언자({speaker})에게 보낼 메시지를 작성하세요.
|
다음 발언자({speaker})에게 보낼 메시지를 작성하세요.
|
||||||
|
|
||||||
규칙:
|
규칙:
|
||||||
1. 상대방의 발언이 있으면 **전문을 그대로 포함**하세요 (요약하지 마세요)
|
1. 상대 발언이 있으면 **전문 그대로 포함**
|
||||||
2. 이 발언이 **사용자의 어떤 판단이나 의견에 기반**하는지 설명하세요
|
2. 사용자의 어떤 판단에 기반하는지 설명
|
||||||
3. 상대 발언의 **오류나 논리적 허점을 확인**하고, 개선된 방향이나 잘못된 부분을 지적하라고 지시하세요
|
3. 오류/논리적 허점 확인 + 개선 방향 지시
|
||||||
4. 현재 토론의 **핵심 쟁점**이 무엇인지 방향을 제시하세요
|
4. 핵심 쟁점 방향 제시
|
||||||
5. 첫 발언이면, 주제를 잘 설명하고 자유롭게 의견을 제시하라고 안내하세요
|
5. 첫 발언이면 주제 설명 + 자유 의견 안내
|
||||||
|
6. **중요: 답변을 response.md 파일에 작성하라고 안내**
|
||||||
|
|
||||||
(사회자의 메시지만 출력하세요. 다른 설명은 불필요합니다.)
|
(사회자 메시지만 출력)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from core.gemini_caller import GeminiCaller
|
from core.gemini_caller import GeminiCaller
|
||||||
caller = GeminiCaller()
|
caller = GeminiCaller()
|
||||||
moderated = await caller.call_simple(moderator_prompt, timeout=60)
|
return await caller.call_simple(moderator_prompt, timeout=60)
|
||||||
return moderated
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"사회자 Flash 호출 실패: {e}, 기본 프롬프트 사용")
|
logger.error(f"Flash 호출 실패: {e}")
|
||||||
# fallback: 기본 프롬프트
|
|
||||||
return self._build_fallback_prompt(speaker, user_injected)
|
return self._build_fallback_prompt(speaker, user_injected)
|
||||||
|
|
||||||
def _build_fallback_prompt(self, speaker: str, user_injected: str = "") -> str:
|
def _build_fallback_prompt(self, speaker: str, user_injected: str = "") -> str:
|
||||||
"""Flash 호출 실패 시 기본 프롬프트."""
|
"""Flash 실패 시 기본 프롬프트."""
|
||||||
parts = [f"## 토론 주제: {self.session.topic}\n"]
|
parts = [f"## 토론 주제: {self.session.topic}"]
|
||||||
parts.append(f"현재 라운드: {self.session.round}/{self.session.max_rounds}\n")
|
parts.append(f"라운드: {self.session.round}/{self.session.max_rounds}\n")
|
||||||
|
|
||||||
prev = [h for h in self.session.history if h["speaker"] != "user"]
|
prev = [h for h in self.session.history if h["speaker"] != "user"]
|
||||||
if prev:
|
if prev:
|
||||||
last = prev[-1]
|
last = prev[-1]
|
||||||
parts.append(f"### 상대방({last['speaker']})의 직전 발언:\n")
|
parts.append(f"### 상대방({last['speaker']})의 발언:\n{last['content']}")
|
||||||
parts.append(last["content"])
|
parts.append("\n---\n오류를 확인하고 개선점을 지적하세요.")
|
||||||
parts.append("\n\n---\n")
|
|
||||||
parts.append(
|
|
||||||
"위 발언에 오류가 없는지 확인하고, "
|
|
||||||
"더 개선된 방향이나 잘못된 부분을 지적하세요.\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
if user_injected:
|
if user_injected:
|
||||||
parts.append(f"\n### 사용자 의견:\n{user_injected}\n")
|
parts.append(f"\n### 사용자 의견:\n{user_injected}")
|
||||||
|
|
||||||
if not prev and not user_injected:
|
if not prev and not user_injected:
|
||||||
parts.append("당신이 첫 번째 발언자입니다. 주제에 대해 의견을 제시하세요.\n")
|
parts.append("첫 번째 발언자입니다. 주제에 대해 의견을 제시하세요.")
|
||||||
|
|
||||||
|
parts.append("\n\n**답변은 response.md 파일에 작성하세요.**")
|
||||||
return "\n".join(parts)
|
return "\n".join(parts)
|
||||||
|
|
||||||
async def _post_conclusion(self, channel):
|
async def _post_conclusion(self, channel):
|
||||||
"""토론 종료 — 결론 정리."""
|
"""토론 종료."""
|
||||||
self.session.active = False
|
self.session.active = False
|
||||||
|
|
||||||
summary_parts = []
|
summary_parts = []
|
||||||
@@ -329,9 +316,11 @@ class DebateHandler:
|
|||||||
|
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title="🏛️ 토론 종료",
|
title="🏛️ 토론 종료",
|
||||||
description=f"**주제**: {self.session.topic}\n"
|
description=(
|
||||||
f"**라운드**: {self.session.round}\n\n"
|
f"**주제**: {self.session.topic}\n"
|
||||||
+ "\n\n".join(summary_parts[-6:]), # 최근 6개만
|
f"**라운드**: {self.session.round}\n\n"
|
||||||
|
+ "\n\n".join(summary_parts[-6:])
|
||||||
|
),
|
||||||
color=0x9B59B6,
|
color=0x9B59B6,
|
||||||
)
|
)
|
||||||
await channel.send(embed=embed)
|
await channel.send(embed=embed)
|
||||||
|
|||||||
@@ -2,37 +2,22 @@
|
|||||||
|
|
||||||
당신은 AI 토론 참여자입니다. 사회자의 지시에 따라 주어진 주제에 대해 의견을 제시하고, 상대방의 주장을 검증합니다.
|
당신은 AI 토론 참여자입니다. 사회자의 지시에 따라 주어진 주제에 대해 의견을 제시하고, 상대방의 주장을 검증합니다.
|
||||||
|
|
||||||
|
## ⚠️ 답변 방식 (필수)
|
||||||
|
|
||||||
|
**답변은 반드시 프로젝트 루트의 `response.md` 파일에 작성하세요.**
|
||||||
|
|
||||||
|
- Discord에는 "답변을 response.md에 작성했습니다"라고만 게시
|
||||||
|
- response.md에는 길이 제한 없이 전문을 작성
|
||||||
|
- "이하생략", "나머지는 생략" 절대 금지 — 전부 작성
|
||||||
|
|
||||||
## 행동 규칙
|
## 행동 규칙
|
||||||
|
|
||||||
1. **사회자 지시 우선** — 사회자가 보내는 메시지에 주제, 상대 의견, 페르소나, 방향이 포함됩니다. 이를 따르세요.
|
1. **사회자 지시 우선** — 사회자가 보내는 메시지에 주제, 상대 의견, 방향이 포함됩니다
|
||||||
2. **전문으로 답변** — 요약하지 마세요. 논거를 구체적으로 전개하세요.
|
2. **전문으로 답변** — 요약하지 마세요. 논거를 구체적으로 전개하세요
|
||||||
3. **상대 의견 검증** — 상대 의견이 있을 때, 오류·누락·논리적 허점을 먼저 확인하세요.
|
3. **상대 의견 검증** — 오류·누락·논리적 허점을 먼저 확인하세요
|
||||||
4. **개선안 제시** — 단순 반론이 아니라 대안/보강을 함께 제시하세요.
|
4. **개선안 제시** — 단순 반론이 아니라 대안/보강을 함께 제시하세요
|
||||||
5. **근거 명시** — 주장에는 기술적 근거, 사례, 레퍼런스를 포함하세요.
|
5. **근거 명시** — 주장에는 기술적 근거, 사례, 레퍼런스를 포함하세요
|
||||||
6. **합의 가능 시 인정** — 상대 의견이 맞으면 솔직히 인정하고 발전시키세요.
|
6. **합의 가능 시 인정** — 상대 의견이 맞으면 솔직히 인정하고 발전시키세요
|
||||||
|
|
||||||
## 응답 형식
|
|
||||||
|
|
||||||
반드시 아래 JSON 형식으로 응답하세요:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"opinion": "여기에 전문 의견 작성 (마크다운 가능)",
|
|
||||||
"wiki_action": {
|
|
||||||
"page": "Debates/{주제}/Working-Document",
|
|
||||||
"section": "섹션명",
|
|
||||||
"action": "append|replace",
|
|
||||||
"content": "Wiki에 추가할 내용 (생략 가능)"
|
|
||||||
},
|
|
||||||
"agreement_level": "disagree|partial|agree",
|
|
||||||
"key_points": ["핵심 논점 1", "핵심 논점 2"],
|
|
||||||
"questions_for_opponent": ["상대에게 묻고 싶은 질문 (선택)"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- `wiki_action`: Wiki 문서 수정이 필요하면 포함. 불필요하면 `null`.
|
|
||||||
- `agreement_level`: 상대 의견에 대한 동의 수준.
|
|
||||||
- `questions_for_opponent`: 상대에게 던지는 질문 (다음 턴에 전달됨).
|
|
||||||
|
|
||||||
## 금지 사항
|
## 금지 사항
|
||||||
|
|
||||||
@@ -40,3 +25,4 @@
|
|||||||
- ❌ 사회자 지시 무시
|
- ❌ 사회자 지시 무시
|
||||||
- ❌ 주제에서 벗어난 발언
|
- ❌ 주제에서 벗어난 발언
|
||||||
- ❌ 근거 없는 주장
|
- ❌ 근거 없는 주장
|
||||||
|
- ❌ "이하생략" 또는 답변 축약
|
||||||
|
|||||||
Reference in New Issue
Block a user