Spaces:
Sleeping
Sleeping
humanda5
commited on
Commit
·
c08ff6f
1
Parent(s):
e3a194a
멀티턴 대화 구현 완료
Browse files- npc_social_network/README.txt +9 -6
- npc_social_network/data/saves/simulation_state.pkl +0 -0
- npc_social_network/data/vectorstores/bob.faiss +0 -0
- npc_social_network/data/vectorstores/charlie.faiss +0 -0
- npc_social_network/data/vectorstores/elin.faiss +0 -0
- npc_social_network/manager/conversation_manager.py +32 -9
- npc_social_network/models/llm_helper.py +61 -3
- npc_social_network/models/llm_prompt_builder.py +0 -98
- npc_social_network/npc/npc_base.py +39 -110
- npc_social_network/npc/npc_manager.py +1 -1
- npc_social_network/npc/npc_relationship.py +6 -4
- npc_social_network/simulation_core.py +20 -0
npc_social_network/README.txt
CHANGED
@@ -39,13 +39,16 @@ portfolio/
|
|
39 |
├── README.txt
|
40 |
│
|
41 |
├── data/
|
42 |
-
│
|
43 |
-
│
|
|
|
|
|
44 |
│
|
45 |
├── routes/
|
46 |
│ └── npc_route.py # ✅ '월드 관찰자' 대시보드의 모든 웹 API 정의
|
47 |
│
|
48 |
├── manager/
|
|
|
49 |
│ └── simulation_manager.py # 시뮬레이션 상태를 저장/로드하는 기능
|
50 |
│
|
51 |
├── scenarios/
|
@@ -60,12 +63,12 @@ portfolio/
|
|
60 |
│ ├── npc_relationship.py # 관계 점수 관리
|
61 |
│ ├── npc_memory_embedder.py # 기억 임베딩 + FAISS 검색
|
62 |
│ ├── emotion_config.py # 감정 구조 정의
|
63 |
-
│
|
|
|
64 |
│
|
65 |
├── models/ # LLM 및 임베딩 모델 관련
|
66 |
-
│
|
67 |
-
│
|
68 |
-
│ └── llm_prompt_builder.py # 프롬프트 생성
|
69 |
│
|
70 |
├── templates/
|
71 |
│ └── dashboard.html # '월드 관찰자' 대시보드 HTML/CSS/JS
|
|
|
39 |
├── README.txt
|
40 |
│
|
41 |
├── data/
|
42 |
+
│ ├── saves/
|
43 |
+
│ │ └── simulation_state.pkl # NPC 월드의 상태가 저장되는 파일
|
44 |
+
│ └── vectorstores/
|
45 |
+
│ └── 영문 캐릭터명 # 각각의 캐릭터에 대한 정보가 저장되는 파일
|
46 |
│
|
47 |
├── routes/
|
48 |
│ └── npc_route.py # ✅ '월드 관찰자' 대시보드의 모든 웹 API 정의
|
49 |
│
|
50 |
├── manager/
|
51 |
+
│ ├── conversation_manager.py # 대화의 시작, 진행, 종료를 총괄하는 역할
|
52 |
│ └── simulation_manager.py # 시뮬레이션 상태를 저장/로드하는 기능
|
53 |
│
|
54 |
├── scenarios/
|
|
|
63 |
│ ├── npc_relationship.py # 관계 점수 관리
|
64 |
│ ├── npc_memory_embedder.py # 기억 임베딩 + FAISS 검색
|
65 |
│ ├── emotion_config.py # 감정 구조 정의
|
66 |
+
│ ├── personality_config.py # 성격 및 나이 프로필
|
67 |
+
│ └── npc_planner.py # 목표와 관련된 구조
|
68 |
│
|
69 |
├── models/ # LLM 및 임베딩 모델 관련
|
70 |
+
│ ├── gemini_setup.py # Gemini API 설정
|
71 |
+
│ └── llm_helper.py # 감정 추론, 응답 생성
|
|
|
72 |
│
|
73 |
├── templates/
|
74 |
│ └── dashboard.html # '월드 관찰자' 대시보드 HTML/CSS/JS
|
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/bob.faiss
CHANGED
Binary files a/npc_social_network/data/vectorstores/bob.faiss and b/npc_social_network/data/vectorstores/bob.faiss differ
|
|
npc_social_network/data/vectorstores/charlie.faiss
CHANGED
Binary files a/npc_social_network/data/vectorstores/charlie.faiss and b/npc_social_network/data/vectorstores/charlie.faiss differ
|
|
npc_social_network/data/vectorstores/elin.faiss
CHANGED
Binary files a/npc_social_network/data/vectorstores/elin.faiss and b/npc_social_network/data/vectorstores/elin.faiss differ
|
|
npc_social_network/manager/conversation_manager.py
CHANGED
@@ -1,7 +1,8 @@
|
|
1 |
# portfolio/npc_social_network/manager/conversation_manager.py
|
2 |
# 대화의 시작, 진행, 종료를 총괄하는 역할
|
3 |
-
from .. import
|
4 |
-
from ..
|
|
|
5 |
import threading
|
6 |
from typing import List, Optional, TYPE_CHECKING
|
7 |
if TYPE_CHECKING:
|
@@ -35,6 +36,7 @@ class ConversationManager:
|
|
35 |
|
36 |
def _add_and_log_utterance(self,speaker: "NPC", utterance: str):
|
37 |
"""대사를 대화 기록과 UI 로그에 모두 추가"""
|
|
|
38 |
if not self.is_conversation_active():
|
39 |
return
|
40 |
log_message = f"[{speaker.korean_name}]: {utterance}"
|
@@ -42,7 +44,8 @@ class ConversationManager:
|
|
42 |
simulation_core.add_log(log_message)
|
43 |
|
44 |
def _summarize_and_remember_in_background(self, conv: "Conversation"):
|
45 |
-
"""백그라운드 스레드 대화를 요약하고
|
|
|
46 |
try:
|
47 |
if not conv.conversation_history:
|
48 |
print("기억할 대화 내용이 없어 종료.")
|
@@ -50,12 +53,30 @@ class ConversationManager:
|
|
50 |
initiator, target = conv.participants[0], conv.participants[1]
|
51 |
full_conversation = "\n".join(conv.conversation_history)
|
52 |
|
|
|
53 |
summary = summarize_text(full_conversation)
|
|
|
|
|
|
|
|
|
54 |
with simulation_core.simulation_lock:
|
|
|
|
|
55 |
memory_content = f"'{conv.topic}'에 대해 대화하며 '{summary}'라는 결론을 내렸다."
|
56 |
-
initiator.remember(content=f"{target.korean_name}
|
57 |
-
target.remember(content=f"{initiator.korean_name}
|
58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
59 |
except Exception as e:
|
60 |
print(f"[에러] 백그라운드 기억 저장 중 문제 발생: {e}")
|
61 |
import traceback
|
@@ -64,19 +85,21 @@ class ConversationManager:
|
|
64 |
def start_conversation(self, initiator: "NPC", target: "NPC",
|
65 |
topic: Optional[str] = None):
|
66 |
"""새로운 대화를 시작, 바로 다음 턴을 호출"""
|
|
|
67 |
if self.is_conversation_active():
|
68 |
return # 이미 다른 대화가 진행중이면 시작 x
|
69 |
|
70 |
conv_topic = topic or "가벼운 인사"
|
71 |
self.active_conversation = Conversation(initiator, target, conv_topic)
|
72 |
-
|
73 |
-
|
74 |
|
75 |
# 1. 첫 마디 생성
|
76 |
self.next_turn()
|
77 |
|
78 |
def end_conversation(self, reason: str = "자연스럽게"):
|
79 |
"""현재 대화를 종료하고, 대화 내용을 요약하여 기억으로 저장"""
|
|
|
80 |
if not self.is_conversation_active():
|
81 |
return
|
82 |
|
@@ -85,7 +108,7 @@ class ConversationManager:
|
|
85 |
|
86 |
# 1. 대화 상태를 즉시 '종료'로 변경하여 메인 루프를 해방
|
87 |
self.active_conversation = None
|
88 |
-
|
89 |
|
90 |
# 2. 시간이 걸리는 '기억 저장' 작업은 별도의 스레드를 생성하여 백그라운드에서 처리.
|
91 |
background_thread = threading.Thread(
|
|
|
1 |
# portfolio/npc_social_network/manager/conversation_manager.py
|
2 |
# 대화의 시작, 진행, 종료를 총괄하는 역할
|
3 |
+
from ..models.llm_helper import summarize_memories, summarize_text, query_llm_for_emotion
|
4 |
+
from ..npc.npc_manager import get_korean_postposition
|
5 |
+
from ..npc.emotion_config import EMOTION_RELATION_IMPACT
|
6 |
import threading
|
7 |
from typing import List, Optional, TYPE_CHECKING
|
8 |
if TYPE_CHECKING:
|
|
|
36 |
|
37 |
def _add_and_log_utterance(self,speaker: "NPC", utterance: str):
|
38 |
"""대사를 대화 기록과 UI 로그에 모두 추가"""
|
39 |
+
from .. import simulation_core
|
40 |
if not self.is_conversation_active():
|
41 |
return
|
42 |
log_message = f"[{speaker.korean_name}]: {utterance}"
|
|
|
44 |
simulation_core.add_log(log_message)
|
45 |
|
46 |
def _summarize_and_remember_in_background(self, conv: "Conversation"):
|
47 |
+
"""백그라운드 스레드 대화를 요약하고 기억하며, 관계와 성격에 반영하는 함수"""
|
48 |
+
from .. import simulation_core
|
49 |
try:
|
50 |
if not conv.conversation_history:
|
51 |
print("기억할 대화 내용이 없어 종료.")
|
|
|
53 |
initiator, target = conv.participants[0], conv.participants[1]
|
54 |
full_conversation = "\n".join(conv.conversation_history)
|
55 |
|
56 |
+
# 1. 대화 내용 요약
|
57 |
summary = summarize_text(full_conversation)
|
58 |
+
|
59 |
+
# 2. 대화의 전반적인 감정 톤 분석
|
60 |
+
overall_emotion = query_llm_for_emotion(summary)
|
61 |
+
|
62 |
with simulation_core.simulation_lock:
|
63 |
+
target_postposition = get_korean_postposition(target.korean_name, "과", "와")
|
64 |
+
# 3. 요약된 내용을 바탕으로 기억 생성
|
65 |
memory_content = f"'{conv.topic}'에 대해 대화하며 '{summary}'라는 결론을 내렸다."
|
66 |
+
initiator.remember(content=f"{target.korean_name}{target_postposition} {memory_content}", importance=7, memory_type="Conversation")
|
67 |
+
target.remember(content=f"{initiator.korean_name}{target_postposition} {memory_content}", importance=7, memory_type="Conversation")
|
68 |
+
|
69 |
+
# 4. 대화의 감정을 바탕으로 관계 상태 업데이트
|
70 |
+
if overall_emotion and overall_emotion in EMOTION_RELATION_IMPACT:
|
71 |
+
initiator.relationships.update_relationship(target.name, overall_emotion, strength=3.0)
|
72 |
+
target.relationships.update_relationship(initiator.name, overall_emotion, strength=3.0)
|
73 |
+
|
74 |
+
# 5. 관계 변화를 바탕으로 양쪽 모두의 성격 업데이트
|
75 |
+
initiator.update_personality()
|
76 |
+
target.update_personality()
|
77 |
+
|
78 |
+
simulation_core.add_log(f"[{initiator.korean_name}, {target.korean_name}] 대화 경험이 기억과 관계, 성격에 반영되었습니다.")
|
79 |
+
|
80 |
except Exception as e:
|
81 |
print(f"[에러] 백그라운드 기억 저장 중 문제 발생: {e}")
|
82 |
import traceback
|
|
|
85 |
def start_conversation(self, initiator: "NPC", target: "NPC",
|
86 |
topic: Optional[str] = None):
|
87 |
"""새로운 대화를 시작, 바로 다음 턴을 호출"""
|
88 |
+
from .. import simulation_core
|
89 |
if self.is_conversation_active():
|
90 |
return # 이미 다른 대화가 진행중이면 시작 x
|
91 |
|
92 |
conv_topic = topic or "가벼운 인사"
|
93 |
self.active_conversation = Conversation(initiator, target, conv_topic)
|
94 |
+
simulation_core.add_log(f"--[대화 시작]--")
|
95 |
+
simulation_core.add_log(f"주제: {conv_topic} / 참여자: {initiator.korean_name}, {target.korean_name}")
|
96 |
|
97 |
# 1. 첫 마디 생성
|
98 |
self.next_turn()
|
99 |
|
100 |
def end_conversation(self, reason: str = "자연스럽게"):
|
101 |
"""현재 대화를 종료하고, 대화 내용을 요약하여 기억으로 저장"""
|
102 |
+
from .. import simulation_core
|
103 |
if not self.is_conversation_active():
|
104 |
return
|
105 |
|
|
|
108 |
|
109 |
# 1. 대화 상태를 즉시 '종료'로 변경하여 메인 루프를 해방
|
110 |
self.active_conversation = None
|
111 |
+
simulation_core.add_log(f"---[대화 종료: {reason}]---\n")
|
112 |
|
113 |
# 2. 시간이 걸리는 '기억 저장' 작업은 별도의 스레드를 생성하여 백그라운드에서 처리.
|
114 |
background_thread = threading.Thread(
|
npc_social_network/models/llm_helper.py
CHANGED
@@ -6,10 +6,11 @@ from npc_social_network.npc.npc_memory import Memory
|
|
6 |
from typing import List
|
7 |
import json
|
8 |
import re
|
9 |
-
from typing import TYPE_CHECKING
|
10 |
|
11 |
if TYPE_CHECKING:
|
12 |
from ..npc.npc_base import NPC
|
|
|
13 |
|
14 |
# Gemini 모델 초기화
|
15 |
gemini_model = load_gemini()
|
@@ -117,7 +118,7 @@ def classify_and_extract_plan_details(plan_step: str) -> dict:
|
|
117 |
# Instruction
|
118 |
Analyze the following action plan. Classify its type and extract the target NPC and topic if applicable.
|
119 |
Your response MUST be in JSON format.
|
120 |
-
Action Types can be "TALK" (for conversing with others) or "SOLO_ACTION" (for acting alone).
|
121 |
|
122 |
# Action Plan
|
123 |
"{plan_step}"
|
@@ -202,6 +203,23 @@ def generate_dialogue_action(speaker: "NPC", target: "NPC", conversation: "Conve
|
|
202 |
history_str = "\n".join(conversation.conversation_history)
|
203 |
memory_str = "\n".join([f"- {m.content}" for m in memories]) if memories else "관련된 특별한 기억 없음"
|
204 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
205 |
if is_final_turn:
|
206 |
task_instruction = """
|
207 |
- 상대방이 대화를 마무리하려고 합니다. 상대방의 마지막 말에 어울리는 자연스러운 작별 인사를 생성하세요. (예: "네, 조심히 가세요.", "다음에 또 봬요.", "알겠습니다. 좋은 하루 보내세요.")
|
@@ -225,6 +243,11 @@ def generate_dialogue_action(speaker: "NPC", target: "NPC", conversation: "Conve
|
|
225 |
- 당신의 성격: {speaker.personality.get_personality_summary()}
|
226 |
- "{target.korean_name}"와의 관계: {speaker.relationships.get_relationship_summary(target.name)}
|
227 |
|
|
|
|
|
|
|
|
|
|
|
228 |
# Relevant Memories
|
229 |
{memory_str}
|
230 |
|
@@ -270,4 +293,39 @@ def generate_dialogue_action(speaker: "NPC", target: "NPC", conversation: "Conve
|
|
270 |
result["dialogue"] = dialogue
|
271 |
return result
|
272 |
|
273 |
-
return {"dialogue": "...", "action": "END"} # 최종 실패 시 안전하게 대화 종료
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
from typing import List
|
7 |
import json
|
8 |
import re
|
9 |
+
from typing import TYPE_CHECKING, List
|
10 |
|
11 |
if TYPE_CHECKING:
|
12 |
from ..npc.npc_base import NPC
|
13 |
+
from ..manager.conversation_manager import Conversation
|
14 |
|
15 |
# Gemini 모델 초기화
|
16 |
gemini_model = load_gemini()
|
|
|
118 |
# Instruction
|
119 |
Analyze the following action plan. Classify its type and extract the target NPC and topic if applicable.
|
120 |
Your response MUST be in JSON format.
|
121 |
+
Action Types can be "TALK" (for conversing with others), "HELP" (for helping someone), or "SOLO_ACTION" (for acting alone).
|
122 |
|
123 |
# Action Plan
|
124 |
"{plan_step}"
|
|
|
203 |
history_str = "\n".join(conversation.conversation_history)
|
204 |
memory_str = "\n".join([f"- {m.content}" for m in memories]) if memories else "관련된 특별한 기억 없음"
|
205 |
|
206 |
+
# 평판 정보 생성
|
207 |
+
reputation_info = ""
|
208 |
+
reputation_score = speaker.reputation.get(target.name, 0)
|
209 |
+
if reputation_score > 5:
|
210 |
+
reputation_info = f"- '{target.korean_name}'에 대한 나의 평판: 신뢰할 만한 사람 같다. (점수: {reputation_score})"
|
211 |
+
elif reputation_score < -5:
|
212 |
+
reputation_info = f"- '{target.korean_name}'에 대한 나의 평판: 좋지 않은 소문을 들었다. (점수: {reputation_score})"
|
213 |
+
|
214 |
+
# 상징 기억 조회
|
215 |
+
symbolic_memories = [mem for mem in speaker.memory_store.get_all_memories() if mem.memory_type == "Symbolic"]
|
216 |
+
symbolic_section = ""
|
217 |
+
if symbolic_memories:
|
218 |
+
symbolic_section_title = "# Core Symbolic Values (Symbolic Memory)"
|
219 |
+
symbolic_contents = "\n".join([f"- {mem.content}" for mem in symbolic_memories])
|
220 |
+
# 프롬프트에 예쁘게 들어갈 수 있도록 앞뒤로 줄바꿈 추가
|
221 |
+
symbolic_section = f"\n{symbolic_section_title}\n{symbolic_contents}"
|
222 |
+
|
223 |
if is_final_turn:
|
224 |
task_instruction = """
|
225 |
- 상대방이 대화를 마무리하려고 합니다. 상대방의 마지막 말에 어울리는 자연스러운 작별 인사를 생성하세요. (예: "네, 조심히 가세요.", "다음에 또 봬요.", "알겠습니다. 좋은 하루 보내세요.")
|
|
|
243 |
- 당신의 성격: {speaker.personality.get_personality_summary()}
|
244 |
- "{target.korean_name}"와의 관계: {speaker.relationships.get_relationship_summary(target.name)}
|
245 |
|
246 |
+
# Reputation Information
|
247 |
+
{reputation_info}
|
248 |
+
|
249 |
+
{symbolic_section}
|
250 |
+
|
251 |
# Relevant Memories
|
252 |
{memory_str}
|
253 |
|
|
|
293 |
result["dialogue"] = dialogue
|
294 |
return result
|
295 |
|
296 |
+
return {"dialogue": "...", "action": "END"} # 최종 실패 시 안전하게 대화 종료
|
297 |
+
|
298 |
+
def evaluate_social_action_outcome(initiator: "NPC", target: "NPC", action_type: str) -> dict:
|
299 |
+
"""두 NPC의 성격과 관계를 바탕으로 사회적 행동의 결과를 추론합니다."""
|
300 |
+
prompt = f"""
|
301 |
+
# Context
|
302 |
+
- 행동자: "{initiator.korean_name}" (성격: {initiator.personality.get_personality_summary()})
|
303 |
+
- 대상자: "{target.korean_name}" (성격: {target.personality.get_personality_summary()})
|
304 |
+
- 둘의 관계: {initiator.relationships.get_relationship_summary(target.name)}
|
305 |
+
- 행동: 행동자는 대상자를 도와주려고 합니다. (Action: {action_type})
|
306 |
+
|
307 |
+
# Your Task
|
308 |
+
위 상황을 바탕으로, 이 행동의 결과를 현실적으로 추론해주세요.
|
309 |
+
결과는 반드시 아래 JSON 형식이어야 합니다.
|
310 |
+
|
311 |
+
# Outcome (JSON format only)
|
312 |
+
{{
|
313 |
+
"success": "true or false"
|
314 |
+
"outcome_summary": "행동의 결과를 한 문장으로 요약 (예: '밥은 찰리의 서투른 도움에 고마워하면서도 조금 답답해했다.')",
|
315 |
+
"initiator_emotion": "행동을 한 후 행동자가 느낄 감정 (아래 'Emotion_List에서 찾아서 한 단어로 표현)"
|
316 |
+
"target_emotion": "행동을 받은 후 대상자가 느낄 감정 (아래 'Emotion_List에서 찾아서 한 단어로 표현)"
|
317 |
+
}}
|
318 |
+
|
319 |
+
# Emotion_List
|
320 |
+
{EMOTION_LIST}
|
321 |
+
"""
|
322 |
+
result = _query_llm_for_json_robustly(prompt) # 3중 방어 시스템 재활용
|
323 |
+
|
324 |
+
if isinstance(result, dict) and "success" in result:
|
325 |
+
return result
|
326 |
+
return {# 최종 실패 시 안전한 기본값 반환
|
327 |
+
"success": False,
|
328 |
+
"outcome_summary": "행동의 결과를 평가하는 데 실패했습니다.",
|
329 |
+
"initiator_emotion": "confusion",
|
330 |
+
"target_emotion": "neutral"
|
331 |
+
}
|
npc_social_network/models/llm_prompt_builder.py
DELETED
@@ -1,98 +0,0 @@
|
|
1 |
-
# portfolio/npc_social_network/models/llm_prompt_builder.py
|
2 |
-
|
3 |
-
from typing import List, Optional, TYPE_CHECKING
|
4 |
-
from ..npc.npc_memory import Memory
|
5 |
-
|
6 |
-
# 타입 검사 시에만 NPC 클래스를 임포트하여 순환 참조 방지
|
7 |
-
if TYPE_CHECKING:
|
8 |
-
from ..npc.npc_base import NPC
|
9 |
-
from ..npc.npc_memory import Memory
|
10 |
-
|
11 |
-
def build_npc_prompt(npc:"NPC", user_input: str, matched_memories: List[Memory],
|
12 |
-
time_context:str, target_npc: Optional["NPC"]=None) -> str:
|
13 |
-
"""
|
14 |
-
NPC 대사를 생성하기 위한 강화된 프롬프트 구성 함수
|
15 |
-
- SocialProfile의 관꼐 요약을 프롬프트에 포함하도록 수정
|
16 |
-
"""
|
17 |
-
# 대화 상대방의 이름을 동적으로 설정
|
18 |
-
interlocutor_name = target_npc.name if target_npc else "player"
|
19 |
-
interlocutor_korean_name = target_npc.korean_name if target_npc else "플레이어"
|
20 |
-
|
21 |
-
# 감정 상태 요약
|
22 |
-
emotion_summary = npc.summarize_emotional_state()
|
23 |
-
# 성격 요약
|
24 |
-
personality_summary = npc.personality.get_personality_summary()
|
25 |
-
# 관계 요약
|
26 |
-
relationship_description = npc.get_relationship_description(interlocutor_name)
|
27 |
-
|
28 |
-
# 평판 정보
|
29 |
-
reputation_info = ""
|
30 |
-
if target_npc:
|
31 |
-
reputation_score = npc.reputation.get(target_npc.name, 0)
|
32 |
-
if reputation_score > 5:
|
33 |
-
reputation_info = f"- '{target_npc.korean_name}'에 대한 평판: 신뢰할 만한 사람 같다. (평판 점수: {reputation_score})"
|
34 |
-
elif reputation_score < -5:
|
35 |
-
reputation_info = f"- '{target_npc.korean_name}'에 대한 평판: 그다지 좋은 소문을 듣지 못했다. (평판 점수: {reputation_score})"
|
36 |
-
|
37 |
-
# 기억 유무에 따른 동적 지침 생성
|
38 |
-
if matched_memories:
|
39 |
-
memory_section_title = "# 연관된 기억"
|
40 |
-
memory_contents = []
|
41 |
-
for mem in matched_memories:
|
42 |
-
tags = f"(감정: {mem.emotion}, 시간: {', '.join(mem.context_tags)})"
|
43 |
-
short_content = mem.content[:70] + "..." if len(mem.content) > 70 else mem.content
|
44 |
-
memory_contents.append(f"- {short_content} {tags}")
|
45 |
-
memory_section = "\n".join(memory_contents)
|
46 |
-
|
47 |
-
# 기억이 있을 때의 지침
|
48 |
-
instruction = f"""
|
49 |
-
나는 '{interlocutor_name}'의 말과 관련된 아래의 기억들을 가지고 있습니다.
|
50 |
-
이 기억들을 바탕으로 자연스럽게 대화하세요."""
|
51 |
-
else:
|
52 |
-
memory_section_title = "# 연관된 기억 없음"
|
53 |
-
memory_section = "관련된 특별한 기억이 없습니다."
|
54 |
-
|
55 |
-
# 기억이 없을 때의 지침
|
56 |
-
instruction = f"""
|
57 |
-
나는 '{interlocutor_name}'의 말과 관련된 명확한 기억이 없습니다.
|
58 |
-
이 사실을 솔직하게 인정하거나, 잘 기억나지 않는다는 뉘앙스로 되물어보세요.
|
59 |
-
**절대로 없는 기억을 지어내면 안 됩니다.**"""
|
60 |
-
|
61 |
-
# 상정 기억이 있다면 프롬프트에 추가
|
62 |
-
symbolic_memories = [mem for mem in npc.memory_store.long_term if mem.memory_type == "Symbolic"]
|
63 |
-
symbolic_section = ""
|
64 |
-
if symbolic_memories:
|
65 |
-
symbolic_section_title = "# 당신의 핵심 가치관 (상징 기억)"
|
66 |
-
symbolic_contents = "\n".join([f"- {mem.content}" for mem in symbolic_memories])
|
67 |
-
symbolic_section = f"{symbolic_section_title}\n{symbolic_contents}\n"
|
68 |
-
|
69 |
-
# 프롬프트 구성
|
70 |
-
prompt = f"""
|
71 |
-
# 지시사항
|
72 |
-
→ 아래 상황에 어울리는 자연스럽고 인간적인 사람 반응을 생성하세요.
|
73 |
-
→ 말투, 반응은 감정 상태에 맞추어 주세요.
|
74 |
-
→ 대답은 NPC의 1인칭 시점으로 작성하세요.
|
75 |
-
{instruction}
|
76 |
-
|
77 |
-
# 나의 프로필
|
78 |
-
- 이름: {npc.korean_name}
|
79 |
-
- 직업: {npc.job}
|
80 |
-
- 현재 대화 상대: {interlocutor_korean_name}
|
81 |
-
- 현재 시간대: {time_context}
|
82 |
-
|
83 |
-
# 나의 내면 상태
|
84 |
-
- 현재 감정: {emotion_summary}
|
85 |
-
- 성격: {personality_summary}
|
86 |
-
- {interlocutor_korean_name}와(과)의 관계: {relationship_description}
|
87 |
-
- {reputation_info}
|
88 |
-
|
89 |
-
{symbolic_section}
|
90 |
-
|
91 |
-
{memory_section_title}
|
92 |
-
{memory_section}
|
93 |
-
|
94 |
-
# 대화 로그
|
95 |
-
{interlocutor_korean_name}: "{user_input}"
|
96 |
-
{npc.korean_name}:
|
97 |
-
"""
|
98 |
-
return prompt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
npc_social_network/npc/npc_base.py
CHANGED
@@ -7,19 +7,23 @@ from datetime import datetime
|
|
7 |
from .npc_memory import Memory, MemoryStore
|
8 |
from .npc_emotion import EmotionManager
|
9 |
from .npc_behavior import BehaviorManager
|
10 |
-
from .emotion_config import EMOTION_LIST, EMOTION_CATEGORY_MAP, EMOTION_DECAY_RATE
|
11 |
-
|
|
|
|
|
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 |
-
|
16 |
-
|
17 |
from .npc_memory_embedder import search_similar_memories, add_memory_to_index
|
|
|
18 |
from .npc_planner import PlannerManager
|
19 |
from .. import simulation_core
|
20 |
-
from typing import TYPE_CHECKING, Tuple
|
21 |
if TYPE_CHECKING:
|
22 |
from .npc_manager import NPCManager
|
|
|
23 |
|
24 |
|
25 |
# Personality 데이터를 관리하고 요약 기능을 제공하는 클래스 정의
|
@@ -165,8 +169,6 @@ class NPC:
|
|
165 |
action = result.get("action", "END").strip().upper()
|
166 |
|
167 |
# 행동하기 (잠금과 함께)
|
168 |
-
# 이 부분은 대화가 끝난 후, 전체 대화 내용을 바탕으로 기억/관계를 한 번에 업데이트 하도록 향후 수정.
|
169 |
-
# 지금은 매 턴 감정만 업데이트
|
170 |
with simulation_core.simulation_lock:
|
171 |
emotion = query_llm_for_emotion(dialogue)
|
172 |
if emotion and emotion in EMOTION_LIST:
|
@@ -174,92 +176,6 @@ class NPC:
|
|
174 |
|
175 |
return dialogue, action
|
176 |
|
177 |
-
# def generate_dialogue(self, user_input: str, time_context: str
|
178 |
-
# , target_npc: Optional["NPC"]=None, use_llm: bool=True
|
179 |
-
# , create_memory: bool = True) -> str:
|
180 |
-
# """
|
181 |
-
# 플레이어 또는 NPC와 상호작용 시 사용되는 통합 대사 생성 함수
|
182 |
-
# - user_input: 입력 문장 (플레이어나 NPC로부터)
|
183 |
-
# - target_npc: NPC 간 대화일 경우 상대 NPC
|
184 |
-
# """
|
185 |
-
# if use_llm and user_input:
|
186 |
-
# # 1. 생각하기
|
187 |
-
# # 1-1. 유사 기억 검색
|
188 |
-
# _, _, matched_memories = search_similar_memories(self, user_input)
|
189 |
-
|
190 |
-
# # 1-2. LLM 프롬프트 생성 및 호출
|
191 |
-
# prompt = build_npc_prompt(self, user_input, matched_memories, time_context, target_npc)
|
192 |
-
# npc_reply = query_llm_with_prompt(prompt)
|
193 |
-
|
194 |
-
# if not npc_reply or "[LLM Error]" in npc_reply:
|
195 |
-
# return "[LLM Error]..." # 에러 시 기본 응답
|
196 |
-
|
197 |
-
# # 1-3. 자신의 응답에서 감정 추론 및 상태 업데이트
|
198 |
-
# emotion_from_reply = query_llm_for_emotion(npc_reply)
|
199 |
-
# # 1-4. 상대방의 말에서 감정 추론 (NPC간 대화일 경우)
|
200 |
-
# emotion_from_input = query_llm_for_emotion(user_input) if target_npc else None
|
201 |
-
|
202 |
-
# # 2. 행동하기
|
203 |
-
# if create_memory:
|
204 |
-
# with simulation_core.simulation_lock:
|
205 |
-
# # 2-1. 기억 요약 및 장기 기억화
|
206 |
-
# if len(matched_memories) >= 3:
|
207 |
-
# self.summarize_and_store_memories(matched_memories)
|
208 |
-
|
209 |
-
# # 2-2. 추론한 감정들을 상태에 업데이트
|
210 |
-
# if emotion_from_reply and emotion_from_reply in EMOTION_LIST:
|
211 |
-
# self.update_emotion(emotion_from_reply , strength=2.0)
|
212 |
-
|
213 |
-
# # 2-3. 상호작용 기억 저장
|
214 |
-
# interlocutor_name = target_npc.name if target_npc else "player"
|
215 |
-
# interlocutor_korean_name = target_npc.korean_name if target_npc else "플레이어"
|
216 |
-
# memory_content = f"[{interlocutor_korean_name}의 말] '{user_input}' → [나의 응답] '{npc_reply}'"
|
217 |
-
# memory = self.remember(content=memory_content, importance=7, emotion=emotion_from_reply , context_tags=[time_context])
|
218 |
-
|
219 |
-
# # 2-4. 관계 상태 업데이트
|
220 |
-
# if emotion_from_reply and emotion_from_reply in EMOTION_RELATION_IMPACT:
|
221 |
-
# self.relationships.update_relationship(interlocutor_name, emotion_from_reply , strength=2.0, memory=memory)
|
222 |
-
# if target_npc and emotion_from_input and emotion_from_input in EMOTION_RELATION_IMPACT:
|
223 |
-
# target_npc.relationships.update_relationship(self.name, emotion_from_input, strength=2.0, memory=memory)
|
224 |
-
|
225 |
-
# # 2-5. 성격 변화 반영
|
226 |
-
# self.update_personality()
|
227 |
-
|
228 |
-
# return npc_reply
|
229 |
-
|
230 |
-
# else:
|
231 |
-
# # LLM 미사용 시: 감정 기반 행동 생성
|
232 |
-
# behavior_output, behavior_trace = self.behavior.perform_sequence(
|
233 |
-
# self.name, self.job, emotion_buffer=self._emotion_buffer, return_trace=True
|
234 |
-
# )
|
235 |
-
|
236 |
-
# # 행동 기반 감정 추론 및 반영
|
237 |
-
# for action, score in behavior_trace:
|
238 |
-
# emotion_name = self.behavior.action_to_emotion.get(action)
|
239 |
-
# if emotion_name:
|
240 |
-
# self.update_emotion(emotion_name, strength=score)
|
241 |
-
|
242 |
-
# # dominant_score 기반 중요도 추정
|
243 |
-
# dominant_emotions = self.behavior.get_layer_dominant_emotions(self._emotion_buffer)
|
244 |
-
# dominant_sequence = self.behavior.decide_layered_sequence(dominant_emotions)
|
245 |
-
# dominant_score = max([score for _, score in dominant_sequence]) if dominant_sequence else 5
|
246 |
-
# importance = min(int(dominant_score), 10)
|
247 |
-
|
248 |
-
# # Memory 저장
|
249 |
-
# memory_entry = Memory(
|
250 |
-
# content=f"행동 수행: {behavior_trace}",
|
251 |
-
# importance=importance,
|
252 |
-
# emotion=self.emotion.get_dominant_emotion(),
|
253 |
-
# behavior_trace=behavior_trace,
|
254 |
-
# context_tags=[time_context]
|
255 |
-
# )
|
256 |
-
# self.memory_store.add_memory(memory_entry)
|
257 |
-
|
258 |
-
# # 성격 업데이트
|
259 |
-
# self.update_personality()
|
260 |
-
|
261 |
-
# return str(behavior_output)
|
262 |
-
|
263 |
def remember(self, content: str, importance: int = 5, emotion: str = None,
|
264 |
strength: float = 1.0, memory_type:str = "Event",
|
265 |
context_tags: Optional[List[str]]=None) -> Memory:
|
@@ -475,25 +391,38 @@ class NPC:
|
|
475 |
# LLM으로 계획을 분석하여 행동을 결정
|
476 |
plan_details = classify_and_extract_plan_details(step)
|
477 |
action_type = plan_details.get("action_type")
|
478 |
-
|
479 |
-
|
480 |
-
|
481 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
482 |
|
483 |
-
|
484 |
-
target_npc = self.manager.get_npc_by_korean_name(target_korean_name)
|
485 |
-
if target_npc and target_npc != self:
|
486 |
-
# 주제가 있는 대화 시작
|
487 |
-
simulation_core.conversation_manager.start_conversation(self, target_npc, topic=topic)
|
488 |
-
else:
|
489 |
-
print(f"[{self.korean_name}] '{target_korean_name}'를 찾을 수 없어 대화 계획을 실행하지 못했습니다.")
|
490 |
-
else:
|
491 |
-
# LLM이 대상을 특정하지 못한 경우
|
492 |
-
print(f"[{self.korean_name}] 대화 대상을 특정하지 못해 계획을 실행하지 못했습니다.")
|
493 |
-
else:
|
494 |
# 대화가 아닌 다른 행동은 그대로 기억에만 기록
|
495 |
self.remember(
|
496 |
-
content=f"'{self.planner.current_goal.description}' 목표를 위해 행동 했다: {step}",
|
497 |
importance = 6,
|
498 |
emotion="engagement",
|
499 |
memory_type="Behavior"
|
|
|
7 |
from .npc_memory import Memory, MemoryStore
|
8 |
from .npc_emotion import EmotionManager
|
9 |
from .npc_behavior import BehaviorManager
|
10 |
+
from .emotion_config import (EMOTION_LIST, EMOTION_CATEGORY_MAP, EMOTION_DECAY_RATE
|
11 |
+
, PERSONALITY_TEMPLATE, EMOTION_RELATION_IMPACT
|
12 |
+
, POSITIVE_RELATION_EMOTIONS, NEGATIVE_RELATION_EMOTIONS
|
13 |
+
, COGNITIVE_RELATION_EMOTIONS)
|
14 |
from .personality_config import AGE_PROFILE, PERSONALITY_PROFILE
|
15 |
from .npc_relationship import RelationshipManager
|
16 |
+
from ..models.llm_helper import (query_llm_with_prompt, query_llm_for_emotion, summarize_memories
|
17 |
+
, analyze_gossip, classify_and_extract_plan_details
|
18 |
+
, generate_dialogue_action, evaluate_social_action_outcome)
|
19 |
from .npc_memory_embedder import search_similar_memories, add_memory_to_index
|
20 |
+
from .npc_manager import get_korean_postposition
|
21 |
from .npc_planner import PlannerManager
|
22 |
from .. import simulation_core
|
23 |
+
from typing import TYPE_CHECKING, Tuple, List
|
24 |
if TYPE_CHECKING:
|
25 |
from .npc_manager import NPCManager
|
26 |
+
from ..manager.conversation_manager import Conversation
|
27 |
|
28 |
|
29 |
# Personality 데이터를 관리하고 요약 기능을 제공하는 클래스 정의
|
|
|
169 |
action = result.get("action", "END").strip().upper()
|
170 |
|
171 |
# 행동하기 (잠금과 함께)
|
|
|
|
|
172 |
with simulation_core.simulation_lock:
|
173 |
emotion = query_llm_for_emotion(dialogue)
|
174 |
if emotion and emotion in EMOTION_LIST:
|
|
|
176 |
|
177 |
return dialogue, action
|
178 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
179 |
def remember(self, content: str, importance: int = 5, emotion: str = None,
|
180 |
strength: float = 1.0, memory_type:str = "Event",
|
181 |
context_tags: Optional[List[str]]=None) -> Memory:
|
|
|
391 |
# LLM으로 계획을 분석하여 행동을 결정
|
392 |
plan_details = classify_and_extract_plan_details(step)
|
393 |
action_type = plan_details.get("action_type")
|
394 |
+
target_korean_name = plan_details.get("target")
|
395 |
+
|
396 |
+
if action_type == "TALK" and target_korean_name:
|
397 |
+
target_npc = self.manager.get_npc_by_korean_name(target_korean_name)
|
398 |
+
if target_npc and target_npc != self:
|
399 |
+
# 주제가 있는 대화 시작
|
400 |
+
simulation_core.conversation_manager.start_conversation(self, target_npc, topic=plan_details.get("topic"))
|
401 |
+
|
402 |
+
elif action_type == "HELP" and target_korean_name:
|
403 |
+
target_npc = self.manager.get_npc_by_korean_name(target_korean_name)
|
404 |
+
target_postposition = get_korean_postposition(target_korean_name, "이", "가")
|
405 |
+
target_postposition_Eul = get_korean_postposition(target_korean_name, "을", "를")
|
406 |
+
|
407 |
+
if target_npc and target_npc != self:
|
408 |
+
# '돕기' 행동의 결과를 시뮬레이션
|
409 |
+
outcome = evaluate_social_action_outcome(self, target_npc, "HELP")
|
410 |
+
summary = outcome.get('outcome_summary')
|
411 |
+
|
412 |
+
# 결과를 바탕으로 양쪽 모두의 상태를 업데이트
|
413 |
+
with simulation_core.simulation_lock:
|
414 |
+
# 행동자(나)의 기억과 관계 업데이트
|
415 |
+
self.remember(content=f"{target_korean_name}{target_postposition_Eul} 도왔다. ({summary})", importance=8, emotion=outcome.get('initiator_emotion'))
|
416 |
+
self.relationships.update_relationship(target_npc.name, outcome.get('initiator_emotion'), strength=3.0)
|
417 |
+
|
418 |
+
# 대상자(상대방)의 기억과 관계 업데이트
|
419 |
+
target_npc.remember(content=f"{self.korean_name}{target_postposition} 나를 도와주었다. ({summary})")
|
420 |
+
target_npc.relationships.update_relationship(self.name, outcome.get('target_emotion'), strength=3.0)
|
421 |
|
422 |
+
else: # SOLOT_ACTION 또는 기타
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
423 |
# 대화가 아닌 다른 행동은 그대로 기억에만 기록
|
424 |
self.remember(
|
425 |
+
content=f"'{self.planner.current_goal.description}' 목표를 위해 혼자 행동 했다: {step}",
|
426 |
importance = 6,
|
427 |
emotion="engagement",
|
428 |
memory_type="Behavior"
|
npc_social_network/npc/npc_manager.py
CHANGED
@@ -142,7 +142,7 @@ class NPCManager:
|
|
142 |
|
143 |
initiator_postposition = get_korean_postposition(initiator.korean_name, "이", "가")
|
144 |
target_postposition = get_korean_postposition(target.korean_name, "에게", "에게")
|
145 |
-
witness_postposition = get_korean_postposition(
|
146 |
|
147 |
gossip_content = f"'{initiator.korean_name}'{initiator_postposition} '{target.korean_name}'{target_postposition} '{initial_utterance[:100]}...'라고 말하는 것을 봤다."
|
148 |
|
|
|
142 |
|
143 |
initiator_postposition = get_korean_postposition(initiator.korean_name, "이", "가")
|
144 |
target_postposition = get_korean_postposition(target.korean_name, "에게", "에게")
|
145 |
+
witness_postposition = get_korean_postposition(witness.korean_name, "이", "가")
|
146 |
|
147 |
gossip_content = f"'{initiator.korean_name}'{initiator_postposition} '{target.korean_name}'{target_postposition} '{initial_utterance[:100]}...'라고 말하는 것을 봤다."
|
148 |
|
npc_social_network/npc/npc_relationship.py
CHANGED
@@ -82,15 +82,17 @@ class RelationshipManager:
|
|
82 |
profile = self._get_or_create_profile(target_name)
|
83 |
return profile.summary
|
84 |
|
85 |
-
def summarize_relationship(self, target_name: str):
|
86 |
""" LLM을 사용하여 특정 대상과의 관계를 주기적으로 요약하고 업데이트"""
|
87 |
from ..models.llm_helper import query_llm_with_prompt
|
|
|
|
|
88 |
profile = self._get_or_create_profile(target_name)
|
89 |
|
90 |
if not profile.key_memories:
|
91 |
return # 요약할 기억이 없으면 실행 안함
|
92 |
|
93 |
-
target_npc =
|
94 |
target_korean_name = target_npc.korean_name if target_npc else target_name
|
95 |
|
96 |
memory_details = "\n".join([f"- {mem.content} (감정: {mem.emotion})" for mem in profile.key_memories])
|
@@ -101,13 +103,13 @@ class RelationshipManager:
|
|
101 |
# '{target_korean_name}'와(과)의 핵심 기억들
|
102 |
{memory_details}
|
103 |
|
104 |
-
→ '{target_korean_name}'에 대한
|
105 |
"""
|
106 |
|
107 |
summary = query_llm_with_prompt(prompt).replace("'", "").strip()
|
108 |
|
109 |
if summary and "[LLM Error]" not in summary:
|
110 |
profile.summary = summary
|
111 |
-
|
112 |
|
113 |
|
|
|
82 |
profile = self._get_or_create_profile(target_name)
|
83 |
return profile.summary
|
84 |
|
85 |
+
def summarize_relationship(self, target_name: str, npc_manager: "NPCManager"):
|
86 |
""" LLM을 사용하여 특정 대상과의 관계를 주기적으로 요약하고 업데이트"""
|
87 |
from ..models.llm_helper import query_llm_with_prompt
|
88 |
+
from .. import simulation_core
|
89 |
+
|
90 |
profile = self._get_or_create_profile(target_name)
|
91 |
|
92 |
if not profile.key_memories:
|
93 |
return # 요약할 기억이 없으면 실행 안함
|
94 |
|
95 |
+
target_npc = npc_manager.get_npc_by_name(target_name)
|
96 |
target_korean_name = target_npc.korean_name if target_npc else target_name
|
97 |
|
98 |
memory_details = "\n".join([f"- {mem.content} (감정: {mem.emotion})" for mem in profile.key_memories])
|
|
|
103 |
# '{target_korean_name}'와(과)의 핵심 기억들
|
104 |
{memory_details}
|
105 |
|
106 |
+
→ '{target_korean_name}'에 대한 {self.owner_npc.korean_name}의 생각:
|
107 |
"""
|
108 |
|
109 |
summary = query_llm_with_prompt(prompt).replace("'", "").strip()
|
110 |
|
111 |
if summary and "[LLM Error]" not in summary:
|
112 |
profile.summary = summary
|
113 |
+
simulation_core.add_log(f"[{self.owner_npc.korean_name}의 관계 요약 업데이트] {profile.summary}")
|
114 |
|
115 |
|
npc_social_network/simulation_core.py
CHANGED
@@ -52,6 +52,26 @@ def tick_simulation():
|
|
52 |
if random.random() < 0.1:
|
53 |
npc.update_autonomous_behavior("자율 행동 시간")
|
54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
55 |
def simulation_loop():
|
56 |
"""백그라운드에서 주기적으로 시뮬레이션을 실행하는 루프"""
|
57 |
add_log("백그라운드 시뮬레이션 루프 시작됨.")
|
|
|
52 |
if random.random() < 0.1:
|
53 |
npc.update_autonomous_behavior("자율 행동 시간")
|
54 |
|
55 |
+
# 고차원적 자아 기능
|
56 |
+
# 1. 관계 재정의 (2% 확률)
|
57 |
+
if random.random() < 0.02:
|
58 |
+
with simulation_lock:
|
59 |
+
# 성찰할 만한 관계가 있는지 확인
|
60 |
+
if npc.relationships.relationships:
|
61 |
+
# 가장 많은 상호작용을 한 관계를 하나 선택
|
62 |
+
target_name_to_reflect = max(
|
63 |
+
npc.relationships.relationships.keys(),
|
64 |
+
key=lambda name: len(npc.relationships.relationships[name].key_memories)
|
65 |
+
)
|
66 |
+
# 해당 관계에 대해 요약 및 재정의
|
67 |
+
npc.relationships.summarize_relationship(target_name_to_reflect, npc_manager)
|
68 |
+
|
69 |
+
# 2. 가치관 형성 (0.5% 확률, 기억이 30개 이상일 때만)
|
70 |
+
if random.random() < 0.005 and len(npc.memory_store.get_all_memories()) > 30:
|
71 |
+
with simulation_lock:
|
72 |
+
npc.create_symbolic_memory()
|
73 |
+
|
74 |
+
|
75 |
def simulation_loop():
|
76 |
"""백그라운드에서 주기적으로 시뮬레이션을 실행하는 루프"""
|
77 |
add_log("백그라운드 시뮬레이션 루프 시작됨.")
|