humanda5 commited on
Commit
d57920f
·
1 Parent(s): 20300ba

NPC 수정 진행 및 1차 정상 작동 확인

Browse files
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/alice.faiss ADDED
Binary file (1.58 kB). View file
 
npc_social_network/data/vectorstores/bob.faiss ADDED
Binary file (1.58 kB). View file
 
npc_social_network/data/vectorstores/charlie.faiss ADDED
Binary file (1.58 kB). View file
 
npc_social_network/data/vectorstores/diana.faiss ADDED
Binary file (1.58 kB). View file
 
npc_social_network/data/vectorstores/elin.faiss ADDED
Binary file (1.58 kB). View file
 
npc_social_network/models/llm_helper.py CHANGED
@@ -4,6 +4,8 @@ from npc_social_network.npc.emotion_config import EMOTION_LIST
4
  from npc_social_network.models.gemini_setup import load_gemini
5
  from npc_social_network.npc.npc_memory import Memory
6
  from typing import List
 
 
7
 
8
  # Gemini 모델 초기화
9
  gemini_model = load_gemini()
@@ -55,4 +57,33 @@ def summarize_memories(memories: List[Memory]) -> str:
55
 
56
  # LLM 호출 함수를 사용하여 요약 요청
57
  summary = query_llm_with_prompt(prompt)
58
- return summary
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  from npc_social_network.models.gemini_setup import load_gemini
5
  from npc_social_network.npc.npc_memory import Memory
6
  from typing import List
7
+ import json
8
+ import re
9
 
10
  # Gemini 모델 초기화
11
  gemini_model = load_gemini()
 
57
 
58
  # LLM 호출 함수를 사용하여 요약 요청
59
  summary = query_llm_with_prompt(prompt)
60
+ return summary
61
+
62
+ def analyze_gossip(gossip_content: str) -> dict:
63
+ """LLM을 이용해 소문을 분석하고, 관련 인물과 긍/부정을 JSON으로 반환합니다."""
64
+ prompt="""
65
+ # 지시사항
66
+ 다음 소문 내용에서 핵심 인물 2명과, 그들 사이의 상호작용이 긍정적인지, 부정적인지, 중립적인지 분석해줘.
67
+ 결과를 반드시 아래 JSON 형식으로만 응답해줘, 다른 설명은 붙이지 마.
68
+
69
+ # 소문 내용
70
+ "{gossip_content}"
71
+
72
+ # 분석 결과 (JSON 형식)
73
+ {{
74
+ "person1": "첫 번째 인물 이름",
75
+ "person2": "두 번째 인물 이름",
76
+ "sentiment": "positive or negative or neutral",
77
+ "reason": "한 문장으로 요약한 이유"
78
+ }}
79
+ """
80
+ try:
81
+ response_text = query_llm_with_prompt(prompt)
82
+ # LLM 응답에서 JSON 부분만 추출
83
+ json_match = re.search(r'\{.*\}', response_text, re.DOTALL)
84
+ if json_match:
85
+ return json.loads(json_match.group())
86
+ return {}
87
+ except (json.JSONDecodeError, Exception) as e:
88
+ print(f"소문 분석 중 오류 발생: {e}")
89
+ return {}
npc_social_network/models/llm_prompt_builder.py CHANGED
@@ -15,7 +15,8 @@ def build_npc_prompt(npc:"NPC", user_input: str, matched_memories: List[Memory],
15
  - SocialProfile의 관꼐 요약을 프롬프트에 포함하도록 수정
16
  """
17
  # 대화 상대방의 이름을 동적으로 설정
18
- interlocutor_name = target_npc.name if target_npc else "플레이어"
 
19
 
20
  # 감정 상태 요약
21
  emotion_summary = npc.summarize_emotional_state()
@@ -24,6 +25,15 @@ def build_npc_prompt(npc:"NPC", user_input: str, matched_memories: List[Memory],
24
  # 관계 요약
25
  relationship_description = npc.get_relationship_description(interlocutor_name)
26
 
 
 
 
 
 
 
 
 
 
27
  # 기억 유무에 따른 동적 지침 생성
28
  if matched_memories:
29
  memory_section_title = "# 연관된 기억"
@@ -65,15 +75,16 @@ def build_npc_prompt(npc:"NPC", user_input: str, matched_memories: List[Memory],
65
  {instruction}
66
 
67
  # 나의 프로필
68
- - 이름: {npc.name}
69
  - 직업: {npc.job}
70
- - 현재 대화 상대: {interlocutor_name}
71
  - 현재 시간대: {time_context}
72
 
73
  # 나의 내면 상태
74
  - 현재 감정: {emotion_summary}
75
  - 성격: {personality_summary}
76
- - {interlocutor_name}와(과)의 관계: {relationship_description}
 
77
 
78
  {symbolic_section}
79
 
@@ -81,7 +92,7 @@ def build_npc_prompt(npc:"NPC", user_input: str, matched_memories: List[Memory],
81
  {memory_section}
82
 
83
  # 대화 로그
84
- {interlocutor_name}: "{user_input}"
85
- {npc.name}:
86
  """
87
  return prompt
 
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()
 
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 = "# 연관된 기억"
 
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
 
 
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
@@ -10,10 +10,13 @@ from .emotion_config import EMOTION_LIST, EMOTION_CATEGORY_MAP, EMOTION_DECAY_RA
10
  from .emotion_config import POSITIVE_RELATION_EMOTIONS, NEGATIVE_RELATION_EMOTIONS, COGNITIVE_RELATION_EMOTIONS
11
  from .personality_config import AGE_PROFILE, PERSONALITY_PROFILE
12
  from .npc_relationship import RelationshipManager
13
- from ..models.llm_helper import query_llm_with_prompt, query_llm_for_emotion, summarize_memories
14
  from ..models.llm_prompt_builder import build_npc_prompt
15
  from .npc_memory_embedder import search_similar_memories
16
  from .npc_planner import PlannerManager
 
 
 
17
 
18
 
19
  # Personality 데이터를 관리하고 요약 기능을 제공하는 클래스 정의
@@ -73,8 +76,10 @@ class NPC:
73
  NPC 클래스
74
  NPC에 대한 기본 정보를 객체화
75
  """
76
- def __init__(self, name: str, job: str, personality: Optional[dict]=None):
 
77
  self.name = name
 
78
  self.job = job
79
 
80
  # npc 기억
@@ -83,7 +88,7 @@ class NPC:
83
  # 감정 상태 등 감정 관리자 초기화
84
  self.personality = PersonalityManager(personality)
85
  self.relationships = RelationshipManager(self)
86
- self.relation = {} # 다른 NPC에 대한 평판 저장
87
  self.emotion = EmotionManager(EMOTION_DECAY_RATE, self.personality)
88
  # 행동 관리자
89
  self.behavior = BehaviorManager()
@@ -157,8 +162,9 @@ class NPC:
157
  self.update_emotion(emotion, strength=2.0)
158
 
159
  # 5. 상호작용 기억 저장
160
- interlocutor_name = target_npc.name if target_npc else "플레이어"
161
- memory_content = f"[{interlocutor_name}의 말] '{user_input}' [나의 응답] '{npc_reply}'"
 
162
  memory = self.remember(content=memory_content, importance=7, emotion=emotion, context_tags=[time_context])
163
 
164
  # 6. 관계 상태 업데이트
@@ -209,7 +215,7 @@ class NPC:
209
 
210
  def remember(self, content: str, importance: int = 5, emotion: str = None,
211
  strength: float = 1.0, memory_type:str = "Event",
212
- context_tags: Optional[List[str]]=None) -> Memory:
213
  """
214
  NPC가 새로운 기억을 저장하고 감정 상태에 반영
215
  """
@@ -224,22 +230,12 @@ class NPC:
224
  if emotion and emotion in EMOTION_LIST:
225
  self.update_emotion(emotion, strength) # strength = importance / 5.0으로도 가능
226
  # 만약 기억이 '소문'이라면, 평판을 업데이트
227
- if memory_type == "Gossip":
228
  # (간단한 예시: LLM으로 소문의 대상과 긍/부정을 파악해야 더 정확함)
229
  # 긍정 소문 예시: "밥이 찰리를 도왔다"
230
  # 부정 소문 예시: "엘린이 앨리스와 다퉜다"
231
-
232
- # 임시로 키워드 기반 평판 변경
233
- if "도왔다" in content or "칭찬했다" in content:
234
- # 긍정적 소문의 대상을 찾아 평판 +1
235
- # ... (대상 파싱 로직) ...
236
- pass
237
- elif "다퉜다" in content or "비난했다" in content:
238
- # 부정적 소문의 대상들을 찾아 평판 -1
239
- # ... (대상 파싱 로직) ...
240
- # 예시: self.update_reputation("엘린", -1)
241
- # self.update_reputation("앨리스", -1)
242
- pass
243
  return memory # 생성된 기억 객체 반환
244
 
245
  def update_emotion(self, emotion:str, strength: float = 1.0):
@@ -406,13 +402,13 @@ class NPC:
406
  summary = summarize_memories(memories)
407
  if "[LLM Error]" in summary: return
408
  self.remember(
409
- Memory(content=f"[요약된 기억] {summary}",
410
- importance=9,
411
- emotion=self.emotion.get_dominant_emotion(),
412
- memory_type="Summary")
413
  )
414
 
415
- def update_autonomous_behavior(self, time_context: str):
416
  """
417
  NPC가 스스로 판단하여 행동을 결정하는 자율 행동 로직
418
  주기적으로 호출되어 목표 설정, 계획 실행 등을 담당.
@@ -438,18 +434,18 @@ class NPC:
438
  gossip_to_spread = random.choice(gossip_memories)
439
 
440
  # 아직 대화한 적 없는 NPC를 타겟으로 선정
441
- potential_targets = [npc for npc in npc_manager.all() if npc.name != self.name] # npc_manager에 접근 가능해야 함
442
  if potential_targets:
443
  target_npc = random.choice(potential_targets)
444
 
445
  # LLM을 활용하여 소문을 퍼뜨리는 대화 생성
446
  gossip_dialogue = self.generate_dialogue(
447
- user_input=f"'{target_npc.name}'에게 '{gossip_to_spread.content}'에 대해 넌지시 이야기한다.",
448
  time_context=time_context,
449
  target_npc=target_npc
450
  )
451
 
452
- add_log(f"[소문 전파] {self.name} -> {target_npc.name}: {gossip_dialogue}")
453
  gossip_to_spread.is_shared = True # 소문이 전파되었음을 표시 (중복 전파 방지)
454
 
455
  # 일정 확률로 새로운 목표를 생성 (매번 생성하지 않도록)
@@ -475,7 +471,7 @@ class NPC:
475
 
476
  prompt = f"""
477
  # 지시사항
478
- 나는 '{self.name}'입니다. 다음은 나의 인생 기억 중 일부입니다.
479
  이 기억들을 바탕으로, "{theme}"이라는 주제에 대해 당신의 삶을 대표하는 상징적인 의미나 깨달음을 한 문장의 독백으로 생성해주세요.
480
 
481
  # 나의 기억들
@@ -494,12 +490,49 @@ class NPC:
494
  emotion = "rumination", # 반추, 성찰과 관련된 감정
495
  memory_type="Symbolic",
496
  )
497
- print(f"[{self.name}]의 새로운 깨달음: {symbolic_thought}")
498
 
499
- def update_reputation(self, target_name: str, score_change: int):
500
  """특정 대상에 대한 평판 점수를 변경"""
501
  if self.name == target_name: return # 자기 자신에 대한 평판x (자기 자신의 평판에 대한 내용도 성격 형성에 중요할 수 있지 않나?)
502
 
503
  current_score = self.reputation.get(target_name, 0)
504
  self.reputation[target_name] = current_score + score_change
505
- print(f"[{self.name}]의 {target_name}에 대한 평판: {self.reputation[target_name]} ({score_change:+.0f})")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  from .emotion_config import POSITIVE_RELATION_EMOTIONS, NEGATIVE_RELATION_EMOTIONS, COGNITIVE_RELATION_EMOTIONS
11
  from .personality_config import AGE_PROFILE, PERSONALITY_PROFILE
12
  from .npc_relationship import RelationshipManager
13
+ from ..models.llm_helper import query_llm_with_prompt, query_llm_for_emotion, summarize_memories, analyze_gossip
14
  from ..models.llm_prompt_builder import build_npc_prompt
15
  from .npc_memory_embedder import search_similar_memories
16
  from .npc_planner import PlannerManager
17
+ from typing import TYPE_CHECKING
18
+ if TYPE_CHECKING:
19
+ from .npc_manager import NPCManager
20
 
21
 
22
  # Personality 데이터를 관리하고 요약 기능을 제공하는 클래스 정의
 
76
  NPC 클래스
77
  NPC에 대한 기본 정보를 객체화
78
  """
79
+ def __init__(self, name: str, korean_name: str, job: str,
80
+ personality: Optional[dict]=None):
81
  self.name = name
82
+ self.korean_name = korean_name
83
  self.job = job
84
 
85
  # npc 기억
 
88
  # 감정 상태 등 감정 관리자 초기화
89
  self.personality = PersonalityManager(personality)
90
  self.relationships = RelationshipManager(self)
91
+ self.reputation = {} # 다른 NPC에 대한 평판 저장
92
  self.emotion = EmotionManager(EMOTION_DECAY_RATE, self.personality)
93
  # 행동 관리자
94
  self.behavior = BehaviorManager()
 
162
  self.update_emotion(emotion, strength=2.0)
163
 
164
  # 5. 상호작용 기억 저장
165
+ interlocutor_name = target_npc.name if target_npc else "player"
166
+ interlocutor_korean_name = target_npc.korean_name if target_npc else "플레이어"
167
+ memory_content = f"[{interlocutor_korean_name}의 말] '{user_input}' → [나의 응답] '{npc_reply}'"
168
  memory = self.remember(content=memory_content, importance=7, emotion=emotion, context_tags=[time_context])
169
 
170
  # 6. 관계 상태 업데이트
 
215
 
216
  def remember(self, content: str, importance: int = 5, emotion: str = None,
217
  strength: float = 1.0, memory_type:str = "Event",
218
+ context_tags: Optional[List[str]]=None, npc_manager: "NPCManager" = None) -> Memory:
219
  """
220
  NPC가 새로운 기억을 저장하고 감정 상태에 반영
221
  """
 
230
  if emotion and emotion in EMOTION_LIST:
231
  self.update_emotion(emotion, strength) # strength = importance / 5.0으로도 가능
232
  # 만약 기억이 '소문'이라면, 평판을 업데이트
233
+ if memory_type == "Gossip" and npc_manager:
234
  # (간단한 예시: LLM으로 소문의 대상과 긍/부정을 파악해야 더 정확함)
235
  # 긍정 소문 예시: "밥이 찰리를 도왔다"
236
  # 부정 소문 예시: "엘린이 앨리스와 다퉜다"
237
+ self.process_gossip_and_update_reputation(memory, npc_manager)
238
+
 
 
 
 
 
 
 
 
 
 
239
  return memory # 생성된 기억 객체 반환
240
 
241
  def update_emotion(self, emotion:str, strength: float = 1.0):
 
402
  summary = summarize_memories(memories)
403
  if "[LLM Error]" in summary: return
404
  self.remember(
405
+ content=f"[요약된 기억] {summary}",
406
+ importance=9,
407
+ emotion=self.emotion.get_dominant_emotion(),
408
+ memory_type="Summary"
409
  )
410
 
411
+ def update_autonomous_behavior(self, time_context: str, npc_manager, add_log):
412
  """
413
  NPC가 스스로 판단하여 행동을 결정하는 자율 행동 로직
414
  주기적으로 호출되어 목표 설정, 계획 실행 등을 담당.
 
434
  gossip_to_spread = random.choice(gossip_memories)
435
 
436
  # 아직 대화한 적 없는 NPC를 타겟으로 선정
437
+ potential_targets = [npc for npc in npc_manager.all() if npc.name != self.name]
438
  if potential_targets:
439
  target_npc = random.choice(potential_targets)
440
 
441
  # LLM을 활용하여 소문을 퍼뜨리는 대화 생성
442
  gossip_dialogue = self.generate_dialogue(
443
+ user_input=f"'{target_npc.korean_name}'에게 '{gossip_to_spread.content}'에 대해 넌지시 이야기한다.",
444
  time_context=time_context,
445
  target_npc=target_npc
446
  )
447
 
448
+ add_log(f"[소문 전파] {self.korean_name} -> {target_npc.korean_name}: {gossip_dialogue}")
449
  gossip_to_spread.is_shared = True # 소문이 전파되었음을 표시 (중복 전파 방지)
450
 
451
  # 일정 확률로 새로운 목표를 생성 (매번 생성하지 않도록)
 
471
 
472
  prompt = f"""
473
  # 지시사항
474
+ 나는 '{self.korean_name}'입니다. 다음은 나의 인생 기억 중 일부입니다.
475
  이 기억들을 바탕으로, "{theme}"이라는 주제에 대해 당신의 삶을 대표하는 상징적인 의미나 깨달음을 한 문장의 독백으로 생성해주세요.
476
 
477
  # 나의 기억들
 
490
  emotion = "rumination", # 반추, 성찰과 관련된 감정
491
  memory_type="Symbolic",
492
  )
493
+ print(f"[{self.korean_name}]의 새로운 깨달음: {symbolic_thought}")
494
 
495
+ def update_reputation(self, target_name: str, score_change: int, npc_manager: "NPCManager"):
496
  """특정 대상에 대한 평판 점수를 변경"""
497
  if self.name == target_name: return # 자기 자신에 대한 평판x (자기 자신의 평판에 대한 내용도 성격 형성에 중요할 수 있지 않나?)
498
 
499
  current_score = self.reputation.get(target_name, 0)
500
  self.reputation[target_name] = current_score + score_change
501
+
502
+ target_npc = npc_manager.get_npc_by_name(target_name)
503
+ target_korean_name = target_npc.korean_name if target_npc else target_name
504
+
505
+ print(f"[{self.korean_name}]의 {target_korean_name}에 대한 평판: {self.reputation[target_name]} ({score_change:+.0f})")
506
+
507
+
508
+ def process_gossip_and_update_reputation(self, gossip_memory: Memory, npc_manager: "NPCManager"):
509
+ """LLM을 이용해 소문을 분석하고 관련 인물들의 평판을 업데이트"""
510
+
511
+ analysis = analyze_gossip(gossip_memory.content)
512
+ if not analysis:
513
+ return
514
+
515
+ person1_korean_name = analysis.get("person1")
516
+ person2_korean_name = analysis.get("person2")
517
+ sentiment = analysis.get("sentiment")
518
+
519
+ if not all([person1_korean_name, person2_korean_name, sentiment]):
520
+ return
521
+
522
+ score_change = 0
523
+
524
+ if sentiment == "positive":
525
+ score_change = 2
526
+ elif sentiment == "negative":
527
+ score_change = -2
528
+
529
+ if score_change != 0:
530
+ # LLM이 반환한 한글 이름으로 NPC 객체를 찾아 영어 ID를 가져온다.
531
+ p1_npc = npc_manager.get_npc_by_korean_name(person1_korean_name)
532
+ p2_npc = npc_manager.get_npc_by_korean_name(person2_korean_name)
533
+
534
+ # 소문의 대상이 된 두 사람에 대한 나의 평판을 변경
535
+ if p1_npc:
536
+ self.update_reputation(p1_npc.name, score_change, npc_manager)
537
+ if p2_npc:
538
+ self.update_reputation(p2_npc.name, score_change, npc_manager)
npc_social_network/npc/npc_manager.py CHANGED
@@ -5,24 +5,37 @@ from typing import TYPE_CHECKING, List, Optional
5
  if TYPE_CHECKING:
6
  from .npc_base import NPC
7
 
 
 
 
 
 
 
 
8
  class NPCManager:
9
  """모든 NPC를 관리하는 중앙 관리자"""
10
  def __init__(self):
11
  self.npcs: list['NPC'] = []
12
  self.npc_dict: dict[str, 'NPC'] = {}
 
13
 
14
  def add_npc(self, npc: 'NPC'):
15
  """NPC를 매니저에 추가합니다."""
16
  if npc.name not in self.npc_dict:
17
  self.npcs.append(npc)
18
  self.npc_dict[npc.name] = npc
 
19
 
20
  def get_npc_by_name(self, name: str) -> Optional['NPC']:
21
  """
22
- NPC 이름을 통해서 NPC의 정보를 반환
23
  """
24
  return self.npc_dict.get(name)
25
 
 
 
 
 
26
  def get_random_npc(self, exclude: Optional['NPC']=None) -> Optional['NPC']:
27
  """
28
  특정 NPC를 제외하고 랜덤한 NPC를 선택
@@ -55,27 +68,30 @@ class NPCManager:
55
  if not initiator or not target:
56
  return
57
 
58
- print(f"\n---[NPC 상호작용 이벤트]---\n{initiator.name}(가) {target.name}에게 상호작용을 시도합니다.")
 
 
 
59
 
60
  # 1. Initiator가 Target에 대해 무슨 말을 할지 LLM으로 생성
61
  prompt = f"""
62
  # 지시사항
63
- 당신은 '{initiator.name}'입니다. 지금 당신은 '{target.name}'와 마주쳤습니다.
64
- '{target.name}'에 대한 당신의 현재 생각과 감정을 바탕으로, 먼저 건넬 자연스러운 첫 마디를 생성하세요.
65
  (예: "앨리스, 어제 시장에서 파는 그 옷 정말 예쁘더라.", "밥, 요즘 허리가 아프다던데 괜찮아?")
66
 
67
  # 당신의 정보
68
- - '{target.name}'와의 관계: {initiator.relationships.get_relationship_summary(target.name)}
69
 
70
  → 첫 마디:
71
  """
72
  initial_utterance = query_llm_with_prompt(prompt).replace("'", "").strip()
73
 
74
  if "[LLM Error]" in initial_utterance:
75
- print(f"[{initiator.name}] 대화 시작에 실패했습니다.")
76
  return None, None
77
 
78
- print(f"[{initiator.name}]: {initial_utterance}")
79
 
80
  # 2. Target이 Initiator의 말을 듣고 응답 생성
81
  # generate_dialogue 함수를 재사용하되, target_npc 인자를 전달
@@ -85,7 +101,7 @@ class NPCManager:
85
  target_npc=initiator
86
  )
87
 
88
- print(f"[{target.name}]: {response_utterance}")
89
  print("-------------------------\n")
90
  return initiator, target, initial_utterance
91
 
 
5
  if TYPE_CHECKING:
6
  from .npc_base import NPC
7
 
8
+ def get_korean_postposition(name, first_char, second_char):
9
+ """이름의 마지막 글자 받침 유무에 따라 올바른 조사를 반환"""
10
+ if (ord(name[-1]) - 0xAC00) % 28 > 0:
11
+ return first_char
12
+ else:
13
+ return second_char
14
+
15
  class NPCManager:
16
  """모든 NPC를 관리하는 중앙 관리자"""
17
  def __init__(self):
18
  self.npcs: list['NPC'] = []
19
  self.npc_dict: dict[str, 'NPC'] = {}
20
+ self.korean_name_to_npc: dict[str, 'NPC'] = {} # 한글 이름 검색용 딕셔너리
21
 
22
  def add_npc(self, npc: 'NPC'):
23
  """NPC를 매니저에 추가합니다."""
24
  if npc.name not in self.npc_dict:
25
  self.npcs.append(npc)
26
  self.npc_dict[npc.name] = npc
27
+ self.korean_name_to_npc[npc.korean_name] = npc
28
 
29
  def get_npc_by_name(self, name: str) -> Optional['NPC']:
30
  """
31
+ NPC 영어 ID를 통해서 NPC의 정보를 반환
32
  """
33
  return self.npc_dict.get(name)
34
 
35
+ def get_npc_by_korean_name(self, korean_name: str) -> Optional['NPC']:
36
+ """NPC 한글 이름을 통해서 NPC의 정보를 반환"""
37
+ return self.korean_name_to_npc.get(korean_name)
38
+
39
  def get_random_npc(self, exclude: Optional['NPC']=None) -> Optional['NPC']:
40
  """
41
  특정 NPC를 제외하고 랜덤한 NPC를 선택
 
68
  if not initiator or not target:
69
  return
70
 
71
+ initiator_postposition = get_korean_postposition(initiator.korean_name, "", "가")
72
+ target_postposition = get_korean_postposition(target.korean_name, "에게", "에게")
73
+
74
+ print(f"\n---[NPC 상호작용 이벤트]---\n{initiator.korean_name}{initiator_postposition} {target.korean_name}{target_postposition} 상호작용을 시도합니다.")
75
 
76
  # 1. Initiator가 Target에 대해 무슨 말을 할지 LLM으로 생성
77
  prompt = f"""
78
  # 지시사항
79
+ 당신은 '{initiator.korean_name}'입니다. 지금 당신은 '{target.korean_name}'와 마주쳤습니다.
80
+ '{target.korean_name}'에 대한 당신의 현재 생각과 감정을 바탕으로, 먼저 건넬 자연스러운 첫 마디를 생성하세요.
81
  (예: "앨리스, 어제 시장에서 파는 그 옷 정말 예쁘더라.", "밥, 요즘 허리가 아프다던데 괜찮아?")
82
 
83
  # 당신의 정보
84
+ - '{target.korean_name}'와의 관계: {initiator.relationships.get_relationship_summary(target.name)}
85
 
86
  → 첫 마디:
87
  """
88
  initial_utterance = query_llm_with_prompt(prompt).replace("'", "").strip()
89
 
90
  if "[LLM Error]" in initial_utterance:
91
+ print(f"[{initiator.korean_name}] 대화 시작에 실패했습니다.")
92
  return None, None
93
 
94
+ print(f"[{initiator.korean_name}]: {initial_utterance}")
95
 
96
  # 2. Target이 Initiator의 말을 듣고 응답 생성
97
  # generate_dialogue 함수를 재사용하되, target_npc 인자를 전달
 
101
  target_npc=initiator
102
  )
103
 
104
+ print(f"[{target.korean_name}]: {response_utterance}")
105
  print("-------------------------\n")
106
  return initiator, target, initial_utterance
107
 
npc_social_network/npc/npc_planner.py CHANGED
@@ -47,17 +47,17 @@ class PlannerManager:
47
 
48
  # LLM에게 전달할 NPC 상태 요약
49
  state_summary = f"""
50
- # 당신의 현재 상태 요약
51
  - 주요 감정: {self.npc.summarize_emotional_state()}
52
  - 최근 기억 3개:
53
  {chr(10).join([f' - {mem.content}' for mem in self.npc.memory_store.get_recent_memories(limit=3)])}
54
- - 플레이어와의 관계: {self.npc.get_relationship_description("플레이어")}
55
  """
56
 
57
  prompt = f"""
58
  # 지시사항
59
- 당신은 '{self.npc.name}'입니다. 당신의 현재 상태를 바탕으로, 지금 가장 중요하게 추구추구해야 할 장기적인 목표를 한 문장으로 제안하세요.
60
- → 목표는 당신의 삶을 더 낫게 만들거나, 문제를 해결하거나, 관계를 개선하는 것이어야 합니다.
61
 
62
  {state_summary}
63
 
@@ -68,10 +68,10 @@ class PlannerManager:
68
 
69
  if goal_description and "[LLM Error]" not in goal_description:
70
  self.current_goal = Goal(description=goal_description)
71
- print(f"[{self.npc.name}의 새로운 목표] {self.current_goal.description}")
72
  self.create_plan_for_goal()
73
  else:
74
- print(f"[{self.npc.name}] 목표 생성에 실패했습니다.")
75
 
76
  def create_plan_for_goal(self):
77
  """
@@ -82,13 +82,11 @@ class PlannerManager:
82
 
83
  prompt = f"""
84
  # 지시사항
85
- 당신은 '{self.npc.name}'입니다. 당신의 목표는 '{self.current_goal.description}'입니다.
86
  → 이 목표를 달성하기 위한 3~5개의 구체적이고 실행 가능한 행동 단계를 번호 목록으로 작성해주세요.
87
  → 각 단계는 짧은 문장으로 표현하세요,
88
 
89
  → 계획:
90
-
91
- 1.
92
  """
93
  plan_text = "1. " + query_llm_with_prompt(prompt).replace("계획:", "").strip()
94
  steps = [step.strip() for step in plan_text.split('\n') if step.strip()]
@@ -98,7 +96,7 @@ class PlannerManager:
98
 
99
  if cleaned_steps:
100
  self.current_plan = Plan(goal=self.current_goal, steps=cleaned_steps)
101
- print(f"[{self.npc.name}의 계획 수립]")
102
  for i, step in enumerate(self.current_plan.steps):
103
  print(f" - {i+1}단계: {step}")
104
 
@@ -113,13 +111,13 @@ class PlannerManager:
113
  step_index = plan.current_step_index
114
 
115
  if step_index >= len(plan.steps):
116
- print(f"[{self.npc.name}] 목표 달성: {plan.goal.description}")
117
  plan.goal.status = "completed"
118
  self.current_plan = None
119
  return f"({plan.goal.description} 목표를 달성하여 만족스럽습니다.)"
120
 
121
  current_step = plan.steps[step_index]
122
- print(f"[{self.npc.name}의 행동 계획 실행] {step_index + 1}/{len(plan.steps)}: {current_step}")
123
 
124
  # 다음 단계로
125
  plan.current_step_index += 1
 
47
 
48
  # LLM에게 전달할 NPC 상태 요약
49
  state_summary = f"""
50
+ # 나의 현재 상태 요약
51
  - 주요 감정: {self.npc.summarize_emotional_state()}
52
  - 최근 기억 3개:
53
  {chr(10).join([f' - {mem.content}' for mem in self.npc.memory_store.get_recent_memories(limit=3)])}
54
+ - 플레이어와의 관계: {self.npc.get_relationship_description("player")}
55
  """
56
 
57
  prompt = f"""
58
  # 지시사항
59
+ 나는 '{self.npc.korean_name}'입니다. 나의 현재 상태를 바탕으로, 지금 가장 중요하게 추구추구해야 할 장기적인 목표를 한 문장으로 제안하세요.
60
+ → 목표는 나의 삶을 더 낫게 만들거나, 문제를 해결하거나, 관계를 개선하는 것이어야 합니다.
61
 
62
  {state_summary}
63
 
 
68
 
69
  if goal_description and "[LLM Error]" not in goal_description:
70
  self.current_goal = Goal(description=goal_description)
71
+ print(f"[{self.npc.korean_name}의 새로운 목표] {self.current_goal.description}")
72
  self.create_plan_for_goal()
73
  else:
74
+ print(f"[{self.npc.korean_name}] 목표 생성에 실패했습니다.")
75
 
76
  def create_plan_for_goal(self):
77
  """
 
82
 
83
  prompt = f"""
84
  # 지시사항
85
+ 나는 '{self.npc.korean_name}'입니다. 나의 목표는 '{self.current_goal.description}'입니다.
86
  → 이 목표를 달성하기 위한 3~5개의 구체적이고 실행 가능한 행동 단계를 번호 목록으로 작성해주세요.
87
  → 각 단계는 짧은 문장으로 표현하세요,
88
 
89
  → 계획:
 
 
90
  """
91
  plan_text = "1. " + query_llm_with_prompt(prompt).replace("계획:", "").strip()
92
  steps = [step.strip() for step in plan_text.split('\n') if step.strip()]
 
96
 
97
  if cleaned_steps:
98
  self.current_plan = Plan(goal=self.current_goal, steps=cleaned_steps)
99
+ print(f"[{self.npc.korean_name}의 계획 수립]")
100
  for i, step in enumerate(self.current_plan.steps):
101
  print(f" - {i+1}단계: {step}")
102
 
 
111
  step_index = plan.current_step_index
112
 
113
  if step_index >= len(plan.steps):
114
+ print(f"[{self.npc.korean_name}] 목표 달성: {plan.goal.description}")
115
  plan.goal.status = "completed"
116
  self.current_plan = None
117
  return f"({plan.goal.description} 목표를 달성하여 만족스럽습니다.)"
118
 
119
  current_step = plan.steps[step_index]
120
+ print(f"[{self.npc.korean_name}의 행동 계획 실행] {step_index + 1}/{len(plan.steps)}: {current_step}")
121
 
122
  # 다음 단계로
123
  plan.current_step_index += 1
npc_social_network/npc/npc_relationship.py CHANGED
@@ -5,6 +5,7 @@ from typing import List, Optional, TYPE_CHECKING
5
  if TYPE_CHECKING:
6
  from .npc_memory import Memory
7
  from .npc_base import NPC
 
8
 
9
  class SocialProfile:
10
  """특정 대상과의 사회적 관계를 종합적으로 관리하는 클래스"""
@@ -78,7 +79,7 @@ class RelationshipManager:
78
  profile = self._get_or_create_profile(target_name)
79
  return profile.summary
80
 
81
- def summarize_relationship(self, target_name: str):
82
  """ LLM을 사용하여 특정 대상과의 관계를 주기적으로 요약하고 업데이트"""
83
  from ..models.llm_helper import query_llm_with_prompt
84
  profile = self._get_or_create_profile(target_name)
@@ -86,21 +87,24 @@ class RelationshipManager:
86
  if not profile.key_memories:
87
  return # 요약할 기억이 없으면 실행 안함
88
 
 
 
 
89
  memory_details = "\n".join([f"- {mem.content} (감정: {mem.emotion})" for mem in profile.key_memories])
90
  prompt = f"""
91
  # 지시사항
92
- 당신은 '{self.owner_npc.name}'입니다. 당신의 기억을 바탕으로 '{target_name}'에 대한 당신의 생각과 감정을 한두 문장으로 솔직하게 요약해주세요.
93
 
94
- # '{target_name}'와(과)의 핵심 기억들
95
  {memory_details}
96
 
97
- → '{target_name}'에 대한 나의 생각:
98
  """
99
 
100
  summary = query_llm_with_prompt(prompt).replace("'", "").strip()
101
 
102
  if summary and "[LLM Error]" not in summary:
103
  profile.summary = summary
104
- print(f"[{self.owner_npc.name}의 관계 요약 업데이트] → {target_name}: {profile.summary}")
105
 
106
 
 
5
  if TYPE_CHECKING:
6
  from .npc_memory import Memory
7
  from .npc_base import NPC
8
+ from .npc_manager import NPCManager
9
 
10
  class SocialProfile:
11
  """특정 대상과의 사회적 관계를 종합적으로 관리하는 클래스"""
 
79
  profile = self._get_or_create_profile(target_name)
80
  return profile.summary
81
 
82
+ def summarize_relationship(self, target_name: str, npc_manager: "NPCManager"):
83
  """ LLM을 사용하여 특정 대상과의 관계를 주기적으로 요약하고 업데이트"""
84
  from ..models.llm_helper import query_llm_with_prompt
85
  profile = self._get_or_create_profile(target_name)
 
87
  if not profile.key_memories:
88
  return # 요약할 기억이 없으면 실행 안함
89
 
90
+ target_npc = npc_manager.get_npc_by_korean_name(target_name)
91
+ target_korean_name = target_npc.korean_name if target_npc else target_name
92
+
93
  memory_details = "\n".join([f"- {mem.content} (감정: {mem.emotion})" for mem in profile.key_memories])
94
  prompt = f"""
95
  # 지시사항
96
+ 당신은 '{self.owner_npc.korean_name}'입니다. 당신의 기억을 바탕으로 '{target_korean_name}'에 대한 당신의 생각과 감정을 한두 문장으로 솔직하게 요약해주세요.
97
 
98
+ # '{target_korean_name}'와(과)의 핵심 기억들
99
  {memory_details}
100
 
101
+ → '{target_korean_name}'에 대한 나의 생각:
102
  """
103
 
104
  summary = query_llm_with_prompt(prompt).replace("'", "").strip()
105
 
106
  if summary and "[LLM Error]" not in summary:
107
  profile.summary = summary
108
+ print(f"[{self.owner_npc.korean_name}의 관계 요약 업데이트] → {target_korean_name}: {profile.summary}")
109
 
110
 
npc_social_network/routes/npc_route.py CHANGED
@@ -13,15 +13,6 @@ npc_bp = Blueprint(
13
  static_folder="../static"
14
  )
15
 
16
- # --- 한글 이름을 안전한 영어 파일명/ID로 변환하는 함수 ---
17
- def to_english_id(name):
18
- name_map = {
19
- "엘린": "elin", "밥": "bob", "앨리스": "alice",
20
- "찰리": "charlie", "다이애나": "diana"
21
- }
22
- return name_map.get(name, name.lower().replace(" ", "_"))
23
-
24
-
25
  @npc_bp.route("/")
26
  def dashboard():
27
  return render_template("dashboard.html")
@@ -38,12 +29,12 @@ def get_world_state():
38
  nodes = []
39
  for npc in all_npcs:
40
  # 각 NPC의 영어 ID를 기반으로 이미지 파일명을 생성합니다. (예: elin -> npc_elin.png)
41
- image_filename = f"images/npc/npc_{to_english_id(npc.name)}.png"
42
 
43
  nodes.append({
44
  # id는 영어로, label은 한글로 분리
45
- "id": to_english_id(npc.name),
46
- "label": npc.name,
47
  "shape": "image", # 노드 모양을 'image'로 지정합니다.
48
  "image": url_for('npc_social.static', filename=image_filename), # 올바른 이미지 경로를 생성합니다.
49
  "size": 40 # 이미지 크기를 적절히 조절합니다.
@@ -53,8 +44,8 @@ def get_world_state():
53
  drawn_relations = set()
54
  for npc in all_npcs:
55
  for target_name, profile in npc.relationships.relationships.items():
56
- from_id = to_english_id(npc.name)
57
- to_id = to_english_id(target_name)
58
 
59
  relation_pair = tuple(sorted((from_id, to_id)))
60
  if relation_pair in drawn_relations: continue
@@ -100,7 +91,7 @@ def inject_event():
100
  npc_name, event_text = data.get("npc_name"), data.get("event_text")
101
 
102
  with simulation_core.simulation_lock:
103
- npc = simulation_core.npc_manager.get_npc_by_name(npc_name)
104
  if npc and event_text:
105
  npc.remember(content=f"[요약된 기억] {event_text}", importance=10, emotion="surprise", memory_type="Summary") # 수정 필요: 신이 돼서 넣는게 아니라, NPC가 마치 과거에 겪은 일처럼 자연스럽게 느끼도록
106
  simulation_core.add_log(f"이벤트 주입(기억 요약) -> {npc_name}: '{event_text}'")
@@ -111,12 +102,12 @@ def inject_event():
111
  @npc_bp.route("/api/npc_details/<npc_name>", methods=['GET'])
112
  def get_npc_details(npc_name):
113
  with simulation_core.simulation_lock:
114
- npc = simulation_core.npc_manager.get_npc_by_name(npc_name)
115
  if not npc:
116
  return jsonify({"error": "NPC not found"}), 404
117
 
118
  details = {
119
- "name": npc.name, "age": npc.age, "job": npc.job,
120
  "personality_summary": npc.personality.get_personality_summary(),
121
  "emotions": npc.get_composite_emotion_state(top_n=5),
122
  "goals": npc.planner.current_goal.description if npc.planner.has_active_plan() else "특별한 목표 없음",
 
13
  static_folder="../static"
14
  )
15
 
 
 
 
 
 
 
 
 
 
16
  @npc_bp.route("/")
17
  def dashboard():
18
  return render_template("dashboard.html")
 
29
  nodes = []
30
  for npc in all_npcs:
31
  # 각 NPC의 영어 ID를 기반으로 이미지 파일명을 생성합니다. (예: elin -> npc_elin.png)
32
+ image_filename = f"images/npc/npc_{npc.name}.png"
33
 
34
  nodes.append({
35
  # id는 영어로, label은 한글로 분리
36
+ "id": npc.name,
37
+ "label": npc.korean_name,
38
  "shape": "image", # 노드 모양을 'image'로 지정합니다.
39
  "image": url_for('npc_social.static', filename=image_filename), # 올바른 이미지 경로를 생성합니다.
40
  "size": 40 # 이미지 크기를 적절히 조절합니다.
 
44
  drawn_relations = set()
45
  for npc in all_npcs:
46
  for target_name, profile in npc.relationships.relationships.items():
47
+ from_id = npc.name
48
+ to_id = target_name
49
 
50
  relation_pair = tuple(sorted((from_id, to_id)))
51
  if relation_pair in drawn_relations: continue
 
91
  npc_name, event_text = data.get("npc_name"), data.get("event_text")
92
 
93
  with simulation_core.simulation_lock:
94
+ npc = simulation_core.npc_manager.get_npc_by_korean_name(npc_name)
95
  if npc and event_text:
96
  npc.remember(content=f"[요약된 기억] {event_text}", importance=10, emotion="surprise", memory_type="Summary") # 수정 필요: 신이 돼서 넣는게 아니라, NPC가 마치 과거에 겪은 일처럼 자연스럽게 느끼도록
97
  simulation_core.add_log(f"이벤트 주입(기억 요약) -> {npc_name}: '{event_text}'")
 
102
  @npc_bp.route("/api/npc_details/<npc_name>", methods=['GET'])
103
  def get_npc_details(npc_name):
104
  with simulation_core.simulation_lock:
105
+ npc = simulation_core.npc_manager.get_npc_by_korean_name(npc_name)
106
  if not npc:
107
  return jsonify({"error": "NPC not found"}), 404
108
 
109
  details = {
110
+ "name": npc.korean_name, "age": npc.age, "job": npc.job,
111
  "personality_summary": npc.personality.get_personality_summary(),
112
  "emotions": npc.get_composite_emotion_state(top_n=5),
113
  "goals": npc.planner.current_goal.description if npc.planner.has_active_plan() else "특별한 목표 없음",
npc_social_network/scenarios/scenario_setup.py CHANGED
@@ -1,6 +1,6 @@
1
  from ..npc.npc_manager import NPCManager
2
  from ..npc.npc_base import NPC
3
- from ..npc.npc_memory import Memory
4
 
5
  def setup_initial_scenario() -> NPCManager:
6
  """
@@ -15,23 +15,23 @@ def setup_initial_scenario() -> NPCManager:
15
  # --- 1. NPC 객체 생성 ---
16
  # 엘린: 호기심 많고 감정적인 마법사
17
  personality_elin = {"sensitive": 0.8, "stoic": 0.3, "cognitive_bias": 0.7}
18
- elin = NPC("엘린", "마법사", personality=personality_elin)
19
 
20
  # 밥: 무뚝뚝하지만 정직한 대장장이
21
  personality_bob = {"sensitive": 0.2, "stoic": 0.8, "cognitive_bias": 0.4}
22
- bob = NPC("밥", "대장장이", personality=personality_bob)
23
 
24
  # 앨리스: 사교적이고 계산적인 상인
25
  personality_alice = {"sensitive": 0.5, "stoic": 0.5, "cognitive_bias": 0.8}
26
- alice = NPC("앨리스", "상인", personality=personality_alice)
27
 
28
  # 찰리: 성실하고 평화로운 농부
29
  personality_charlie = {"sensitive": 0.6, "stoic": 0.6, "cognitive_bias": 0.3}
30
- charlie = NPC("찰리", "농부", personality=personality_charlie)
31
 
32
  # 다이애나: 조용하고 관찰력 있는 사서
33
  personality_diana = {"sensitive": 0.7, "stoic": 0.7, "cognitive_bias": 0.9}
34
- diana = NPC("다이애나", "사서", personality=personality_diana)
35
 
36
  # --- 2. 초기 기억 주입 ---
37
  elin.remember(content="어젯밤 앨리스와 시장 가격 때문에 크게 다퉜다.", importance=8, emotion="anger")
@@ -49,15 +49,20 @@ def setup_initial_scenario() -> NPCManager:
49
 
50
  # --- 4. 초기 관계 설정 ---
51
  # 엘린 <-> 앨리스 (나쁜 관계)
52
- elin.relationships.update_relationship("앨리스", "anger", strength=5.0)
53
- alice.relationships.update_relationship("엘린", "resentment", strength=4.0)
54
 
55
  # 밥 <-> 찰리 (좋은 관계)
56
- bob.relationships.update_relationship("찰리", "gratitude", strength=6.0)
57
- charlie.relationships.update_relationship("", "joy", strength=7.0)
58
 
59
  # 다이애나 -> 밥 (약간 부정적)
60
- diana.relationships.update_relationship("", "disgust", strength=3.0)
 
 
 
 
 
61
 
62
  print("✅ '시작의 마을' 시나리오 설정이 완료되었습니다.")
63
  return npc_manager
 
1
  from ..npc.npc_manager import NPCManager
2
  from ..npc.npc_base import NPC
3
+ from ..npc.npc_memory_embedder import embed_npc_memories
4
 
5
  def setup_initial_scenario() -> NPCManager:
6
  """
 
15
  # --- 1. NPC 객체 생성 ---
16
  # 엘린: 호기심 많고 감정적인 마법사
17
  personality_elin = {"sensitive": 0.8, "stoic": 0.3, "cognitive_bias": 0.7}
18
+ elin = NPC('elin', "엘린", "마법사", personality=personality_elin)
19
 
20
  # 밥: 무뚝뚝하지만 정직한 대장장이
21
  personality_bob = {"sensitive": 0.2, "stoic": 0.8, "cognitive_bias": 0.4}
22
+ bob = NPC('bob', "밥", "대장장이", personality=personality_bob)
23
 
24
  # 앨리스: 사교적이고 계산적인 상인
25
  personality_alice = {"sensitive": 0.5, "stoic": 0.5, "cognitive_bias": 0.8}
26
+ alice = NPC('alice', "앨리스", "상인", personality=personality_alice)
27
 
28
  # 찰리: 성실하고 평화로운 농부
29
  personality_charlie = {"sensitive": 0.6, "stoic": 0.6, "cognitive_bias": 0.3}
30
+ charlie = NPC('charlie', "찰리", "농부", personality=personality_charlie)
31
 
32
  # 다이애나: 조용하고 관찰력 있는 사서
33
  personality_diana = {"sensitive": 0.7, "stoic": 0.7, "cognitive_bias": 0.9}
34
+ diana = NPC('diana', "다이애나", "사서", personality=personality_diana)
35
 
36
  # --- 2. 초기 기억 주입 ---
37
  elin.remember(content="어젯밤 앨리스와 시장 가격 때문에 크게 다퉜다.", importance=8, emotion="anger")
 
49
 
50
  # --- 4. 초기 관계 설정 ---
51
  # 엘린 <-> 앨리스 (나쁜 관계)
52
+ elin.relationships.update_relationship("alice", "anger", strength=5.0)
53
+ alice.relationships.update_relationship("elin", "resentment", strength=4.0)
54
 
55
  # 밥 <-> 찰리 (좋은 관계)
56
+ bob.relationships.update_relationship("charlie", "gratitude", strength=6.0)
57
+ charlie.relationships.update_relationship("bob", "joy", strength=7.0)
58
 
59
  # 다이애나 -> 밥 (약간 부정적)
60
+ diana.relationships.update_relationship("bob", "disgust", strength=3.0)
61
+
62
+ # 모든 NPC의 초기 기억을 FAISS 인덱스로 저장
63
+ print("모든 NPC의 초기 기억에 대한 FAISS 인덱스를 생성합니다...")
64
+ for npc in npc_manager.all():
65
+ embed_npc_memories(npc)
66
 
67
  print("✅ '시작의 마을' 시나리오 설정이 완료되었습니다.")
68
  return npc_manager
npc_social_network/simulation_core.py CHANGED
@@ -30,16 +30,48 @@ def add_log(message):
30
 
31
  def tick_simulation():
32
  """시뮬레이션의 시간을 한 단계 진행"""
33
- if not npc_manager or simulation_paused:
34
  return
35
 
36
  with simulation_lock:
 
 
 
37
  add_log("시뮬레이션 틱 시작")
38
 
39
  for npc in npc_manager.all():
40
  npc.decay_emotions()
41
  npc.decay_memories()
42
- npc.update_autonomous_behavior("자율 행동 시간")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
  if len(npc_manager.all()) >= 2:
45
  try:
 
30
 
31
  def tick_simulation():
32
  """시뮬레이션의 시간을 한 단계 진행"""
33
+ if not npc_manager:
34
  return
35
 
36
  with simulation_lock:
37
+ if simulation_paused: # 정지 상태에서는 틱 실행 x
38
+ return
39
+
40
  add_log("시뮬레이션 틱 시작")
41
 
42
  for npc in npc_manager.all():
43
  npc.decay_emotions()
44
  npc.decay_memories()
45
+ if random.random() < 0.1:
46
+ npc.update_autonomous_behavior("자율 행동 시간", npc_manager, add_log)
47
+
48
+ if len(npc_manager.all()) >= 3: # 목격자가 있으려면 최소 3명 필요
49
+ try:
50
+ add_log("NPC 간 자율 상호작용 및 목격 시도")
51
+ # 1. 상호작용을 호출하고 참여자들을 반환받음
52
+ initiator, target, initial_utterance = npc_manager.initiate_npc_to_npc_interaction("자율 행동 시간")
53
+
54
+ # 2. 상호작용이 성공했고, 목격자가 될 NPC가 있다면
55
+ if initiator and target:
56
+ potential_witnesses = [npc for npc in npc_manager.all() if npc not in [initiator, target]]
57
+ if potential_witnesses:
58
+ witness = random.choice(potential_witnesses)
59
+
60
+ # 3. 목격한 내용을 바탕으로 '소문' 기억 생성 (대화 내용 요약, 관계에 따른 거짓말 등 고도화 가능)
61
+ gossip_content = f"'{initiator.name}'이(가) '{target.name}'에게 '{initial_utterance[:100]}...'라고 말하는 것을 봤다."
62
+
63
+ # 4. 목격자가 '소문'을 기억하도록 함
64
+ witness.remember(
65
+ content=gossip_content,
66
+ importance=4,
67
+ emotion="curiosity",
68
+ memory_type="Gossip",
69
+ npc_manager = npc_manager
70
+ )
71
+ add_log(f"[목격] {witness.name}이(가) {initiator.name}와 {target.name}의 대화를 목격함.")
72
+
73
+ except Exception as e:
74
+ add_log(f"상호작용/목격 중 오류 발생: {e}")
75
 
76
  if len(npc_manager.all()) >= 2:
77
  try: