mem-llm 1.0.2__py3-none-any.whl → 2.1.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.

Files changed (41) hide show
  1. mem_llm/__init__.py +71 -8
  2. mem_llm/api_server.py +595 -0
  3. mem_llm/base_llm_client.py +201 -0
  4. mem_llm/builtin_tools.py +311 -0
  5. mem_llm/builtin_tools_async.py +170 -0
  6. mem_llm/cli.py +254 -0
  7. mem_llm/clients/__init__.py +22 -0
  8. mem_llm/clients/lmstudio_client.py +393 -0
  9. mem_llm/clients/ollama_client.py +354 -0
  10. mem_llm/config.yaml.example +1 -1
  11. mem_llm/config_from_docs.py +1 -1
  12. mem_llm/config_manager.py +5 -3
  13. mem_llm/conversation_summarizer.py +372 -0
  14. mem_llm/data_export_import.py +640 -0
  15. mem_llm/dynamic_prompt.py +298 -0
  16. mem_llm/llm_client.py +77 -14
  17. mem_llm/llm_client_factory.py +260 -0
  18. mem_llm/logger.py +129 -0
  19. mem_llm/mem_agent.py +1178 -87
  20. mem_llm/memory_db.py +290 -59
  21. mem_llm/memory_manager.py +60 -1
  22. mem_llm/prompt_security.py +304 -0
  23. mem_llm/response_metrics.py +221 -0
  24. mem_llm/retry_handler.py +193 -0
  25. mem_llm/thread_safe_db.py +301 -0
  26. mem_llm/tool_system.py +537 -0
  27. mem_llm/vector_store.py +278 -0
  28. mem_llm/web_launcher.py +129 -0
  29. mem_llm/web_ui/README.md +44 -0
  30. mem_llm/web_ui/__init__.py +7 -0
  31. mem_llm/web_ui/index.html +641 -0
  32. mem_llm/web_ui/memory.html +569 -0
  33. mem_llm/web_ui/metrics.html +75 -0
  34. mem_llm-2.1.0.dist-info/METADATA +753 -0
  35. mem_llm-2.1.0.dist-info/RECORD +40 -0
  36. {mem_llm-1.0.2.dist-info → mem_llm-2.1.0.dist-info}/WHEEL +1 -1
  37. mem_llm-2.1.0.dist-info/entry_points.txt +3 -0
  38. mem_llm/prompt_templates.py +0 -244
  39. mem_llm-1.0.2.dist-info/METADATA +0 -382
  40. mem_llm-1.0.2.dist-info/RECORD +0 -15
  41. {mem_llm-1.0.2.dist-info → mem_llm-2.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,278 @@
1
+ """
2
+ Vector Store Abstraction Layer
3
+ Supports multiple vector databases (Chroma, FAISS, etc.)
4
+ """
5
+
6
+ from abc import ABC, abstractmethod
7
+ from typing import List, Dict, Optional, Any
8
+ import logging
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class VectorStore(ABC):
14
+ """Abstract interface for vector stores"""
15
+
16
+ @abstractmethod
17
+ def add_documents(self, documents: List[Dict[str, Any]]) -> None:
18
+ """
19
+ Add documents to vector store
20
+
21
+ Args:
22
+ documents: List of dicts with 'id', 'text', 'metadata'
23
+ """
24
+ pass
25
+
26
+ @abstractmethod
27
+ def search(self, query: str, limit: int = 5, filter_metadata: Optional[Dict] = None) -> List[Dict[str, Any]]:
28
+ """
29
+ Search similar documents
30
+
31
+ Args:
32
+ query: Search query text
33
+ limit: Maximum number of results
34
+ filter_metadata: Optional metadata filters
35
+
36
+ Returns:
37
+ List of similar documents with scores
38
+ """
39
+ pass
40
+
41
+ @abstractmethod
42
+ def delete_collection(self) -> None:
43
+ """Delete all vectors in collection"""
44
+ pass
45
+
46
+ @abstractmethod
47
+ def get_stats(self) -> Dict[str, Any]:
48
+ """Get statistics about the vector store"""
49
+ pass
50
+
51
+
52
+ try:
53
+ import chromadb
54
+ from chromadb.config import Settings
55
+ CHROMA_AVAILABLE = True
56
+ except ImportError:
57
+ CHROMA_AVAILABLE = False
58
+ # Don't warn on import, only when actually trying to use it
59
+
60
+
61
+ class ChromaVectorStore(VectorStore):
62
+ """ChromaDB implementation of VectorStore"""
63
+
64
+ def __init__(self, collection_name: str = "knowledge_base",
65
+ persist_directory: Optional[str] = None,
66
+ embedding_model: str = "all-MiniLM-L6-v2"):
67
+ """
68
+ Initialize ChromaDB vector store
69
+
70
+ Args:
71
+ collection_name: Name of the collection
72
+ persist_directory: Directory to persist data (None = in-memory)
73
+ embedding_model: Embedding model name (sentence-transformers compatible)
74
+ """
75
+ if not CHROMA_AVAILABLE:
76
+ raise ImportError(
77
+ "ChromaDB is not installed. Install with: pip install chromadb"
78
+ )
79
+
80
+ self.collection_name = collection_name
81
+ self.persist_directory = persist_directory
82
+ self.embedding_model = embedding_model
83
+
84
+ # Initialize Chroma client
85
+ if persist_directory:
86
+ self.client = chromadb.PersistentClient(path=persist_directory)
87
+ else:
88
+ self.client = chromadb.Client()
89
+
90
+ # Lazy load embedding model
91
+ self._embedding_fn = None
92
+
93
+ # Get or create collection with embedding function
94
+ try:
95
+ # Create embedding function
96
+ embedding_fn = self._get_embedding_function()
97
+
98
+ self.collection = self.client.get_or_create_collection(
99
+ name=collection_name,
100
+ embedding_function=embedding_fn,
101
+ metadata={"hnsw:space": "cosine"}
102
+ )
103
+ except Exception as e:
104
+ logger.error(f"Failed to create Chroma collection: {e}")
105
+ raise
106
+
107
+ def _get_embedding_function(self):
108
+ """Lazy load embedding function"""
109
+ if self._embedding_fn is None:
110
+ try:
111
+ # Try to use ChromaDB's native SentenceTransformerEmbeddingFunction
112
+ try:
113
+ # Try different import paths for ChromaDB embedding functions
114
+ try:
115
+ from chromadb.utils import embedding_functions
116
+ embedding_fn_class = embedding_functions.SentenceTransformerEmbeddingFunction
117
+ except (ImportError, AttributeError):
118
+ try:
119
+ from chromadb.utils.embedding_functions import SentenceTransformerEmbeddingFunction as embedding_fn_class
120
+ except ImportError:
121
+ embedding_fn_class = None
122
+
123
+ if embedding_fn_class:
124
+ self._embedding_fn = embedding_fn_class(model_name=self.embedding_model)
125
+ logger.info(f"Loaded embedding model using ChromaDB native function: {self.embedding_model}")
126
+ else:
127
+ raise AttributeError("SentenceTransformerEmbeddingFunction not found")
128
+
129
+ except (ImportError, AttributeError, Exception) as e:
130
+ # Fallback: Custom embedding function wrapper compatible with ChromaDB
131
+ from sentence_transformers import SentenceTransformer
132
+ model = SentenceTransformer(self.embedding_model)
133
+
134
+ class CustomEmbeddingFunction:
135
+ def __init__(self, model, model_name):
136
+ self.model = model
137
+ self.model_name = model_name
138
+ self.name = model_name # ChromaDB may check for 'name' attribute
139
+
140
+ def __call__(self, texts: List[str]) -> List[List[float]]:
141
+ embeddings = self.model.encode(texts, show_progress_bar=False)
142
+ return embeddings.tolist()
143
+
144
+ def encode_queries(self, queries: List[str]) -> List[List[float]]:
145
+ return self.__call__(queries)
146
+
147
+ self._embedding_fn = CustomEmbeddingFunction(model, self.embedding_model)
148
+ logger.info(f"Loaded embedding model using custom wrapper: {self.embedding_model} (fallback: {e})")
149
+ except ImportError:
150
+ raise ImportError(
151
+ "sentence-transformers not installed. "
152
+ "Install with: pip install sentence-transformers"
153
+ )
154
+
155
+ return self._embedding_fn
156
+
157
+ def add_documents(self, documents: List[Dict[str, Any]]) -> None:
158
+ """Add documents to ChromaDB"""
159
+ if not documents:
160
+ return
161
+
162
+ # Prepare data
163
+ ids = []
164
+ texts = []
165
+ metadatas = []
166
+
167
+ for doc in documents:
168
+ doc_id = str(doc.get('id', doc.get('text', ''))[:100])
169
+ # Ensure unique IDs
170
+ if doc_id in ids:
171
+ doc_id = f"{doc_id}_{len(ids)}"
172
+ ids.append(doc_id)
173
+ texts.append(doc['text'])
174
+ # Ensure metadata values are JSON-serializable
175
+ metadata = doc.get('metadata', {})
176
+ clean_metadata = {}
177
+ for k, v in metadata.items():
178
+ if isinstance(v, (str, int, float, bool)) or v is None:
179
+ clean_metadata[k] = v
180
+ else:
181
+ clean_metadata[k] = str(v)
182
+ metadatas.append(clean_metadata)
183
+
184
+ # Add to collection (Chroma will use embedding function automatically)
185
+ try:
186
+ self.collection.add(
187
+ ids=ids,
188
+ documents=texts,
189
+ metadatas=metadatas
190
+ )
191
+
192
+ logger.debug(f"Added {len(documents)} documents to Chroma")
193
+ except Exception as e:
194
+ logger.error(f"Error adding documents to Chroma: {e}")
195
+ raise
196
+
197
+ def search(self, query: str, limit: int = 5,
198
+ filter_metadata: Optional[Dict] = None) -> List[Dict[str, Any]]:
199
+ """Search in ChromaDB"""
200
+ try:
201
+ # Build where clause for metadata filtering
202
+ where = None
203
+ if filter_metadata:
204
+ where = filter_metadata
205
+
206
+ # Search (Chroma will use embedding function automatically)
207
+ results = self.collection.query(
208
+ query_texts=[query],
209
+ n_results=limit,
210
+ where=where
211
+ )
212
+
213
+ # Format results
214
+ formatted_results = []
215
+ if results.get('documents') and len(results['documents']) > 0 and len(results['documents'][0]) > 0:
216
+ num_results = len(results['documents'][0])
217
+ distances = results.get('distances', [[0.0] * num_results])
218
+
219
+ for i in range(num_results):
220
+ # ChromaDB uses cosine distance (0 = identical, 1 = opposite)
221
+ # Convert to similarity score (1 = identical, 0 = opposite)
222
+ distance = distances[0][i] if distances and len(distances[0]) > i else 0.0
223
+ similarity = 1.0 - distance if distance <= 1.0 else max(0.0, 1.0 / (1.0 + distance))
224
+
225
+ formatted_results.append({
226
+ 'id': results['ids'][0][i] if results.get('ids') and len(results['ids'][0]) > i else f"doc_{i}",
227
+ 'text': results['documents'][0][i],
228
+ 'metadata': results['metadatas'][0][i] if results.get('metadatas') and len(results['metadatas'][0]) > i else {},
229
+ 'score': similarity
230
+ })
231
+
232
+ return formatted_results
233
+ except Exception as e:
234
+ logger.error(f"Error searching Chroma: {e}")
235
+ return []
236
+
237
+ def delete_collection(self) -> None:
238
+ """Delete collection"""
239
+ try:
240
+ self.client.delete_collection(self.collection_name)
241
+ logger.info(f"Deleted Chroma collection: {self.collection_name}")
242
+ except Exception as e:
243
+ logger.error(f"Error deleting collection: {e}")
244
+
245
+ def get_stats(self) -> Dict[str, Any]:
246
+ """Get collection statistics"""
247
+ try:
248
+ count = self.collection.count()
249
+ return {
250
+ 'total_documents': count,
251
+ 'collection_name': self.collection_name,
252
+ 'embedding_model': self.embedding_model
253
+ }
254
+ except Exception as e:
255
+ logger.error(f"Error getting stats: {e}")
256
+ return {'total_documents': 0}
257
+
258
+
259
+ def create_vector_store(store_type: str = "chroma", **kwargs) -> Optional[VectorStore]:
260
+ """
261
+ Factory function to create vector store
262
+
263
+ Args:
264
+ store_type: Type of vector store ('chroma', 'faiss', etc.)
265
+ **kwargs: Store-specific parameters
266
+
267
+ Returns:
268
+ VectorStore instance or None if not available
269
+ """
270
+ if store_type == "chroma":
271
+ if not CHROMA_AVAILABLE:
272
+ logger.info("ℹ️ Vector search disabled (ChromaDB not installed). For semantic search, run: pip install chromadb sentence-transformers")
273
+ return None
274
+ return ChromaVectorStore(**kwargs)
275
+ else:
276
+ logger.warning(f"Unknown vector store type: {store_type}")
277
+ return None
278
+
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Web UI Launcher for Mem-LLM
4
+ Starts the API server and opens the Web UI in the browser.
5
+ """
6
+
7
+ import sys
8
+ import time
9
+ import webbrowser
10
+ import requests
11
+ import threading
12
+
13
+ def check_backend_available():
14
+ """Check if Ollama or LM Studio is available."""
15
+ backends = []
16
+
17
+ # Check Ollama
18
+ try:
19
+ response = requests.get("http://localhost:11434/api/tags", timeout=2)
20
+ if response.status_code == 200:
21
+ backends.append("Ollama (http://localhost:11434)")
22
+ except:
23
+ pass
24
+
25
+ # Check LM Studio
26
+ try:
27
+ response = requests.get("http://localhost:1234/v1/models", timeout=2)
28
+ if response.status_code == 200:
29
+ backends.append("LM Studio (http://localhost:1234)")
30
+ except:
31
+ pass
32
+
33
+ return backends
34
+
35
+ def check_api_ready(url="http://localhost:8000/api/v1/health", timeout=30):
36
+ """Wait for API server to be ready."""
37
+ start_time = time.time()
38
+ while time.time() - start_time < timeout:
39
+ try:
40
+ response = requests.get(url, timeout=1)
41
+ if response.status_code == 200:
42
+ return True
43
+ except:
44
+ pass
45
+ time.sleep(0.5)
46
+ return False
47
+
48
+ def start_api_server():
49
+ """Start the FastAPI server in a subprocess."""
50
+ try:
51
+ # Import here to avoid circular imports
52
+ from mem_llm.api_server import app
53
+ import uvicorn
54
+
55
+ # Run server in a thread
56
+ def run_server():
57
+ uvicorn.run(app, host="0.0.0.0", port=8000, log_level="info")
58
+
59
+ server_thread = threading.Thread(target=run_server, daemon=True)
60
+ server_thread.start()
61
+
62
+ return server_thread
63
+ except Exception as e:
64
+ print(f"❌ Failed to start API server: {e}")
65
+ return None
66
+
67
+ def main():
68
+ """Main entry point for the web launcher."""
69
+ print("🚀 Mem-LLM Web UI Launcher")
70
+ print("=" * 50)
71
+
72
+ # Check available backends
73
+ print("\n🔍 Checking available backends...")
74
+ backends = check_backend_available()
75
+
76
+ if backends:
77
+ print("✅ Available backends:")
78
+ for backend in backends:
79
+ print(f" - {backend}")
80
+ else:
81
+ print("⚠️ No local backends detected!")
82
+ print(" Please start Ollama or LM Studio first:")
83
+ print(" - Ollama: https://ollama.ai")
84
+ print(" - LM Studio: https://lmstudio.ai")
85
+ response = input("\n Continue anyway? (y/n): ")
86
+ if response.lower() != 'y':
87
+ sys.exit(0)
88
+
89
+ # Start API server
90
+ print("\n🌐 Starting API server...")
91
+ server_thread = start_api_server()
92
+
93
+ if not server_thread:
94
+ print("❌ Failed to start API server!")
95
+ sys.exit(1)
96
+
97
+ # Wait for server to be ready
98
+ print("⏳ Waiting for API server to be ready...")
99
+ if not check_api_ready():
100
+ print("❌ API server failed to start within 30 seconds!")
101
+ sys.exit(1)
102
+
103
+ print("✅ API server is ready!")
104
+
105
+ # Open browser
106
+ web_url = "http://localhost:8000/"
107
+ print(f"\n🌐 Opening Web UI at {web_url}")
108
+ webbrowser.open(web_url)
109
+
110
+ print("\n" + "=" * 50)
111
+ print("✅ Mem-LLM Web UI is running!")
112
+ print(" - Chat: http://localhost:8000/")
113
+ print(" - Memory: http://localhost:8000/memory")
114
+ print(" - Metrics: http://localhost:8000/metrics")
115
+ print(" - API Docs: http://localhost:8000/docs")
116
+ print("\n Press Ctrl+C to stop the server.")
117
+ print("=" * 50)
118
+
119
+ # Keep the main thread alive
120
+ try:
121
+ while True:
122
+ time.sleep(1)
123
+ except KeyboardInterrupt:
124
+ print("\n\n👋 Shutting down...")
125
+ sys.exit(0)
126
+
127
+ if __name__ == "__main__":
128
+ main()
129
+
@@ -0,0 +1,44 @@
1
+ # Mem-LLM Web UI
2
+
3
+ Modern web interface for Mem-LLM with streaming support, memory management, and metrics dashboard.
4
+
5
+ ## 📄 Pages
6
+
7
+ 1. **💬 Chat (index.html)** - Main chat interface with real-time streaming
8
+ 2. **🧠 Memory (memory.html)** - Memory management and search
9
+ 3. **📊 Metrics (metrics.html)** - System metrics and statistics
10
+
11
+ ## 🚀 Usage
12
+
13
+ ```bash
14
+ # Install mem-llm with API support
15
+ pip install mem-llm[api]
16
+
17
+ # Launch Web UI (recommended)
18
+ mem-llm-web
19
+
20
+ # Or use launcher script
21
+ python start_web_ui.py
22
+ ```
23
+
24
+ ## 📋 Requirements
25
+
26
+ - Python 3.8+
27
+ - FastAPI
28
+ - Uvicorn
29
+ - WebSockets
30
+
31
+ ## 🔧 Configuration
32
+
33
+ Configure backend and model in the Web UI sidebar, or edit `api_server.py` defaults.
34
+
35
+ ## 📚 More Info
36
+
37
+ - [Main README](../README.md)
38
+ - [API Docs](http://localhost:8000/docs)
39
+ - [Examples](../../examples/)
40
+
41
+ ## 📄 License
42
+
43
+ MIT License
44
+
@@ -0,0 +1,7 @@
1
+ """
2
+ Mem-LLM Web UI
3
+ Modern web interface for Mem-LLM with real-time streaming support.
4
+ """
5
+
6
+ __all__ = []
7
+