humanda5
๋ฐฐํฌ ๋ฐ ์ตœ์ข… ์ˆ˜์ • ์™„๋ฃŒ ๋ฒ„์ „
b03bfdc
# portfolio/npc_social_network/manager/conversation_manager.py
# ๋Œ€ํ™”์˜ ์‹œ์ž‘, ์ง„ํ–‰, ์ข…๋ฃŒ๋ฅผ ์ด๊ด„ํ•˜๋Š” ์—ญํ• 
from ..models.llm_helper import (summarize_text, query_llm_for_emotion
, evaluate_goal_achievement)
from ..npc.npc_manager import get_korean_postposition
from ..npc.emotion_config import EMOTION_RELATION_IMPACT, EMOTION_LIST
import threading
from typing import List, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from ..npc.npc_base import NPC
class Conversation:
"""๋‹จ์ผ ๋Œ€ํ™”์˜ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฐ์ดํ„ฐ ํด๋ž˜์Šค"""
def __init__(self, initiator: "NPC", target: "NPC", topic: str):
self.participants: List["NPC"] = [initiator, target]
self.conversation_history: List[str] = []
self.topic: str = topic
self.turn_index: int = 0 # 0: initiator, 1: target
self.is_ending: bool = False
self.waiting_for_player: bool = False # ํ”Œ๋ ˆ์ด์–ด์˜ ์ž…๋ ฅ ๋Œ€๊ธฐ
def get_current_speaker(self) -> "NPC":
"""์ด๋ฒˆ์— ๋งํ•  ์ฐจ๋ก€์ธ NPC๋ฅผ ๋ฐ˜ํ™˜"""
return self.participants[self.turn_index]
def switch_turn(self):
"""๋Œ€ํ™” ํ„ด์„ ์ƒ๋Œ€๋ฐฉ์—๊ฒŒ ๋„˜๊น€"""
self.turn_index = 1 - self.turn_index
class ConversationManager:
"""ํ™œ์„ฑํ™”๋œ ๋Œ€ํ™”๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์ค‘์•™ ๊ด€๋ฆฌ์ž"""
def __init__(self):
self.active_conversation: Optional[Conversation] = None
def is_conversation_active(self) -> bool:
"""ํ˜„์žฌ ์ง„ํ–‰ ์ค‘์ธ ๋Œ€ํ™”๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ"""
return self.active_conversation is not None
def _add_and_log_utterance(self,speaker: "NPC", utterance: str):
"""๋Œ€์‚ฌ๋ฅผ ๋Œ€ํ™” ๊ธฐ๋ก๊ณผ UI ๋กœ๊ทธ์— ๋ชจ๋‘ ์ถ”๊ฐ€"""
from .. import simulation_core
if not self.is_conversation_active():
return
log_message = f"[{speaker.korean_name}]: {utterance}"
self.active_conversation.conversation_history.append(log_message)
simulation_core.add_log(log_message)
def _summarize_and_remember_in_background(self, conv: "Conversation"):
"""๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋Œ€ํ™”๋ฅผ ์š”์•ฝํ•˜๊ณ  ๊ธฐ์–ตํ•˜๋ฉฐ, ๊ด€๊ณ„์™€ ์„ฑ๊ฒฉ์— ๋ฐ˜์˜, ๋ชฉํ‘œ ๋‹ฌ์„ฑ ํ‰๊ฐ€๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ํ•จ์ˆ˜"""
from .. import simulation_core
try:
if not conv.conversation_history:
print("๊ธฐ์–ตํ•  ๋Œ€ํ™” ๋‚ด์šฉ์ด ์—†์–ด ์ข…๋ฃŒ.")
return
initiator, target = conv.participants[0], conv.participants[1]
full_conversation = "\n".join(conv.conversation_history)
# 1. ๋Œ€ํ™” ๋‚ด์šฉ ์š”์•ฝ
summary = summarize_text(full_conversation)
# 2. ๋Œ€ํ™”์˜ ์ „๋ฐ˜์ ์ธ ๊ฐ์ • ํ†ค ๋ถ„์„
overall_emotion = query_llm_for_emotion(summary)
# 3. ๋ชฉํ‘œ ๋‹ฌ์„ฑ ์—ฌ๋ถ€ ํ‰๊ฐ€
initiator = conv.participants[0]
evaluation = evaluate_goal_achievement(
initiator_name=initiator.korean_name,
goal=conv.topic,
conversation_transcript=full_conversation
)
with simulation_core.simulation_lock:
# 4. ํ‰๊ฐ€ ๊ฒฐ๊ณผ ๋กœ๊ทธ ์ถ”๊ฐ€
simulation_core.add_log(f"[{initiator.korean_name}์˜ ๋ชฉํ‘œ ๋‹ฌ์„ฑ ํ‰๊ฐ€]\n"
f"'{conv.topic}' -> ๋‹ฌ์„ฑ: {evaluation.get('goal_achieved')}.\n"
f"์ด์œ : {evaluation.get('reason')}")
target_postposition = get_korean_postposition(target.korean_name, "๊ณผ", "์™€")
# 5. ํ‰๊ฐ€ ๊ฒฐ๊ณผ์— ๋”ฐ๋ฅธ ๊ฐ์ • ์—…๋ฐ์ดํŠธ
initiator_emotion = evaluation.get('initiator_emotion')
if initiator_emotion and initiator_emotion in EMOTION_LIST:
initiator.update_emotion(initiator_emotion, strength=5.0) # ๋ชฉํ‘œ ๋‹ฌ์„ฑ ์—ฌ๋ถ€๋Š” ๊ฐ•ํ•œ ๊ฐ์ • ์œ ๋ฐœ
# 6. ์š”์•ฝ๋œ ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ๊ธฐ์–ต ์ƒ์„ฑ
memory_content = f"'{conv.topic}'์— ๋Œ€ํ•ด ๋Œ€ํ™”ํ•˜๋ฉฐ '{summary}'๋ผ๋Š” ๊ฒฐ๋ก ์„ ๋‚ด๋ ธ๋‹ค."
initiator.remember(content=f"{target.korean_name}{target_postposition} {memory_content}", importance=7, memory_type="Conversation")
target.remember(content=f"{initiator.korean_name}{target_postposition} {memory_content}", importance=7, memory_type="Conversation")
# 7. ๋Œ€ํ™”์˜ ๊ฐ์ •์„ ๋ฐ”ํƒ•์œผ๋กœ ๊ด€๊ณ„ ์ƒํƒœ ์—…๋ฐ์ดํŠธ
if overall_emotion and overall_emotion in EMOTION_RELATION_IMPACT:
initiator.relationships.update_relationship(target.name, overall_emotion, strength=3.0)
target.relationships.update_relationship(initiator.name, overall_emotion, strength=3.0)
# 8. ๊ด€๊ณ„ ๋ณ€ํ™”๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์–‘์ชฝ ๋ชจ๋‘์˜ ์„ฑ๊ฒฉ ์—…๋ฐ์ดํŠธ
initiator.update_personality()
target.update_personality()
simulation_core.add_log(f"[{initiator.korean_name}, {target.korean_name}] ๋Œ€ํ™” ๊ฒฝํ—˜์ด ๊ธฐ์–ต๊ณผ ๊ด€๊ณ„, ์„ฑ๊ฒฉ์— ๋ฐ˜์˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
# 9. ๋ชจ๋“  ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ์ข…๋ฃŒ ํ›„, ํ˜„์žฌ๊นŒ์ง€์˜ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์ „์ฒด ์ƒํƒœ๋ฅผ ์ฆ‰์‹œ ์ €์žฅ.
simulation_core.save_simulation(simulation_core.npc_manager)
except Exception as e:
print(f"[์—๋Ÿฌ] ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๊ธฐ์–ต ์ €์žฅ ์ค‘ ๋ฌธ์ œ ๋ฐœ์ƒ: {e}")
import traceback
traceback.print_exc()
def start_conversation(self, initiator: "NPC", target: "NPC",
topic: Optional[str] = None):
"""์ƒˆ๋กœ์šด ๋Œ€ํ™”๋ฅผ ์‹œ์ž‘, ๋ฐ”๋กœ ๋‹ค์Œ ํ„ด์„ ํ˜ธ์ถœ"""
from .. import simulation_core
if self.is_conversation_active():
return # ์ด๋ฏธ ๋‹ค๋ฅธ ๋Œ€ํ™”๊ฐ€ ์ง„ํ–‰์ค‘์ด๋ฉด ์‹œ์ž‘ x
conv_topic = topic or "๊ฐ€๋ฒผ์šด ์ธ์‚ฌ"
self.active_conversation = Conversation(initiator, target, conv_topic)
simulation_core.add_log(f"--[๋Œ€ํ™” ์‹œ์ž‘]--")
simulation_core.add_log(f"์ฃผ์ œ: {conv_topic} / ์ฐธ์—ฌ์ž: {initiator.korean_name}, {target.korean_name}")
# 1. ์ฒซ ๋งˆ๋”” ์ƒ์„ฑ
self.next_turn()
def end_conversation(self, reason: str = "์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ"):
"""ํ˜„์žฌ ๋Œ€ํ™”๋ฅผ ์ข…๋ฃŒํ•˜๊ณ , ๋Œ€ํ™” ๋‚ด์šฉ์„ ์š”์•ฝํ•˜์—ฌ ๊ธฐ์–ต์œผ๋กœ ์ €์žฅ"""
from .. import simulation_core
if not self.is_conversation_active():
return
# ๋Œ€ํ™”๊ฐ€ ๋๋‚˜๋ฉด, ์ „์ฒด ๋Œ€ํ™” ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ๊ธฐ์–ต์„ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๊ณ„๋ฅผ ์—…๋ฐ์ดํŠธ
conv_to_process = self.active_conversation
# 1. ๋Œ€ํ™” ์ƒํƒœ๋ฅผ ์ฆ‰์‹œ '์ข…๋ฃŒ'๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ๋ฉ”์ธ ๋ฃจํ”„๋ฅผ ํ•ด๋ฐฉ
self.active_conversation = None
simulation_core.add_log(f"---[๋Œ€ํ™” ์ข…๋ฃŒ: {reason}]---\n")
# 2. ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฌ๋Š” '๊ธฐ์–ต ์ €์žฅ' ์ž‘์—…์€ ๋ณ„๋„์˜ ์Šค๋ ˆ๋“œ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ฒ˜๋ฆฌ.
background_thread = threading.Thread(
target=self._summarize_and_remember_in_background,
args=(conv_to_process,)
)
background_thread.start()
def next_turn(self):
"""๋Œ€ํ™”์˜ ๋‹ค์Œ ํ„ด์„ ์ง„ํ–‰, ์ƒ์„ฑ๋œ ๋Œ€์‚ฌ ์ฒ˜๋ฆฌ"""
if not self.is_conversation_active():
return
conv = self.active_conversation
speaker = conv.get_current_speaker()
# ํ”Œ๋ ˆ์ด์–ด ํ„ด ์ฒ˜๋ฆฌ ๋กœ์ง
if speaker.name == "player":
conv.waiting_for_player = True
return # ํ”Œ๋ ˆ์ด์–ด์˜ ์‘๋‹ต์ด ์˜ฌ ๋•Œ๊นŒ์ง€ ๋Œ€ํ™” ํ„ด ์ง„ํ–‰์„ ๋ฉˆ์ถค
# ์ƒํƒœ 1: ๋งˆ๋ฌด๋ฆฌ ๋‹จ๊ณ„ (์ž‘๋ณ„ ์ธ์‚ฌ)
if conv.is_ending:
utterance, _ = speaker.generate_dialogue_turn(conv, is_final_turn=True)
self._add_and_log_utterance(speaker, utterance)
self.end_conversation("์ž‘๋ณ„ ์ธ์‚ฌ ์™„๋ฃŒ")
return
# ์ƒํƒœ 2: ์ง„ํ–‰ ๋‹จ๊ณ„ (์ผ๋ฐ˜ ๋Œ€ํ™”)
utterance, action = speaker.generate_dialogue_turn(conv)
self._add_and_log_utterance(speaker, utterance)
# ์ƒํƒœ ์ „ํ™˜ ๊ฒฐ์ •
if action != "CONTINUE" or len(conv.conversation_history) >= 10:
conv.is_ending = True
conv.switch_turn()
else:
conv.switch_turn()