Spaces:
Sleeping
Sleeping
이슈 수정 ver3
Browse files- npc_social_network/data/saves/simulation_state.pkl +0 -0
- npc_social_network/data/vectorstores/alice.faiss +0 -0
- npc_social_network/data/vectorstores/bob.faiss +0 -0
- npc_social_network/data/vectorstores/charlie.faiss +0 -0
- npc_social_network/data/vectorstores/diana.faiss +0 -0
- npc_social_network/data/vectorstores/elin.faiss +0 -0
- npc_social_network/npc/npc_base.py +19 -14
- npc_social_network/npc/npc_manager.py +1 -0
- npc_social_network/npc/npc_memory_embedder.py +1 -1
- npc_social_network/npc/npc_relationship.py +6 -6
- npc_social_network/simulation_core.py +1 -2
npc_social_network/data/saves/simulation_state.pkl
DELETED
Binary file (13 kB)
|
|
npc_social_network/data/vectorstores/alice.faiss
DELETED
Binary file (1.58 kB)
|
|
npc_social_network/data/vectorstores/bob.faiss
DELETED
Binary file (1.58 kB)
|
|
npc_social_network/data/vectorstores/charlie.faiss
DELETED
Binary file (1.58 kB)
|
|
npc_social_network/data/vectorstores/diana.faiss
DELETED
Binary file (1.58 kB)
|
|
npc_social_network/data/vectorstores/elin.faiss
DELETED
Binary file (1.58 kB)
|
|
npc_social_network/npc/npc_base.py
CHANGED
@@ -12,7 +12,7 @@ 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:
|
@@ -81,6 +81,7 @@ class NPC:
|
|
81 |
self.name = name
|
82 |
self.korean_name = korean_name
|
83 |
self.job = job
|
|
|
84 |
|
85 |
# npc 기억
|
86 |
self.memory_store = MemoryStore()
|
@@ -215,7 +216,7 @@ class NPC:
|
|
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
|
219 |
"""
|
220 |
NPC가 새로운 기억을 저장하고 감정 상태에 반영
|
221 |
"""
|
@@ -227,14 +228,18 @@ class NPC:
|
|
227 |
context_tags=context_tags
|
228 |
)
|
229 |
self.memory_store.add_memory(memory)
|
|
|
|
|
|
|
|
|
230 |
if emotion and emotion in EMOTION_LIST:
|
231 |
self.update_emotion(emotion, strength) # strength = importance / 5.0으로도 가능
|
232 |
# 만약 기억이 '소문'이라면, 평판을 업데이트
|
233 |
-
if memory_type == "Gossip"
|
234 |
# (간단한 예시: LLM으로 소문의 대상과 긍/부정을 파악해야 더 정확함)
|
235 |
# 긍정 소문 예시: "밥이 찰리를 도왔다"
|
236 |
# 부정 소문 예시: "엘린이 앨리스와 다퉜다"
|
237 |
-
self.process_gossip_and_update_reputation(memory
|
238 |
|
239 |
return memory # 생성된 기억 객체 반환
|
240 |
|
@@ -408,7 +413,7 @@ class NPC:
|
|
408 |
memory_type="Summary"
|
409 |
)
|
410 |
|
411 |
-
def update_autonomous_behavior(self, time_context: str
|
412 |
"""
|
413 |
NPC가 스스로 판단하여 행동을 결정하는 자율 행동 로직
|
414 |
주기적으로 호출되어 목표 설정, 계획 실행 등을 담당.
|
@@ -434,7 +439,7 @@ class NPC:
|
|
434 |
gossip_to_spread = random.choice(gossip_memories)
|
435 |
|
436 |
# 아직 대화한 적 없는 NPC를 타겟으로 선정
|
437 |
-
potential_targets = [npc for npc in
|
438 |
if potential_targets:
|
439 |
target_npc = random.choice(potential_targets)
|
440 |
|
@@ -445,7 +450,7 @@ class NPC:
|
|
445 |
target_npc=target_npc
|
446 |
)
|
447 |
|
448 |
-
|
449 |
gossip_to_spread.is_shared = True # 소문이 전파되었음을 표시 (중복 전파 방지)
|
450 |
|
451 |
# 일정 확률로 새로운 목표를 생성 (매번 생성하지 않도록)
|
@@ -492,20 +497,20 @@ class NPC:
|
|
492 |
)
|
493 |
print(f"[{self.korean_name}]의 새로운 깨달음: {symbolic_thought}")
|
494 |
|
495 |
-
def update_reputation(self, target_name: str, score_change: int
|
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 =
|
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
|
509 |
"""LLM을 이용해 소문을 분석하고 관련 인물들의 평판을 업데이트"""
|
510 |
|
511 |
analysis = analyze_gossip(gossip_memory.content)
|
@@ -528,11 +533,11 @@ class NPC:
|
|
528 |
|
529 |
if score_change != 0:
|
530 |
# LLM이 반환한 한글 이름으로 NPC 객체를 찾아 영어 ID를 가져온다.
|
531 |
-
p1_npc =
|
532 |
-
p2_npc =
|
533 |
|
534 |
# 소문의 대상이 된 두 사람에 대한 나의 평판을 변경
|
535 |
if p1_npc:
|
536 |
-
self.update_reputation(p1_npc.name, score_change
|
537 |
if p2_npc:
|
538 |
-
self.update_reputation(p2_npc.name, score_change
|
|
|
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, add_memory_to_index
|
16 |
from .npc_planner import PlannerManager
|
17 |
from typing import TYPE_CHECKING
|
18 |
if TYPE_CHECKING:
|
|
|
81 |
self.name = name
|
82 |
self.korean_name = korean_name
|
83 |
self.job = job
|
84 |
+
self.manager: Optional['NPCManager'] = None
|
85 |
|
86 |
# npc 기억
|
87 |
self.memory_store = MemoryStore()
|
|
|
216 |
|
217 |
def remember(self, content: str, importance: int = 5, emotion: str = None,
|
218 |
strength: float = 1.0, memory_type:str = "Event",
|
219 |
+
context_tags: Optional[List[str]]=None) -> Memory:
|
220 |
"""
|
221 |
NPC가 새로운 기억을 저장하고 감정 상태에 반영
|
222 |
"""
|
|
|
228 |
context_tags=context_tags
|
229 |
)
|
230 |
self.memory_store.add_memory(memory)
|
231 |
+
# 중요도가 3 이상인 기억만 벡터 인덱스에 추가하여 효율성 확보
|
232 |
+
if memory.importance >= 3:
|
233 |
+
add_memory_to_index(self, memory)
|
234 |
+
|
235 |
if emotion and emotion in EMOTION_LIST:
|
236 |
self.update_emotion(emotion, strength) # strength = importance / 5.0으로도 가능
|
237 |
# 만약 기억이 '소문'이라면, 평판을 업데이트
|
238 |
+
if memory_type == "Gossip":
|
239 |
# (간단한 예시: LLM으로 소문의 대상과 긍/부정을 파악해야 더 정확함)
|
240 |
# 긍정 소문 예시: "밥이 찰리를 도왔다"
|
241 |
# 부정 소문 예시: "엘린이 앨리스와 다퉜다"
|
242 |
+
self.process_gossip_and_update_reputation(memory)
|
243 |
|
244 |
return memory # 생성된 기억 객체 반환
|
245 |
|
|
|
413 |
memory_type="Summary"
|
414 |
)
|
415 |
|
416 |
+
def update_autonomous_behavior(self, time_context: str):
|
417 |
"""
|
418 |
NPC가 스스로 판단하여 행동을 결정하는 자율 행동 로직
|
419 |
주기적으로 호출되어 목표 설정, 계획 실행 등을 담당.
|
|
|
439 |
gossip_to_spread = random.choice(gossip_memories)
|
440 |
|
441 |
# 아직 대화한 적 없는 NPC를 타겟으로 선정
|
442 |
+
potential_targets = [npc for npc in self.manager.all() if npc.name != self.name]
|
443 |
if potential_targets:
|
444 |
target_npc = random.choice(potential_targets)
|
445 |
|
|
|
450 |
target_npc=target_npc
|
451 |
)
|
452 |
|
453 |
+
print(f"[소문 전파] {self.korean_name} -> {target_npc.korean_name}: {gossip_dialogue}")
|
454 |
gossip_to_spread.is_shared = True # 소문이 전파되었음을 표시 (중복 전파 방지)
|
455 |
|
456 |
# 일정 확률로 새로운 목표를 생성 (매번 생성하지 않도록)
|
|
|
497 |
)
|
498 |
print(f"[{self.korean_name}]의 새로운 깨달음: {symbolic_thought}")
|
499 |
|
500 |
+
def update_reputation(self, target_name: str, score_change: int):
|
501 |
"""특정 대상에 대한 평판 점수를 변경"""
|
502 |
if self.name == target_name: return # 자기 자신에 대한 평판x (자기 자신의 평판에 대한 내용도 성격 형성에 중요할 수 있지 않나?)
|
503 |
|
504 |
current_score = self.reputation.get(target_name, 0)
|
505 |
self.reputation[target_name] = current_score + score_change
|
506 |
|
507 |
+
target_npc = self.manager.get_npc_by_name(target_name)
|
508 |
target_korean_name = target_npc.korean_name if target_npc else target_name
|
509 |
|
510 |
print(f"[{self.korean_name}]의 {target_korean_name}에 대한 평판: {self.reputation[target_name]} ({score_change:+.0f})")
|
511 |
|
512 |
|
513 |
+
def process_gossip_and_update_reputation(self, gossip_memory: Memory):
|
514 |
"""LLM을 이용해 소문을 분석하고 관련 인물들의 평판을 업데이트"""
|
515 |
|
516 |
analysis = analyze_gossip(gossip_memory.content)
|
|
|
533 |
|
534 |
if score_change != 0:
|
535 |
# LLM이 반환한 한글 이름으로 NPC 객체를 찾아 영어 ID를 가져온다.
|
536 |
+
p1_npc = self.manager.get_npc_by_korean_name(person1_korean_name)
|
537 |
+
p2_npc = self.manager.get_npc_by_korean_name(person2_korean_name)
|
538 |
|
539 |
# 소문의 대상이 된 두 사람에 대한 나의 평판을 변경
|
540 |
if p1_npc:
|
541 |
+
self.update_reputation(p1_npc.name, score_change)
|
542 |
if p2_npc:
|
543 |
+
self.update_reputation(p2_npc.name, score_change)
|
npc_social_network/npc/npc_manager.py
CHANGED
@@ -25,6 +25,7 @@ class NPCManager:
|
|
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 |
"""
|
|
|
25 |
self.npcs.append(npc)
|
26 |
self.npc_dict[npc.name] = npc
|
27 |
self.korean_name_to_npc[npc.korean_name] = npc
|
28 |
+
npc.manager = self
|
29 |
|
30 |
def get_npc_by_name(self, name: str) -> Optional['NPC']:
|
31 |
"""
|
npc_social_network/npc/npc_memory_embedder.py
CHANGED
@@ -55,7 +55,7 @@ def embed_npc_memories(npc: "NPC"):
|
|
55 |
texts = [memory_to_text(mem) for mem in memories]
|
56 |
embeddings = model.encode(texts)
|
57 |
index = faiss.IndexFlatL2(DIMENSION)
|
58 |
-
index.add(np.
|
59 |
|
60 |
safe_filename = sanitize_filename(npc.name)
|
61 |
save_path = os.path.join(VECTOR_DIR, f"{safe_filename}.faiss")
|
|
|
55 |
texts = [memory_to_text(mem) for mem in memories]
|
56 |
embeddings = model.encode(texts)
|
57 |
index = faiss.IndexFlatL2(DIMENSION)
|
58 |
+
index.add(np.array(embeddings, dtype=np.float32))
|
59 |
|
60 |
safe_filename = sanitize_filename(npc.name)
|
61 |
save_path = os.path.join(VECTOR_DIR, f"{safe_filename}.faiss")
|
npc_social_network/npc/npc_relationship.py
CHANGED
@@ -30,7 +30,7 @@ class RelationshipManager:
|
|
30 |
return self.relationships[target_name]
|
31 |
|
32 |
def update_relationship(self, target_name: str, emotion: str, strength: float=1.0,
|
33 |
-
memory: Optional["Memory"]=None
|
34 |
"""
|
35 |
특정 감정 기반으로 관계 수치 조정
|
36 |
- 긍정/부정만이 아니라 감정 유형에 따른 영향 차별화
|
@@ -40,9 +40,6 @@ class RelationshipManager:
|
|
40 |
|
41 |
profile = self._get_or_create_profile(target_name)
|
42 |
|
43 |
-
target_npc = npc_manager.get_npc_by_korean_name(target_name)
|
44 |
-
target_korean_name = target_npc.korean_name if target_npc else target_name
|
45 |
-
|
46 |
impact = EMOTION_RELATION_IMPACT.get(emotion, 0.0)
|
47 |
delta = impact * strength
|
48 |
|
@@ -57,6 +54,9 @@ class RelationshipManager:
|
|
57 |
if memory not in profile.key_memories:
|
58 |
profile.key_memories.append(memory)
|
59 |
profile.key_memories = profile.key_memories[-5:] # 최근 5개만 유지
|
|
|
|
|
|
|
60 |
print(f"[{self.owner_npc.korean_name}] '{target_korean_name}'와의 새로운 핵심 기억 추가: {memory.content[:30]}...")
|
61 |
|
62 |
def update_relationship_type(self, profile:SocialProfile):
|
@@ -82,7 +82,7 @@ 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)
|
@@ -90,7 +90,7 @@ class RelationshipManager:
|
|
90 |
if not profile.key_memories:
|
91 |
return # 요약할 기억이 없으면 실행 안함
|
92 |
|
93 |
-
target_npc =
|
94 |
target_korean_name = target_npc.korean_name if target_npc else target_name
|
95 |
|
96 |
memory_details = "\n".join([f"- {mem.content} (감정: {mem.emotion})" for mem in profile.key_memories])
|
|
|
30 |
return self.relationships[target_name]
|
31 |
|
32 |
def update_relationship(self, target_name: str, emotion: str, strength: float=1.0,
|
33 |
+
memory: Optional["Memory"]=None):
|
34 |
"""
|
35 |
특정 감정 기반으로 관계 수치 조정
|
36 |
- 긍정/부정만이 아니라 감정 유형에 따른 영향 차별화
|
|
|
40 |
|
41 |
profile = self._get_or_create_profile(target_name)
|
42 |
|
|
|
|
|
|
|
43 |
impact = EMOTION_RELATION_IMPACT.get(emotion, 0.0)
|
44 |
delta = impact * strength
|
45 |
|
|
|
54 |
if memory not in profile.key_memories:
|
55 |
profile.key_memories.append(memory)
|
56 |
profile.key_memories = profile.key_memories[-5:] # 최근 5개만 유지
|
57 |
+
|
58 |
+
target_npc = self.owner_npc.manager.get_npc_by_name(target_name)
|
59 |
+
target_korean_name = target_npc.korean_name if target_npc else target_name
|
60 |
print(f"[{self.owner_npc.korean_name}] '{target_korean_name}'와의 새로운 핵심 기억 추가: {memory.content[:30]}...")
|
61 |
|
62 |
def update_relationship_type(self, profile:SocialProfile):
|
|
|
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)
|
|
|
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])
|
npc_social_network/simulation_core.py
CHANGED
@@ -43,7 +43,7 @@ def tick_simulation():
|
|
43 |
npc.decay_emotions()
|
44 |
npc.decay_memories()
|
45 |
if random.random() < 0.1:
|
46 |
-
npc.update_autonomous_behavior("자율 행동 시간"
|
47 |
|
48 |
if len(npc_manager.all()) >= 3: # 목격자가 있으려면 최소 3명 필요
|
49 |
try:
|
@@ -66,7 +66,6 @@ def tick_simulation():
|
|
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 |
|
|
|
43 |
npc.decay_emotions()
|
44 |
npc.decay_memories()
|
45 |
if random.random() < 0.1:
|
46 |
+
npc.update_autonomous_behavior("자율 행동 시간")
|
47 |
|
48 |
if len(npc_manager.all()) >= 3: # 목격자가 있으려면 최소 3명 필요
|
49 |
try:
|
|
|
66 |
importance=4,
|
67 |
emotion="curiosity",
|
68 |
memory_type="Gossip",
|
|
|
69 |
)
|
70 |
add_log(f"[목격] {witness.name}이(가) {initiator.name}와 {target.name}의 대화를 목격함.")
|
71 |
|