mem-llm 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mem-llm might be problematic. Click here for more details.
- mem_llm-1.0.0.dist-info/METADATA +382 -0
- mem_llm-1.0.0.dist-info/RECORD +14 -0
- mem_llm-1.0.0.dist-info/WHEEL +5 -0
- mem_llm-1.0.0.dist-info/top_level.txt +1 -0
- memory_llm/__init__.py +34 -0
- memory_llm/config.yaml.example +52 -0
- memory_llm/config_manager.py +229 -0
- memory_llm/knowledge_loader.py +88 -0
- memory_llm/llm_client.py +162 -0
- memory_llm/mem_agent.py +512 -0
- memory_llm/memory_db.py +376 -0
- memory_llm/memory_manager.py +257 -0
- memory_llm/memory_tools.py +253 -0
- memory_llm/prompt_templates.py +244 -0
memory_llm/memory_db.py
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SQL Database Memory Management
|
|
3
|
+
Stores memory data using SQLite - Production-ready
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sqlite3
|
|
7
|
+
import json
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import Dict, List, Optional, Tuple
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SQLMemoryManager:
|
|
14
|
+
"""SQLite-based memory management system"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, db_path: str = "memories.db"):
|
|
17
|
+
"""
|
|
18
|
+
Args:
|
|
19
|
+
db_path: SQLite database file path
|
|
20
|
+
"""
|
|
21
|
+
self.db_path = Path(db_path)
|
|
22
|
+
self.conn = None
|
|
23
|
+
self._init_database()
|
|
24
|
+
|
|
25
|
+
def _init_database(self) -> None:
|
|
26
|
+
"""Create database and tables"""
|
|
27
|
+
self.conn = sqlite3.connect(str(self.db_path), check_same_thread=False)
|
|
28
|
+
self.conn.row_factory = sqlite3.Row
|
|
29
|
+
|
|
30
|
+
cursor = self.conn.cursor()
|
|
31
|
+
|
|
32
|
+
# User profiles table
|
|
33
|
+
cursor.execute("""
|
|
34
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
35
|
+
user_id TEXT PRIMARY KEY,
|
|
36
|
+
name TEXT,
|
|
37
|
+
first_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
38
|
+
last_interaction TIMESTAMP,
|
|
39
|
+
preferences TEXT,
|
|
40
|
+
summary TEXT,
|
|
41
|
+
metadata TEXT
|
|
42
|
+
)
|
|
43
|
+
""")
|
|
44
|
+
|
|
45
|
+
# Conversations table
|
|
46
|
+
cursor.execute("""
|
|
47
|
+
CREATE TABLE IF NOT EXISTS conversations (
|
|
48
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
49
|
+
user_id TEXT NOT NULL,
|
|
50
|
+
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
51
|
+
user_message TEXT NOT NULL,
|
|
52
|
+
bot_response TEXT NOT NULL,
|
|
53
|
+
metadata TEXT,
|
|
54
|
+
sentiment TEXT,
|
|
55
|
+
resolved BOOLEAN DEFAULT 0,
|
|
56
|
+
FOREIGN KEY (user_id) REFERENCES users(user_id)
|
|
57
|
+
)
|
|
58
|
+
""")
|
|
59
|
+
|
|
60
|
+
# İndeksler - Performans için
|
|
61
|
+
cursor.execute("""
|
|
62
|
+
CREATE INDEX IF NOT EXISTS idx_user_timestamp
|
|
63
|
+
ON conversations(user_id, timestamp DESC)
|
|
64
|
+
""")
|
|
65
|
+
|
|
66
|
+
cursor.execute("""
|
|
67
|
+
CREATE INDEX IF NOT EXISTS idx_resolved
|
|
68
|
+
ON conversations(user_id, resolved)
|
|
69
|
+
""")
|
|
70
|
+
|
|
71
|
+
# Senaryo şablonları tablosu
|
|
72
|
+
cursor.execute("""
|
|
73
|
+
CREATE TABLE IF NOT EXISTS scenario_templates (
|
|
74
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
75
|
+
name TEXT NOT NULL UNIQUE,
|
|
76
|
+
description TEXT,
|
|
77
|
+
system_prompt TEXT NOT NULL,
|
|
78
|
+
example_interactions TEXT,
|
|
79
|
+
metadata TEXT,
|
|
80
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
81
|
+
)
|
|
82
|
+
""")
|
|
83
|
+
|
|
84
|
+
# Problem/FAQ veritabanı
|
|
85
|
+
cursor.execute("""
|
|
86
|
+
CREATE TABLE IF NOT EXISTS knowledge_base (
|
|
87
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
88
|
+
category TEXT NOT NULL,
|
|
89
|
+
question TEXT NOT NULL,
|
|
90
|
+
answer TEXT NOT NULL,
|
|
91
|
+
keywords TEXT,
|
|
92
|
+
priority INTEGER DEFAULT 0,
|
|
93
|
+
active BOOLEAN DEFAULT 1,
|
|
94
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
95
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
96
|
+
)
|
|
97
|
+
""")
|
|
98
|
+
|
|
99
|
+
cursor.execute("""
|
|
100
|
+
CREATE INDEX IF NOT EXISTS idx_category
|
|
101
|
+
ON knowledge_base(category, active)
|
|
102
|
+
""")
|
|
103
|
+
|
|
104
|
+
self.conn.commit()
|
|
105
|
+
|
|
106
|
+
def add_user(self, user_id: str, name: Optional[str] = None,
|
|
107
|
+
metadata: Optional[Dict] = None) -> None:
|
|
108
|
+
"""
|
|
109
|
+
Add new user or update existing
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
user_id: User ID
|
|
113
|
+
name: User name
|
|
114
|
+
metadata: Additional information
|
|
115
|
+
"""
|
|
116
|
+
cursor = self.conn.cursor()
|
|
117
|
+
cursor.execute("""
|
|
118
|
+
INSERT INTO users (user_id, name, metadata)
|
|
119
|
+
VALUES (?, ?, ?)
|
|
120
|
+
ON CONFLICT(user_id) DO UPDATE SET
|
|
121
|
+
name = COALESCE(excluded.name, users.name),
|
|
122
|
+
metadata = COALESCE(excluded.metadata, users.metadata)
|
|
123
|
+
""", (user_id, name, json.dumps(metadata or {})))
|
|
124
|
+
self.conn.commit()
|
|
125
|
+
|
|
126
|
+
def add_interaction(self, user_id: str, user_message: str,
|
|
127
|
+
bot_response: str, metadata: Optional[Dict] = None,
|
|
128
|
+
resolved: bool = False) -> int:
|
|
129
|
+
"""
|
|
130
|
+
Record new interaction
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
user_id: User ID
|
|
134
|
+
user_message: User's message
|
|
135
|
+
bot_response: Bot's response
|
|
136
|
+
metadata: Additional information
|
|
137
|
+
resolved: Is issue resolved?
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Added record ID
|
|
141
|
+
"""
|
|
142
|
+
cursor = self.conn.cursor()
|
|
143
|
+
|
|
144
|
+
# Create user if not exists
|
|
145
|
+
self.add_user(user_id)
|
|
146
|
+
|
|
147
|
+
# Record interaction
|
|
148
|
+
cursor.execute("""
|
|
149
|
+
INSERT INTO conversations
|
|
150
|
+
(user_id, user_message, bot_response, metadata, resolved)
|
|
151
|
+
VALUES (?, ?, ?, ?, ?)
|
|
152
|
+
""", (user_id, user_message, bot_response,
|
|
153
|
+
json.dumps(metadata or {}), resolved))
|
|
154
|
+
|
|
155
|
+
interaction_id = cursor.lastrowid
|
|
156
|
+
|
|
157
|
+
# Update user's last interaction time
|
|
158
|
+
cursor.execute("""
|
|
159
|
+
UPDATE users
|
|
160
|
+
SET last_interaction = CURRENT_TIMESTAMP
|
|
161
|
+
WHERE user_id = ?
|
|
162
|
+
""", (user_id,))
|
|
163
|
+
|
|
164
|
+
self.conn.commit()
|
|
165
|
+
return interaction_id
|
|
166
|
+
|
|
167
|
+
def get_recent_conversations(self, user_id: str, limit: int = 10) -> List[Dict]:
|
|
168
|
+
"""
|
|
169
|
+
Kullanıcının son konuşmalarını getirir
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
user_id: Kullanıcı kimliği
|
|
173
|
+
limit: Getirilecek konuşma sayısı
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Konuşmalar listesi
|
|
177
|
+
"""
|
|
178
|
+
cursor = self.conn.cursor()
|
|
179
|
+
cursor.execute("""
|
|
180
|
+
SELECT timestamp, user_message, bot_response, metadata, resolved
|
|
181
|
+
FROM conversations
|
|
182
|
+
WHERE user_id = ?
|
|
183
|
+
ORDER BY timestamp DESC
|
|
184
|
+
LIMIT ?
|
|
185
|
+
""", (user_id, limit))
|
|
186
|
+
|
|
187
|
+
rows = cursor.fetchall()
|
|
188
|
+
return [dict(row) for row in rows]
|
|
189
|
+
|
|
190
|
+
def search_conversations(self, user_id: str, keyword: str) -> List[Dict]:
|
|
191
|
+
"""
|
|
192
|
+
Konuşmalarda anahtar kelime arar
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
user_id: Kullanıcı kimliği
|
|
196
|
+
keyword: Aranacak kelime
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Eşleşen konuşmalar
|
|
200
|
+
"""
|
|
201
|
+
cursor = self.conn.cursor()
|
|
202
|
+
cursor.execute("""
|
|
203
|
+
SELECT timestamp, user_message, bot_response, metadata, resolved
|
|
204
|
+
FROM conversations
|
|
205
|
+
WHERE user_id = ?
|
|
206
|
+
AND (user_message LIKE ? OR bot_response LIKE ? OR metadata LIKE ?)
|
|
207
|
+
ORDER BY timestamp DESC
|
|
208
|
+
""", (user_id, f"%{keyword}%", f"%{keyword}%", f"%{keyword}%"))
|
|
209
|
+
|
|
210
|
+
rows = cursor.fetchall()
|
|
211
|
+
return [dict(row) for row in rows]
|
|
212
|
+
|
|
213
|
+
def get_user_profile(self, user_id: str) -> Optional[Dict]:
|
|
214
|
+
"""
|
|
215
|
+
Kullanıcı profilini getirir
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
user_id: Kullanıcı kimliği
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
Kullanıcı profili veya None
|
|
222
|
+
"""
|
|
223
|
+
cursor = self.conn.cursor()
|
|
224
|
+
cursor.execute("""
|
|
225
|
+
SELECT * FROM users WHERE user_id = ?
|
|
226
|
+
""", (user_id,))
|
|
227
|
+
|
|
228
|
+
row = cursor.fetchone()
|
|
229
|
+
if row:
|
|
230
|
+
return dict(row)
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
def update_user_profile(self, user_id: str, updates: Dict) -> None:
|
|
234
|
+
"""
|
|
235
|
+
Kullanıcı profilini günceller
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
user_id: Kullanıcı kimliği
|
|
239
|
+
updates: Güncellenecek alanlar
|
|
240
|
+
"""
|
|
241
|
+
allowed_fields = ['name', 'preferences', 'summary', 'metadata']
|
|
242
|
+
set_clause = []
|
|
243
|
+
values = []
|
|
244
|
+
|
|
245
|
+
for field, value in updates.items():
|
|
246
|
+
if field in allowed_fields:
|
|
247
|
+
set_clause.append(f"{field} = ?")
|
|
248
|
+
if isinstance(value, (dict, list)):
|
|
249
|
+
values.append(json.dumps(value))
|
|
250
|
+
else:
|
|
251
|
+
values.append(value)
|
|
252
|
+
|
|
253
|
+
if set_clause:
|
|
254
|
+
values.append(user_id)
|
|
255
|
+
cursor = self.conn.cursor()
|
|
256
|
+
cursor.execute(f"""
|
|
257
|
+
UPDATE users
|
|
258
|
+
SET {', '.join(set_clause)}
|
|
259
|
+
WHERE user_id = ?
|
|
260
|
+
""", values)
|
|
261
|
+
self.conn.commit()
|
|
262
|
+
|
|
263
|
+
def add_knowledge(self, category: str, question: str, answer: str,
|
|
264
|
+
keywords: Optional[List[str]] = None,
|
|
265
|
+
priority: int = 0) -> int:
|
|
266
|
+
"""
|
|
267
|
+
Bilgi bankasına yeni kayıt ekler
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
category: Kategori (örn: "kargo", "iade", "ödeme")
|
|
271
|
+
question: Soru
|
|
272
|
+
answer: Cevap
|
|
273
|
+
keywords: Anahtar kelimeler
|
|
274
|
+
priority: Öncelik (yüksek = önce gösterilir)
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
Kayıt ID'si
|
|
278
|
+
"""
|
|
279
|
+
cursor = self.conn.cursor()
|
|
280
|
+
cursor.execute("""
|
|
281
|
+
INSERT INTO knowledge_base
|
|
282
|
+
(category, question, answer, keywords, priority)
|
|
283
|
+
VALUES (?, ?, ?, ?, ?)
|
|
284
|
+
""", (category, question, answer,
|
|
285
|
+
json.dumps(keywords or []), priority))
|
|
286
|
+
|
|
287
|
+
self.conn.commit()
|
|
288
|
+
return cursor.lastrowid
|
|
289
|
+
|
|
290
|
+
def search_knowledge(self, query: str, category: Optional[str] = None,
|
|
291
|
+
limit: int = 5) -> List[Dict]:
|
|
292
|
+
"""
|
|
293
|
+
Bilgi bankasında arama yapar
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
query: Arama sorgusu
|
|
297
|
+
category: Kategori filtresi (opsiyonel)
|
|
298
|
+
limit: Maksimum sonuç sayısı
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
Bulunan kayıtlar
|
|
302
|
+
"""
|
|
303
|
+
cursor = self.conn.cursor()
|
|
304
|
+
|
|
305
|
+
if category:
|
|
306
|
+
cursor.execute("""
|
|
307
|
+
SELECT category, question, answer, priority
|
|
308
|
+
FROM knowledge_base
|
|
309
|
+
WHERE active = 1
|
|
310
|
+
AND category = ?
|
|
311
|
+
AND (question LIKE ? OR answer LIKE ? OR keywords LIKE ?)
|
|
312
|
+
ORDER BY priority DESC, id DESC
|
|
313
|
+
LIMIT ?
|
|
314
|
+
""", (category, f"%{query}%", f"%{query}%", f"%{query}%", limit))
|
|
315
|
+
else:
|
|
316
|
+
cursor.execute("""
|
|
317
|
+
SELECT category, question, answer, priority
|
|
318
|
+
FROM knowledge_base
|
|
319
|
+
WHERE active = 1
|
|
320
|
+
AND (question LIKE ? OR answer LIKE ? OR keywords LIKE ?)
|
|
321
|
+
ORDER BY priority DESC, id DESC
|
|
322
|
+
LIMIT ?
|
|
323
|
+
""", (f"%{query}%", f"%{query}%", f"%{query}%", limit))
|
|
324
|
+
|
|
325
|
+
return [dict(row) for row in cursor.fetchall()]
|
|
326
|
+
|
|
327
|
+
def get_statistics(self) -> Dict:
|
|
328
|
+
"""
|
|
329
|
+
Genel istatistikleri döndürür
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
İstatistik bilgileri
|
|
333
|
+
"""
|
|
334
|
+
cursor = self.conn.cursor()
|
|
335
|
+
|
|
336
|
+
# Toplam kullanıcı
|
|
337
|
+
cursor.execute("SELECT COUNT(*) as count FROM users")
|
|
338
|
+
total_users = cursor.fetchone()['count']
|
|
339
|
+
|
|
340
|
+
# Toplam etkileşim
|
|
341
|
+
cursor.execute("SELECT COUNT(*) as count FROM conversations")
|
|
342
|
+
total_interactions = cursor.fetchone()['count']
|
|
343
|
+
|
|
344
|
+
# Çözülmemiş sorunlar
|
|
345
|
+
cursor.execute("SELECT COUNT(*) as count FROM conversations WHERE resolved = 0")
|
|
346
|
+
unresolved = cursor.fetchone()['count']
|
|
347
|
+
|
|
348
|
+
# Bilgi bankası kayıt sayısı
|
|
349
|
+
cursor.execute("SELECT COUNT(*) as count FROM knowledge_base WHERE active = 1")
|
|
350
|
+
kb_count = cursor.fetchone()['count']
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
"total_users": total_users,
|
|
354
|
+
"total_interactions": total_interactions,
|
|
355
|
+
"unresolved_issues": unresolved,
|
|
356
|
+
"knowledge_base_entries": kb_count,
|
|
357
|
+
"avg_interactions_per_user": total_interactions / total_users if total_users > 0 else 0
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
def clear_memory(self, user_id: str) -> None:
|
|
361
|
+
"""Delete all user conversations"""
|
|
362
|
+
cursor = self.conn.cursor()
|
|
363
|
+
cursor.execute("DELETE FROM conversations WHERE user_id = ?", (user_id,))
|
|
364
|
+
self.conn.commit()
|
|
365
|
+
|
|
366
|
+
def close(self) -> None:
|
|
367
|
+
"""Veritabanı bağlantısını kapatır"""
|
|
368
|
+
if self.conn:
|
|
369
|
+
self.conn.close()
|
|
370
|
+
|
|
371
|
+
def __enter__(self):
|
|
372
|
+
return self
|
|
373
|
+
|
|
374
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
375
|
+
self.close()
|
|
376
|
+
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Memory Manager - Memory Management System
|
|
3
|
+
Stores, updates and remembers user interactions.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import Dict, List, Optional
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MemoryManager:
|
|
14
|
+
"""Memory system that manages user interactions and context"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, memory_dir: str = "memories"):
|
|
17
|
+
"""
|
|
18
|
+
Args:
|
|
19
|
+
memory_dir: Directory where memory files will be stored
|
|
20
|
+
"""
|
|
21
|
+
self.memory_dir = Path(memory_dir)
|
|
22
|
+
self.memory_dir.mkdir(exist_ok=True)
|
|
23
|
+
self.conversations: Dict[str, List[Dict]] = {}
|
|
24
|
+
self.user_profiles: Dict[str, Dict] = {}
|
|
25
|
+
|
|
26
|
+
def _get_user_file(self, user_id: str) -> Path:
|
|
27
|
+
"""Returns the path of user's memory file"""
|
|
28
|
+
return self.memory_dir / f"{user_id}.json"
|
|
29
|
+
|
|
30
|
+
def load_memory(self, user_id: str) -> Dict:
|
|
31
|
+
"""
|
|
32
|
+
Load user's memory
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
user_id: User ID
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
User's memory data
|
|
39
|
+
"""
|
|
40
|
+
user_file = self._get_user_file(user_id)
|
|
41
|
+
|
|
42
|
+
if user_file.exists():
|
|
43
|
+
with open(user_file, 'r', encoding='utf-8') as f:
|
|
44
|
+
data = json.load(f)
|
|
45
|
+
self.conversations[user_id] = data.get('conversations', [])
|
|
46
|
+
self.user_profiles[user_id] = data.get('profile', {})
|
|
47
|
+
return data
|
|
48
|
+
else:
|
|
49
|
+
# Create empty memory for new user
|
|
50
|
+
self.conversations[user_id] = []
|
|
51
|
+
self.user_profiles[user_id] = {
|
|
52
|
+
'user_id': user_id,
|
|
53
|
+
'first_seen': datetime.now().isoformat(),
|
|
54
|
+
'preferences': {},
|
|
55
|
+
'summary': {}
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
'conversations': [],
|
|
59
|
+
'profile': self.user_profiles[user_id]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
def save_memory(self, user_id: str) -> None:
|
|
63
|
+
"""
|
|
64
|
+
Save user's memory to disk
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
user_id: User ID
|
|
68
|
+
"""
|
|
69
|
+
user_file = self._get_user_file(user_id)
|
|
70
|
+
|
|
71
|
+
data = {
|
|
72
|
+
'conversations': self.conversations.get(user_id, []),
|
|
73
|
+
'profile': self.user_profiles.get(user_id, {}),
|
|
74
|
+
'last_updated': datetime.now().isoformat()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
with open(user_file, 'w', encoding='utf-8') as f:
|
|
78
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
79
|
+
|
|
80
|
+
def add_interaction(self, user_id: str, user_message: str,
|
|
81
|
+
bot_response: str, metadata: Optional[Dict] = None) -> None:
|
|
82
|
+
"""
|
|
83
|
+
Record a new interaction
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
user_id: User ID
|
|
87
|
+
user_message: User's message
|
|
88
|
+
bot_response: Bot's response
|
|
89
|
+
metadata: Additional information (order no, issue type, etc.)
|
|
90
|
+
"""
|
|
91
|
+
if user_id not in self.conversations:
|
|
92
|
+
self.load_memory(user_id)
|
|
93
|
+
|
|
94
|
+
interaction = {
|
|
95
|
+
'timestamp': datetime.now().isoformat(),
|
|
96
|
+
'user_message': user_message,
|
|
97
|
+
'bot_response': bot_response,
|
|
98
|
+
'metadata': metadata or {}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
self.conversations[user_id].append(interaction)
|
|
102
|
+
self.save_memory(user_id)
|
|
103
|
+
|
|
104
|
+
def update_profile(self, user_id: str, updates: Dict) -> None:
|
|
105
|
+
"""
|
|
106
|
+
Update user profile
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
user_id: User ID
|
|
110
|
+
updates: Information to update
|
|
111
|
+
"""
|
|
112
|
+
if user_id not in self.user_profiles:
|
|
113
|
+
self.load_memory(user_id)
|
|
114
|
+
|
|
115
|
+
self.user_profiles[user_id].update(updates)
|
|
116
|
+
self.save_memory(user_id)
|
|
117
|
+
|
|
118
|
+
def get_recent_conversations(self, user_id: str, limit: int = 5) -> List[Dict]:
|
|
119
|
+
"""
|
|
120
|
+
Get last N conversations
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
user_id: User ID
|
|
124
|
+
limit: Number of conversations to retrieve
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
List of recent conversations
|
|
128
|
+
"""
|
|
129
|
+
if user_id not in self.conversations:
|
|
130
|
+
self.load_memory(user_id)
|
|
131
|
+
|
|
132
|
+
return self.conversations[user_id][-limit:]
|
|
133
|
+
|
|
134
|
+
def search_memory(self, user_id: str, keyword: str) -> List[Dict]:
|
|
135
|
+
"""
|
|
136
|
+
Search for keyword in memory
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
user_id: User ID
|
|
140
|
+
keyword: Word to search for
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Matching interactions
|
|
144
|
+
"""
|
|
145
|
+
if user_id not in self.conversations:
|
|
146
|
+
self.load_memory(user_id)
|
|
147
|
+
|
|
148
|
+
results = []
|
|
149
|
+
keyword_lower = keyword.lower()
|
|
150
|
+
|
|
151
|
+
for interaction in self.conversations[user_id]:
|
|
152
|
+
if (keyword_lower in interaction['user_message'].lower() or
|
|
153
|
+
keyword_lower in interaction['bot_response'].lower() or
|
|
154
|
+
keyword_lower in str(interaction.get('metadata', {})).lower()):
|
|
155
|
+
results.append(interaction)
|
|
156
|
+
|
|
157
|
+
return results
|
|
158
|
+
|
|
159
|
+
def get_summary(self, user_id: str) -> str:
|
|
160
|
+
"""
|
|
161
|
+
Create summary of user's past interactions
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
user_id: User ID
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Summary text
|
|
168
|
+
"""
|
|
169
|
+
if user_id not in self.conversations:
|
|
170
|
+
self.load_memory(user_id)
|
|
171
|
+
|
|
172
|
+
profile = self.user_profiles.get(user_id, {})
|
|
173
|
+
conversations = self.conversations.get(user_id, [])
|
|
174
|
+
|
|
175
|
+
if not conversations:
|
|
176
|
+
return "No interactions with this user yet."
|
|
177
|
+
|
|
178
|
+
summary_parts = [
|
|
179
|
+
f"User ID: {user_id}",
|
|
180
|
+
f"First conversation: {profile.get('first_seen', 'Unknown')}",
|
|
181
|
+
f"Total interactions: {len(conversations)}",
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
# Add last 3 interactions
|
|
185
|
+
if conversations:
|
|
186
|
+
summary_parts.append("\nRecent interactions:")
|
|
187
|
+
for i, conv in enumerate(conversations[-3:], 1):
|
|
188
|
+
timestamp = conv.get('timestamp', 'Unknown')
|
|
189
|
+
user_msg = conv.get('user_message', '')[:50]
|
|
190
|
+
summary_parts.append(f"{i}. {timestamp}: {user_msg}...")
|
|
191
|
+
|
|
192
|
+
# Metadata summary
|
|
193
|
+
all_metadata = [c.get('metadata', {}) for c in conversations if c.get('metadata')]
|
|
194
|
+
if all_metadata:
|
|
195
|
+
summary_parts.append("\nSaved information:")
|
|
196
|
+
# Example: order numbers, issues, etc.
|
|
197
|
+
for meta in all_metadata[-3:]:
|
|
198
|
+
for key, value in meta.items():
|
|
199
|
+
summary_parts.append(f" - {key}: {value}")
|
|
200
|
+
|
|
201
|
+
return "\n".join(summary_parts)
|
|
202
|
+
|
|
203
|
+
def clear_memory(self, user_id: str) -> None:
|
|
204
|
+
"""
|
|
205
|
+
Completely delete user's memory
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
user_id: User ID
|
|
209
|
+
"""
|
|
210
|
+
user_file = self._get_user_file(user_id)
|
|
211
|
+
if user_file.exists():
|
|
212
|
+
user_file.unlink()
|
|
213
|
+
|
|
214
|
+
if user_id in self.conversations:
|
|
215
|
+
del self.conversations[user_id]
|
|
216
|
+
if user_id in self.user_profiles:
|
|
217
|
+
del self.user_profiles[user_id]
|
|
218
|
+
|
|
219
|
+
def search_conversations(self, user_id: str, keyword: str) -> List[Dict]:
|
|
220
|
+
"""
|
|
221
|
+
Search for keyword in conversations (for JSON version)
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
user_id: User ID
|
|
225
|
+
keyword: Word to search for
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Matching conversations
|
|
229
|
+
"""
|
|
230
|
+
if user_id not in self.conversations:
|
|
231
|
+
self.load_memory(user_id)
|
|
232
|
+
|
|
233
|
+
results = []
|
|
234
|
+
keyword_lower = keyword.lower()
|
|
235
|
+
|
|
236
|
+
for interaction in self.conversations.get(user_id, []):
|
|
237
|
+
if (keyword_lower in interaction['user_message'].lower() or
|
|
238
|
+
keyword_lower in interaction['bot_response'].lower()):
|
|
239
|
+
results.append(interaction)
|
|
240
|
+
|
|
241
|
+
return results
|
|
242
|
+
|
|
243
|
+
def get_user_profile(self, user_id: str) -> Optional[Dict]:
|
|
244
|
+
"""
|
|
245
|
+
Get user profile (for JSON version)
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
user_id: User ID
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
User profile or None
|
|
252
|
+
"""
|
|
253
|
+
if user_id not in self.user_profiles:
|
|
254
|
+
self.load_memory(user_id)
|
|
255
|
+
|
|
256
|
+
return self.user_profiles.get(user_id)
|
|
257
|
+
|