Spaces:
Running
Running
humanda5
commited on
Commit
·
0b572d3
1
Parent(s):
b5ff045
멀티턴 대화 시스템 구현 - 진행중
Browse files- npc_social_network/data/saves/simulation_state.pkl +0 -0
- npc_social_network/data/vectorstores/diana.faiss +0 -0
- npc_social_network/manager/conversation_manager.py +97 -0
- npc_social_network/models/llm_helper.py +34 -2
- npc_social_network/npc/npc_base.py +127 -84
- npc_social_network/simulation_core.py +14 -5
npc_social_network/data/saves/simulation_state.pkl
CHANGED
Binary files a/npc_social_network/data/saves/simulation_state.pkl and b/npc_social_network/data/saves/simulation_state.pkl differ
|
|
npc_social_network/data/vectorstores/diana.faiss
CHANGED
Binary files a/npc_social_network/data/vectorstores/diana.faiss and b/npc_social_network/data/vectorstores/diana.faiss differ
|
|
npc_social_network/manager/conversation_manager.py
ADDED
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# portfolio/npc_social_network/manager/conversation_manager.py
|
2 |
+
# 대화의 시작, 진행, 종료를 총괄하는 역할
|
3 |
+
from .. import simulation_core
|
4 |
+
from typing import List, Optional, TYPE_CHECKING
|
5 |
+
if TYPE_CHECKING:
|
6 |
+
from ..npc.npc_base import NPC
|
7 |
+
|
8 |
+
class Conversation:
|
9 |
+
"""단일 대화의 상태를 저장하는 데이터 클래스"""
|
10 |
+
def __init__(self, initiator: "NPC", target: "NPC", topic: str):
|
11 |
+
self.participants: List["NPC"] = [initiator, target]
|
12 |
+
self.conversation_history: List[str] = []
|
13 |
+
self.topic: str = topic
|
14 |
+
self.turn_index: int = 0 # 0: initiator, 1: target
|
15 |
+
|
16 |
+
def add_utterance(self, speaker: "NPC", utterance: str):
|
17 |
+
"""대화 내역을 추가"""
|
18 |
+
self.conversation_history.append(f"[{speaker.korean_name}]: {utterance}")
|
19 |
+
|
20 |
+
def get_current_speaker(self) -> "NPC":
|
21 |
+
"""이번에 말할 차례인 NPC를 반환"""
|
22 |
+
return self.participants[self.turn_index]
|
23 |
+
|
24 |
+
def switch_turn(self):
|
25 |
+
"""대화 턴을 상대방에게 넘김"""
|
26 |
+
self.turn_index = 1 - self.turn_index
|
27 |
+
|
28 |
+
class ConversationManager:
|
29 |
+
"""활성화된 대화를 관리하는 중앙 관리자"""
|
30 |
+
def __init__(self):
|
31 |
+
self.active_conversation: Optional[Conversation] = None
|
32 |
+
|
33 |
+
def is_conversation_active(self) -> bool:
|
34 |
+
"""현재 진행 중인 대화가 있는지 확인"""
|
35 |
+
return self.active_conversation is not None
|
36 |
+
|
37 |
+
def start_conversation(self, initiator: "NPC", target: "NPC",
|
38 |
+
topic: Optional[str] = None):
|
39 |
+
"""새로운 대화를 시작"""
|
40 |
+
if self.is_conversation_active():
|
41 |
+
return # 이미 다른 대화가 진행중이면 시작 x
|
42 |
+
|
43 |
+
conv_topic = topic or "가벼운 인사"
|
44 |
+
self.active_conversation = Conversation(initiator, target, conv_topic)
|
45 |
+
print(f"--[대화 시작]--")
|
46 |
+
print(f"주제: {conv_topic} / 참여자: {initiator.korean_name}, {target.korean_name}")
|
47 |
+
|
48 |
+
# 1. 첫 마디 생성
|
49 |
+
first_utterance = initiator.generate_first_utterance(conv_topic, target)
|
50 |
+
if "..." in first_utterance:
|
51 |
+
self.end_conversation("첫 마디 생성 실패")
|
52 |
+
return
|
53 |
+
|
54 |
+
# 2. 첫 마디를 기록에 추가
|
55 |
+
self.active_conversation.add_utterance(initiator, first_utterance)
|
56 |
+
|
57 |
+
# 3. 턴을 상대방에게 넘김 (이제 상대방이 대답할 차례)
|
58 |
+
self.active_conversation.switch_turn()
|
59 |
+
|
60 |
+
def end_conversation(self, reason: str = "자연스럽게"):
|
61 |
+
"""현재 대화를 종료"""
|
62 |
+
if self.is_conversation_active():
|
63 |
+
# 대화가 끝나면, 전체 대화 내용을 바탕으로 기억을 생성하고 관계를 업데이트
|
64 |
+
conv = self.active_conversation
|
65 |
+
initiator = conv.participants[0]
|
66 |
+
target = conv.participants[1]
|
67 |
+
full_conversation = "\n".join(conv.conversation_history)
|
68 |
+
|
69 |
+
with simulation_core.simulation_lock:
|
70 |
+
# 양쪽 모두에게 대화 기억을 저장
|
71 |
+
initiator.remember(content=f"{target.korean_name}와 '{conv.topic}'에 대해 대화했다:\n{full_conversation}", importance=7, memory_type="Conversation")
|
72 |
+
target.remember(content=f"{initiator.korean_name}와 '{conv.topic}'에 대해 대화했다:\n{full_conversation}", importance=7, memory_type="Conversation")
|
73 |
+
|
74 |
+
print(f"---[대화 종료: {reason}]---\n")
|
75 |
+
self.active_conversation = None
|
76 |
+
|
77 |
+
def next_turn(self):
|
78 |
+
"""대화의 다음 턴을 진행"""
|
79 |
+
if not self.is_conversation_active():
|
80 |
+
return
|
81 |
+
|
82 |
+
conv = self.active_conversation
|
83 |
+
speaker = conv.get_current_speaker()
|
84 |
+
|
85 |
+
# 대답 생성 로직 (다음 단계에서 수정)
|
86 |
+
utterance, action = speaker.generate_dialogue_turn(conv)
|
87 |
+
|
88 |
+
if "[LLM Error]" in utterance:
|
89 |
+
self.end_conversation("오류 발생")
|
90 |
+
return
|
91 |
+
|
92 |
+
conv.add_utterance(speaker, utterance)
|
93 |
+
|
94 |
+
if action == "END" or len(conv.conversation_history) >= 10: #대화가 너무 길어지면 강제 종료
|
95 |
+
self.end_conversation()
|
96 |
+
else:
|
97 |
+
conv.switch_turn()
|
npc_social_network/models/llm_helper.py
CHANGED
@@ -114,7 +114,7 @@ Action Types can be "TALK" (for conversing with others) or "SOLO_ACTION" (for ac
|
|
114 |
json_match = re.search(r'\{.*\}', response_text, re.DOTALL)
|
115 |
|
116 |
if json_match:
|
117 |
-
return json.
|
118 |
|
119 |
# 분석 실패 시 혼자 하는 행동으로 간주
|
120 |
return {"action_type": "SOLO_ACTION", "target": None, "topic": plan_step}
|
@@ -173,4 +173,36 @@ Your response MUST be a valid JSON array of strings, where each string is a step
|
|
173 |
result = _query_llm_for_json_robustly(prompt)
|
174 |
if isinstance(result, list) and all(isinstance(s, str) for s in result):
|
175 |
return result
|
176 |
-
return ["[계획 생성에 실패했습니다]"] # 최종 실패 시 안전한 값 반환
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
114 |
json_match = re.search(r'\{.*\}', response_text, re.DOTALL)
|
115 |
|
116 |
if json_match:
|
117 |
+
return json.loads(json_match.group())
|
118 |
|
119 |
# 분석 실패 시 혼자 하는 행동으로 간주
|
120 |
return {"action_type": "SOLO_ACTION", "target": None, "topic": plan_step}
|
|
|
173 |
result = _query_llm_for_json_robustly(prompt)
|
174 |
if isinstance(result, list) and all(isinstance(s, str) for s in result):
|
175 |
return result
|
176 |
+
return ["[계획 생성에 실패했습니다]"] # 최종 실패 시 안전한 값 반환
|
177 |
+
|
178 |
+
def generate_dialogue_action(speaker_name: str, conversation_history: List[str], topic: str) -> dict:
|
179 |
+
"""대화 기록을 바탕으로 다음 대사와 행동(계속/종료)을 생성"""
|
180 |
+
history_str = "\n".join(conversation_history)
|
181 |
+
|
182 |
+
prompt = f"""
|
183 |
+
# Persona
|
184 |
+
당신은 "{speaker_name}"입니다.
|
185 |
+
|
186 |
+
# Context
|
187 |
+
- 현재 대화 주제: "{topic}"
|
188 |
+
- 지금까지의 대화 내용:
|
189 |
+
{history_str}
|
190 |
+
|
191 |
+
# Instruction
|
192 |
+
- 위 대화의 흐름과 주제에 맞춰, 당신이 이번 턴에 할 자연스러운 대사를 생성하셍.
|
193 |
+
- 그리고, 이 대화를 계속 이어갈지("CONTINUE"), 아니면 이 대사를 끝으로 대화를 마무리할지("END") 결정하세요.
|
194 |
+
- 당신의 응답은 반드시 아래 JSON 형식이어야 합니다.
|
195 |
+
- 대사에는 이름, 행동 묘사, 따옴표를 절대 포함하지 마세요.
|
196 |
+
|
197 |
+
# Response (JSON format only)
|
198 |
+
{{
|
199 |
+
"dialogue": "실제 말할 대사 내용",
|
200 |
+
"action": "CONTINUE or END"
|
201 |
+
}}
|
202 |
+
"""
|
203 |
+
result = _query_llm_for_json_robustly(prompt) # 3중 방어 시스템 재활용
|
204 |
+
|
205 |
+
# 결과 포맷 검증 및 기본값 처리
|
206 |
+
if isinstance(result, dict) and "dialogue" in result and "action" in result:
|
207 |
+
return result
|
208 |
+
return {"dialogue": "...", "action": "END"} # 최종 실패 시 안전하게 대화 종료
|
npc_social_network/npc/npc_base.py
CHANGED
@@ -11,7 +11,8 @@ from .emotion_config import EMOTION_LIST, EMOTION_CATEGORY_MAP, EMOTION_DECAY_RA
|
|
11 |
from .emotion_config import POSITIVE_RELATION_EMOTIONS, NEGATIVE_RELATION_EMOTIONS, COGNITIVE_RELATION_EMOTIONS
|
12 |
from .personality_config import AGE_PROFILE, PERSONALITY_PROFILE
|
13 |
from .npc_relationship import RelationshipManager
|
14 |
-
from ..models.llm_helper import query_llm_with_prompt, query_llm_for_emotion, summarize_memories
|
|
|
15 |
from ..models.llm_prompt_builder import build_npc_prompt
|
16 |
from .npc_memory_embedder import search_similar_memories, add_memory_to_index
|
17 |
from .npc_planner import PlannerManager
|
@@ -141,91 +142,133 @@ class NPC:
|
|
141 |
self.personality_stage = None
|
142 |
self.update_personality_stage()
|
143 |
|
144 |
-
def
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
- user_input: 입력 문장 (플레이어나 NPC로부터)
|
150 |
-
- target_npc: NPC 간 대화일 경우 상대 NPC
|
151 |
-
"""
|
152 |
-
if use_llm and user_input:
|
153 |
-
# 1. 생각하기
|
154 |
-
# 1-1. 유사 기억 검색
|
155 |
-
_, _, matched_memories = search_similar_memories(self, user_input)
|
156 |
-
|
157 |
-
# 1-2. LLM 프롬프트 생성 및 호출
|
158 |
-
prompt = build_npc_prompt(self, user_input, matched_memories, time_context, target_npc)
|
159 |
-
npc_reply = query_llm_with_prompt(prompt)
|
160 |
-
|
161 |
-
if not npc_reply or "[LLM Error]" in npc_reply:
|
162 |
-
return "[LLM Error]..." # 에러 시 기본 응답
|
163 |
-
|
164 |
-
# 1-3. 자신의 응답에서 감정 추론 및 상태 업데이트
|
165 |
-
emotion_from_reply = query_llm_for_emotion(npc_reply)
|
166 |
-
# 1-4. 상대방의 말에서 감정 추론 (NPC간 대화일 경우)
|
167 |
-
emotion_from_input = query_llm_for_emotion(user_input) if target_npc else None
|
168 |
-
|
169 |
-
# 2. 행동하기
|
170 |
-
if create_memory:
|
171 |
-
with simulation_core.simulation_lock:
|
172 |
-
# 2-1. 기억 요약 및 장기 기억화
|
173 |
-
if len(matched_memories) >= 3:
|
174 |
-
self.summarize_and_store_memories(matched_memories)
|
175 |
-
|
176 |
-
# 2-2. 추론한 감정들을 상태에 업데이트
|
177 |
-
if emotion_from_reply and emotion_from_reply in EMOTION_LIST:
|
178 |
-
self.update_emotion(emotion_from_reply , strength=2.0)
|
179 |
-
|
180 |
-
# 2-3. 상호작용 기억 저장
|
181 |
-
interlocutor_name = target_npc.name if target_npc else "player"
|
182 |
-
interlocutor_korean_name = target_npc.korean_name if target_npc else "플레이어"
|
183 |
-
memory_content = f"[{interlocutor_korean_name}의 말] '{user_input}' → [나의 응답] '{npc_reply}'"
|
184 |
-
memory = self.remember(content=memory_content, importance=7, emotion=emotion_from_reply , context_tags=[time_context])
|
185 |
-
|
186 |
-
# 2-4. 관계 상태 업데이트
|
187 |
-
if emotion_from_reply and emotion_from_reply in EMOTION_RELATION_IMPACT:
|
188 |
-
self.relationships.update_relationship(interlocutor_name, emotion_from_reply , strength=2.0, memory=memory)
|
189 |
-
if target_npc and emotion_from_input and emotion_from_input in EMOTION_RELATION_IMPACT:
|
190 |
-
target_npc.relationships.update_relationship(self.name, emotion_from_input, strength=2.0, memory=memory)
|
191 |
-
|
192 |
-
# 2-5. 성격 변화 반영
|
193 |
-
self.update_personality()
|
194 |
-
|
195 |
-
return npc_reply
|
196 |
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
)
|
202 |
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
if emotion_name:
|
207 |
-
self.update_emotion(emotion_name, strength=score)
|
208 |
-
|
209 |
-
# dominant_score 기반 중요도 추정
|
210 |
-
dominant_emotions = self.behavior.get_layer_dominant_emotions(self._emotion_buffer)
|
211 |
-
dominant_sequence = self.behavior.decide_layered_sequence(dominant_emotions)
|
212 |
-
dominant_score = max([score for _, score in dominant_sequence]) if dominant_sequence else 5
|
213 |
-
importance = min(int(dominant_score), 10)
|
214 |
-
|
215 |
-
# Memory 저장
|
216 |
-
memory_entry = Memory(
|
217 |
-
content=f"행동 수행: {behavior_trace}",
|
218 |
-
importance=importance,
|
219 |
-
emotion=self.emotion.get_dominant_emotion(),
|
220 |
-
behavior_trace=behavior_trace,
|
221 |
-
context_tags=[time_context]
|
222 |
-
)
|
223 |
-
self.memory_store.add_memory(memory_entry)
|
224 |
|
225 |
-
|
226 |
-
|
|
|
|
|
227 |
|
228 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
229 |
|
230 |
def remember(self, content: str, importance: int = 5, emotion: str = None,
|
231 |
strength: float = 1.0, memory_type:str = "Event",
|
@@ -451,7 +494,7 @@ class NPC:
|
|
451 |
target_npc = self.manager.get_npc_by_korean_name(target_korean_name)
|
452 |
if target_npc and target_npc != self:
|
453 |
# 주제가 있는 대화 시작
|
454 |
-
|
455 |
else:
|
456 |
print(f"[{self.korean_name}] '{target_korean_name}'를 찾을 수 없어 대화 계획을 실행하지 못했습니다.")
|
457 |
else:
|
@@ -520,7 +563,7 @@ class NPC:
|
|
520 |
target_npc = random.choices(potential_targets, weights=weights, k=1)[0]
|
521 |
|
522 |
# 주제가 없는 가벼운 대화 시작
|
523 |
-
|
524 |
|
525 |
def create_symbolic_memory(self):
|
526 |
"""
|
|
|
11 |
from .emotion_config import POSITIVE_RELATION_EMOTIONS, NEGATIVE_RELATION_EMOTIONS, COGNITIVE_RELATION_EMOTIONS
|
12 |
from .personality_config import AGE_PROFILE, PERSONALITY_PROFILE
|
13 |
from .npc_relationship import RelationshipManager
|
14 |
+
from ..models.llm_helper import query_llm_with_prompt, query_llm_for_emotion, summarize_memories
|
15 |
+
from ..models.llm_helper import analyze_gossip, classify_and_extract_plan_details, generate_dialogue_action
|
16 |
from ..models.llm_prompt_builder import build_npc_prompt
|
17 |
from .npc_memory_embedder import search_similar_memories, add_memory_to_index
|
18 |
from .npc_planner import PlannerManager
|
|
|
142 |
self.personality_stage = None
|
143 |
self.update_personality_stage()
|
144 |
|
145 |
+
def generate_first_utterance(self, topic: str, target_npc: "NPC") -> str:
|
146 |
+
"""대화의 첫 마디를 생성. (대화 기록이 필요 없음)"""
|
147 |
+
prompt = f"""
|
148 |
+
# Persona
|
149 |
+
당신은 "{self.korean_name}"입니다.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
150 |
|
151 |
+
# Context
|
152 |
+
- 당신은 지금 "{target_npc.korean_name}"에게 말을 걸려고 합니다.
|
153 |
+
- 당신과 상대의 관계: {self.relationships.get_relationship_summary(target_npc.name)}
|
154 |
+
- 대화 주제: "{topic}"
|
|
|
155 |
|
156 |
+
# Instruction
|
157 |
+
- 주어진 상황과 대화 주제에 맞춰, 상대방에게 건넬 자연스러운 첫 대사 **한 문장만** 생성하세요.
|
158 |
+
- **절대로** 당신의 이름, 행동 묘사, 주석, 따옴표를 포함하지 마세요.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
159 |
|
160 |
+
{self.korean_name}:
|
161 |
+
"""
|
162 |
+
utterance = query_llm_with_prompt(prompt).strip().strip('"')
|
163 |
+
return utterance if utterance and "[LLM Error]" not in utterance else "..."
|
164 |
|
165 |
+
def generate_dialogue_turn(self, conversation: "Conversation") -> (str, str):
|
166 |
+
"""대화의 현재 턴에 대한 응답과 행동을 생성"""
|
167 |
+
|
168 |
+
# 생각하기
|
169 |
+
result = generate_dialogue_action(
|
170 |
+
self.korean_name,
|
171 |
+
conversation.conversation_history,
|
172 |
+
conversation.topic
|
173 |
+
)
|
174 |
+
dialogue = result.get("dialogue", "...")
|
175 |
+
action = result.get("action", "END")
|
176 |
+
|
177 |
+
# 행동하기 (잠금과 함께)
|
178 |
+
# 이 부분은 대화가 끝난 후, 전체 대화 내용을 바탕으로 기억/관계를 한 번에 업데이트 하도록 향후 수정.
|
179 |
+
# 지금은 매 턴 감정만 업데이트
|
180 |
+
with simulation_core.simulation_lock:
|
181 |
+
emotion = query_llm_for_emotion(dialogue)
|
182 |
+
if emotion and emotion in EMOTION_LIST:
|
183 |
+
self.update_emotion(emotion, 1.0)
|
184 |
+
|
185 |
+
return dialogue, action
|
186 |
+
|
187 |
+
# def generate_dialogue(self, user_input: str, time_context: str
|
188 |
+
# , target_npc: Optional["NPC"]=None, use_llm: bool=True
|
189 |
+
# , create_memory: bool = True) -> str:
|
190 |
+
# """
|
191 |
+
# 플레이어 또는 NPC와 상호작용 시 사용되는 통합 대사 생성 함수
|
192 |
+
# - user_input: 입력 문장 (플레이어나 NPC로부터)
|
193 |
+
# - target_npc: NPC 간 대화일 경우 상대 NPC
|
194 |
+
# """
|
195 |
+
# if use_llm and user_input:
|
196 |
+
# # 1. 생각하기
|
197 |
+
# # 1-1. 유사 기억 검색
|
198 |
+
# _, _, matched_memories = search_similar_memories(self, user_input)
|
199 |
+
|
200 |
+
# # 1-2. LLM 프롬프트 생성 및 호출
|
201 |
+
# prompt = build_npc_prompt(self, user_input, matched_memories, time_context, target_npc)
|
202 |
+
# npc_reply = query_llm_with_prompt(prompt)
|
203 |
+
|
204 |
+
# if not npc_reply or "[LLM Error]" in npc_reply:
|
205 |
+
# return "[LLM Error]..." # 에러 시 기본 응답
|
206 |
+
|
207 |
+
# # 1-3. 자신의 응답에서 감정 추론 및 상태 업데이트
|
208 |
+
# emotion_from_reply = query_llm_for_emotion(npc_reply)
|
209 |
+
# # 1-4. 상대방의 말에서 감정 추론 (NPC간 대화일 경우)
|
210 |
+
# emotion_from_input = query_llm_for_emotion(user_input) if target_npc else None
|
211 |
+
|
212 |
+
# # 2. 행동하기
|
213 |
+
# if create_memory:
|
214 |
+
# with simulation_core.simulation_lock:
|
215 |
+
# # 2-1. 기억 요약 및 장기 기억화
|
216 |
+
# if len(matched_memories) >= 3:
|
217 |
+
# self.summarize_and_store_memories(matched_memories)
|
218 |
+
|
219 |
+
# # 2-2. 추론한 감정들을 상태에 업데이트
|
220 |
+
# if emotion_from_reply and emotion_from_reply in EMOTION_LIST:
|
221 |
+
# self.update_emotion(emotion_from_reply , strength=2.0)
|
222 |
+
|
223 |
+
# # 2-3. 상호작용 기억 저장
|
224 |
+
# interlocutor_name = target_npc.name if target_npc else "player"
|
225 |
+
# interlocutor_korean_name = target_npc.korean_name if target_npc else "플레이어"
|
226 |
+
# memory_content = f"[{interlocutor_korean_name}의 말] '{user_input}' → [나의 응답] '{npc_reply}'"
|
227 |
+
# memory = self.remember(content=memory_content, importance=7, emotion=emotion_from_reply , context_tags=[time_context])
|
228 |
+
|
229 |
+
# # 2-4. 관계 상태 업데이트
|
230 |
+
# if emotion_from_reply and emotion_from_reply in EMOTION_RELATION_IMPACT:
|
231 |
+
# self.relationships.update_relationship(interlocutor_name, emotion_from_reply , strength=2.0, memory=memory)
|
232 |
+
# if target_npc and emotion_from_input and emotion_from_input in EMOTION_RELATION_IMPACT:
|
233 |
+
# target_npc.relationships.update_relationship(self.name, emotion_from_input, strength=2.0, memory=memory)
|
234 |
+
|
235 |
+
# # 2-5. 성격 변화 반영
|
236 |
+
# self.update_personality()
|
237 |
+
|
238 |
+
# return npc_reply
|
239 |
+
|
240 |
+
# else:
|
241 |
+
# # LLM 미사용 시: 감정 기반 행동 생성
|
242 |
+
# behavior_output, behavior_trace = self.behavior.perform_sequence(
|
243 |
+
# self.name, self.job, emotion_buffer=self._emotion_buffer, return_trace=True
|
244 |
+
# )
|
245 |
+
|
246 |
+
# # 행동 기반 감정 추론 및 반영
|
247 |
+
# for action, score in behavior_trace:
|
248 |
+
# emotion_name = self.behavior.action_to_emotion.get(action)
|
249 |
+
# if emotion_name:
|
250 |
+
# self.update_emotion(emotion_name, strength=score)
|
251 |
+
|
252 |
+
# # dominant_score 기반 중요도 추정
|
253 |
+
# dominant_emotions = self.behavior.get_layer_dominant_emotions(self._emotion_buffer)
|
254 |
+
# dominant_sequence = self.behavior.decide_layered_sequence(dominant_emotions)
|
255 |
+
# dominant_score = max([score for _, score in dominant_sequence]) if dominant_sequence else 5
|
256 |
+
# importance = min(int(dominant_score), 10)
|
257 |
+
|
258 |
+
# # Memory 저장
|
259 |
+
# memory_entry = Memory(
|
260 |
+
# content=f"행동 수행: {behavior_trace}",
|
261 |
+
# importance=importance,
|
262 |
+
# emotion=self.emotion.get_dominant_emotion(),
|
263 |
+
# behavior_trace=behavior_trace,
|
264 |
+
# context_tags=[time_context]
|
265 |
+
# )
|
266 |
+
# self.memory_store.add_memory(memory_entry)
|
267 |
+
|
268 |
+
# # 성격 업데이트
|
269 |
+
# self.update_personality()
|
270 |
+
|
271 |
+
# return str(behavior_output)
|
272 |
|
273 |
def remember(self, content: str, importance: int = 5, emotion: str = None,
|
274 |
strength: float = 1.0, memory_type:str = "Event",
|
|
|
494 |
target_npc = self.manager.get_npc_by_korean_name(target_korean_name)
|
495 |
if target_npc and target_npc != self:
|
496 |
# 주제가 있는 대화 시작
|
497 |
+
simulation_core.conversation_manager.start_conversation(self, target_npc, topic=topic)
|
498 |
else:
|
499 |
print(f"[{self.korean_name}] '{target_korean_name}'를 찾을 수 없어 대화 계획을 실행하지 못했습니다.")
|
500 |
else:
|
|
|
563 |
target_npc = random.choices(potential_targets, weights=weights, k=1)[0]
|
564 |
|
565 |
# 주제가 없는 가벼운 대화 시작
|
566 |
+
simulation_core.conversation_manager.start_conversation(self, target_npc)
|
567 |
|
568 |
def create_symbolic_memory(self):
|
569 |
"""
|
npc_social_network/simulation_core.py
CHANGED
@@ -7,6 +7,9 @@ import random
|
|
7 |
from .npc.npc_manager import NPCManager
|
8 |
from .scenarios.scenario_setup import setup_initial_scenario
|
9 |
from .manager.simulation_manager import save_simulation, load_simulation
|
|
|
|
|
|
|
10 |
|
11 |
#------------------------------------------
|
12 |
# 1. 시뮬레이션 상태 (전역 변수)
|
@@ -14,8 +17,9 @@ from .manager.simulation_manager import save_simulation, load_simulation
|
|
14 |
#------------------------------------------
|
15 |
npc_manager: NPCManager = None
|
16 |
simulation_paused = True
|
17 |
-
event_log = []
|
18 |
simulation_lock = threading.Lock() # 데이터 동시 접근을 막는 잠금장치
|
|
|
19 |
|
20 |
#------------------------------------------
|
21 |
# 2. 시뮬레이션 로직 함수
|
@@ -45,7 +49,7 @@ def tick_simulation():
|
|
45 |
with simulation_lock:
|
46 |
npc.decay_emotions()
|
47 |
npc.decay_memories()
|
48 |
-
if random.random() < 0.
|
49 |
npc.update_autonomous_behavior("자율 행동 시간")
|
50 |
|
51 |
def simulation_loop():
|
@@ -57,7 +61,11 @@ def simulation_loop():
|
|
57 |
tick_counter = 0
|
58 |
|
59 |
while True:
|
60 |
-
|
|
|
|
|
|
|
|
|
61 |
tick_simulation()
|
62 |
|
63 |
# 틱이 발생할 때마다 카운터 증가
|
@@ -67,12 +75,13 @@ def simulation_loop():
|
|
67 |
save_simulation(npc_manager)
|
68 |
tick_counter = 0
|
69 |
|
70 |
-
time.sleep(5) # 5초에 한 번씩 틱 발생
|
71 |
|
72 |
def initialize_simulation():
|
73 |
"""서버 시작 시 시뮬레이션을 초기화합니다."""
|
74 |
-
global npc_manager
|
75 |
npc_manager = load_simulation()
|
|
|
76 |
if npc_manager is None:
|
77 |
npc_manager = setup_initial_scenario()
|
78 |
save_simulation(npc_manager)
|
|
|
7 |
from .npc.npc_manager import NPCManager
|
8 |
from .scenarios.scenario_setup import setup_initial_scenario
|
9 |
from .manager.simulation_manager import save_simulation, load_simulation
|
10 |
+
from .manager.conversation_manager import ConversationManager
|
11 |
+
|
12 |
+
from typing import List, Optional
|
13 |
|
14 |
#------------------------------------------
|
15 |
# 1. 시뮬레이션 상태 (전역 변수)
|
|
|
17 |
#------------------------------------------
|
18 |
npc_manager: NPCManager = None
|
19 |
simulation_paused = True
|
20 |
+
event_log:List[str] = []
|
21 |
simulation_lock = threading.Lock() # 데이터 동시 접근을 막는 잠금장치
|
22 |
+
conversation_manager: Optional[ConversationManager] = None
|
23 |
|
24 |
#------------------------------------------
|
25 |
# 2. 시뮬레이션 로직 함수
|
|
|
49 |
with simulation_lock:
|
50 |
npc.decay_emotions()
|
51 |
npc.decay_memories()
|
52 |
+
if random.random() < 0.1:
|
53 |
npc.update_autonomous_behavior("자율 행동 시간")
|
54 |
|
55 |
def simulation_loop():
|
|
|
61 |
tick_counter = 0
|
62 |
|
63 |
while True:
|
64 |
+
# 대화 중일 때는 대화만 진행
|
65 |
+
if conversation_manager and conversation_manager.is_conversation_active():
|
66 |
+
conversation_manager.next_turn()
|
67 |
+
|
68 |
+
elif not simulation_paused:
|
69 |
tick_simulation()
|
70 |
|
71 |
# 틱이 발생할 때마다 카운터 증가
|
|
|
75 |
save_simulation(npc_manager)
|
76 |
tick_counter = 0
|
77 |
|
78 |
+
time.sleep(5) # 5초에 한 번씩 틱 발생 (이것보다 줄이면 Gemini API 사용량 초과 발생 가능)
|
79 |
|
80 |
def initialize_simulation():
|
81 |
"""서버 시작 시 시뮬레이션을 초기화합니다."""
|
82 |
+
global npc_manager, conversation_manager
|
83 |
npc_manager = load_simulation()
|
84 |
+
conversation_manager = ConversationManager()
|
85 |
if npc_manager is None:
|
86 |
npc_manager = setup_initial_scenario()
|
87 |
save_simulation(npc_manager)
|