rorshi commited on
Commit
5a9f257
Β·
1 Parent(s): f302085

Temp Complete embedding

Browse files
npc_social_network/data/vectorstores/npc.faiss ADDED
Binary file (4.65 kB). View file
 
npc_social_network/npc/npc_memory_embedder.py CHANGED
@@ -6,21 +6,25 @@ import faiss
6
  import os
7
  from .npc_memory import MemoryStore
8
  from .npc_base import NPC
 
9
 
10
  # 사전 ν›ˆλ ¨λœ λ¬Έμž₯ μž„λ² λ”© λͺ¨λΈ
11
  model = SentenceTransformer("all-MiniLM-L6-v2")
12
 
13
  # λ¬Έμž₯ μž„λ² λ”© μ €μž₯ μž₯μ†Œ
14
- VECTOR_DIR = "portfolio/npc_social_network/data/vectorstores"
 
15
 
16
  def memory_to_text(memory):
17
  """
18
- Memoty 객체λ₯Ό μž„λ² λ”© κ°€λŠ₯ν•œ ν…μŠ€νŠΈ ν˜•νƒœλ‘œ λ³€ν™˜
19
  """
20
  lines = [f"Content: {memory.content}"]
21
- if memory.emotion:
22
  emotion_str = ", ".join([f"{k}:{v:.2f}" for k, v in memory.emotion.items()])
23
  lines.append(f"Emotion: {emotion_str}")
 
 
24
  lines.append(f"Importance: {memory.importance}")
25
  return " | ".join(lines)
26
 
@@ -39,13 +43,58 @@ def embed_npc_memories(npc: NPC):
39
  if not os.path.exists(VECTOR_DIR):
40
  os.makedirs(VECTOR_DIR)
41
 
42
- memories = npc.memory.get_all()
43
  texts = [memory_to_text(mem) for mem in memories]
44
  embeddings = model.encode(texts)
45
 
46
  index = faiss.IndexFlatL2(len(embeddings[0]))
47
  index.add(np.array(embeddings, dtype=np.float32))
48
 
49
- faiss.write_index(index, f"{VECTOR_DIR}/{npc.name}")
 
 
50
 
51
- print(f"[μž„λ² λ”© μ™„λ£Œ] {npc.name}의 κΈ°μ–΅ {len(memories)}개λ₯Ό λ²‘ν„°ν™”ν•˜μ—¬ μ €μž₯ν–ˆμŠ΅λ‹ˆλ‹€.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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")
13
 
14
  # λ¬Έμž₯ μž„λ² λ”© μ €μž₯ μž₯μ†Œ
15
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
16
+ VECTOR_DIR = os.path.join(BASE_DIR, "..", "data", "vectorstores")
17
 
18
  def memory_to_text(memory):
19
  """
20
+ Memory 객체λ₯Ό μž„λ² λ”© κ°€λŠ₯ν•œ ν…μŠ€νŠΈ ν˜•νƒœλ‘œ λ³€ν™˜
21
  """
22
  lines = [f"Content: {memory.content}"]
23
+ if isinstance(memory.emotion, dict):
24
  emotion_str = ", ".join([f"{k}:{v:.2f}" for k, v in memory.emotion.items()])
25
  lines.append(f"Emotion: {emotion_str}")
26
+ else:
27
+ lines.append(f"Emotion: {memory.emotion}")
28
  lines.append(f"Importance: {memory.importance}")
29
  return " | ".join(lines)
30
 
 
43
  if not os.path.exists(VECTOR_DIR):
44
  os.makedirs(VECTOR_DIR)
45
 
46
+ memories = npc.memory_store.get_all_memories()
47
  texts = [memory_to_text(mem) for mem in memories]
48
  embeddings = model.encode(texts)
49
 
50
  index = faiss.IndexFlatL2(len(embeddings[0]))
51
  index.add(np.array(embeddings, dtype=np.float32))
52
 
53
+ safe_filename = sanitize_filename(npc.name)
54
+ save_path = os.path.join(VECTOR_DIR, f"{safe_filename}.faiss")
55
+ faiss.write_index(index, save_path)
56
 
57
+ print(f"[μž„λ² λ”© μ™„λ£Œ] {npc.name}의 κΈ°μ–΅ {len(memories)}개λ₯Ό λ²‘ν„°ν™”ν•˜μ—¬ μ €μž₯ν–ˆμŠ΅λ‹ˆλ‹€.")
58
+ print(f"[μž„λ² λ”© μ™„λ£Œ] {npc.name} β†’ μ €μž₯ μœ„μΉ˜: {save_path}")
59
+
60
+ def load_npc_faiss_index(npc_name):
61
+ """
62
+ μ €μž₯된 FAISS 인덱슀λ₯Ό 뢈러옴
63
+ """
64
+ safe_filename = sanitize_filename(npc_name)
65
+ index_path = f"{VECTOR_DIR}/{safe_filename}.faiss"
66
+ if not os.path.exists(index_path):
67
+ raise FileNotFoundError(f"{index_path} FAISS μΈλ±μŠ€κ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.")
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
+ """
96
+ ν•œκΈ€/특수문자 등을 μ œκ±°ν•˜κ³  μ•ˆμ „ν•œ ASCII 파일λͺ…μœΌλ‘œ λ³€ν™˜
97
+ 예: μ—˜λΌ β†’ Ella, λ°₯ β†’ Bob λ“±μ˜ 이름은 직접 λ§€ν•‘ν•˜κ±°λ‚˜ μ˜μ–΄λ‘œ 사전 ꡬ성해도 됨
98
+ """
99
+ name_ascii = name.encode('ascii', 'ignore').decode()
100
+ return re.sub(r'[^a-zA-Z0-9_-]', '_', name_ascii or "npc")
npc_social_network/routes/npc_route.py CHANGED
@@ -3,6 +3,7 @@ from flask import Blueprint, render_template, request, jsonify
3
  from npc_social_network.models.gemini_setup import load_gemini
4
  from npc_social_network.npc.npc_base import NPC
5
  from npc_social_network.npc.npc_manager import NPCManager
 
6
  import threading
7
  import time
8
 
@@ -25,6 +26,14 @@ npc_manager.add_npc(NPC(name="μ•¨λ¦¬μŠ€", job="상인", path=[], image=None))
25
  npc_manager.add_npc(NPC(name="λ°₯", job="λŒ€μž₯μž₯이", path=[], image=None))
26
  npc_manager.add_npc(NPC(name="찰리", job="농뢀", path=[], image=None))
27
 
 
 
 
 
 
 
 
 
28
  # --------------------
29
  # 라우트 μ •μ˜
30
  # --------------------
@@ -103,6 +112,35 @@ def trigger_npc_interactions():
103
  npc_manager.npc_interactions()
104
  return jsonify({"status": "NPC interactions triggered"})
105
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  # Background Thread ν•¨μˆ˜ μ •μ˜
107
  def npc_interactions_loop(interval_seconds = 10):
108
  while True:
 
3
  from npc_social_network.models.gemini_setup import load_gemini
4
  from npc_social_network.npc.npc_base import NPC
5
  from npc_social_network.npc.npc_manager import NPCManager
6
+ from npc_social_network.npc.npc_memory_embedder import embed_npc_memories, search_similar_memories
7
  import threading
8
  import time
9
 
 
26
  npc_manager.add_npc(NPC(name="λ°₯", job="λŒ€μž₯μž₯이", path=[], image=None))
27
  npc_manager.add_npc(NPC(name="찰리", job="농뢀", path=[], image=None))
28
 
29
+ # [πŸ’‘ μΆ”κ°€: μ—˜λΌμ—κ²Œ κΈ°μ–΅ 3개 μˆ˜λ™ μΆ”κ°€]
30
+ from npc_social_network.npc.npc_memory import Memory
31
+
32
+ ella = npc_manager.get_npc_by_name("μ—˜λΌ")
33
+ ella.memory_store.add_memory(Memory(content="μ–΄μ œ μ•¨λ¦¬μŠ€μ™€ μ‹¬ν•˜κ²Œ λ‹€ν‰œλ‹€", importance=9))
34
+ ella.memory_store.add_memory(Memory(content="λ°₯이 μ„ λ¬Όν•΄μ€€ 검이 λ§ˆμŒμ— λ“€μ—ˆλ‹€", importance=6))
35
+ ella.memory_store.add_memory(Memory(content="μ˜€λŠ˜μ€ 찰리λ₯Ό 도와 농사λ₯Ό μ§€μ—ˆλ‹€", importance=5))
36
+
37
  # --------------------
38
  # 라우트 μ •μ˜
39
  # --------------------
 
112
  npc_manager.npc_interactions()
113
  return jsonify({"status": "NPC interactions triggered"})
114
 
115
+ @npc_bp.route("/test_embed_memory", methods=["POST"])
116
+ def test_embed_memory():
117
+ npc_name = request.json.get("npc")
118
+ npc = npc_manager.get_npc_by_name(npc_name)
119
+
120
+ if not npc:
121
+ return jsonify({"error": "NPC not found"}), 404
122
+
123
+ embed_npc_memories(npc)
124
+ return jsonify({"status": f"{npc_name} μž„λ² λ”© μ™„λ£Œ"})
125
+
126
+ @npc_bp.route("/test_search_memory", methods=["POST"])
127
+ def test_search_memory():
128
+ npc_name = request.json.get("npc")
129
+ query = request.json.get("query")
130
+
131
+ indices, distances, texts = search_similar_memories(npc_name, query)
132
+ return jsonify({
133
+ "npc": npc_name,
134
+ "query": query,
135
+ "matched_indices": [int(i) for i in indices.tolist()],
136
+ "distances": [round(float(d), 4) for d in distances],
137
+ "matched_memories": texts
138
+ })
139
+
140
+ @npc_bp.route("/test_memory_tools", methods=["GET"])
141
+ def test_memory_tools():
142
+ return render_template("test_memory_tools.html")
143
+
144
  # Background Thread ν•¨μˆ˜ μ •μ˜
145
  def npc_interactions_loop(interval_seconds = 10):
146
  while True:
npc_social_network/templates/test_memory_tools.html ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- portfolio/npc_social_network/templates/test_memory_tools.html -->
2
+ <!DOCTYPE html>
3
+ <html lang="ko">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>NPC Memory Test Tools</title>
7
+ </head>
8
+ <body>
9
+ <h2>🧠 NPC κΈ°μ–΅ μž„λ² λ”© μš”μ²­</h2>
10
+ <form id="embedForm">
11
+ NPC 이름: <input type="text" id="embedNpcName" value="μ—˜λΌ">
12
+ <button type="submit">μž„λ² λ”© μš”μ²­</button>
13
+ </form>
14
+ <pre id="embedResult"></pre>
15
+
16
+ <hr>
17
+
18
+ <h2>πŸ” NPC κΈ°μ–΅ 검색 μš”μ²­</h2>
19
+ <form id="searchForm">
20
+ NPC 이름: <input type="text" id="searchNpcName" value="μ—˜λΌ"><br>
21
+ 질의 λ‚΄μš©: <input type="text" id="searchQuery" value="λˆ„κ΅°κ°€μ™€ λ‹€ν‰œλ˜ 기얡이 μžˆμ–΄?">
22
+ <button type="submit">검색 μš”μ²­</button>
23
+ </form>
24
+ <pre id="searchResult"></pre>
25
+
26
+ <script>
27
+ // μž„λ² λ”© μš”μ²­
28
+ document.getElementById('embedForm').addEventListener('submit', async (e) => {
29
+ e.preventDefault();
30
+ const npc = document.getElementById('embedNpcName').value;
31
+ const res = await fetch("/npc_social_network/test_embed_memory", {
32
+ method: "POST",
33
+ headers: { "Content-Type": "application/json" },
34
+ body: JSON.stringify({ npc })
35
+ });
36
+ const json = await res.json();
37
+ document.getElementById('embedResult').innerText = JSON.stringify(json, null, 2);
38
+ });
39
+
40
+ // 검색 μš”μ²­
41
+ document.getElementById('searchForm').addEventListener('submit', async (e) => {
42
+ e.preventDefault();
43
+ const npc = document.getElementById('searchNpcName').value;
44
+ const query = document.getElementById('searchQuery').value;
45
+ const res = await fetch("/npc_social_network/test_search_memory", {
46
+ method: "POST",
47
+ headers: { "Content-Type": "application/json" },
48
+ body: JSON.stringify({ npc, query })
49
+ });
50
+ const json = await res.json();
51
+ document.getElementById('searchResult').innerText = JSON.stringify(json, null, 2);
52
+ });
53
+ </script>
54
+ </body>
55
+ </html>