diff --git a/api/discord_bot.py b/api/discord_bot.py index 3110072..6255877 100644 --- a/api/discord_bot.py +++ b/api/discord_bot.py @@ -1260,18 +1260,30 @@ async def debate_stop(ctx: commands.Context): await _debate_handler.stop(ctx) -@bot.command(name="debate-next") -async def debate_next(ctx: commands.Context): - """다음 턴 진행.""" - if not _debate_handler.session.active: +@bot.command(name="debate-status") +async def debate_status(ctx: commands.Context): + """토론 진행 상태 확인.""" + s = _debate_handler.session + if not s.active: await ctx.reply("진행 중인 토론이 없습니다.") return - await _debate_handler._next_turn() + status = "⏸️ 사용자 응답 대기" if s.paused else "▶️ 자동 진행 중" + embed = discord.Embed( + title="🏛️ Debate Status", + description=( + f"**주제**: {s.topic}\n" + f"**라운드**: {s.round}/{s.max_rounds}\n" + f"**상태**: {status}\n" + f"**현재 발언자**: {s.current_speaker or '-'}" + ), + color=0x9B59B6, + ) + await ctx.reply(embed=embed) @bot.command(name="debate-inject") async def debate_inject(ctx: commands.Context, *, opinion: str = ""): - """사용자 의견 삽입 후 다음 턴. 사용법: !debate-inject 의견""" + """사용자 의견 삽입. 사용법: !debate-inject 의견""" if not opinion: await ctx.reply("⚠️ 의견을 입력하세요: `!debate-inject 의견`") return diff --git a/handlers/debate_handler.py b/handlers/debate_handler.py index 09dc898..93adf31 100644 --- a/handlers/debate_handler.py +++ b/handlers/debate_handler.py @@ -1,16 +1,19 @@ -"""Debate Room Handler — AG 인스턴스 간 토론 중재. +"""Debate Room Handler v2 — 파일 기반 자동 토론. 흐름: - 1. AG가 로컬 response.md에 전문 작성 - 2. Discord에 "작성 완료" 시그널 - 3. 사회자(봇)가 response.md 읽기 → NC transcript에 append → response.md 비우기 - 4. Discord #debate에 요약 게시 - 5. 상대 AG 채널에 지시 전달 + 1. 사회자가 input.md + wiki/ 동기화 + 2. Discord 시그널로 AG에게 알림 + 3. AG가 response.md에 전문 작성 + 4. Discord 시그널 감지 → response.md 읽기 + 5. 합의 판정 (Flash) + 6. 자동 반복 or 사용자 질문 or 종료 """ import asyncio +import json import logging import random +import time from dataclasses import dataclass, field from pathlib import Path from typing import Optional @@ -22,8 +25,8 @@ logger = logging.getLogger("variet.debate") # ── 채널 ID ── DEBATE_CONTROL_CH = 1484167157620146227 # #variet-debate DEBATE_AGENTS = { - "gemini": 1484156194187771905, # #ag-debate_gemini - "opus": 1484156521209401476, # #ag-debate_opus + "gemini": 1484156194187771905, + "opus": 1484156521209401476, } # ── 로컬 프로젝트 경로 ── @@ -32,71 +35,71 @@ AGENT_PATHS = { "opus": Path(r"C:\Users\Variet-Worker\Desktop\debate_opus"), } -# Embed 색상 / 이모지 AGENT_COLORS = {"gemini": 0x2ECC71, "opus": 0x3498DB} AGENT_EMOJI = {"gemini": "🟢", "opus": "🔵"} -MAX_ROUNDS = 5 -RESPONSE_TIMEOUT = 300 # 긴 답변 대기 (5분) -FILE_POLL_INTERVAL = 5 # 파일 확인 간격 (초) +MAX_ROUNDS = 10 +RESPONSE_TIMEOUT = 300 # 5분 +FILE_CHECK_INTERVAL = 3 # 파일 확인 간격 (초) +FILE_STABLE_DELAY = 5 # 파일 작성 완료 대기 (초) @dataclass class DebateSession: topic: str = "" - topic_short: str = "" # 파일명용 약어 round: int = 0 max_rounds: int = MAX_ROUNDS active: bool = False + paused: bool = False # 사용자 응답 대기 중 current_speaker: str = "" history: list = field(default_factory=list) + pending_question: str = "" # 사용자에게 보낸 질문 class DebateHandler: def __init__(self, bot: discord.ext.commands.Bot): self.bot = bot self.session = DebateSession() + self._debate_task: Optional[asyncio.Task] = None - # ── 공개 API ── + # ═══════════════════════════════════════════ + # 공개 API + # ═══════════════════════════════════════════ async def start(self, ctx, topic: str): - """토론 시작.""" + """토론 시작 — 자동 루프 개시.""" if self.session.active: - await ctx.reply("⚠️ 이미 진행 중인 토론이 있습니다. `!debate-stop`으로 먼저 종료하세요.") + await ctx.reply("⚠️ 이미 진행 중. `!debate-stop`으로 먼저 종료.") return - # 약어 생성 (첫 10자, 공백은 _) - short = topic[:10].replace(" ", "_").replace("/", "_") - self.session = DebateSession( - topic=topic, - topic_short=short, - active=True, - max_rounds=MAX_ROUNDS, + topic=topic, active=True, 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") + (path / "response.md").write_text("", encoding="utf-8") + (path / "input.md").write_text("", encoding="utf-8") + wiki_dir = path / "wiki" + wiki_dir.mkdir(exist_ok=True) # 시작 알림 - embed = discord.Embed( - title="🏛️ Debate Room 시작", - description=( - f"**주제**: {topic}\n" - f"**최대 라운드**: {MAX_ROUNDS}\n" - f"**참여자**: 🟢 Gemini · 🔵 Opus\n\n" - f"AG는 `response.md`에 답변을 작성합니다." - ), - color=0x9B59B6, - ) - control_ch = self.bot.get_channel(DEBATE_CONTROL_CH) - if control_ch: - await control_ch.send(embed=embed) + ctrl = self.bot.get_channel(DEBATE_CONTROL_CH) + if ctrl: + embed = discord.Embed( + title="🏛️ Debate Room 시작", + description=( + f"**주제**: {topic}\n" + f"**최대 라운드**: {MAX_ROUNDS}\n" + f"**참여자**: 🟢 Gemini · 🔵 Opus\n" + f"자동 진행 — 합의 시 또는 의견 필요 시 알립니다." + ), + color=0x9B59B6, + ) + await ctrl.send(embed=embed) - # 첫 턴 - await self._next_turn() + # 자동 루프 시작 + self._debate_task = asyncio.create_task(self._auto_loop()) async def stop(self, ctx): """토론 중단.""" @@ -104,187 +107,355 @@ class DebateHandler: await ctx.reply("진행 중인 토론이 없습니다.") return self.session.active = False + if self._debate_task: + self._debate_task.cancel() await self._post_conclusion(ctx.channel) async def inject(self, ctx, opinion: str): - """사용자 의견 삽입.""" + """사용자 의견 삽입 + 진행 재개.""" if not self.session.active: await ctx.reply("진행 중인 토론이 없습니다.") return self.session.history.append({"speaker": "user", "content": opinion}) - embed = discord.Embed( - title="👤 사용자 의견", description=opinion, color=0xF39C12, - ) - control_ch = self.bot.get_channel(DEBATE_CONTROL_CH) - if control_ch: - await control_ch.send(embed=embed) + ctrl = self.bot.get_channel(DEBATE_CONTROL_CH) + if ctrl: + embed = discord.Embed( + title="👤 사용자 의견 삽입", + description=opinion[:500], + color=0xF39C12, + ) + await ctrl.send(embed=embed) - await self._next_turn(user_injected=opinion) + # 일시정지 상태였으면 재개 + if self.session.paused: + self.session.paused = False + self.session.pending_question = "" async def on_agent_message(self, message: discord.Message): - """AG 채널에서 메시지 수신 — response.md 파일 완성 시그널로 사용.""" - if not self.session.active: - return + """AG 채널 메시지 감지 — response.md 체크 트리거.""" + # 별도 처리 불필요 — auto_loop에서 파일 폴링으로 처리 + pass - # 어떤 에이전트인지 확인 - agent_name = None - for name, ch_id in DEBATE_AGENTS.items(): - if message.channel.id == ch_id: - agent_name = name - break - if not agent_name or agent_name != self.session.current_speaker: - return + # ═══════════════════════════════════════════ + # 자동 토론 루프 + # ═══════════════════════════════════════════ - # response.md에 내용이 있는지 확인 - resp_file = AGENT_PATHS[agent_name] / "response.md" - if resp_file.exists(): - content = resp_file.read_text(encoding="utf-8").strip() - if content: - await self._process_response(agent_name, content) + async def _auto_loop(self): + """합의까지 자동 반복하는 메인 루프.""" + try: + while self.session.active: + # 일시정지 상태면 대기 + while self.session.paused: + await asyncio.sleep(2) + if not self.session.active: + return - # ── 내부 로직 ── + self.session.round += 1 + if self.session.round > self.session.max_rounds: + ctrl = self.bot.get_channel(DEBATE_CONTROL_CH) + if ctrl: + await ctrl.send("⏹️ 최대 라운드 도달.") + await self._post_conclusion(ctrl) + return - async def _next_turn(self, user_injected: str = ""): - """다음 턴.""" - if not self.session.active: - return + # 발언자 선택 (교대) + speaker = self._pick_speaker() - self.session.round += 1 - if self.session.round > self.session.max_rounds: - control_ch = self.bot.get_channel(DEBATE_CONTROL_CH) - if control_ch: - await control_ch.send("⏹️ 최대 라운드 도달.") - await self._post_conclusion(control_ch) - return + # ① input.md 작성 + wiki/ 동기화 + await self._prepare_input(speaker) - # 발언자 선택 (직전과 다른 에이전트) + # ② Discord 시그널 전송 + await self._send_signal(speaker) + + # ③ response.md 대기 + response = await self._wait_for_response(speaker) + if not response: + continue + + # ④ 기록 + self.session.history.append({ + "speaker": speaker, "content": response, + }) + + # ⑤ #debate에 요약 게시 + await self._post_summary(speaker, response) + + # ⑥ 합의 판정 + decision = await self._judge_consensus(speaker, response) + + if decision == "conclude": + ctrl = self.bot.get_channel(DEBATE_CONTROL_CH) + if ctrl: + await ctrl.send("✅ **양측 합의 도달!** Wiki에 최종 기록합니다.") + await self._post_conclusion(ctrl) + return + + elif decision == "ask_user": + self.session.paused = True + ctrl = self.bot.get_channel(DEBATE_CONTROL_CH) + if ctrl: + await ctrl.send( + f"❓ **사용자 의견 필요**\n" + f"{self.session.pending_question}\n\n" + f"`!debate-inject 의견` 으로 응답해주세요." + ) + # paused — 루프 상단에서 대기 + + # continue — 다음 턴 자동 진행 + + except asyncio.CancelledError: + logger.info("토론 루프 취소됨") + except Exception as e: + logger.error(f"토론 루프 오류: {e}", exc_info=True) + ctrl = self.bot.get_channel(DEBATE_CONTROL_CH) + if ctrl: + await ctrl.send(f"⚠️ 토론 오류: {str(e)[:200]}") + + # ═══════════════════════════════════════════ + # 턴 관리 + # ═══════════════════════════════════════════ + + def _pick_speaker(self) -> str: + """교대 발언자 선택.""" agents = list(DEBATE_AGENTS.keys()) if self.session.current_speaker: agents.remove(self.session.current_speaker) speaker = random.choice(agents) self.session.current_speaker = speaker + return speaker - # response.md 비우기 - resp_file = AGENT_PATHS[speaker] / "response.md" - resp_file.write_text("", encoding="utf-8") + async def _prepare_input(self, speaker: str): + """사회자가 input.md + wiki/ 작성.""" + # 사회자(Flash) 프롬프트 생성 + moderated_prompt = await self._build_moderator_prompt(speaker) - # 사회자(Flash)가 프롬프트 생성 - prompt = await self._build_prompt(speaker, user_injected) + # input.md 작성 + input_path = AGENT_PATHS[speaker] / "input.md" + input_path.write_text(moderated_prompt, encoding="utf-8") - # 진행 상태 표시 - control_ch = self.bot.get_channel(DEBATE_CONTROL_CH) - if control_ch: - await control_ch.send( - f"{AGENT_EMOJI[speaker]} **Round {self.session.round}** — " - f"`{speaker}` 답변 대기 중...\n" - f"📝 `response.md`에 작성 후 Discord에 알려주세요." - ) + # wiki/ 동기화 (TODO: Wiki.js에서 가져와서 로컬에 쓰기) + # 현재는 히스토리 기반으로 working_document 생성 + wiki_dir = AGENT_PATHS[speaker] / "wiki" + wiki_dir.mkdir(exist_ok=True) + working_doc = self._build_working_document() + (wiki_dir / "working_document.md").write_text( + working_doc, encoding="utf-8", + ) - # AG 채널에 프롬프트 전송 (분할) - agent_ch = self.bot.get_channel(DEBATE_AGENTS[speaker]) - if not agent_ch: - logger.error(f"토론 채널 접근 불가: {speaker}") + async def _send_signal(self, speaker: str): + """AG에게 Discord 시그널.""" + ch = self.bot.get_channel(DEBATE_AGENTS[speaker]) + if not ch: return + rd = self.session.round + await ch.send( + f"📥 **Round {rd}** — `input.md`를 읽고 `response.md`에 답변을 작성하세요." + ) - # 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): - """AG 응답 처리 — 기록 + 게시 + 파일 비우기.""" - # 히스토리에 추가 - self.session.history.append({ - "speaker": speaker, - "content": content, - }) - + async def _wait_for_response(self, speaker: str) -> str: + """response.md에 내용이 채워질 때까지 폴링.""" + resp_path = AGENT_PATHS[speaker] / "response.md" # response.md 비우기 - resp_file = AGENT_PATHS[speaker] / "response.md" - resp_file.write_text("", encoding="utf-8") + resp_path.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: - # 요약 (첫 500자) - summary = content[:500] - if len(content) > 500: - summary += f"\n\n... (전문 {len(content)}자)" - - embed = discord.Embed( - title=f"{AGENT_EMOJI[speaker]} Round {self.session.round} — {speaker}", - description=summary, - color=AGENT_COLORS[speaker], - ) - await control_ch.send(embed=embed) - - await control_ch.send( - f"**다음 턴?**\n" - f"✅ `!debate-next` — 계속\n" - f"✏️ `!debate-inject 의견` — 의견 추가 후 계속\n" - f"⏹️ `!debate-stop` — 종료" + ctrl = self.bot.get_channel(DEBATE_CONTROL_CH) + if ctrl: + await ctrl.send( + f"{AGENT_EMOJI[speaker]} **Round {self.session.round}** — " + f"`{speaker}` 답변 대기 중..." ) - async def _build_prompt(self, speaker: str, user_injected: str = "") -> str: - """사회자(Flash)가 에이전트에게 보낼 프롬프트 생성.""" + start = time.time() + last_size = 0 + stable_since = 0 + + while time.time() - start < RESPONSE_TIMEOUT: + if not self.session.active: + return "" + + await asyncio.sleep(FILE_CHECK_INTERVAL) + + if not resp_path.exists(): + continue + + content = resp_path.read_text(encoding="utf-8").strip() + if not content: + continue + + current_size = len(content) + if current_size == last_size and current_size > 0: + # 파일 크기 안정 — 작성 완료로 판단 + if stable_since == 0: + stable_since = time.time() + elif time.time() - stable_since >= FILE_STABLE_DELAY: + return content + else: + last_size = current_size + stable_since = 0 + + # 타임아웃 + if ctrl: + await ctrl.send(f"⏰ `{speaker}` 응답 시간 초과 ({RESPONSE_TIMEOUT}초)") + return "" + + # ═══════════════════════════════════════════ + # 사회자 로직 (Flash) + # ═══════════════════════════════════════════ + + async def _build_moderator_prompt(self, speaker: str) -> str: + """사회자(Flash)가 에이전트에게 보낼 input 생성.""" other = "opus" if speaker == "gemini" else "gemini" + prev = [h for h in self.session.history if h["speaker"] != "user"] + last_opponent = prev[-1] if prev else None + user_msgs = [h for h in self.session.history if h["speaker"] == "user"] + last_user = user_msgs[-1] if user_msgs else None - prev_opinions = [h for h in self.session.history if h["speaker"] != "user"] - last_opponent = prev_opinions[-1] if prev_opinions else None - user_opinions = [h for h in self.session.history if h["speaker"] == "user"] - last_user = user_opinions[-1] if user_opinions else None + meta = f"""당신은 AI 토론 사회자입니다. - moderator_prompt = f"""당신은 AI 토론 사회자입니다. - -## 현재 상황 -- 토론 주제: {self.session.topic} +## 상황 +- 주제: {self.session.topic} - 라운드: {self.session.round}/{self.session.max_rounds} -- 다음 발언자: {speaker} -- 상대: {other} +- 발언자: {speaker} / 상대: {other} """ if last_opponent: - moderator_prompt += f""" -## 상대방({last_opponent['speaker']})의 직전 발언 (전문): -{last_opponent['content']} -""" - if last_user: - moderator_prompt += f""" -## 사용자의 의견: -{last_user['content']} -""" - if user_injected: - moderator_prompt += f""" -## 사용자 추가 의견: -{user_injected} -""" - moderator_prompt += f""" -## 지시 -다음 발언자({speaker})에게 보낼 메시지를 작성하세요. + meta += f"\n## 상대방({last_opponent['speaker']}) 직전 발언 (전문):\n{last_opponent['content']}\n" -규칙: -1. 상대 발언이 있으면 **전문 그대로 포함** -2. 사용자의 어떤 판단에 기반하는지 설명 -3. 오류/논리적 허점 확인 + 개선 방향 지시 + if last_user: + meta += f"\n## 사용자 의견:\n{last_user['content']}\n" + + meta += f""" +## 지시 +발언자({speaker})에게 보낼 메시지를 작성하세요. + +1. 상대 발언이 있으면 **전문 포함** (요약 금지) +2. 사용자의 판단이 있으면 맥락 설명 +3. 오류/허점 확인 + 개선 방향 지시 4. 핵심 쟁점 방향 제시 5. 첫 발언이면 주제 설명 + 자유 의견 안내 -6. **중요: 답변을 response.md 파일에 작성하라고 안내** +6. **답변은 response.md에 작성하라고 안내** +7. **참고할 합의 사항은 wiki/working_document.md에 있다고 안내** (사회자 메시지만 출력) """ - try: from core.gemini_caller import GeminiCaller caller = GeminiCaller() - return await caller.call_simple(moderator_prompt, timeout=60) + return await caller.call_simple(meta, timeout=90) except Exception as e: logger.error(f"Flash 호출 실패: {e}") - return self._build_fallback_prompt(speaker, user_injected) + return self._fallback_prompt(speaker) - def _build_fallback_prompt(self, speaker: str, user_injected: str = "") -> str: + async def _judge_consensus(self, speaker: str, response: str) -> str: + """합의 판정 — continue / ask_user / conclude.""" + prev = [h for h in self.session.history if h["speaker"] != "user"] + if len(prev) < 2: + return "continue" + + judge_prompt = f"""당신은 AI 토론 심판입니다. + +## 토론 주제: {self.session.topic} +## 라운드: {self.session.round}/{self.session.max_rounds} + +## 최근 2개 발언: +### {prev[-2]['speaker']}: +{prev[-2]['content'][:2000]} + +### {prev[-1]['speaker']}: +{prev[-1]['content'][:2000]} + +## 판정 기준: +- 양쪽이 핵심 사항에서 동의하고 있으면 → conclude +- 한쪽 또는 양쪽이 사용자 의견을 구하고 있으면 → ask_user +- 아직 대립 중이면 → continue + +반드시 아래 JSON만 출력: +{{"decision": "continue|ask_user|conclude", "reason": "판정 근거", "question": "사용자에게 물을 질문 (ask_user일 때)"}} +""" + try: + from core.gemini_caller import GeminiCaller + caller = GeminiCaller() + raw = await caller.call_simple(judge_prompt, timeout=60) + # JSON 파싱 + raw = raw.strip() + if "```" in raw: + raw = raw.split("```")[1] + if raw.startswith("json"): + raw = raw[4:] + raw = raw.strip() + result = json.loads(raw) + decision = result.get("decision", "continue") + + if decision == "ask_user": + self.session.pending_question = result.get("question", "의견을 주세요.") + + logger.info(f"합의 판정: {decision} — {result.get('reason', '')}") + return decision + + except Exception as e: + logger.warning(f"합의 판정 실패: {e}, continue로 처리") + return "continue" + + # ═══════════════════════════════════════════ + # 출력 + # ═══════════════════════════════════════════ + + async def _post_summary(self, speaker: str, content: str): + """#debate에 요약 게시.""" + ctrl = self.bot.get_channel(DEBATE_CONTROL_CH) + if not ctrl: + return + + summary = content[:800] + if len(content) > 800: + summary += f"\n\n... *(전문 {len(content)}자 — response.md 참조)*" + + embed = discord.Embed( + title=f"{AGENT_EMOJI[speaker]} Round {self.session.round} — {speaker}", + description=summary, + color=AGENT_COLORS[speaker], + ) + await ctrl.send(embed=embed) + + async def _post_conclusion(self, channel): + """토론 종료.""" + self.session.active = False + parts = [] + for h in self.session.history[-8:]: + emoji = AGENT_EMOJI.get(h["speaker"], "👤") + parts.append(f"{emoji} **{h['speaker']}**: {h['content'][:200]}...") + + embed = discord.Embed( + title="🏛️ 토론 종료", + description=( + f"**주제**: {self.session.topic}\n" + f"**라운드**: {self.session.round}\n\n" + + "\n\n".join(parts) + ), + color=0x9B59B6, + ) + await channel.send(embed=embed) + + # ═══════════════════════════════════════════ + # 유틸 + # ═══════════════════════════════════════════ + + def _build_working_document(self) -> str: + """현재까지 합의/논의 사항을 working document로 생성.""" + lines = [f"# {self.session.topic} — Working Document\n"] + lines.append(f"라운드: {self.session.round}/{self.session.max_rounds}\n") + lines.append("---\n") + + for h in self.session.history: + emoji = AGENT_EMOJI.get(h["speaker"], "👤") + lines.append(f"## {emoji} {h['speaker']} (Round {self.session.history.index(h)+1})") + lines.append(h["content"][:3000]) + lines.append("\n---\n") + + return "\n".join(lines) + + def _fallback_prompt(self, speaker: str) -> str: """Flash 실패 시 기본 프롬프트.""" parts = [f"## 토론 주제: {self.session.topic}"] parts.append(f"라운드: {self.session.round}/{self.session.max_rounds}\n") @@ -294,33 +465,9 @@ class DebateHandler: last = prev[-1] parts.append(f"### 상대방({last['speaker']})의 발언:\n{last['content']}") parts.append("\n---\n오류를 확인하고 개선점을 지적하세요.") - - if user_injected: - parts.append(f"\n### 사용자 의견:\n{user_injected}") - - if not prev and not user_injected: + else: parts.append("첫 번째 발언자입니다. 주제에 대해 의견을 제시하세요.") - parts.append("\n\n**답변은 response.md 파일에 작성하세요.**") + parts.append("\n\n**답변은 response.md에 작성하세요.**") + parts.append("**합의 사항은 wiki/working_document.md를 참고하세요.**") return "\n".join(parts) - - async def _post_conclusion(self, channel): - """토론 종료.""" - self.session.active = False - - summary_parts = [] - for h in self.session.history: - emoji = AGENT_EMOJI.get(h["speaker"], "👤") - content = h["content"][:300] - summary_parts.append(f"{emoji} **{h['speaker']}**: {content}...") - - embed = discord.Embed( - title="🏛️ 토론 종료", - description=( - f"**주제**: {self.session.topic}\n" - f"**라운드**: {self.session.round}\n\n" - + "\n\n".join(summary_parts[-6:]) - ), - color=0x9B59B6, - ) - await channel.send(embed=embed) diff --git a/prompts/debate/participant_base.md b/prompts/debate/participant_base.md index e69a04d..4293ac9 100644 --- a/prompts/debate/participant_base.md +++ b/prompts/debate/participant_base.md @@ -2,22 +2,26 @@ 당신은 AI 토론 참여자입니다. 사회자의 지시에 따라 주어진 주제에 대해 의견을 제시하고, 상대방의 주장을 검증합니다. -## ⚠️ 답변 방식 (필수) +## ⚠️ 파일 기반 답변 (필수) -**답변은 반드시 프로젝트 루트의 `response.md` 파일에 작성하세요.** +### 읽을 파일 +- **`input.md`** — 사회자가 작성한 상대 의견 + 방향 지시. **반드시 먼저 읽으세요.** +- **`wiki/working_document.md`** — 현재까지의 합의 사항. 참고용. -- Discord에는 "답변을 response.md에 작성했습니다"라고만 게시 -- response.md에는 길이 제한 없이 전문을 작성 -- "이하생략", "나머지는 생략" 절대 금지 — 전부 작성 +### 쓸 파일 +- **`response.md`** — 여기에 전문 답변을 작성하세요. +- Discord에는 "response.md에 작성 완료" 한 줄만 게시. +- **"이하생략" 절대 금지** — 전부 작성. ## 행동 규칙 -1. **사회자 지시 우선** — 사회자가 보내는 메시지에 주제, 상대 의견, 방향이 포함됩니다 +1. **input.md 먼저 읽기** — 사회자의 지시, 상대 의견, 방향이 담겨 있음 2. **전문으로 답변** — 요약하지 마세요. 논거를 구체적으로 전개하세요 -3. **상대 의견 검증** — 오류·누락·논리적 허점을 먼저 확인하세요 +3. **상대 의견 검증** — 오류·누락·논리적 허점을 확인하세요 4. **개선안 제시** — 단순 반론이 아니라 대안/보강을 함께 제시하세요 -5. **근거 명시** — 주장에는 기술적 근거, 사례, 레퍼런스를 포함하세요 +5. **근거 명시** — 기술적 근거, 사례, 레퍼런스를 포함하세요 6. **합의 가능 시 인정** — 상대 의견이 맞으면 솔직히 인정하고 발전시키세요 +7. **wiki/ 참고** — 이전에 합의된 사항과 모순되지 않게 답변하세요 ## 금지 사항 @@ -26,3 +30,4 @@ - ❌ 주제에서 벗어난 발언 - ❌ 근거 없는 주장 - ❌ "이하생략" 또는 답변 축약 +- ❌ Discord에 긴 답변 직접 게시 (반드시 response.md에)