humanda5
ํ”Œ๋ ˆ์ด์–ด ์ถ”๊ฐ€ ๋ฐ ํ…Œ์ŠคํŠธ ์ค‘
1a6fb12
# portfolio/npc_social_network/npc/npc_manager.py
import random
from typing import TYPE_CHECKING, List, Optional
from .. import simulation_core
if TYPE_CHECKING:
from .npc_base import NPC
def get_korean_postposition(name, first_char, second_char):
"""์ด๋ฆ„์˜ ๋งˆ์ง€๋ง‰ ๊ธ€์ž ๋ฐ›์นจ ์œ ๋ฌด์— ๋”ฐ๋ผ ์˜ฌ๋ฐ”๋ฅธ ์กฐ์‚ฌ๋ฅผ ๋ฐ˜ํ™˜"""
if (ord(name[-1]) - 0xAC00) % 28 > 0:
return first_char
else:
return second_char
class NPCManager:
"""๋ชจ๋“  NPC๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์ค‘์•™ ๊ด€๋ฆฌ์ž"""
def __init__(self):
self.npcs: list['NPC'] = []
self.npc_dict: dict[str, 'NPC'] = {}
self.korean_name_to_npc: dict[str, 'NPC'] = {} # ํ•œ๊ธ€ ์ด๋ฆ„ ๊ฒ€์ƒ‰์šฉ ๋”•์…”๋„ˆ๋ฆฌ
self.player_is_active = True # ํ”Œ๋ ˆ์ด์–ด ํ™œ์„ฑํ™” ์ƒํƒœ ํ”Œ๋ž˜๊ทธ
def add_npc(self, npc: 'NPC'):
"""NPC๋ฅผ ๋งค๋‹ˆ์ €์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค."""
if npc.name not in self.npc_dict:
self.npcs.append(npc)
self.npc_dict[npc.name] = npc
self.korean_name_to_npc[npc.korean_name] = npc
npc.manager = self
def set_player_active(self, is_active: bool):
"""ํ”Œ๋ ˆ์ด์–ด์˜ ํ™œ์„ฑํ™” ์ƒํƒœ๋ฅผ ์„ค์ •"""
self.player_is_active = is_active
def get_all_npcs_except_player(self) -> List['NPC']:
"""ํ”Œ๋ ˆ์ด์–ด๋ฅผ ์ œ์™ธํ•œ ์ˆœ์ˆ˜ NPC ๋ชฉ๋ก์„ ๋ฐ˜ํ™˜"""
return [npc for npc in self.npcs if npc.name != "player"]
def get_interactive_npcs(self) -> List['NPC']:
"""ํ˜„์žฌ ์ƒํ™”์ž‘์šฉ์ด ๊ฐ€๋Šฅํ•œ NPC ๋ชฉ๋ก์„ ๋ฐ˜ํ™˜"""
if self.player_is_active:
return self.npcs # ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ํ™œ์„ฑํ™” ์ƒํƒœ์ด๋ฉด ๋ชจ๋‘ ๋ฐ˜ํ™˜
else:
return self.get_all_npcs_except_player() # ๋น„ํ™œ์„ฑํ™” ์ƒํƒœ์ด๋ฉด ํ”Œ๋ ˆ์ด์–ด ์ œ์™ธ
def get_npc_by_name(self, name: str) -> Optional['NPC']:
"""
NPC ์˜์–ด ID๋ฅผ ํ†ตํ•ด์„œ NPC์˜ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜
"""
return self.npc_dict.get(name)
def get_npc_by_korean_name(self, korean_name: str) -> Optional['NPC']:
"""NPC ํ•œ๊ธ€ ์ด๋ฆ„์„ ํ†ตํ•ด์„œ NPC์˜ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜"""
return self.korean_name_to_npc.get(korean_name)
def get_random_npc(self, exclude: Optional['NPC']=None) -> Optional['NPC']:
"""
ํŠน์ • NPC๋ฅผ ์ œ์™ธํ•˜๊ณ  ๋žœ๋คํ•œ NPC๋ฅผ ์„ ํƒ
์ˆ˜์ • ํ•„์š”: ์ƒ๋Œ€ NPC๊ฐ€ ๋žœ๋ค์ด ์•„๋‹ˆ๋ผ, ์ƒํ˜ธ์ž‘์šฉํ•  ๋งŒํ•œ ๊ทผ๊ฑฐ๊ฐ€ ์žˆ์–ด์•ผํ•œ๋‹ค.
์˜ˆ) ๊ทผ์ฒ˜์— ์‚ฐ๋‹ค, ํŠน๋ณ„ํ•œ ์ด๋ฒคํŠธ๊ฐ€ ์žˆ์—ˆ๋‹ค ๋“ฑ ๊ณผ ๊ฐ™์€ ์ด์œ 
"""
possible_targets = [n for n in self.npcs if n != exclude]
if not possible_targets:
return None
return random.choice(possible_targets)
def all(self) -> List['NPC']:
"""๊ด€๋ฆฌ ์ค‘์ธ ๋ชจ๋“  NPC์˜ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค."""
return self.npcs
def initiate_npc_to_npc_interaction(self, initiator: 'NPC', target: 'NPC',
time_context: str, topic: Optional[str] = None):
"""
NPC ๊ฐ„์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ์‹œ์ž‘์‹œํ‚ค๋Š” ํ•จ์ˆ˜
- ์ฐธ์—ฌํ•œ NPC๋ฅผ ๋ฐ˜ํ™˜ํ•ด์„œ ์†Œ๋ฌธ์„ ํผํŠธ๋ฆด '๋ชฉ๊ฒฉ์ž' ์ƒ์„ฑ
- ์ฃผ์ œ๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ, ๋ชฉํ‘œ ์ง€ํ–ฅ์  ๋Œ€ํ™”๋ฅผ ์ƒ์„ฑ
"""
from ..models.llm_helper import query_llm_with_prompt
if len(self.npcs) < 2:
return None, None # ์ƒํ˜ธ์ž‘์šฉํ•  NPC๊ฐ€ ์ตœ์†Œ 2๋ช… ํ•„์š”
initiator_postposition = get_korean_postposition(initiator.korean_name, "์ด", "๊ฐ€")
target_postposition = get_korean_postposition(target.korean_name, "์—๊ฒŒ", "์—๊ฒŒ")
simulation_core.add_log(f"\n---[NPC ์ƒํ˜ธ์ž‘์šฉ ์ด๋ฒคํŠธ]---\n{initiator.korean_name}{initiator_postposition} {target.korean_name}{target_postposition} ์ƒํ˜ธ์ž‘์šฉ์„ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.")
if topic:
# ์ฃผ์ œ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ
prompt = f"""
# Persona
๋‹น์‹ ์€ '{initiator.korean_name}'์ž…๋‹ˆ๋‹ค.
# Context
- ๋‹น์‹ ์€ ์ง€๊ธˆ '{target.korean_name}'์™€ ๋งˆ์ฃผ์ณค์Šต๋‹ˆ๋‹ค.
- ๋‹น์‹ ๊ณผ ์ƒ๋Œ€์˜ ๊ด€๊ณ„: {initiator.relationships.get_relationship_summary(target.name)}
- ๋Œ€ํ™” ์ฃผ์ œ: "{topic}"
# Instruction
- ์ฃผ์–ด์ง„ ์ƒํ™ฉ๊ณผ ๋Œ€ํ™” ์ฃผ์ œ์— ๋งž์ถฐ, ์ƒ๋Œ€๋ฐฉ์—๊ฒŒ ๊ฑด๋„ฌ ์ž์—ฐ์Šค๋Ÿฌ์šด ์ฒซ ๋Œ€์‚ฌ **ํ•œ ๋ฌธ์žฅ๋งŒ** ์ƒ์„ฑํ•˜์„ธ์š”.
- **์ ˆ๋Œ€๋กœ** ๋‹ค๋ฅธ ๋ถ€๊ฐ€ ์„ค๋ช…, ์ด์œ , ์ฃผ์„ ๋“ฑ์„ ํฌํ•จํ•˜์ง€ ๋งˆ์„ธ์š”.
- ๋‹น์‹ ์˜ ์‘๋‹ต์€ ์˜ค์ง ์ƒ๋Œ€๋ฐฉ์—๊ฒŒ ๋งํ•  ๋Œ€์‚ฌ ๋‚ด์šฉ ๊ทธ ์ž์ฒด์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
{initiator.korean_name}:
"""
else:
# ์ฃผ์ œ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ (๊ธฐ์กด์˜ ๊ฐ€๋ฒผ์šด ์ธ์‚ฌ)
prompt = f"""
# Persona
๋‹น์‹ ์€ "{initiator.korean_name}"์ž…๋‹ˆ๋‹ค.
# Context
- ๋‹น์‹ ์€ ์ง€๊ธˆ "{target.korean_name}"์™€ ๋งˆ์ฃผ์ณค์Šต๋‹ˆ๋‹ค.
- ๋‹น์‹ ๊ณผ ์ƒ๋Œ€์˜ ๊ด€๊ณ„: {initiator.relationships.get_relationship_summary(target.name)}
# Instruction
- ์ฃผ์–ด์ง„ ์ƒํ™ฉ์— ๋งž์ถฐ, ์ƒ๋Œ€๋ฐฉ์—๊ฒŒ ๊ฑด๋„ฌ ์ž์—ฐ์Šค๋Ÿฝ๊ณ  ๊ฐ€๋ฒผ์šด ์ฒซ ์ธ์‚ฌ ๋Œ€์‚ฌ **ํ•œ ๋ฌธ์žฅ๋งŒ** ์ƒ์„ฑํ•˜์„ธ์š”.
- **์ ˆ๋Œ€๋กœ** ๋‹ค๋ฅธ ๋ถ€๊ฐ€ ์„ค๋ช…, ์ด์œ , ์ฃผ์„ ๋“ฑ์„ ํฌํ•จํ•˜์ง€ ๋งˆ์„ธ์š”.
- ๋‹น์‹ ์˜ ์‘๋‹ต์€ ์˜ค์ง ์ƒ๋Œ€๋ฐฉ์—๊ฒŒ ๋งํ•  ๋Œ€์‚ฌ ๋‚ด์šฉ ๊ทธ ์ž์ฒด์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
{initiator.korean_name}:
"""
initial_utterance = query_llm_with_prompt(prompt).strip()
# ๋”ฐ์˜ดํ‘œ๋„ ์ œ๊ฑฐ
clean_utterance = initial_utterance.strip('"')
# ์ด๋ฆ„ ์ ‘๋‘์‚ฌ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ์ œ๊ฑฐ
name_prefix = f"{initiator.korean_name}:"
if clean_utterance.startswith(name_prefix):
clean_utterance = clean_utterance[len(name_prefix):].strip()
# ๋”ฐ์˜ดํ‘œ๋„ ์ œ๊ฑฐ
final_utterance = clean_utterance.strip().strip('"')
if "[LLM Error]" in final_utterance or not final_utterance:
simulation_core.add_log("[Error: {initiator.korean_name} ๋Œ€ํ™” ์‹œ์ž‘ ์‹คํŒจ]")
print(f"[{initiator.korean_name}] ๋Œ€ํ™” ์‹œ์ž‘์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.")
return None, None, None
simulation_core.add_log(f"[{initiator.korean_name}]: {final_utterance}")
# 2. Target์ด Initiator์˜ ๋ง์„ ๋“ฃ๊ณ  ์‘๋‹ต ์ƒ์„ฑ
# generate_dialogue ํ•จ์ˆ˜๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๋˜, target_npc ์ธ์ž๋ฅผ ์ „๋‹ฌ
response_utterance = target.generate_dialogue(
user_input=final_utterance,
time_context = time_context,
target_npc=initiator
)
simulation_core.add_log(f"[{target.korean_name}]: {response_utterance}")
# 3. ๋Œ€ํ™”๊ฐ€ ๋๋‚œ ํ›„, ๋ชฉ๊ฒฉ์ž๋ฅผ ์„ ์ •ํ•˜์—ฌ ์†Œ๋ฌธ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
potential_witnesses = [npc for npc in self.npcs if npc not in [initiator, target]]
if potential_witnesses and random.random() < 0.25: # 25% ํ™•๋ฅ ๋กœ ๋ชฉ๊ฒฉ์ž ๋ฐœ์ƒ
witness = random.choice(potential_witnesses)
initiator_postposition = get_korean_postposition(initiator.korean_name, "์ด", "๊ฐ€")
target_postposition = get_korean_postposition(target.korean_name, "์—๊ฒŒ", "์—๊ฒŒ")
witness_postposition = get_korean_postposition(witness.korean_name, "์ด", "๊ฐ€")
gossip_content = f"'{initiator.korean_name}'{initiator_postposition} '{target.korean_name}'{target_postposition} '{initial_utterance[:100]}...'๋ผ๊ณ  ๋งํ•˜๋Š” ๊ฒƒ์„ ๋ดค๋‹ค."
# ๋ชฉ๊ฒฉ์ž๊ฐ€ '์†Œ๋ฌธ'์„ ๊ธฐ์–ตํ•˜๋„๋ก ํ•จ
witness.remember(
content=gossip_content,
importance=4,
emotion="curiosity",
memory_type="Gossip",
)
# ๋กœ๊ทธ๋Š” simulation_core์˜ add_log๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ print๋กœ ๋Œ€์ฒด
simulation_core.add_log(f"[๋ชฉ๊ฒฉ] {witness.korean_name}{witness_postposition} {initiator.korean_name}์™€ {target.korean_name}์˜ ๋Œ€ํ™”๋ฅผ ๋ชฉ๊ฒฉํ•จ.")
return initiator, target, final_utterance