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.

@@ -0,0 +1,512 @@
1
+ """
2
+ Mem-Agent: Unified Powerful System
3
+ ==================================
4
+
5
+ A powerful Mem-Agent that combines all features in a single system.
6
+
7
+ Features:
8
+ - ✅ SQL and JSON memory support
9
+ - ✅ Prompt templates system
10
+ - ✅ Knowledge base integration
11
+ - ✅ User tools system
12
+ - ✅ Configuration management
13
+ - ✅ Advanced logging
14
+ - ✅ Production-ready structure
15
+
16
+ Usage:
17
+ ```python
18
+ from memory_llm import MemAgent
19
+
20
+ # Simple usage
21
+ agent = MemAgent()
22
+
23
+ # Advanced usage
24
+ agent = MemAgent(
25
+ config_file="config.yaml",
26
+ use_sql=True,
27
+ load_knowledge_base=True
28
+ )
29
+ ```
30
+ """
31
+
32
+ from typing import Optional, Dict, List, Any, Union
33
+ from datetime import datetime
34
+ import logging
35
+ import json
36
+ import os
37
+
38
+ # Core dependencies
39
+ from .memory_manager import MemoryManager
40
+ from .llm_client import OllamaClient
41
+
42
+ # Advanced features (optional)
43
+ try:
44
+ from .memory_db import SQLMemoryManager
45
+ from .prompt_templates import prompt_manager
46
+ from .knowledge_loader import KnowledgeLoader
47
+ from .config_manager import get_config
48
+ from .memory_tools import ToolExecutor, MemoryTools
49
+ ADVANCED_AVAILABLE = True
50
+ except ImportError:
51
+ ADVANCED_AVAILABLE = False
52
+ print("⚠️ Advanced features not available (install additional packages)")
53
+
54
+
55
+ class MemAgent:
56
+ """
57
+ Powerful and unified Mem-Agent system
58
+
59
+ Production-ready assistant that combines all features in one place.
60
+ """
61
+
62
+ def __init__(self,
63
+ model: str = "granite4:tiny-h",
64
+ config_file: Optional[str] = None,
65
+ use_sql: bool = True,
66
+ memory_dir: Optional[str] = None,
67
+ load_knowledge_base: bool = True,
68
+ ollama_url: str = "http://localhost:11434"):
69
+ """
70
+ Args:
71
+ model: LLM model to use
72
+ config_file: Configuration file (optional)
73
+ use_sql: Use SQL database (True) or JSON (False)
74
+ memory_dir: Memory directory
75
+ load_knowledge_base: Automatically load knowledge base
76
+ ollama_url: Ollama API URL
77
+ """
78
+
79
+ # Load configuration
80
+ self.config = None
81
+ if ADVANCED_AVAILABLE and config_file:
82
+ try:
83
+ self.config = get_config(config_file)
84
+ except Exception:
85
+ print("⚠️ Config file could not be loaded, using default settings")
86
+
87
+ # Determine usage mode
88
+ self.usage_mode = "business" # default
89
+ if self.config:
90
+ self.usage_mode = self.config.get("usage_mode", "business")
91
+ elif config_file:
92
+ # Config file exists but couldn't be loaded
93
+ self.usage_mode = "business"
94
+ else:
95
+ # No config file
96
+ self.usage_mode = "personal"
97
+
98
+ # Setup logging
99
+ self._setup_logging()
100
+
101
+ # Memory system selection
102
+ if use_sql and ADVANCED_AVAILABLE:
103
+ # SQL memory (advanced)
104
+ db_path = memory_dir or self.config.get("memory.db_path", "memories.db") if self.config else "memories.db"
105
+ self.memory = SQLMemoryManager(db_path)
106
+ self.logger.info(f"SQL memory system active: {db_path}")
107
+ else:
108
+ # JSON memory (simple)
109
+ json_dir = memory_dir or self.config.get("memory.json_dir", "memories") if self.config else "memories"
110
+ self.memory = MemoryManager(json_dir)
111
+ self.logger.info(f"JSON memory system active: {json_dir}")
112
+
113
+ # LLM client
114
+ self.llm = OllamaClient(model, ollama_url)
115
+ self.logger.info(f"LLM client ready: {model}")
116
+
117
+ # Advanced features (if available)
118
+ if ADVANCED_AVAILABLE:
119
+ self._setup_advanced_features(load_knowledge_base)
120
+ else:
121
+ print("⚠️ Load additional packages for advanced features")
122
+
123
+ # Active user and system prompt
124
+ self.current_user: Optional[str] = None
125
+ self.current_system_prompt: Optional[str] = None
126
+
127
+ # Tool system (always available)
128
+ self.tool_executor = ToolExecutor(self.memory)
129
+
130
+ self.logger.info("MemAgent successfully initialized")
131
+
132
+ # === UNIFIED SYSTEM METHODS ===
133
+
134
+ def _setup_logging(self) -> None:
135
+ """Setup logging system"""
136
+ log_config = {}
137
+ if ADVANCED_AVAILABLE and hasattr(self, 'config') and self.config:
138
+ log_config = self.config.get("logging", {})
139
+
140
+ if log_config.get("enabled", True):
141
+ logging.basicConfig(
142
+ level=getattr(logging, log_config.get("level", "INFO")),
143
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
144
+ handlers=[
145
+ logging.FileHandler(log_config.get("file", "mem_agent.log")),
146
+ logging.StreamHandler()
147
+ ]
148
+ )
149
+
150
+ self.logger = logging.getLogger("MemAgent")
151
+
152
+ def _setup_advanced_features(self, load_knowledge_base: bool) -> None:
153
+ """Setup advanced features"""
154
+ # Load knowledge base (according to usage mode)
155
+ if load_knowledge_base:
156
+ kb_loader = KnowledgeLoader(self.memory)
157
+
158
+ # Get KB settings from config
159
+ if hasattr(self, 'config') and self.config:
160
+ kb_config = self.config.get("knowledge_base", {})
161
+
162
+ # Select default KB according to usage mode
163
+ if self.usage_mode == "business":
164
+ default_kb = kb_config.get("default_kb", "business_tech_support")
165
+ else: # personal
166
+ default_kb = kb_config.get("default_kb", "personal_learning")
167
+
168
+ try:
169
+ if default_kb == "ecommerce":
170
+ count = kb_loader.load_default_ecommerce_kb()
171
+ self.logger.info(f"E-commerce knowledge base loaded: {count} records")
172
+ elif default_kb == "tech_support":
173
+ count = kb_loader.load_default_tech_support_kb()
174
+ self.logger.info(f"Technical support knowledge base loaded: {count} records")
175
+ elif default_kb == "business_tech_support":
176
+ count = kb_loader.load_default_tech_support_kb()
177
+ self.logger.info(f"Corporate technical support knowledge base loaded: {count} records")
178
+ elif default_kb == "personal_learning":
179
+ # Simple KB for personal learning
180
+ count = kb_loader.load_default_ecommerce_kb() # Temporarily use the same KB
181
+ self.logger.info(f"Personal learning knowledge base loaded: {count} records")
182
+ except Exception as e:
183
+ self.logger.error(f"Knowledge base loading error: {e}")
184
+
185
+ # Load system prompt (according to usage mode)
186
+ if hasattr(self, 'config') and self.config:
187
+ prompt_config = self.config.get("prompt", {})
188
+
189
+ # Select default template according to usage mode
190
+ if self.usage_mode == "business":
191
+ default_template = "business_customer_service"
192
+ else: # personal
193
+ default_template = "personal_assistant"
194
+
195
+ template_name = prompt_config.get("template", default_template)
196
+ variables = prompt_config.get("variables", {})
197
+
198
+ # Additional variables for business mode
199
+ if self.usage_mode == "business":
200
+ business_config = self.config.get("business", {})
201
+ variables.update({
202
+ "company_name": business_config.get("company_name", "Our Company"),
203
+ "founded_year": business_config.get("founded_year", "2010"),
204
+ "employee_count": business_config.get("employee_count", "100+"),
205
+ "industry": business_config.get("industry", "Teknoloji")
206
+ })
207
+ else: # personal
208
+ personal_config = self.config.get("personal", {})
209
+ variables.update({
210
+ "user_name": personal_config.get("user_name", "User"),
211
+ "timezone": personal_config.get("timezone", "Europe/London")
212
+ })
213
+
214
+ try:
215
+ variables['current_date'] = datetime.now().strftime("%Y-%m-%d")
216
+ self.current_system_prompt = prompt_manager.render_prompt(template_name, **variables)
217
+ self.logger.info(f"Prompt template loaded: {template_name} (Mode: {self.usage_mode})")
218
+ except Exception as e:
219
+ self.logger.error(f"Prompt template loading error: {e}")
220
+ self.current_system_prompt = f"You are a helpful assistant in {self.usage_mode} mode."
221
+
222
+ def check_setup(self) -> Dict[str, Any]:
223
+ """Check system setup"""
224
+ ollama_running = self.llm.check_connection()
225
+ models = self.llm.list_models()
226
+ model_exists = self.llm.model in models
227
+
228
+ # Memory statistics
229
+ try:
230
+ if hasattr(self.memory, 'get_statistics'):
231
+ stats = self.memory.get_statistics()
232
+ else:
233
+ # Simple statistics for JSON memory
234
+ stats = {
235
+ "total_users": 0,
236
+ "total_interactions": 0,
237
+ "knowledge_base_entries": 0
238
+ }
239
+ except Exception:
240
+ stats = {
241
+ "total_users": 0,
242
+ "total_interactions": 0,
243
+ "knowledge_base_entries": 0
244
+ }
245
+
246
+ return {
247
+ "ollama_running": ollama_running,
248
+ "available_models": models,
249
+ "target_model": self.llm.model,
250
+ "model_ready": model_exists,
251
+ "memory_backend": "SQL" if ADVANCED_AVAILABLE and isinstance(self.memory, SQLMemoryManager) else "JSON",
252
+ "total_users": stats.get('total_users', 0),
253
+ "total_interactions": stats.get('total_interactions', 0),
254
+ "kb_entries": stats.get('knowledge_base_entries', 0),
255
+ "status": "ready" if (ollama_running and model_exists) else "not_ready"
256
+ }
257
+
258
+ def set_user(self, user_id: str, name: Optional[str] = None) -> None:
259
+ """
260
+ Set active user
261
+
262
+ Args:
263
+ user_id: User ID
264
+ name: User name (optional)
265
+ """
266
+ self.current_user = user_id
267
+
268
+ # Add user for SQL memory
269
+ if ADVANCED_AVAILABLE and isinstance(self.memory, SQLMemoryManager):
270
+ self.memory.add_user(user_id, name)
271
+
272
+ # Update user name (if provided)
273
+ if name:
274
+ if hasattr(self.memory, 'update_user_profile'):
275
+ self.memory.update_user_profile(user_id, {"name": name})
276
+
277
+ self.logger.debug(f"Active user set: {user_id}")
278
+
279
+ def chat(self, message: str, user_id: Optional[str] = None,
280
+ metadata: Optional[Dict] = None) -> str:
281
+ """
282
+ Chat with user
283
+
284
+ Args:
285
+ message: User's message
286
+ user_id: User ID (optional)
287
+ metadata: Additional information
288
+
289
+ Returns:
290
+ Bot's response
291
+ """
292
+ # Determine user
293
+ if user_id:
294
+ self.set_user(user_id)
295
+ elif not self.current_user:
296
+ return "Error: User ID not specified."
297
+
298
+ user_id = self.current_user
299
+
300
+ # Check tool commands first
301
+ tool_result = self.tool_executor.execute_user_command(message, user_id)
302
+ if tool_result:
303
+ return tool_result
304
+
305
+ # Knowledge base search (if using SQL)
306
+ kb_context = ""
307
+ if ADVANCED_AVAILABLE and isinstance(self.memory, SQLMemoryManager) and hasattr(self, 'config') and self.config:
308
+ if self.config.get("response.use_knowledge_base", True):
309
+ try:
310
+ kb_results = self.memory.search_knowledge(
311
+ query=message,
312
+ limit=self.config.get("knowledge_base.search_limit", 5)
313
+ )
314
+
315
+ if kb_results:
316
+ kb_context = "\n\nRelevant Information:\n"
317
+ for i, result in enumerate(kb_results, 1):
318
+ kb_context += f"{i}. S: {result['question']}\n C: {result['answer']}\n"
319
+ except Exception as e:
320
+ self.logger.error(f"Knowledge base search error: {e}")
321
+
322
+ # Get conversation history
323
+ messages = []
324
+ if self.current_system_prompt:
325
+ messages.append({"role": "system", "content": self.current_system_prompt})
326
+
327
+ # Add memory history
328
+ try:
329
+ if hasattr(self.memory, 'get_recent_conversations'):
330
+ recent_limit = self.config.get("response.recent_conversations_limit", 5) if hasattr(self, 'config') and self.config else 5
331
+ recent_convs = self.memory.get_recent_conversations(user_id, recent_limit)
332
+
333
+ for conv in reversed(recent_convs):
334
+ messages.append({"role": "user", "content": conv.get('user_message', '')})
335
+ messages.append({"role": "assistant", "content": conv.get('bot_response', '')})
336
+ except Exception as e:
337
+ self.logger.error(f"Memory history loading error: {e}")
338
+
339
+ # Add knowledge base context
340
+ if kb_context:
341
+ messages.append({
342
+ "role": "system",
343
+ "content": f"You can use this information when answering the user's question:{kb_context}"
344
+ })
345
+
346
+ # Add current message
347
+ messages.append({"role": "user", "content": message})
348
+
349
+ # Get response from LLM
350
+ try:
351
+ response = self.llm.chat(
352
+ messages=messages,
353
+ temperature=self.config.get("llm.temperature", 0.7) if hasattr(self, 'config') and self.config else 0.7,
354
+ max_tokens=self.config.get("llm.max_tokens", 500) if hasattr(self, 'config') and self.config else 500
355
+ )
356
+ except Exception as e:
357
+ self.logger.error(f"LLM response error: {e}")
358
+ response = "Sorry, I cannot respond right now. Please try again later."
359
+
360
+ # Save interaction
361
+ try:
362
+ if hasattr(self.memory, 'add_interaction'):
363
+ self.memory.add_interaction(
364
+ user_id=user_id,
365
+ user_message=message,
366
+ bot_response=response,
367
+ metadata=metadata
368
+ )
369
+ except Exception as e:
370
+ self.logger.error(f"Interaction saving error: {e}")
371
+
372
+ return response
373
+
374
+ def add_knowledge(self, category: str, question: str, answer: str,
375
+ keywords: Optional[List[str]] = None, priority: int = 0) -> int:
376
+ """Add new record to knowledge base"""
377
+ if not ADVANCED_AVAILABLE or not isinstance(self.memory, SQLMemoryManager):
378
+ return 0
379
+
380
+ try:
381
+ kb_id = self.memory.add_knowledge(category, question, answer, keywords, priority)
382
+ self.logger.info(f"New knowledge added: {category} - {kb_id}")
383
+ return kb_id
384
+ except Exception as e:
385
+ self.logger.error(f"Knowledge adding error: {e}")
386
+ return 0
387
+
388
+ def get_statistics(self) -> Dict[str, Any]:
389
+ """Returns general statistics"""
390
+ try:
391
+ if hasattr(self.memory, 'get_statistics'):
392
+ return self.memory.get_statistics()
393
+ else:
394
+ # Simple statistics for JSON memory
395
+ return {
396
+ "total_users": 0,
397
+ "total_interactions": 0,
398
+ "memory_backend": "JSON"
399
+ }
400
+ except Exception as e:
401
+ self.logger.error(f"Statistics retrieval error: {e}")
402
+ return {}
403
+
404
+ def search_history(self, keyword: str, user_id: Optional[str] = None) -> List[Dict]:
405
+ """Search in user history"""
406
+ uid = user_id or self.current_user
407
+ if not uid:
408
+ return []
409
+
410
+ try:
411
+ if hasattr(self.memory, 'search_conversations'):
412
+ return self.memory.search_conversations(uid, keyword)
413
+ else:
414
+ return []
415
+ except Exception as e:
416
+ self.logger.error(f"History search error: {e}")
417
+ return []
418
+
419
+ def show_user_info(self, user_id: Optional[str] = None) -> str:
420
+ """Shows user information"""
421
+ uid = user_id or self.current_user
422
+ if not uid:
423
+ return "User ID not specified."
424
+
425
+ try:
426
+ if hasattr(self.memory, 'get_user_profile'):
427
+ profile = self.memory.get_user_profile(uid)
428
+ if profile:
429
+ return f"User: {uid}\nName: {profile.get('name', 'Unknown')}\nFirst conversation: {profile.get('first_seen', 'Unknown')}"
430
+ else:
431
+ return f"User {uid} not found."
432
+ else:
433
+ return "This feature is not available."
434
+ except Exception as e:
435
+ return f"Error: {str(e)}"
436
+
437
+ def export_memory(self, user_id: Optional[str] = None, format: str = "json") -> str:
438
+ """Export user data"""
439
+ uid = user_id or self.current_user
440
+ if not uid:
441
+ return "User ID not specified."
442
+
443
+ try:
444
+ if hasattr(self.memory, 'get_recent_conversations') and hasattr(self.memory, 'get_user_profile'):
445
+ conversations = self.memory.get_recent_conversations(uid, 1000)
446
+ profile = self.memory.get_user_profile(uid)
447
+
448
+ if format == "json":
449
+ export_data = {
450
+ "user_id": uid,
451
+ "export_date": datetime.now().isoformat(),
452
+ "profile": profile,
453
+ "conversations": conversations
454
+ }
455
+ return json.dumps(export_data, ensure_ascii=False, indent=2)
456
+ elif format == "txt":
457
+ result = f"{uid} user conversation history\n"
458
+ result += f"Export date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
459
+ result += "=" * 60 + "\n\n"
460
+
461
+ for i, conv in enumerate(conversations, 1):
462
+ result += f"Conversation {i}:\n"
463
+ result += f"Date: {conv.get('timestamp', 'Unknown')}\n"
464
+ result += f"User: {conv.get('user_message', '')}\n"
465
+ result += f"Bot: {conv.get('bot_response', '')}\n"
466
+ result += "-" * 40 + "\n"
467
+
468
+ return result
469
+ else:
470
+ return "Unsupported format. Use json or txt."
471
+ else:
472
+ return "This feature is not available."
473
+ except Exception as e:
474
+ return f"Export error: {str(e)}"
475
+
476
+ def clear_user_data(self, user_id: Optional[str] = None, confirm: bool = False) -> str:
477
+ """Delete user data"""
478
+ uid = user_id or self.current_user
479
+ if not uid:
480
+ return "User ID not specified."
481
+
482
+ if not confirm:
483
+ return "Use confirm=True parameter to delete data."
484
+
485
+ try:
486
+ if hasattr(self.memory, 'clear_memory'):
487
+ self.memory.clear_memory(uid)
488
+ return f"All data for user {uid} has been deleted."
489
+ else:
490
+ return "This feature is not available."
491
+ except Exception as e:
492
+ return f"Deletion error: {str(e)}"
493
+
494
+ def list_available_tools(self) -> str:
495
+ """List available tools"""
496
+ if ADVANCED_AVAILABLE:
497
+ return self.tool_executor.memory_tools.list_available_tools()
498
+ else:
499
+ return "Tool system not available."
500
+
501
+ def close(self) -> None:
502
+ """Clean up resources"""
503
+ if hasattr(self.memory, 'close'):
504
+ self.memory.close()
505
+ self.logger.info("MemAgent closed")
506
+
507
+ def __enter__(self):
508
+ return self
509
+
510
+ def __exit__(self, exc_type, exc_val, exc_tb):
511
+ self.close()
512
+