humanda5 commited on
Commit
9df6a7e
·
1 Parent(s): 1ab2ba0

에러 수정 및 리팩토링

Browse files
npc_social_network/README.txt CHANGED
@@ -117,7 +117,7 @@ portfolio/
117
 
118
  ## 🔁 주요 시퀀스 흐름
119
 
120
- ### 🗨️ `interact_with_player()` 전체 흐름
121
  1. LLM으로 감정 추론 (`query_llm_for_emotion`)
122
  2. 기억 검색 (FAISS + embedding)
123
  3. LLM 프롬프트 생성 (`build_npc_prompt`)
@@ -152,7 +152,7 @@ GET /run_npc_simulation
152
  | 2단계 | NPC 감정 상태 및 감정 HUD 구현 | ✅ 완료 | 감정 상태 및 HUD 표시 (Pygame + 웹)
153
  | 3단계 | 감정 ↔ 행동 연동 구조 구현 | ✅ 완료 | 감정 ↔ 행동 매핑 구현
154
  | 4단계 | 감정 ↔ 기억 저장 및 decay 구현 | ✅ 완료 | Memory 감정 및 decay 적용 구조
155
- | 5단계 | 감정 ↔ 관계 변화 구조 구현 | 🔸 부분 완료 | 구조는 구현됨, 플레이어 대상 반영 보완
156
  | 6단계 | 성격 시스템 도입 | ✅ 완료 | 성격 분리 + 초기 bias 설계
157
  | 7단계 | NPC 간 상호작용 구조화 | ✅ 완료 | NPC 상호작용 → 감정/관계 반영
158
  | 8단계 | Memory 임베딩 + FAISS 기반 검색 | ✅ 완료 | FAISS 검색 + 임베딩 저장 구조
@@ -160,7 +160,7 @@ GET /run_npc_simulation
160
  | 10단계 | 감정 상태 HUD 시각화 (웹 + 게임 화면) | ✅ 완료 | 감정 상태 HUD (웹/게임 연동)
161
 
162
  # 핵심 시스템 고도화
163
- | 11단계 | 감정/기억/관계 통합 정비 | 🔄 진행중 | 관계 통합, 감정-기억-관계 연결 완성도 향상 중
164
  | 12단계 | LLM 대사 생성 고도화 | ✅ 완료 | LLM 대사 생성 개선, 프롬프트 완성
165
  | 13단계 | 기억 decay 정교화 | ⏳ 미진행 | decay 시점별 변화율 조정 등 정교화 미실행
166
  | 14-1단계 | 기억 임베딩 구조 확장 | ✅ 완료 | Memory 객체 임베딩 구조 도입
@@ -173,16 +173,16 @@ GET /run_npc_simulation
173
  | 20단계 | NPC 사회 네트워크 시각화 | ⏳ 미진행 |
174
 
175
  # 고급 인간화 및 자각형 NPC 확장
176
- | 21단계 | 성격 지속적 변화 설계 완성 |
177
- | 22단계 | 행동 유도 기반 계획형 NPC |
178
- | 23단계 | 윤리 시스템 실험 (자각형 구조화) |
179
- | 24단계 | NPC 자아 형성 시퀀스 테스트 |
180
- | 25단계 | 플레이어 기억 및 반응 시스템 구축 |
181
- | 26단계 | 멀티캐릭터 다자 간 상호작용 루프 |
182
- | 27단계 | 시뮬레이션 UI 시각화 고도화 |
183
- | 28단계 | 메타기억 시스템 |
184
- | 29단계 | LLM+RAG+플래너 종합 구조 통합 |
185
- | 30단계 | 논문/연구 결과 정리 구조화 |
186
 
187
 
188
  ---
 
117
 
118
  ## 🔁 주요 시퀀스 흐름
119
 
120
+ ### 🗨️ `generate_dialogue()` 전체 흐름
121
  1. LLM으로 감정 추론 (`query_llm_for_emotion`)
122
  2. 기억 검색 (FAISS + embedding)
123
  3. LLM 프롬프트 생성 (`build_npc_prompt`)
 
152
  | 2단계 | NPC 감정 상태 및 감정 HUD 구현 | ✅ 완료 | 감정 상태 및 HUD 표시 (Pygame + 웹)
153
  | 3단계 | 감정 ↔ 행동 연동 구조 구현 | ✅ 완료 | 감정 ↔ 행동 매핑 구현
154
  | 4단계 | 감정 ↔ 기억 저장 및 decay 구현 | ✅ 완료 | Memory 감정 및 decay 적용 구조
155
+ | 5단계 | 감정 ↔ 관계 변화 구조 구현 | 🔸 부분 완료 | 구조는 구현됨, 다른 NPC 관련 확인 필요
156
  | 6단계 | 성격 시스템 도입 | ✅ 완료 | 성격 분리 + 초기 bias 설계
157
  | 7단계 | NPC 간 상호작용 구조화 | ✅ 완료 | NPC 상호작용 → 감정/관계 반영
158
  | 8단계 | Memory 임베딩 + FAISS 기반 검색 | ✅ 완료 | FAISS 검색 + 임베딩 저장 구조
 
160
  | 10단계 | 감정 상태 HUD 시각화 (웹 + 게임 화면) | ✅ 완료 | 감정 상태 HUD (웹/게임 연동)
161
 
162
  # 핵심 시스템 고도화
163
+ | 11단계 | 감정/기억/관계 통합 정비 | 완료 | 관계 통합, 감정-기억-관계 연결 완성도 향상 중
164
  | 12단계 | LLM 대사 생성 고도화 | ✅ 완료 | LLM 대사 생성 개선, 프롬프트 완성
165
  | 13단계 | 기억 decay 정교화 | ⏳ 미진행 | decay 시점별 변화율 조정 등 정교화 미실행
166
  | 14-1단계 | 기억 임베딩 구조 확장 | ✅ 완료 | Memory 객체 임베딩 구조 도입
 
173
  | 20단계 | NPC 사회 네트워크 시각화 | ⏳ 미진행 |
174
 
175
  # 고급 인간화 및 자각형 NPC 확장
176
+ | 21단계 | 성격 지속적 변화 설계 완성 | ⏳ 미진행 |
177
+ | 22단계 | 행동 유도 기반 계획형 NPC | ⏳ 미진행 |
178
+ | 23단계 | 윤리 시스템 실험 (자각형 구조화) | ⏳ 미진행 |
179
+ | 24단계 | NPC 자아 형성 시퀀스 테스트 | ⏳ 미진행 |
180
+ | 25단계 | 플레이어 기억 및 반응 시스템 구축 | ⏳ 미진행 |
181
+ | 26단계 | 멀티캐릭터 다자 간 상호작용 루프 | ⏳ 미진행 |
182
+ | 27단계 | 시뮬레이션 UI 시각화 고도화 | ⏳ 미진행 |
183
+ | 28단계 | 메타기억 시스템 | ⏳ 미진행 |
184
+ | 29단계 | LLM+RAG+플래너 종합 구조 통합 | ⏳ 미진행 |
185
+ | 30단계 | 논문/연구 결과 정리 구조화 | ⏳ 미진행 |
186
 
187
 
188
  ---
npc_social_network/maps/engine/game_engine.py CHANGED
@@ -142,7 +142,7 @@ class GameEngine:
142
  if npc:
143
  print(f"\n👤 {npc.name} ({npc.job})")
144
  print(f" - 위치: ({tile_x}, {tile_y})")
145
- print(f" - 대화: {npc.generate_dialogue()}")
146
  print(f" - 기억: {npc.recall()}")
147
  self.set_selected_npc(npc)
148
 
 
142
  if npc:
143
  print(f"\n👤 {npc.name} ({npc.job})")
144
  print(f" - 위치: ({tile_x}, {tile_y})")
145
+ print(f" - 대화: {npc.generate_dialogue(user_input='', use_llm=False)}")
146
  print(f" - 기억: {npc.recall()}")
147
  self.set_selected_npc(npc)
148
 
npc_social_network/models/llm_helper.py CHANGED
@@ -1,23 +1,12 @@
1
  # npc_social_network/models/llm_helper.py
2
 
3
  from npc_social_network.npc.emotion_config import EMOTION_LIST
 
 
 
4
 
5
- # 임시 LLM 호출 함수 예시 (Gemini / GPT 연결 가능)
6
- def query_llm_for_response(npc_name, npc_job, user_input):
7
- """
8
- LLM을 통해 NPC 응답 생성
9
- """
10
-
11
- # 실제 구현시 Gemini API 호출 코드 사용 가능
12
- prompt = f"""
13
- NPC 이름: {npc_name}
14
- NPC 직업: {npc_job}
15
- 플레이어가 다음과 같이 말했습니다. "{user_input}"
16
-
17
- 이에 대해 자연스럽고 친근한 한국어로 응답을 작성하세요.
18
- """
19
- # 예시 응답 (추후 LLM 연결 시 교체)
20
- return f"{npc_name}이(가) 대답했습니다: '네, 정말 흥미롭네요!'"
21
 
22
  def query_llm_for_emotion(user_input):
23
  """
@@ -31,29 +20,39 @@ def query_llm_for_emotion(user_input):
31
 
32
  반드시 감정 이름을 한 개만 출력하세요. (예: joy)
33
  """
34
- # 예시 결과 (추후 LLM 연결 시 교체)
35
- return "joy"
 
 
 
36
 
37
  def query_llm_with_prompt(prompt: str) -> str:
38
  """
39
  prompt 문자열을 받아 LLM 호출
40
  """
41
- print(f"[LLM 호출 프롬프트 미리보기]\n{prompt[:300]}...\n")
42
- lines = [l.strip() for l in prompt.strip().splitlines() if l.strip()]
43
- summary_line = lines[-1] if lines else "(요청 없음)"
44
- return f"(응답 예시) {summary_line}"
 
45
 
46
- def summarize_memories(memory_texts: list[str]) -> str:
47
  """
48
- 기억 리스트를 1~2문장으로 요약
49
  """
50
- joined = "\n".join([f"- {t}" for t in memory_texts])
51
- prompt = f"""다듬은 한 인물의 과거 기억들입니다:
 
 
 
 
 
 
52
 
53
  {joined}
54
 
55
- → 이 사람은 어떤 경험을 했는지 1~2문장으로 요약해 주세요.
56
- """
57
 
58
- print(f"[요약 프롬프트 미리보기]\n{prompt}")
59
- return f"(요약 결과) {' / '.join(memory_texts)}" # 실제 LLM 연결 시 교체
 
 
1
  # npc_social_network/models/llm_helper.py
2
 
3
  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()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  def query_llm_for_emotion(user_input):
12
  """
 
20
 
21
  반드시 감정 이름을 한 개만 출력하세요. (예: joy)
22
  """
23
+ try:
24
+ response = gemini_model.generate_content(prompt)
25
+ return response.text.strip()
26
+ except Exception as e:
27
+ return f"[LLM Error] {str(e)}"
28
 
29
  def query_llm_with_prompt(prompt: str) -> str:
30
  """
31
  prompt 문자열을 받아 LLM 호출
32
  """
33
+ try:
34
+ response = gemini_model.generate_content(prompt)
35
+ return response.text.strip()
36
+ except Exception as e:
37
+ return f"[LLM Error] {str(e)}"
38
 
39
+ def summarize_memories(memories: List[Memory]) -> str:
40
  """
41
+ Memory 객체 리스트를 받아 LLM을 통해 1~2문장으로 요약
42
  """
43
+ if not memories:
44
+ return ""
45
+
46
+ # Memory 객체에서 .content를 추출하여 사용
47
+ memory_contents = [mem.content for mem in memories]
48
+ joined = "\n".join([f"- {content}" for content in memory_contents])
49
+
50
+ prompt = f"""다음은 한 인물의 과거 기억들입니다:
51
 
52
  {joined}
53
 
54
+ → 이 사람은 어떤 경험을 했는지 1~2문장으로 요약해 주세요."""
 
55
 
56
+ # LLM 호출 함수를 사용하여 요약 요청
57
+ summary = query_llm_with_prompt(prompt)
58
+ return summary
npc_social_network/models/llm_prompt_builder.py CHANGED
@@ -1,29 +1,62 @@
1
  # portfolio/npc_social_network/models/llm_prompt_builder.py
2
 
3
- def build_npc_prompt(npc, user_input: str, matched_memories: list[str]) -> str:
 
 
 
 
 
 
 
 
4
  """
5
- NPC 정보 + 사용자 입력 + 검색된 기억을 기반으로 프롬프트 생성
 
 
6
  """
 
 
 
7
  # 감정 상태 요약
8
- emotions_text = ", ".join([f"{e}({v})" for e, v in npc.get_composite_emotion_state()])
 
 
 
 
 
 
 
 
 
 
 
9
 
10
- # 관련 기억 구성
11
- memory_block = "\n".join([f"- {m}" for m in matched_memories]) if matched_memories else "관련 기억 없음"
12
 
13
  # 프롬프트 구성
14
  prompt = f"""
15
- NPC 이름: {npc.name}
16
- 직업: {npc.job}
17
- 현재 감정 상태: {emotions_text}
18
- 성격 요약: {npc.personality}
19
- 플레이어와의 관계: {npc.get_relationship_description("플레이어")}
 
 
 
 
 
 
 
 
 
20
 
21
- # 과거 관련 기억:
22
- {memory_block}
23
 
24
- # 플레이어 질문:
25
- {user_input}
26
 
27
- 정보를 바탕으로 자연스럽고 상황에 맞는 대답을 해주세요.
 
 
28
  """
29
- return prompt.strip()
 
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], target_npc: Optional["NPC"]=None) -> str:
12
  """
13
+ NPC 대사를 생성하기 위한 강화된 프롬프트 구성 함수
14
+ - 감정 요약 / 성격 요약 / 관계 / 기억 등 포함
15
+ - target_npc 인자를 받아 플레이어 또는 다른 NPC와의 대화 모두 처리
16
  """
17
+ # 대화 상대방의 이름을 동적으로 설정
18
+ interlocutor_name = target_npc.name if target_npc else "플레이어"
19
+
20
  # 감정 상태 요약
21
+ emotion_summary = npc.summarize_emotional_state()
22
+ # 성격 요약
23
+ personality_summary = npc.personality.get_personality_summary()
24
+ # 관계 요약
25
+ relationship_description = npc.get_relationship_description(interlocutor_name)
26
+
27
+ # 기억 요약 정비
28
+ memory_summaries = []
29
+ for mem in matched_memories:
30
+ # Memory 객체의 content 속성을 사용하도록 수정
31
+ short_content = mem.content[:50] + "..." if len(mem.content) > 50 else mem.content
32
+ memory_summaries.append(f"- {short_content} ({mem.emotion})")
33
 
34
+ memory_section = "\n".join(memory_summaries[:3]) if memory_summaries else "없음"
 
35
 
36
  # 프롬프트 구성
37
  prompt = f"""
38
+ 당신은 '{npc.name}'이라는 이름을 가진 사람입니다.
39
+ 당신의 직업은 '{npc.job}'이고, 현재 '{interlocutor_name}'와(과) 대화를 나누고 있습니다.
40
+
41
+ # 당신의 현재 감정 상태:
42
+ {emotion_summary}
43
+
44
+ # 당신의 성격 요약:
45
+ {personality_summary}
46
+
47
+ # 당신과 {interlocutor_name}와(과)의 현재 관계:
48
+ {relationship_description}
49
+
50
+ # 최근 기억:
51
+ {memory_section}
52
 
53
+ # {interlocutor_name}의 발화
54
+ "{user_input}"
55
 
56
+ ---
 
57
 
58
+ 상황에 어울리는 당신의 자연스럽고 인간적인 사람 반응을 생성하세요.
59
+ → 말투, 반응은 감정 상태에 맞추어 주세요.
60
+ → 대답은 NPC의 1인칭 시점으로 작성하세요.
61
  """
62
+ return prompt
npc_social_network/npc/emotion_config.py CHANGED
@@ -157,19 +157,43 @@ COGNITIVE_RELATION_EMOTIONS = [
157
 
158
  # 감정별 관계 영향도 정의 (관계에 영향 주는 감정만 등록, 정규화된 [-2.0 ~ +2.0] 범위 안에서 정의)
159
  EMOTION_RELATION_IMPACT = {
160
- "joy": +1.0,
161
- "gratitude": +1.2,
162
- "love": +2.0,
163
- "admiration": +0.8,
164
- "pride": +0.7,
165
- "calm": +0.5,
166
- "sadness": -0.8,
167
- "anger": -1.5,
 
 
 
 
 
 
 
 
168
  "disgust": -2.0,
169
- "fear": -1.0,
170
  "shame": -1.2,
171
- "resentment": -1.5,
 
 
172
  "regret": -0.9,
173
- "hope": +0.9,
174
- "comfort": +0.6,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  }
 
157
 
158
  # 감정별 관계 영향도 정의 (관계에 영향 주는 감정만 등록, 정규화된 [-2.0 ~ +2.0] 범위 안에서 정의)
159
  EMOTION_RELATION_IMPACT = {
160
+ "joy": 1.0,
161
+ "surprise": 0.2,
162
+ "gratitude": 1.2,
163
+ "pride": 0.5,
164
+ "love": 2.0,
165
+ "admiration": 0.8,
166
+ "empathy": 1.0,
167
+ "hope": 0.9,
168
+ "anticipatory_joy": 1.2,
169
+ "calm": 0.7,
170
+ "engagement": 0.3,
171
+ "relief": 0.6,
172
+ "interest": 0.6,
173
+
174
+ "sadness": -1.0,
175
+ "anger": -1.8,
176
  "disgust": -2.0,
177
+ "fear": -1.2,
178
  "shame": -1.2,
179
+ "guilt": 0.3,
180
+ "jealousy": -1.2,
181
+ "resentment": -1.8,
182
  "regret": -0.9,
183
+ "schadenfreude": -1.5,
184
+ "anxiety": -0.5,
185
+ "confusion": -0.2,
186
+ "skepticism": -0.2,
187
+ "boredom": -0.2,
188
+
189
+ "compassion": 0.3,
190
+ "awe": 0.6,
191
+ "attachment": 1.7,
192
+ "anticipation": 0.7,
193
+ "curiosity": 0.6,
194
+ "nostalgia": 0.2,
195
+ "bittersweet": 0.1,
196
+ "rumination": -0.6,
197
+ "groundedness": 0.5,
198
+ "comfort": 0.6,
199
  }
npc_social_network/npc/npc_base.py CHANGED
@@ -1,8 +1,9 @@
1
  # portfolio/npc_social_network/npc/npc_base.py
 
2
  from .npc_memory import Memory, MemoryStore
3
  from .npc_emotion import EmotionManager
4
  from .npc_behavior import BehaviorManager
5
- from .emotion_config import EMOTION_CATEGORY_MAP, EMOTION_DECAY_RATE, PERSONALITY_TEMPLATE, EMOTION_RELATION_IMPACT
6
  from .emotion_config import POSITIVE_RELATION_EMOTIONS, NEGATIVE_RELATION_EMOTIONS, COGNITIVE_RELATION_EMOTIONS
7
  from .personality_config import AGE_PROFILE, PERSONALITY_PROFILE
8
  from .npc_relationship import RelationshipManager
@@ -13,6 +14,57 @@ from datetime import datetime
13
  import random
14
  import copy
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  # NPC 클래스 정의
17
  class NPC:
18
  """
@@ -28,11 +80,11 @@ class NPC:
28
 
29
  # npc 기억
30
  self.memory_store = MemoryStore()
 
31
  # 감정 상태 등 감정 관리자 초기화
32
- self.emotion_decay_rate = EMOTION_DECAY_RATE
33
- self.personality = copy.deepcopy(personality) if personality else copy.deepcopy(PERSONALITY_TEMPLATE)
34
  self.relationships = RelationshipManager() # 관계 시스템 초기화
35
- self.emotion = EmotionManager(self.emotion_decay_rate, self.personality)
36
 
37
  # 행동 관리자
38
  self.behavior = BehaviorManager()
@@ -43,8 +95,8 @@ class NPC:
43
  for emo in self._emotion_buffer:
44
  base_min = 0.05
45
  base_max = 0.3
46
-
47
  category = EMOTION_CATEGORY_MAP.get(emo, None)
 
48
 
49
  if category == "core":
50
  bias = self.personality.get("affect_bias", 1.0)
@@ -107,87 +159,80 @@ class NPC:
107
  x, y = self.get_position()
108
  screen.blit(self.image, (x * tile_size, y * tile_size))
109
 
110
- def generate_dialogue(self,user_input=None, use_llm=True):
 
 
 
 
111
  """
112
- 대화 생성 함수 (기억 검색 기반 프롬프트 포함)
113
- """
114
  if use_llm and user_input:
115
- # 관련 기억 검색
116
- _, _, matched_memories = search_similar_memories(self.name, user_input)
117
- # 요약 & 장기 기억화 (3개 이상일 때만)
 
118
  if len(matched_memories) >= 3:
119
  self.summarize_and_store_memories(matched_memories)
120
 
121
- # 프롬프트 생성
122
- prompt = build_npc_prompt(self, user_input, matched_memories)
123
 
124
- # LLM 호출
125
  npc_reply = query_llm_with_prompt(prompt)
126
 
127
- # Memory 기록
 
 
 
 
 
128
  self.remember(
129
- content = f"[플레이어] '{user_input}' → [NPC:{self.name}] '{npc_reply}'",
130
- importance = 7,
131
- emotion = self.emotion.get_dominant_emotion()
132
  )
133
 
134
- # Personality 업데이트
135
- self.update_personality()
 
 
136
 
137
- # Emotion update는 LLM 기반 generate 에서는 따로 역매핑이 애매하므로 dominant_emotion만 반영 (원하면 감정 분석 결과 기반으로 개선 가능)
138
- dominant_emotion = self.emotion.get_dominant_emotion()
139
- if dominant_emotion:
140
- self.update_emotion(dominant_emotion, strength=2.0)
141
 
142
  return npc_reply
 
143
  else:
144
- # 감정 상태와 직업에 따른 복합 행동 시퀀스 기반 대사 생성 + Behavior Trace Memory 기록
145
  behavior_output, behavior_trace = self.behavior.perform_sequence(
146
- self.name, self.job, emotion_buffer=self._emotion_buffer, return_trace = True
147
  )
148
 
149
- # fallback (감정 기반 행동)
150
- behavior_output = self.behavior.perform_sequence(
151
- self.name,
152
- self.job,
153
- emotion_buffer=self._emotion_buffer,
154
- return_trace=False
155
- )
156
 
157
- # 단계별 dominant emotion sequence 계산
158
  dominant_emotions = self.behavior.get_layer_dominant_emotions(self._emotion_buffer)
159
  dominant_sequence = self.behavior.decide_layered_sequence(dominant_emotions)
 
 
160
 
161
- # dominant_sequence가 empty인 경우 방어적 처리
162
- if dominant_sequence:
163
- dominant_score = max([score for _, score in dominant_sequence])
164
- else:
165
- # dominant_sequence가 비었으면 기본값 사용 (예: 감정 없음 상태)
166
- dominant_score = 5
167
-
168
- # 중요도 제한
169
- importance = min(int(dominant_score), 10) # importance 1~10제한
170
-
171
- # Behavior Trace를 Memory에 기록
172
  memory_entry = Memory(
173
  content=f"행동 수행: {behavior_trace}",
174
  importance=importance,
175
- emotion = self.emotion.get_dominant_emotion(),
176
- behavior_trace = behavior_trace
177
  )
178
  self.memory_store.add_memory(memory_entry)
179
 
180
- # 행동에 해당하는 감정에 update_emotion 호출 (역매핑)
181
- for action, score in behavior_trace:
182
- emotion_name = self.behavior.action_to_emotion.get(action)
183
- if emotion_name:
184
- self.update_emotion(emotion_name, strength=score)
185
-
186
- # Personality 업데이트
187
  self.update_personality()
188
 
189
  return str(behavior_output)
190
-
191
  def remember(self, content: str, importance: int = 5, emotion: str = None, strength: float = 1.0, memory_type:str = "Event"):
192
  """
193
  NPC가 새로운 기억을 저장하고 감정 상태에 반영
@@ -230,20 +275,9 @@ class NPC:
230
  """
231
  감정 상태 요약 텍스트 생성
232
  """
233
- buffer = self.emotion.get_buffer() # EmotionManager에서 최�� 감정 상태 복사본 가져오기
234
- nonzero = [v for v in buffer.values() if v > 0]
235
- avg_strength = round(sum(nonzero) / len(nonzero), 2) if nonzero else 0.0
236
- composite = sorted(buffer.items(), key=lambda x: x[1], reverse=True)
237
- composite = [(emo, round(score, 2)) for emo, score in composite[:3] if score > 0]
238
- composite_str = ", ".join(f"{emo}({val})" for emo, val in composite)
239
- return f"감정 평균 강도: {avg_strength} / 대표 감정: {composite_str if composite else '없음' }"
240
-
241
- def interact_with(self, other_npc_name: str, emotion: str, positive: bool):
242
- """
243
- 다른 NPC와 상호작용 시 감정에 따른 관계 변화를 반영
244
- """
245
- delta = self._get_emotion_influence(emotion, positive)
246
- self.relationships.update_relationship(other_npc_name, delta)
247
 
248
  def _get_emotion_influence(self, emotion: str, positive: bool) -> float:
249
  """
@@ -303,91 +337,6 @@ class NPC:
303
  """
304
  return self.emotion.get_top_emotions(top_n=top_n)
305
 
306
- def interact_with_player(self, player_input: str) -> str:
307
- """
308
- 플레이어 입력에 반응 (LLM 기반 응답 생성 + Memory/Emotion/관계 반영)
309
- """
310
- # STEP 1: 감정 추론 (선택적)
311
- emotion = query_llm_for_emotion(player_input)
312
- if emotion:
313
- self.update_emotion(emotion, strength=2.0)
314
-
315
- # STEP 2: 관련 기억 검색
316
- matched_memories = search_similar_memories(self.name, player_input)
317
-
318
- # STEP 3: 프롬프트 생성
319
- prompt = build_npc_prompt(cnp=self,
320
- user_input=player_input,
321
- matched_memories=matched_memories)
322
-
323
- # STEP 4: LLM 응답 생성
324
- npc_reply = query_llm_with_prompt(prompt)
325
-
326
- # STEP 5: 기억 기록
327
- memory = Memory(
328
- content=f"[플레이어] '{player_input}' → [NPC:{self.name}] '{npc_reply}'",
329
- importance=6,
330
- emotion=emotion
331
- )
332
- self.memory_store.add_memory(memory)
333
-
334
- # STEP 6: 감정 기반 관계 변화 반영
335
- if isinstance(emotion, dict):
336
- delta = 0.0
337
- for emo, strength in emotion.items():
338
- if emo in EMOTION_RELATION_IMPACT:
339
- delta += EMOTION_RELATION_IMPACT[emo] * strength
340
-
341
- if abs(delta) > 0.01: # 영향이 충분히 있을 때만 반영
342
- self.relationships.update_relationship("플레이어", delta=delta)
343
- print(f"[관계 변화] '{self.name}' → 플레이어: {delta:+.2f}")
344
-
345
- return npc_reply
346
-
347
- def interact_with_npc(self, target_npc):
348
- """
349
- 다른 NPC와 상호작용 (기본적으로 감정적 메시지 주고 받기 → Memory/Emotion/Relationship 반영)
350
- """
351
- # 1. 상호작용 감정 선택 (가장 강한 감정 사용 예시)
352
- dominant_emotion = self.emotion.get_dominant_emotion()
353
-
354
- # 방어 코드: dominant_emotion이 None인 경우 → 기본값 대체
355
- if dominant_emotion is None:
356
- dominant_emotion = "neutral"
357
-
358
- # 2. 메시지 구성 (단순 예시)
359
- message = f"{self.name}이(가) {target_npc.name}에게 {dominant_emotion} 감정을 표현했습니다."
360
-
361
- # 3. 상대방 Memory 기록
362
- target_npc.remember(
363
- content = message,
364
- importance = 6,
365
- emotion = dominant_emotion,
366
- strength = 0.0 # 감정 중복 update 방지
367
- )
368
-
369
- # 상대방 Personality 업데이트
370
- target_npc.update_personality()
371
-
372
- # 4. 상대방 Emotion 반영
373
- target_npc.update_emotion(dominant_emotion, strength=1.5)
374
-
375
- # 5. 상대방 Relationship 반영
376
- positive = dominant_emotion in POSITIVE_RELATION_EMOTIONS
377
- target_npc.interact_with(self.name, dominant_emotion, positive)
378
-
379
- # 6. 자신도 Memory 기록 (행동 기록)
380
- self.remember(
381
- content = f"{self.name}이(가) {target_npc.name}에게 감정을 표현함: {dominant_emotion}",
382
- importance = 5,
383
- emotion = dominant_emotion
384
- )
385
-
386
- # 자신 Personality 업데이트
387
- self.update_personality()
388
-
389
- return message
390
-
391
  def update_personality(self):
392
  """
393
  Memory / Emotion 기반으로 Personality 변화 적용
@@ -555,15 +504,51 @@ class NPC:
555
  # fallback → 정상화
556
  return 1.0
557
 
558
- def ummarize_and_store_memories(self, memory_texts: list[str]):
559
  """
560
  기억 리스트를 요약하고, NPC 장기 기억으로 저장
561
  """
562
- if not memory_texts:
 
 
 
 
 
 
563
  return
564
 
565
- summary = summarize_memories(memory_texts)
566
  self.memory_store.add_memory(
567
  Memory(content=summary, importance=9, emotion=self.emotion.get_dominant_emotion(), memory_type="Summary")
568
  )
569
- print(f"[요약 기억 저장됨] {summary}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # portfolio/npc_social_network/npc/npc_base.py
2
+ from typing import Optional, List
3
  from .npc_memory import Memory, MemoryStore
4
  from .npc_emotion import EmotionManager
5
  from .npc_behavior import BehaviorManager
6
+ from .emotion_config import EMOTION_LIST, EMOTION_CATEGORY_MAP, EMOTION_DECAY_RATE, PERSONALITY_TEMPLATE, EMOTION_RELATION_IMPACT
7
  from .emotion_config import POSITIVE_RELATION_EMOTIONS, NEGATIVE_RELATION_EMOTIONS, COGNITIVE_RELATION_EMOTIONS
8
  from .personality_config import AGE_PROFILE, PERSONALITY_PROFILE
9
  from .npc_relationship import RelationshipManager
 
14
  import random
15
  import copy
16
 
17
+ # Personality 데이터를 관리하고 요약 기능을 제공하는 클래스 정의
18
+ class PersonalityManager:
19
+ """
20
+ NPC의 성격 데이터를 관리하고 관련 기능을 제공하는 클래스
21
+ """
22
+ def __init__(self, initial_traits=None):
23
+ self.traits = copy.deepcopy(initial_traits) if initial_traits else copy.deepcopy(PERSONALITY_TEMPLATE)
24
+
25
+ def get(self, key, default=None):
26
+ return self.traits.get(key, default)
27
+
28
+ def __getitem__(self, key):
29
+ return self.traits[key]
30
+
31
+ def __setitem__(self, key, value):
32
+ self.traits[key] = value
33
+
34
+ def keys(self):
35
+ return self.traits.keys()
36
+
37
+ def get_personality_summary(self) -> str:
38
+ """
39
+ 현재 성격 특성을 바탕으로 자연어 요약을 생성합니다.
40
+ """
41
+ sensitive = self.traits.get("sensitive", 0.5)
42
+ stoic = self.traits.get("stoic", 0.5)
43
+ cognitive_bias = self.traits.get("cognitive_bias", 0.5)
44
+
45
+ summary_parts = []
46
+ if sensitive > 0.7:
47
+ summary_parts.append("매우 민감하고 감정적인 편입니다.")
48
+ elif sensitive < 0.3:
49
+ summary_parts.append("상당히 무던하고 감정 표현이 적습니다.")
50
+ else:
51
+ summary_parts.append("보통 수준의 감수성을 지녔습니다.")
52
+
53
+ if stoic > 0.7:
54
+ summary_parts.append("역경에 내성이 강하고 감정을 잘 드러내지 않습니다.")
55
+ elif stoic < 0.3:
56
+ summary_parts.append("감정의 영향을 쉽게 받는 편입니다.")
57
+
58
+ if cognitive_bias > 0.7:
59
+ summary_parts.append("이성적이고 논리적인 사고를 선호합니다.")
60
+ elif cognitive_bias < 0.3:
61
+ summary_parts.append("직관과 감정에 따라 판단하는 경향이 있습니다.")
62
+
63
+ if not summary_parts:
64
+ return "평범하고 균형 잡힌 성격입니다."
65
+
66
+ return " ".join(summary_parts)
67
+
68
  # NPC 클래스 정의
69
  class NPC:
70
  """
 
80
 
81
  # npc 기억
82
  self.memory_store = MemoryStore()
83
+
84
  # 감정 상태 등 감정 관리자 초기화
85
+ self.personality = PersonalityManager(personality)
 
86
  self.relationships = RelationshipManager() # 관계 시스템 초기화
87
+ self.emotion = EmotionManager(EMOTION_DECAY_RATE, self.personality)
88
 
89
  # 행동 관리자
90
  self.behavior = BehaviorManager()
 
95
  for emo in self._emotion_buffer:
96
  base_min = 0.05
97
  base_max = 0.3
 
98
  category = EMOTION_CATEGORY_MAP.get(emo, None)
99
+ bias = 1.0
100
 
101
  if category == "core":
102
  bias = self.personality.get("affect_bias", 1.0)
 
159
  x, y = self.get_position()
160
  screen.blit(self.image, (x * tile_size, y * tile_size))
161
 
162
+ def generate_dialogue(self, user_input: str, target_npc: Optional["NPC"]=None, use_llm: bool=True) -> str:
163
+ """
164
+ 플레이어 또는 NPC와 상호작용 시 사용되는 통합 대사 생성 함수
165
+ - user_input: 입력 문장 (플레이어나 NPC로부터)
166
+ - target_npc: NPC 간 대화일 경우 상대 NPC
167
  """
 
 
168
  if use_llm and user_input:
169
+ # 1. 유사 기억 검색
170
+ _, _, matched_memories = search_similar_memories(self, user_input)
171
+
172
+ # 2. 기억 요약 및 장기 기억화
173
  if len(matched_memories) >= 3:
174
  self.summarize_and_store_memories(matched_memories)
175
 
176
+ # 3. LLM 프롬프트 생성
177
+ prompt = build_npc_prompt(self, user_input, matched_memories, target_npc)
178
 
179
+ # 4. LLM 호출
180
  npc_reply = query_llm_with_prompt(prompt)
181
 
182
+ # 5. 감정 추론
183
+ emotion = query_llm_for_emotion(npc_reply)
184
+ if emotion and emotion in EMOTION_LIST:
185
+ self.update_emotion(emotion, strength=2.0)
186
+
187
+ # 6. Memory 저장
188
  self.remember(
189
+ content=f"[입력] '{user_input}' → [응답] '{npc_reply}'",
190
+ importance=7,
191
+ emotion=emotion
192
  )
193
 
194
+ # 7. 관계 반영 (플레이어나 다른 NPC)
195
+ target = target_npc.name if target_npc else "플레이어"
196
+ if emotion and emotion in EMOTION_RELATION_IMPACT:
197
+ self.relationships.update_relationship(target, emotion, strength=2.0)
198
 
199
+ # 8. 성격 변화 반영
200
+ self.update_personality()
 
 
201
 
202
  return npc_reply
203
+
204
  else:
205
+ # LLM 미사용 시: 감정 기반 행동 생성
206
  behavior_output, behavior_trace = self.behavior.perform_sequence(
207
+ self.name, self.job, emotion_buffer=self._emotion_buffer, return_trace=True
208
  )
209
 
210
+ # 행동 기반 감정 추론 및 반영
211
+ for action, score in behavior_trace:
212
+ emotion_name = self.behavior.action_to_emotion.get(action)
213
+ if emotion_name:
214
+ self.update_emotion(emotion_name, strength=score)
 
 
215
 
216
+ # dominant_score 기반 중요도 추정
217
  dominant_emotions = self.behavior.get_layer_dominant_emotions(self._emotion_buffer)
218
  dominant_sequence = self.behavior.decide_layered_sequence(dominant_emotions)
219
+ dominant_score = max([score for _, score in dominant_sequence]) if dominant_sequence else 5
220
+ importance = min(int(dominant_score), 10)
221
 
222
+ # Memory 저장
 
 
 
 
 
 
 
 
 
 
223
  memory_entry = Memory(
224
  content=f"행동 수행: {behavior_trace}",
225
  importance=importance,
226
+ emotion=self.emotion.get_dominant_emotion(),
227
+ behavior_trace=behavior_trace
228
  )
229
  self.memory_store.add_memory(memory_entry)
230
 
231
+ # 성격 업데이트
 
 
 
 
 
 
232
  self.update_personality()
233
 
234
  return str(behavior_output)
235
+
236
  def remember(self, content: str, importance: int = 5, emotion: str = None, strength: float = 1.0, memory_type:str = "Event"):
237
  """
238
  NPC가 새로운 기억을 저장하고 감정 상태에 반영
 
275
  """
276
  감정 상태 요약 텍스트 생성
277
  """
278
+ composite = self.get_composite_emotion_state()
279
+ dominant = self.emotion.get_dominant_emotion()
280
+ return f"감정 평균 강도: {dominant} / 대표 감정: {composite if composite else '없음' }"
 
 
 
 
 
 
 
 
 
 
 
281
 
282
  def _get_emotion_influence(self, emotion: str, positive: bool) -> float:
283
  """
 
337
  """
338
  return self.emotion.get_top_emotions(top_n=top_n)
339
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  def update_personality(self):
341
  """
342
  Memory / Emotion 기반으로 Personality 변화 적용
 
504
  # fallback → 정상화
505
  return 1.0
506
 
507
+ def summarize_and_store_memories(self, memories: List[Memory]):
508
  """
509
  기억 리스트를 요약하고, NPC 장기 기억으로 저장
510
  """
511
+ if not memories:
512
+ return
513
+
514
+ summary = summarize_memories(memories)
515
+
516
+ if "[LLM Error]" in summary:
517
+ print(f"[요약 실패] LLM 호출 중 에러가 발생하여 요약 기억을 저장하지 않습니다. 에러: {summary}")
518
  return
519
 
 
520
  self.memory_store.add_memory(
521
  Memory(content=summary, importance=9, emotion=self.emotion.get_dominant_emotion(), memory_type="Summary")
522
  )
523
+ print(f"[요약 기억 저장됨] {summary}")
524
+
525
+ def reflect_memory_emotions_on_relationship(self, target_name: str, memory_limit: int=10):
526
+ """
527
+ 최근 기억에 담긴 감정을 기반으로 대상과의 관계를 갱신
528
+ - 특정 대상 (플레이어 등)에 대해 감정이 담긴 Memory를 수집
529
+ - 각 감정에 대해 가중치를 곱해 관계 변화량 누적
530
+ """
531
+ recent_memories = self.memory_store.get_recent_memories(limit=memory_limit)
532
+ emotion_accumulator = {}
533
+
534
+ for mem in recent_memories:
535
+ if target_name not in mem.content:
536
+ continue # 대상이 언급된 기억만 추출
537
+
538
+ if isinstance(mem.emotion, dict):
539
+ for emo, value in mem.emotion.items():
540
+ if emo not in EMOTION_RELATION_IMPACT:
541
+ continue
542
+ emotion_accumulator[emo] = emotion_accumulator.get(emo, 0.0) + value
543
+
544
+ # 전체 감정 누적값을 바탕으로 관계 변화량 계선
545
+ total_delta = 0.0
546
+ for emo, value in emotion_accumulator.items():
547
+ for emo, value in emotion_accumulator.items():
548
+ weight = EMOTION_RELATION_IMPACT[emo]
549
+ delta = weight * value
550
+ total_delta += delta
551
+
552
+ if abs(total_delta) > 0.01:
553
+ self.relationships.update_relationship(target_name, delta=total_delta)
554
+ print(f"[기억 기반 관계 변화] {self.name} ↔ {target_name}: {total_delta:.2f}")
npc_social_network/npc/npc_memory_embedder.py CHANGED
@@ -4,9 +4,10 @@ from sentence_transformers import SentenceTransformer
4
  import numpy as np
5
  import faiss
6
  import os
7
- from .npc_memory import MemoryStore
8
- from .npc_base import NPC
9
  import re
 
 
 
10
 
11
  # 사전 훈련된 문장 임베딩 모델
12
  model = SentenceTransformer("all-MiniLM-L6-v2")
@@ -36,7 +37,7 @@ def embed_memory(memory):
36
  embedding = model.encode(text)
37
  return embedding
38
 
39
- def embed_npc_memories(npc: NPC):
40
  """
41
  특정 NPC의 기억 전체를 임베딩하고 FAISS index로 저장
42
  """
@@ -68,28 +69,21 @@ def load_npc_faiss_index(npc_name):
68
  index = faiss.read_index(index_path)
69
  return index
70
 
71
- def search_similar_memories(npc_name, query, top_k=3):
72
  """
73
  질의 문장을 벡터로 변환하고 FAISS에서 유사한 기억 검색
74
  """
75
- from ..routes.npc_route import npc_manager
76
-
77
- index = load_npc_faiss_index(npc_name)
78
  query_vec = model.encode([query])
79
  distances, indices = index.search(np.array(query_vec, dtype=np.float32), top_k)
80
 
81
- # 보다 정확한 디버깅을 위해 추가 (나중에 제거 가능)
82
- npc = npc_manager.get_npc_by_name(npc_name)
83
  all_memories = npc.memory_store.get_all_memories()
84
 
85
- matched_texts = [memory_to_text(all_memories[i]) for i in indices[0]]
 
 
86
 
87
- # 디버깅 출력
88
- print(f"[{npc_name}] '{query}'에 대한 유사 기억 검색 결과:")
89
- for rank, (idx, dist) in enumerate(zip(indices[0], distances[0])):
90
- print(f" {rank+1}. index: {idx}, distance: {dist:.4f} → {all_memories[idx].content}")
91
-
92
- return indices[0], distances[0], matched_texts
93
 
94
  def sanitize_filename(name):
95
  """
 
4
  import numpy as np
5
  import faiss
6
  import os
 
 
7
  import re
8
+ from typing import TYPE_CHECKING
9
+ if TYPE_CHECKING:
10
+ from .npc_base import NPC
11
 
12
  # 사전 훈련된 문장 임베딩 모델
13
  model = SentenceTransformer("all-MiniLM-L6-v2")
 
37
  embedding = model.encode(text)
38
  return embedding
39
 
40
+ def embed_npc_memories(npc: "NPC"):
41
  """
42
  특정 NPC의 기억 전체를 임베딩하고 FAISS index로 저장
43
  """
 
69
  index = faiss.read_index(index_path)
70
  return index
71
 
72
+ def search_similar_memories(npc: "NPC", query: str, top_k=3):
73
  """
74
  질의 문장을 벡터로 변환하고 FAISS에서 유사한 기억 검색
75
  """
76
+ index = load_npc_faiss_index(npc.name)
 
 
77
  query_vec = model.encode([query])
78
  distances, indices = index.search(np.array(query_vec, dtype=np.float32), top_k)
79
 
 
 
80
  all_memories = npc.memory_store.get_all_memories()
81
 
82
+ # 검색된 인덱스가 메모리 범위를 벗어나지 않는지 확인
83
+ valid_indices = [i for i in indices[0] if i < len(all_memories)]
84
+ matched_memories = [all_memories[i] for i in valid_indices]
85
 
86
+ return valid_indices, distances[0][:len(valid_indices)], matched_memories
 
 
 
 
 
87
 
88
  def sanitize_filename(name):
89
  """
npc_social_network/npc/npc_relationship.py CHANGED
@@ -6,12 +6,26 @@ class RelationshipManager:
6
  # 관계 사전: {상대방 이름: 점수 (float)}
7
  self.relationships = {}
8
 
9
- # 상대 NPC와의 관계 점수를 갱신
10
- def update_relationship(self, other_npc_name: str, delta: float):
11
- if other_npc_name not in self.relationships:
12
- self.relationships[other_npc_name] = 0.0
13
- self.relationships[other_npc_name] += delta
14
- self.relationships[other_npc_name] = max(-100.0, min(100.0, self.relationships[other_npc_name])) # [-100, 100] 제한
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  # 상대 NPC와의 현재 관계 점수를 반환
17
  def get_relationship(self, other_npc_name:str) -> float:
 
6
  # 관계 사전: {상대방 이름: 점수 (float)}
7
  self.relationships = {}
8
 
9
+ def update_relationship(self, other: str, emotion: str, strength: float=1.0):
10
+ """
11
+ 특정 감정 기반으로 관계 수치 조정
12
+ - 긍정/부정만이 아니라 감정 유형에 따른 영향 차별화
13
+ """
14
+ from .emotion_config import EMOTION_RELATION_IMPACT
15
+
16
+ # 기본 로직
17
+ prev = self.relationships.get(other, 0.0)
18
+
19
+ impact = EMOTION_RELATION_IMPACT.get(emotion, 0.0)
20
+ new_value = prev + (impact * strength)
21
+
22
+ # 감쇠 적용 (값이 커질수록 변화폭을 줄여 급격한 변화 방지)
23
+ if abs(new_value) > abs(prev):
24
+ new_value = prev + (impact * strength * 0.7) # 감쇠 비율 예시
25
+
26
+ # 클리핑 (-100 ~ 100 범위 제한)
27
+ new_value = max(min(new_value, 100), -100)
28
+ self.relationships[other] = round(new_value, 2)
29
 
30
  # 상대 NPC와의 현재 관계 점수를 반환
31
  def get_relationship(self, other_npc_name:str) -> float:
npc_social_network/routes/npc_route.py CHANGED
@@ -63,12 +63,15 @@ def chat():
63
  if npc is None:
64
  return jsonify({"error": "NPC not found"}), 404
65
 
66
- # 플레이어 상호 작용 진행
67
- npc_reply, emotion = npc.interact_with_player(user_input, player_name = "플레이어")
 
 
 
68
 
69
  return jsonify({
70
  "npc_reply": npc_reply,
71
- "dominant_emotion": emotion,
72
  "memory_summary": npc.summarize_emotional_state(),
73
  "emotion_state": npc.get_composite_emotion_state(),
74
  "relationship_with_player": npc.get_relationship_description("플레이어")
 
63
  if npc is None:
64
  return jsonify({"error": "NPC not found"}), 404
65
 
66
+ # generate_dialogue를 통해 응답 생성 및 내부 상태 업데이트
67
+ npc_reply = npc.generate_dialogue(user_input, use_llm=True)
68
+
69
+ # 업데이트된 상태를 별도로 조회
70
+ dominant_emotion = npc.emotion.get_dominant_emotion()
71
 
72
  return jsonify({
73
  "npc_reply": npc_reply,
74
+ "dominant_emotion": dominant_emotion,
75
  "memory_summary": npc.summarize_emotional_state(),
76
  "emotion_state": npc.get_composite_emotion_state(),
77
  "relationship_with_player": npc.get_relationship_description("플레이어")
run_npc_interactions.py CHANGED
@@ -1,14 +1,39 @@
1
  # portfolio/run_npc_interactions.py
2
 
3
  import time
4
- from npc_social_network.routes.npc_route import npc_manager
 
5
 
6
- def run_npc_interactions(interval_seconds=10):
7
- while True:
8
- print(f"[Background] NPC interactions triggered.", flush=True)
9
- npc_manager.npc_interactions()
10
- time.sleep(interval_seconds)
11
 
12
- if __name__ == '__main__':
13
- print("[Background] Starting NPC interactions loop...", flush=True)
14
- run_npc_interactions(interval_seconds=10)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # portfolio/run_npc_interactions.py
2
 
3
  import time
4
+ from npc_social_network.npc.npc_manager import npc_manager
5
+ from npc_social_network.npc.npc_base import NPC
6
 
7
+ def run_npc_test_scenario(npc_name="엘라"):
8
+ npc = npc_manager.get_npc_by_name[npc_name]
 
 
 
9
 
10
+ test_inputs = [
11
+ "오늘 하루는 최악이었어. 다들 날 무시하는 거지?",
12
+ "고마워. 나 도와줘서 정말 감사했어.",
13
+ "그때 화낸 건 미안해. 내 잘못이었어.",
14
+ "내가 선물한 책 어땠어?",
15
+ "우리 예전에 함께 일했던 거 기억나?",
16
+ ]
17
+
18
+ for input_text in test_inputs:
19
+ print(f"\n[플레이어 입력] {input_text}")
20
+ response = npc.interact_with_player(input_text)
21
+ print(f"[NPC 응답] {response}")
22
+
23
+ # 기억 기반 관계 반영까지 테스트
24
+ npc.reflect_memory_emotions_on_relationship("플레이어")
25
+
26
+ # 상태 요약 출력
27
+ print("\n🧠 현재 감정 상태 요약:")
28
+ print(npc.summarize_emotional_state())
29
+
30
+ print("\n📚 최근 기억:")
31
+ for m in npc.memory_store.get_recent_memories(limit=5):
32
+ print(f"- {m.content} ({m.emotion})")
33
+
34
+ print("\n🔗 플레이어와의 관계:")
35
+ print(npc.get_relationship_description("플레이어"))
36
+
37
+
38
+ if __name__ == "__main__":
39
+ run_npc_test_scenario()
test.ipynb CHANGED
@@ -3133,6 +3133,139 @@
3133
  "# 복합 감정 기반 행동 시퀀스 출력\n",
3134
  "print(npc.generate_dialogue())"
3135
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3136
  }
3137
  ],
3138
  "metadata": {
 
3133
  "# 복합 감정 기반 행동 시퀀스 출력\n",
3134
  "print(npc.generate_dialogue())"
3135
  ]
3136
+ },
3137
+ {
3138
+ "cell_type": "code",
3139
+ "execution_count": 1,
3140
+ "id": "246a140b",
3141
+ "metadata": {},
3142
+ "outputs": [
3143
+ {
3144
+ "name": "stderr",
3145
+ "output_type": "stream",
3146
+ "text": [
3147
+ "c:\\Users\\human\\.conda\\envs\\portfolio\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
3148
+ " from .autonotebook import tqdm as notebook_tqdm\n"
3149
+ ]
3150
+ },
3151
+ {
3152
+ "name": "stdout",
3153
+ "output_type": "stream",
3154
+ "text": [
3155
+ "\n",
3156
+ "[플레이어 입력] 오늘 하루는 최악이었어. 왜 다들 날 무시하는 거지?\n",
3157
+ "[요약 기억 저장됨] 이 사람은 최근 찰리와 함께 농사일을 돕고, 밥에게 선물 받은 검을 마음에 들어 하는 긍정적이거나 일상적인 경험과 더불어 앨리스와 심하게 다투는 갈등 상황도 겪었습니다. 즉, 다양한 사람들과 여러 종류의 상호작용과 활동을 경험한 것으로 보입니다.\n",
3158
+ "[Personality Update] 엘라 → sensitive: 1.00, stoic: 0.00, cognitive_bias: 1.00\n",
3159
+ "[NPC 응답] 으음... 오늘 하루가 최악이었다니... 정말 힘들었겠네.\n",
3160
+ "\n",
3161
+ "'다들' 이라니... 정확히 누가, 왜 그랬다는 거야? 무시당하는 기분은... 나도 알 것 같기도 하고...\n",
3162
+ "\n",
3163
+ "[플레이어 입력] 고마워. 나 도와줘서 정말 감사했어.\n",
3164
+ "[요약 실패] LLM 호출 중 에러가 발생하여 요약 기억을 저장하지 않습니다. 에러: [LLM Error] 500 An internal error has occurred. Please retry or report in https://developers.generativeai.google/guide/troubleshooting\n",
3165
+ "[Personality Update] 엘라 → sensitive: 1.00, stoic: 0.00, cognitive_bias: 1.00\n",
3166
+ "[NPC 응답] 아냐, 괜찮아. 별거 아니었는데 뭘. 네가 그렇게 말해주니 오히려 내가 다 고맙네.\n",
3167
+ "\n",
3168
+ "[플레이어 입력] 그때 화낸 건 미안해. 내 잘못이었어.\n",
3169
+ "[요약 기억 저장됨] 이 인물은 최근 찰리와 농사일을 하고 밥에게 선물을 받는 등 긍정적인 경험과 더불어, 앨리스와 심하게 다투고 타인에게 무시당한다는 느낌을 받는 등 부정적인 갈등 상황도 겪었습니다. 즉, 다양한 사람들과 교류하며 좋은 경험과 나쁜 경험을 모두 경험했습니다.\n",
3170
+ "[Personality Update] 엘라 → sensitive: 1.00, stoic: 0.00, cognitive_bias: 1.00\n",
3171
+ "[NPC 응답] 아... 그때 화냈던 거 말이지...?\n",
3172
+ "\n",
3173
+ "음... 괜찮아. 누구나 가끔은 감정에 휩쓸릴 때가 있는 거니까. 나도 그런 걸.\n",
3174
+ "\n",
3175
+ "그렇게 말해줘서 고마워.\n",
3176
+ "\n",
3177
+ "[플레이어 입력] 내가 선물한 책 어땠어?\n",
3178
+ "[요약 실패] LLM 호출 중 에러가 발생하여 요약 기억을 저장하지 않습니다. 에러: [LLM Error] 500 An internal error has occurred. Please retry or report in https://developers.generativeai.google/guide/troubleshooting\n",
3179
+ "[Personality Update] 엘라 → sensitive: 1.00, stoic: 0.00, cognitive_bias: 1.00\n",
3180
+ "[NPC 응답] 아... 네가 선물해 준 그 책 말이지...?\n",
3181
+ "\n",
3182
+ "(잠깐 생각에 잠긴 듯)\n",
3183
+ "\n",
3184
+ "음... 꽤... 아니, 아주 흥미로운 내용이었어. 특히 그... (책 내용 중 특정 부분을 떠올리려는 듯) 아, 맞아. 그 이론에 대해 설명하는 부분 말이야. 논리적으로 잘 정리되어 있어서 이해하기 쉬웠어. 내... 내 마법 연구에도 조금 도움이 될 것 같다는 생각도 들었고.\n",
3185
+ "\n",
3186
+ "...사실 요즘 이것저것 생각할 게 많아서 머리가 복잡했는데, 네가 준 책을 읽는 동안에는 다른 생각들을 다 잊고 내용에만 집중할 수 있었어.\n",
3187
+ "\n",
3188
+ "정말 좋은 선물이었어. 고마워.\n",
3189
+ "\n",
3190
+ "[플레이어 입력] 우리 예전에 함께 일했던 거 기억나?\n",
3191
+ "[요약 실패] LLM 호출 중 에러가 발생하여 요약 기억을 저장하지 않습니다. 에러: [LLM Error] 500 An internal error has occurred. Please retry or report in https://developers.generativeai.google/guide/troubleshooting\n",
3192
+ "[Personality Update] 엘라 → sensitive: 1.00, stoic: 0.00, cognitive_bias: 1.00\n",
3193
+ "[NPC 응답] ```korean\n",
3194
+ "엘라: 응... 우리 함께 일했던 거? 기억나지, 물론.\n",
3195
+ "\n",
3196
+ "엘라: 음... 어떤 때를 말하는 거야? 우리 함께 했던 일들은... 대부분 좋은 기억으로 남아있으니까.\n",
3197
+ "```\n",
3198
+ "\n",
3199
+ "🧠 현재 감정 상태 요약:\n",
3200
+ "감정 평균 강도: nostalgia / 대표 감정: [('nostalgia', 3.0)]\n",
3201
+ "\n",
3202
+ "📚 최근 기억:\n",
3203
+ "- [입력] '우리 예전�� 함께 일했던 거 기억나?' → [응답] '```korean\n",
3204
+ "엘라: 응... 우리 함께 일했던 거? 기억나지, 물론.\n",
3205
+ "\n",
3206
+ "엘라: 음... 어떤 때를 말하는 거야? 우리 함께 했던 일들은... 대부분 좋은 기억으로 남아있으니까.\n",
3207
+ "```' (nostalgia)\n",
3208
+ "- [입력] '내가 선물한 책 어땠어?' → [응답] '아... 네가 선물해 준 그 책 말이지...?\n",
3209
+ "\n",
3210
+ "(잠깐 생각에 잠긴 듯)\n",
3211
+ "\n",
3212
+ "음... 꽤... 아니, 아주 흥미로운 내용이었어. 특히 그... (책 내용 중 특정 부분을 떠올리려는 듯) 아, 맞아. 그 이론에 대해 설명하는 부분 말이야. 논리적으로 잘 정리되어 있어서 이해하기 쉬웠어. 내... 내 마법 연구에도 조금 도움이 될 것 같다는 생각도 들었고.\n",
3213
+ "\n",
3214
+ "...사실 요즘 이것저것 생각할 게 많아서 머리가 복잡했는데, 네가 준 책을 읽는 동안에는 다른 생각들을 다 잊고 내용에만 집중할 수 있었어.\n",
3215
+ "\n",
3216
+ "정말 좋은 선물이었어. 고마워.' (gratitude)\n",
3217
+ "- [입력] '그때 화낸 건 미안해. 내 잘못이었어.' → [응답] '아... 그때 화냈던 거 말이지...?\n",
3218
+ "\n",
3219
+ "음... 괜찮아. 누구나 가끔은 감정에 휩쓸릴 때가 있는 거니까. 나도 그런 걸.\n",
3220
+ "\n",
3221
+ "그렇게 말해줘서 고마워.' (gratitude)\n",
3222
+ "- 이 인물은 최근 찰리와 농사일을 하고 밥에게 선물을 받는 등 긍정적인 경험과 더불어, 앨리스와 심하게 다투고 타인에게 무시당한다는 느낌을 받는 등 부정적인 갈등 상황도 겪었습니다. 즉, 다양한 사람들과 교류하며 좋은 경험과 나쁜 경험을 모두 경험했습니다. (None)\n",
3223
+ "- [입력] '고마워. 나 도와줘서 정말 감사했어.' → [응답] '아냐, 괜찮아. 별거 아니었는데 뭘. 네가 그렇게 말해주니 오히려 내가 다 고맙네.' (gratitude)\n",
3224
+ "\n",
3225
+ "🔗 플레이어와의 관계:\n",
3226
+ "호감 있음\n"
3227
+ ]
3228
+ }
3229
+ ],
3230
+ "source": [
3231
+ "# portfolio/test.ipynb\n",
3232
+ "\n",
3233
+ "import time\n",
3234
+ "from npc_social_network.routes.npc_route import npc_manager\n",
3235
+ "\n",
3236
+ "def run_npc_test_scenario(npc_name=\"엘라\"):\n",
3237
+ " npc = npc_manager.get_npc_by_name(npc_name)\n",
3238
+ "\n",
3239
+ " test_inputs = [\n",
3240
+ " \"오늘 하루는 최악이었어. 왜 다들 날 무시하는 거지?\",\n",
3241
+ " \"고마워. 나 도와줘서 정말 감사했어.\",\n",
3242
+ " \"그때 화낸 건 미안해. 내 잘못이었어.\",\n",
3243
+ " \"내가 선물한 책 어땠어?\",\n",
3244
+ " \"우리 예전에 함께 일했던 거 기억나?\",\n",
3245
+ " ]\n",
3246
+ "\n",
3247
+ " for input_text in test_inputs:\n",
3248
+ " print(f\"\\n[플레이어 입력] {input_text}\")\n",
3249
+ " response = npc.generate_dialogue(input_text)\n",
3250
+ " print(f\"[NPC 응답] {response}\")\n",
3251
+ "\n",
3252
+ " # 기억 기반 관계 반영까지 테스트\n",
3253
+ " npc.reflect_memory_emotions_on_relationship(\"플레이어\")\n",
3254
+ "\n",
3255
+ " # 상태 요약 출력\n",
3256
+ " print(\"\\n🧠 현재 감정 상태 요약:\")\n",
3257
+ " print(npc.summarize_emotional_state())\n",
3258
+ "\n",
3259
+ " print(\"\\n📚 최근 기억:\")\n",
3260
+ " for m in npc.memory_store.get_recent_memories(limit=5):\n",
3261
+ " print(f\"- {m.content} ({m.emotion})\")\n",
3262
+ "\n",
3263
+ " print(\"\\n🔗 플레이어와의 관계:\")\n",
3264
+ " print(npc.get_relationship_description(\"플레이어\"))\n",
3265
+ "\n",
3266
+ "\n",
3267
+ "run_npc_test_scenario()"
3268
+ ]
3269
  }
3270
  ],
3271
  "metadata": {