humanda5 commited on
Commit
c08ff6f
·
1 Parent(s): e3a194a

멀티턴 대화 구현 완료

Browse files
npc_social_network/README.txt CHANGED
@@ -39,13 +39,16 @@ portfolio/
39
  ├── README.txt
40
 
41
  ├── data/
42
- └── saves/
43
- └── simulation_state.pkl # NPC 월드의 상태가 저장되는 파일
 
 
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
- └── personality_config.py # 성격 및 나이 프로필
 
64
 
65
  ├── models/ # LLM 및 임베딩 모델 관련
66
- ├── gemini_setup.py # Gemini API 설정
67
- ├── llm_helper.py # 감정 추론, 응답 생성
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 simulation_core
4
- from ..models.llm_helper import summarize_memories, summarize_text
 
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}와(과) {memory_content}", importance=7, memory_type="Conversation")
57
- target.remember(content=f"{initiator.korean_name}와(과) {memory_content}", importance=7, memory_type="Conversation")
58
- print(f"[{initiator.korean_name}, {target.korean_name}] 대화 내용이 기억에 저장되었습니다.")
 
 
 
 
 
 
 
 
 
 
 
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
- print(f"--[대화 시작]--")
73
- print(f"주제: {conv_topic} / 참여자: {initiator.korean_name}, {target.korean_name}")
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
- print(f"---[대화 종료: {reason}]---\n")
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, PERSONALITY_TEMPLATE, EMOTION_RELATION_IMPACT
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
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
- if action_type == "TALK":
480
- target_korean_name = plan_details.get("target")
481
- topic = plan_details.get("topic")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
482
 
483
- if target_korean_name:
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(initiator.korean_name, "이", "가")
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 = self.manager.get_npc_by_korean_name(target_name)
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
- print(f"[{self.owner_npc.korean_name}의 관계 요약 업데이트] {target_korean_name}: {profile.summary}")
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("백그라운드 시뮬레이션 루프 시작됨.")