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.
@@ -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
+
@@ -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
+