mem-llm 2.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.
- mem_llm/__init__.py +98 -0
- mem_llm/api_server.py +595 -0
- mem_llm/base_llm_client.py +201 -0
- mem_llm/builtin_tools.py +311 -0
- mem_llm/cli.py +254 -0
- mem_llm/clients/__init__.py +22 -0
- mem_llm/clients/lmstudio_client.py +393 -0
- mem_llm/clients/ollama_client.py +354 -0
- mem_llm/config.yaml.example +52 -0
- mem_llm/config_from_docs.py +180 -0
- mem_llm/config_manager.py +231 -0
- mem_llm/conversation_summarizer.py +372 -0
- mem_llm/data_export_import.py +640 -0
- mem_llm/dynamic_prompt.py +298 -0
- mem_llm/knowledge_loader.py +88 -0
- mem_llm/llm_client.py +225 -0
- mem_llm/llm_client_factory.py +260 -0
- mem_llm/logger.py +129 -0
- mem_llm/mem_agent.py +1611 -0
- mem_llm/memory_db.py +612 -0
- mem_llm/memory_manager.py +321 -0
- mem_llm/memory_tools.py +253 -0
- mem_llm/prompt_security.py +304 -0
- mem_llm/response_metrics.py +221 -0
- mem_llm/retry_handler.py +193 -0
- mem_llm/thread_safe_db.py +301 -0
- mem_llm/tool_system.py +429 -0
- mem_llm/vector_store.py +278 -0
- mem_llm/web_launcher.py +129 -0
- mem_llm/web_ui/README.md +44 -0
- mem_llm/web_ui/__init__.py +7 -0
- mem_llm/web_ui/index.html +641 -0
- mem_llm/web_ui/memory.html +569 -0
- mem_llm/web_ui/metrics.html +75 -0
- mem_llm-2.0.0.dist-info/METADATA +667 -0
- mem_llm-2.0.0.dist-info/RECORD +39 -0
- mem_llm-2.0.0.dist-info/WHEEL +5 -0
- mem_llm-2.0.0.dist-info/entry_points.txt +3 -0
- mem_llm-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,321 @@
|
|
|
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
|
+
profile = data.get('profile', {})
|
|
47
|
+
|
|
48
|
+
# Parse preferences if it's a JSON string (legacy format)
|
|
49
|
+
if isinstance(profile.get('preferences'), str):
|
|
50
|
+
try:
|
|
51
|
+
profile['preferences'] = json.loads(profile['preferences'])
|
|
52
|
+
except:
|
|
53
|
+
profile['preferences'] = {}
|
|
54
|
+
|
|
55
|
+
self.user_profiles[user_id] = profile
|
|
56
|
+
return data
|
|
57
|
+
else:
|
|
58
|
+
# Create empty memory for new user
|
|
59
|
+
self.conversations[user_id] = []
|
|
60
|
+
self.user_profiles[user_id] = {
|
|
61
|
+
'user_id': user_id,
|
|
62
|
+
'first_seen': datetime.now().isoformat(),
|
|
63
|
+
'preferences': {},
|
|
64
|
+
'summary': {}
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
'conversations': [],
|
|
68
|
+
'profile': self.user_profiles[user_id]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
def save_memory(self, user_id: str) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Save user's memory to disk
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
user_id: User ID
|
|
77
|
+
"""
|
|
78
|
+
user_file = self._get_user_file(user_id)
|
|
79
|
+
|
|
80
|
+
data = {
|
|
81
|
+
'conversations': self.conversations.get(user_id, []),
|
|
82
|
+
'profile': self.user_profiles.get(user_id, {}),
|
|
83
|
+
'last_updated': datetime.now().isoformat()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
with open(user_file, 'w', encoding='utf-8') as f:
|
|
87
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
88
|
+
|
|
89
|
+
def add_interaction(self, user_id: str, user_message: str,
|
|
90
|
+
bot_response: str, metadata: Optional[Dict] = None) -> None:
|
|
91
|
+
"""
|
|
92
|
+
Record a new interaction
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
user_id: User ID
|
|
96
|
+
user_message: User's message
|
|
97
|
+
bot_response: Bot's response
|
|
98
|
+
metadata: Additional information (order no, issue type, etc.)
|
|
99
|
+
"""
|
|
100
|
+
if user_id not in self.conversations:
|
|
101
|
+
self.load_memory(user_id)
|
|
102
|
+
|
|
103
|
+
interaction = {
|
|
104
|
+
'timestamp': datetime.now().isoformat(),
|
|
105
|
+
'user_message': user_message,
|
|
106
|
+
'bot_response': bot_response,
|
|
107
|
+
'metadata': metadata or {}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
self.conversations[user_id].append(interaction)
|
|
111
|
+
self.save_memory(user_id)
|
|
112
|
+
|
|
113
|
+
# Alias for compatibility
|
|
114
|
+
def add_conversation(self, user_id: str, user_message: str, bot_response: str, metadata: Optional[Dict] = None) -> None:
|
|
115
|
+
"""Alias for add_interaction"""
|
|
116
|
+
return self.add_interaction(user_id, user_message, bot_response, metadata)
|
|
117
|
+
|
|
118
|
+
def update_profile(self, user_id: str, updates: Dict) -> None:
|
|
119
|
+
"""
|
|
120
|
+
Update user profile
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
user_id: User ID
|
|
124
|
+
updates: Information to update
|
|
125
|
+
"""
|
|
126
|
+
if user_id not in self.user_profiles:
|
|
127
|
+
self.load_memory(user_id)
|
|
128
|
+
|
|
129
|
+
self.user_profiles[user_id].update(updates)
|
|
130
|
+
self.save_memory(user_id)
|
|
131
|
+
|
|
132
|
+
def get_recent_conversations(self, user_id: str, limit: int = 5) -> List[Dict]:
|
|
133
|
+
"""
|
|
134
|
+
Get last N conversations
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
user_id: User ID
|
|
138
|
+
limit: Number of conversations to retrieve
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
List of recent conversations
|
|
142
|
+
"""
|
|
143
|
+
if user_id not in self.conversations:
|
|
144
|
+
self.load_memory(user_id)
|
|
145
|
+
|
|
146
|
+
return self.conversations[user_id][-limit:]
|
|
147
|
+
|
|
148
|
+
def search_memory(self, user_id: str, keyword: str) -> List[Dict]:
|
|
149
|
+
"""
|
|
150
|
+
Search for keyword in memory
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
user_id: User ID
|
|
154
|
+
keyword: Word to search for
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Matching interactions
|
|
158
|
+
"""
|
|
159
|
+
if user_id not in self.conversations:
|
|
160
|
+
self.load_memory(user_id)
|
|
161
|
+
|
|
162
|
+
results = []
|
|
163
|
+
keyword_lower = keyword.lower()
|
|
164
|
+
|
|
165
|
+
for interaction in self.conversations[user_id]:
|
|
166
|
+
if (keyword_lower in interaction['user_message'].lower() or
|
|
167
|
+
keyword_lower in interaction['bot_response'].lower() or
|
|
168
|
+
keyword_lower in str(interaction.get('metadata', {})).lower()):
|
|
169
|
+
results.append(interaction)
|
|
170
|
+
|
|
171
|
+
return results
|
|
172
|
+
|
|
173
|
+
def get_summary(self, user_id: str) -> str:
|
|
174
|
+
"""
|
|
175
|
+
Create summary of user's past interactions
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
user_id: User ID
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Summary text
|
|
182
|
+
"""
|
|
183
|
+
if user_id not in self.conversations:
|
|
184
|
+
self.load_memory(user_id)
|
|
185
|
+
|
|
186
|
+
profile = self.user_profiles.get(user_id, {})
|
|
187
|
+
conversations = self.conversations.get(user_id, [])
|
|
188
|
+
|
|
189
|
+
if not conversations:
|
|
190
|
+
return "No interactions with this user yet."
|
|
191
|
+
|
|
192
|
+
summary_parts = [
|
|
193
|
+
f"User ID: {user_id}",
|
|
194
|
+
f"First conversation: {profile.get('first_seen', 'Unknown')}",
|
|
195
|
+
f"Total interactions: {len(conversations)}",
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
# Add last 3 interactions
|
|
199
|
+
if conversations:
|
|
200
|
+
summary_parts.append("\nRecent interactions:")
|
|
201
|
+
for i, conv in enumerate(conversations[-3:], 1):
|
|
202
|
+
timestamp = conv.get('timestamp', 'Unknown')
|
|
203
|
+
user_msg = conv.get('user_message', '')[:50]
|
|
204
|
+
summary_parts.append(f"{i}. {timestamp}: {user_msg}...")
|
|
205
|
+
|
|
206
|
+
# Metadata summary
|
|
207
|
+
all_metadata = [c.get('metadata', {}) for c in conversations if c.get('metadata')]
|
|
208
|
+
if all_metadata:
|
|
209
|
+
summary_parts.append("\nSaved information:")
|
|
210
|
+
# Example: order numbers, issues, etc.
|
|
211
|
+
for meta in all_metadata[-3:]:
|
|
212
|
+
for key, value in meta.items():
|
|
213
|
+
summary_parts.append(f" - {key}: {value}")
|
|
214
|
+
|
|
215
|
+
return "\n".join(summary_parts)
|
|
216
|
+
|
|
217
|
+
def clear_memory(self, user_id: str) -> None:
|
|
218
|
+
"""
|
|
219
|
+
Completely delete user's memory
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
user_id: User ID
|
|
223
|
+
"""
|
|
224
|
+
user_file = self._get_user_file(user_id)
|
|
225
|
+
if user_file.exists():
|
|
226
|
+
user_file.unlink()
|
|
227
|
+
|
|
228
|
+
if user_id in self.conversations:
|
|
229
|
+
del self.conversations[user_id]
|
|
230
|
+
if user_id in self.user_profiles:
|
|
231
|
+
del self.user_profiles[user_id]
|
|
232
|
+
|
|
233
|
+
def search_conversations(self, user_id: str, keyword: str) -> List[Dict]:
|
|
234
|
+
"""
|
|
235
|
+
Search for keyword in conversations (for JSON version)
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
user_id: User ID
|
|
239
|
+
keyword: Word to search for
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
Matching conversations
|
|
243
|
+
"""
|
|
244
|
+
if user_id not in self.conversations:
|
|
245
|
+
self.load_memory(user_id)
|
|
246
|
+
|
|
247
|
+
results = []
|
|
248
|
+
keyword_lower = keyword.lower()
|
|
249
|
+
|
|
250
|
+
for interaction in self.conversations.get(user_id, []):
|
|
251
|
+
if (keyword_lower in interaction['user_message'].lower() or
|
|
252
|
+
keyword_lower in interaction['bot_response'].lower()):
|
|
253
|
+
results.append(interaction)
|
|
254
|
+
|
|
255
|
+
return results
|
|
256
|
+
|
|
257
|
+
def get_user_profile(self, user_id: str) -> Optional[Dict]:
|
|
258
|
+
"""
|
|
259
|
+
Get user profile (for JSON version)
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
user_id: User ID
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
User profile or None
|
|
266
|
+
"""
|
|
267
|
+
if user_id not in self.user_profiles:
|
|
268
|
+
self.load_memory(user_id)
|
|
269
|
+
|
|
270
|
+
return self.user_profiles.get(user_id)
|
|
271
|
+
|
|
272
|
+
def update_user_profile(self, user_id: str, updates: Dict) -> None:
|
|
273
|
+
"""
|
|
274
|
+
Update user profile (SQL-compatible alias)
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
user_id: User ID
|
|
278
|
+
updates: Fields to update
|
|
279
|
+
"""
|
|
280
|
+
return self.update_profile(user_id, updates)
|
|
281
|
+
|
|
282
|
+
def add_user(self, user_id: str, name: Optional[str] = None, metadata: Optional[Dict] = None) -> None:
|
|
283
|
+
"""
|
|
284
|
+
Add or update user (SQL-compatible method)
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
user_id: User ID
|
|
288
|
+
name: User name (optional)
|
|
289
|
+
metadata: Additional metadata (optional)
|
|
290
|
+
"""
|
|
291
|
+
self.load_memory(user_id)
|
|
292
|
+
if name and 'name' not in self.user_profiles[user_id]:
|
|
293
|
+
self.user_profiles[user_id]['name'] = name
|
|
294
|
+
if metadata:
|
|
295
|
+
self.user_profiles[user_id].update(metadata)
|
|
296
|
+
self.save_memory(user_id)
|
|
297
|
+
|
|
298
|
+
def get_statistics(self) -> Dict:
|
|
299
|
+
"""
|
|
300
|
+
Get general statistics (SQL-compatible method)
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
Statistics dictionary
|
|
304
|
+
"""
|
|
305
|
+
all_users = list(self.memory_dir.glob("*.json"))
|
|
306
|
+
total_interactions = 0
|
|
307
|
+
|
|
308
|
+
for user_file in all_users:
|
|
309
|
+
try:
|
|
310
|
+
with open(user_file, 'r', encoding='utf-8') as f:
|
|
311
|
+
data = json.load(f)
|
|
312
|
+
total_interactions += len(data.get('conversations', []))
|
|
313
|
+
except:
|
|
314
|
+
pass
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
'total_users': len(all_users),
|
|
318
|
+
'total_interactions': total_interactions,
|
|
319
|
+
'knowledge_base_entries': 0 # JSON doesn't have KB
|
|
320
|
+
}
|
|
321
|
+
|
mem_llm/memory_tools.py
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""
|
|
2
|
+
User Tools System
|
|
3
|
+
Tools for users to manage their memory data
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Dict, List, Optional, Any
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MemoryTools:
|
|
13
|
+
"""User memory management tools"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, memory_manager):
|
|
16
|
+
"""
|
|
17
|
+
Args:
|
|
18
|
+
memory_manager: Memory manager (MemoryManager or SQLMemoryManager)
|
|
19
|
+
"""
|
|
20
|
+
self.memory = memory_manager
|
|
21
|
+
self.tools = {
|
|
22
|
+
"list_memories": {
|
|
23
|
+
"description": "Lists all user conversations",
|
|
24
|
+
"parameters": {
|
|
25
|
+
"user_id": "User ID",
|
|
26
|
+
"limit": "Number of conversations to show (default: 10)"
|
|
27
|
+
},
|
|
28
|
+
"function": self._list_memories
|
|
29
|
+
},
|
|
30
|
+
"search_memories": {
|
|
31
|
+
"description": "Search for keywords in conversations",
|
|
32
|
+
"parameters": {
|
|
33
|
+
"user_id": "User ID",
|
|
34
|
+
"keyword": "Keyword to search",
|
|
35
|
+
"limit": "Number of results to show (default: 5)"
|
|
36
|
+
},
|
|
37
|
+
"function": self._search_memories
|
|
38
|
+
},
|
|
39
|
+
"show_user_info": {
|
|
40
|
+
"description": "Show information about user",
|
|
41
|
+
"parameters": {
|
|
42
|
+
"user_id": "User ID"
|
|
43
|
+
},
|
|
44
|
+
"function": self._show_user_info
|
|
45
|
+
},
|
|
46
|
+
"export_memories": {
|
|
47
|
+
"description": "Export user data",
|
|
48
|
+
"parameters": {
|
|
49
|
+
"user_id": "User ID",
|
|
50
|
+
"format": "Format (json or txt)"
|
|
51
|
+
},
|
|
52
|
+
"function": self._export_memories
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
def _list_memories(self, user_id: str, limit: int = 10) -> str:
|
|
57
|
+
"""List user conversations"""
|
|
58
|
+
try:
|
|
59
|
+
conversations = self.memory.get_recent_conversations(user_id, limit)
|
|
60
|
+
|
|
61
|
+
if not conversations:
|
|
62
|
+
return f"❌ No conversations found for user {user_id}."
|
|
63
|
+
|
|
64
|
+
result = f"📝 Last {len(conversations)} conversations for user {user_id}:\n\n"
|
|
65
|
+
|
|
66
|
+
for i, conv in enumerate(conversations, 1):
|
|
67
|
+
timestamp = conv.get('timestamp', 'Unknown')
|
|
68
|
+
user_msg = conv.get('user_message', '')[:100]
|
|
69
|
+
bot_response = conv.get('bot_response', '')[:100]
|
|
70
|
+
|
|
71
|
+
result += f"{i}. [{timestamp}]\n"
|
|
72
|
+
result += f" 👤 User: {user_msg}...\n"
|
|
73
|
+
result += f" 🤖 Bot: {bot_response}...\n\n"
|
|
74
|
+
|
|
75
|
+
return result
|
|
76
|
+
|
|
77
|
+
except Exception as e:
|
|
78
|
+
return f"❌ Error: {str(e)}"
|
|
79
|
+
|
|
80
|
+
def _search_memories(self, user_id: str, keyword: str, limit: int = 5) -> str:
|
|
81
|
+
"""Search in conversations"""
|
|
82
|
+
try:
|
|
83
|
+
results = self.memory.search_conversations(user_id, keyword)
|
|
84
|
+
|
|
85
|
+
if not results:
|
|
86
|
+
return f"❌ No results found for keyword '{keyword}' for user {user_id}."
|
|
87
|
+
|
|
88
|
+
result = f"🔍 {len(results)} results found for keyword '{keyword}':\n\n"
|
|
89
|
+
|
|
90
|
+
for i, conv in enumerate(results[:limit], 1):
|
|
91
|
+
timestamp = conv.get('timestamp', 'Unknown')
|
|
92
|
+
user_msg = conv.get('user_message', '')
|
|
93
|
+
bot_response = conv.get('bot_response', '')
|
|
94
|
+
|
|
95
|
+
result += f"{i}. [{timestamp}]\n"
|
|
96
|
+
result += f" 👤 User: {user_msg}\n"
|
|
97
|
+
result += f" 🤖 Bot: {bot_response}\n\n"
|
|
98
|
+
|
|
99
|
+
if len(results) > limit:
|
|
100
|
+
result += f"... and {len(results) - limit} more results."
|
|
101
|
+
|
|
102
|
+
return result
|
|
103
|
+
|
|
104
|
+
except Exception as e:
|
|
105
|
+
return f"❌ Search error: {str(e)}"
|
|
106
|
+
|
|
107
|
+
def _show_user_info(self, user_id: str) -> str:
|
|
108
|
+
"""Show user information"""
|
|
109
|
+
try:
|
|
110
|
+
profile = self.memory.get_user_profile(user_id)
|
|
111
|
+
|
|
112
|
+
if not profile:
|
|
113
|
+
return f"❌ User {user_id} not found."
|
|
114
|
+
|
|
115
|
+
result = f"👤 User information for {user_id}:\n\n"
|
|
116
|
+
|
|
117
|
+
if profile.get('name'):
|
|
118
|
+
result += f"Name: {profile['name']}\n"
|
|
119
|
+
|
|
120
|
+
if profile.get('first_seen'):
|
|
121
|
+
result += f"First conversation: {profile['first_seen']}\n"
|
|
122
|
+
|
|
123
|
+
return result
|
|
124
|
+
|
|
125
|
+
except Exception as e:
|
|
126
|
+
return f"❌ Information retrieval error: {str(e)}"
|
|
127
|
+
|
|
128
|
+
def _export_memories(self, user_id: str, format: str = "json") -> str:
|
|
129
|
+
"""Export user data"""
|
|
130
|
+
try:
|
|
131
|
+
if format == "json":
|
|
132
|
+
profile = self.memory.get_user_profile(user_id)
|
|
133
|
+
conversations = self.memory.get_recent_conversations(user_id, 1000)
|
|
134
|
+
|
|
135
|
+
export_data = {
|
|
136
|
+
"user_id": user_id,
|
|
137
|
+
"export_date": datetime.now().isoformat(),
|
|
138
|
+
"profile": profile,
|
|
139
|
+
"conversations": conversations
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return json.dumps(export_data, ensure_ascii=False, indent=2)
|
|
143
|
+
|
|
144
|
+
elif format == "txt":
|
|
145
|
+
conversations = self.memory.get_recent_conversations(user_id, 1000)
|
|
146
|
+
|
|
147
|
+
result = f"Conversation history for user {user_id}\n"
|
|
148
|
+
result += f"Export date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
|
|
149
|
+
result += "=" * 60 + "\n\n"
|
|
150
|
+
|
|
151
|
+
for i, conv in enumerate(conversations, 1):
|
|
152
|
+
result += f"Conversation {i}:\n"
|
|
153
|
+
result += f"Date: {conv.get('timestamp', 'Unknown')}\n"
|
|
154
|
+
result += f"User: {conv.get('user_message', '')}\n"
|
|
155
|
+
result += f"Bot: {conv.get('bot_response', '')}\n"
|
|
156
|
+
result += "-" * 40 + "\n"
|
|
157
|
+
|
|
158
|
+
return result
|
|
159
|
+
|
|
160
|
+
else:
|
|
161
|
+
return "❌ Unsupported format. Use json or txt."
|
|
162
|
+
|
|
163
|
+
except Exception as e:
|
|
164
|
+
return f"❌ Export error: {str(e)}"
|
|
165
|
+
|
|
166
|
+
def execute_tool(self, tool_name: str, parameters: Dict[str, Any]) -> str:
|
|
167
|
+
"""Execute the specified tool"""
|
|
168
|
+
if tool_name not in self.tools:
|
|
169
|
+
return f"❌ Tool '{tool_name}' not found."
|
|
170
|
+
|
|
171
|
+
tool = self.tools[tool_name]
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
if "user_id" in parameters:
|
|
175
|
+
result = tool["function"](**parameters)
|
|
176
|
+
else:
|
|
177
|
+
return "❌ user_id parameter required."
|
|
178
|
+
|
|
179
|
+
return result
|
|
180
|
+
|
|
181
|
+
except Exception as e:
|
|
182
|
+
return f"❌ Tool execution error: {str(e)}"
|
|
183
|
+
|
|
184
|
+
def list_available_tools(self) -> str:
|
|
185
|
+
"""List available tools"""
|
|
186
|
+
result = "🛠️ Available Tools:\n\n"
|
|
187
|
+
|
|
188
|
+
for name, tool in self.tools.items():
|
|
189
|
+
result += f"🔧 {name}\n"
|
|
190
|
+
result += f" Description: {tool['description']}\n"
|
|
191
|
+
result += " Parameters:\n"
|
|
192
|
+
|
|
193
|
+
for param, desc in tool['parameters'].items():
|
|
194
|
+
result += f" • {param}: {desc}\n"
|
|
195
|
+
|
|
196
|
+
result += "\n"
|
|
197
|
+
|
|
198
|
+
return result
|
|
199
|
+
|
|
200
|
+
def parse_user_command(self, user_message: str) -> tuple:
|
|
201
|
+
"""Extract tool call from user message"""
|
|
202
|
+
patterns = {
|
|
203
|
+
"list_memories": [
|
|
204
|
+
r"show.*my.*past.*conversations",
|
|
205
|
+
r"list.*my.*conversations",
|
|
206
|
+
],
|
|
207
|
+
"show_user_info": [
|
|
208
|
+
r"what.*do.*you.*know.*about.*me",
|
|
209
|
+
r"show.*my.*profile"
|
|
210
|
+
],
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
message_lower = user_message.lower()
|
|
214
|
+
|
|
215
|
+
for tool_name, pattern_list in patterns.items():
|
|
216
|
+
for pattern in pattern_list:
|
|
217
|
+
match = re.search(pattern, message_lower)
|
|
218
|
+
if match:
|
|
219
|
+
parameters = {"user_id": "current_user"}
|
|
220
|
+
return tool_name, parameters
|
|
221
|
+
|
|
222
|
+
return None, None
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class ToolExecutor:
|
|
226
|
+
"""Tool executor"""
|
|
227
|
+
|
|
228
|
+
def __init__(self, memory_manager, current_user_id: str = None):
|
|
229
|
+
"""
|
|
230
|
+
Args:
|
|
231
|
+
memory_manager: Memory manager
|
|
232
|
+
current_user_id: Current user ID
|
|
233
|
+
"""
|
|
234
|
+
self.memory_tools = MemoryTools(memory_manager)
|
|
235
|
+
self.current_user_id = current_user_id
|
|
236
|
+
|
|
237
|
+
def execute_user_command(self, user_message: str, user_id: str = None) -> str:
|
|
238
|
+
"""Detect and execute tool call from user message"""
|
|
239
|
+
uid = user_id or self.current_user_id
|
|
240
|
+
|
|
241
|
+
tool_name, parameters = self.memory_tools.parse_user_command(user_message)
|
|
242
|
+
|
|
243
|
+
if tool_name and uid:
|
|
244
|
+
parameters["user_id"] = uid
|
|
245
|
+
return self.memory_tools.execute_tool(tool_name, parameters)
|
|
246
|
+
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
def is_tool_command(self, user_message: str) -> bool:
|
|
250
|
+
"""Check if message is a tool command"""
|
|
251
|
+
tool_name, _ = self.memory_tools.parse_user_command(user_message)
|
|
252
|
+
return tool_name is not None
|
|
253
|
+
|