Spaces:
Sleeping
Sleeping
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 |
-
|
|
|
15 |
|
16 |
def memory_to_text(memory):
|
17 |
"""
|
18 |
-
|
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.
|
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 |
-
|
|
|
|
|
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>
|