noesium 0.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.
Files changed (86) hide show
  1. noesium/core/__init__.py +4 -0
  2. noesium/core/agent/__init__.py +14 -0
  3. noesium/core/agent/base.py +227 -0
  4. noesium/core/consts.py +6 -0
  5. noesium/core/goalith/conflict/conflict.py +104 -0
  6. noesium/core/goalith/conflict/detector.py +53 -0
  7. noesium/core/goalith/decomposer/__init__.py +6 -0
  8. noesium/core/goalith/decomposer/base.py +46 -0
  9. noesium/core/goalith/decomposer/callable_decomposer.py +65 -0
  10. noesium/core/goalith/decomposer/llm_decomposer.py +326 -0
  11. noesium/core/goalith/decomposer/prompts.py +140 -0
  12. noesium/core/goalith/decomposer/simple_decomposer.py +61 -0
  13. noesium/core/goalith/errors.py +22 -0
  14. noesium/core/goalith/goalgraph/graph.py +526 -0
  15. noesium/core/goalith/goalgraph/node.py +179 -0
  16. noesium/core/goalith/replanner/base.py +31 -0
  17. noesium/core/goalith/replanner/replanner.py +36 -0
  18. noesium/core/goalith/service.py +26 -0
  19. noesium/core/llm/__init__.py +154 -0
  20. noesium/core/llm/base.py +152 -0
  21. noesium/core/llm/litellm.py +528 -0
  22. noesium/core/llm/llamacpp.py +487 -0
  23. noesium/core/llm/message.py +184 -0
  24. noesium/core/llm/ollama.py +459 -0
  25. noesium/core/llm/openai.py +520 -0
  26. noesium/core/llm/openrouter.py +89 -0
  27. noesium/core/llm/prompt.py +551 -0
  28. noesium/core/memory/__init__.py +11 -0
  29. noesium/core/memory/base.py +464 -0
  30. noesium/core/memory/memu/__init__.py +24 -0
  31. noesium/core/memory/memu/config/__init__.py +26 -0
  32. noesium/core/memory/memu/config/activity/config.py +46 -0
  33. noesium/core/memory/memu/config/event/config.py +46 -0
  34. noesium/core/memory/memu/config/markdown_config.py +241 -0
  35. noesium/core/memory/memu/config/profile/config.py +48 -0
  36. noesium/core/memory/memu/llm_adapter.py +129 -0
  37. noesium/core/memory/memu/memory/__init__.py +31 -0
  38. noesium/core/memory/memu/memory/actions/__init__.py +40 -0
  39. noesium/core/memory/memu/memory/actions/add_activity_memory.py +299 -0
  40. noesium/core/memory/memu/memory/actions/base_action.py +342 -0
  41. noesium/core/memory/memu/memory/actions/cluster_memories.py +262 -0
  42. noesium/core/memory/memu/memory/actions/generate_suggestions.py +198 -0
  43. noesium/core/memory/memu/memory/actions/get_available_categories.py +66 -0
  44. noesium/core/memory/memu/memory/actions/link_related_memories.py +515 -0
  45. noesium/core/memory/memu/memory/actions/run_theory_of_mind.py +254 -0
  46. noesium/core/memory/memu/memory/actions/update_memory_with_suggestions.py +514 -0
  47. noesium/core/memory/memu/memory/embeddings.py +130 -0
  48. noesium/core/memory/memu/memory/file_manager.py +306 -0
  49. noesium/core/memory/memu/memory/memory_agent.py +578 -0
  50. noesium/core/memory/memu/memory/recall_agent.py +376 -0
  51. noesium/core/memory/memu/memory_store.py +628 -0
  52. noesium/core/memory/models.py +149 -0
  53. noesium/core/msgbus/__init__.py +12 -0
  54. noesium/core/msgbus/base.py +395 -0
  55. noesium/core/orchestrix/__init__.py +0 -0
  56. noesium/core/py.typed +0 -0
  57. noesium/core/routing/__init__.py +20 -0
  58. noesium/core/routing/base.py +66 -0
  59. noesium/core/routing/router.py +241 -0
  60. noesium/core/routing/strategies/__init__.py +9 -0
  61. noesium/core/routing/strategies/dynamic_complexity.py +361 -0
  62. noesium/core/routing/strategies/self_assessment.py +147 -0
  63. noesium/core/routing/types.py +38 -0
  64. noesium/core/toolify/__init__.py +39 -0
  65. noesium/core/toolify/base.py +360 -0
  66. noesium/core/toolify/config.py +138 -0
  67. noesium/core/toolify/mcp_integration.py +275 -0
  68. noesium/core/toolify/registry.py +214 -0
  69. noesium/core/toolify/toolkits/__init__.py +1 -0
  70. noesium/core/tracing/__init__.py +37 -0
  71. noesium/core/tracing/langgraph_hooks.py +308 -0
  72. noesium/core/tracing/opik_tracing.py +144 -0
  73. noesium/core/tracing/token_tracker.py +166 -0
  74. noesium/core/utils/__init__.py +10 -0
  75. noesium/core/utils/logging.py +172 -0
  76. noesium/core/utils/statistics.py +12 -0
  77. noesium/core/utils/typing.py +17 -0
  78. noesium/core/vector_store/__init__.py +79 -0
  79. noesium/core/vector_store/base.py +94 -0
  80. noesium/core/vector_store/pgvector.py +304 -0
  81. noesium/core/vector_store/weaviate.py +383 -0
  82. noesium-0.1.0.dist-info/METADATA +525 -0
  83. noesium-0.1.0.dist-info/RECORD +86 -0
  84. noesium-0.1.0.dist-info/WHEEL +5 -0
  85. noesium-0.1.0.dist-info/licenses/LICENSE +21 -0
  86. noesium-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,376 @@
1
+ """
2
+ RecallAgent for MemU Memory System
3
+
4
+ A simple workflow for intelligent memory retrieval based on markdown configurations.
5
+ Handles context=all (full content) and context=rag (search with limitations) based on config.
6
+ """
7
+
8
+ import json
9
+ import logging
10
+ import math
11
+ from pathlib import Path
12
+ from typing import Any, Dict, List
13
+
14
+ from ..config.markdown_config import get_config_manager
15
+ from .file_manager import MemoryFileManager
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class RecallAgent:
21
+ """
22
+ Enhanced workflow for intelligent memory retrieval with three distinct methods.
23
+ Automatically scans {agent_id}/{user_id}/{category}.md files in memory directory.
24
+
25
+ Three core retrieval methods:
26
+ 1. retrieve_default_category: Get content from ['profile', 'event'] categories
27
+ 2. retrieve_relevant_category: Get top-k similar category names (excluding profile/event/activity)
28
+ 3. retrieve_relevant_memories: Get top-k memories using embedding search
29
+ """
30
+
31
+ def __init__(self, memory_dir: str = "memu/server/memory", agent_id: str = None, user_id: str = None):
32
+ """
33
+ Initialize Recall Agent
34
+
35
+ Args:
36
+ memory_dir: Directory where memory files are stored
37
+ agent_id: Agent identifier
38
+ user_id: User identifier
39
+ """
40
+ self.memory_dir = Path(memory_dir)
41
+
42
+ # Initialize config manager
43
+ self.config_manager = get_config_manager()
44
+ self.memory_types = self.config_manager.get_file_types_mapping()
45
+
46
+ # Initialize file-based storage manager
47
+ self.storage_manager = MemoryFileManager(memory_dir, agent_id=agent_id, user_id=user_id)
48
+
49
+ # Initialize embedding client for semantic search
50
+ try:
51
+ self.embedding_client = get_default_embedding_client()
52
+ self.semantic_search_enabled = True
53
+ logger.info("Semantic search enabled")
54
+ except Exception as e:
55
+ logger.warning(f"Failed to initialize embedding client: {e}. Semantic search disabled.")
56
+ self.embedding_client = None
57
+ self.semantic_search_enabled = False
58
+
59
+ # Default categories for core retrieval
60
+ self.default_categories = ["profile", "event"]
61
+
62
+ logger.info(f"Recall Agent initialized with memory directory: {self.memory_dir}")
63
+
64
+ def retrieve_default_category(self, agent_id: str = None, user_id: str = None) -> Dict[str, Any]:
65
+ """
66
+ Method 1: Retrieve Default Category
67
+ Get complete content from ['profile', 'event'] categories
68
+
69
+ Args:
70
+ agent_id: Agent identifier
71
+ user_id: User identifier
72
+
73
+ Returns:
74
+ Dict containing default category content
75
+ """
76
+ try:
77
+ results = []
78
+
79
+ all_categories = self.storage_manager.list_memory_files()
80
+ existing_defaults = [cat for cat in self.default_categories if cat in all_categories]
81
+
82
+ for category in self.default_categories:
83
+ content = self.storage_manager.read_memory_file(category)
84
+ if content:
85
+ results.append(
86
+ {
87
+ "category": category,
88
+ "content": content,
89
+ "content_type": "default_category",
90
+ "length": len(content),
91
+ "lines": len(content.split("\n")),
92
+ "file_exists": category in all_categories,
93
+ }
94
+ )
95
+ else:
96
+ logger.debug(f"No content found for {agent_id}:{user_id}:{category}")
97
+
98
+ return {
99
+ "success": True,
100
+ "method": "retrieve_default_category",
101
+ "requested_categories": self.default_categories,
102
+ "existing_categories": existing_defaults,
103
+ "all_categories_found": all_categories,
104
+ "results": results,
105
+ "total_items": len(results),
106
+ "message": f"Retrieved {len(results)} default categories for {agent_id}:{user_id} (found {len(existing_defaults)}/{len(self.default_categories)} requested files)",
107
+ }
108
+
109
+ except Exception as e:
110
+ logger.error(f"Error in retrieve_default_category for {agent_id}:{user_id}: {e}")
111
+ return {
112
+ "success": False,
113
+ "error": str(e),
114
+ "method": "retrieve_default_category",
115
+ }
116
+
117
+ def retrieve_relevant_category(self, agent_id: str, user_id: str, query: str, top_k: int = 5) -> Dict[str, Any]:
118
+ """
119
+ Method 2: Retrieve Relevant Category
120
+ Retrieve relevant contents from top-k similar category names (excluding profile, event, and activity)
121
+ Scans actual {agent_id}/{user_id}/{category}.md files in memory directory
122
+
123
+ Args:
124
+ agent_id: Agent identifier
125
+ user_id: User identifier
126
+ query: Search query for category relevance
127
+ top_k: Number of top categories to return
128
+
129
+ Returns:
130
+ Dict containing relevant category content
131
+ """
132
+ try:
133
+ all_categories = self.storage_manager.list_memory_files("all")
134
+ excluded_categories = self.default_categories + ["activity"]
135
+ relevant_categories = [cat for cat in all_categories if cat not in excluded_categories]
136
+
137
+ if not relevant_categories:
138
+ return {
139
+ "success": True,
140
+ "method": "retrieve_relevant_category",
141
+ "query": query,
142
+ "results": [],
143
+ "total_items": 0,
144
+ "message": "No categories available (excluding profile/event/activity)",
145
+ }
146
+
147
+ # Calculate category relevance scores
148
+ category_scores = []
149
+ query_lower = query.lower()
150
+ query_words = set(query_lower.split())
151
+
152
+ for category in relevant_categories:
153
+ # Check if category has content for this character
154
+ content = self.storage_manager.read_memory_file(category)
155
+ if not content:
156
+ continue
157
+
158
+ # Semantic search for content relevance
159
+ content_relevance = 0.0
160
+ if self.semantic_search_enabled and self.embedding_client:
161
+ try:
162
+ # Generate embeddings for query and content
163
+ query_embedding = self.embedding_client.embed(query)
164
+ content_embedding = self.embedding_client.embed(
165
+ content[:1000]
166
+ ) # Limit content length for embedding
167
+
168
+ # Calculate semantic similarity
169
+ semantic_similarity = self._cosine_similarity(query_embedding, content_embedding)
170
+ content_relevance = semantic_similarity
171
+ except Exception as e:
172
+ logger.warning(f"Semantic search failed for {category}: {e}")
173
+ # Fallback to simple keyword matching
174
+ content_lower = content.lower()
175
+ content_relevance = (
176
+ sum(1 for word in query_words if word in content_lower) / len(query_words)
177
+ if query_words
178
+ else 0
179
+ )
180
+ else:
181
+ # Fallback to simple keyword matching when semantic search is not available
182
+ content_lower = content.lower()
183
+ content_relevance = (
184
+ sum(1 for word in query_words if word in content_lower) / len(query_words) if query_words else 0
185
+ )
186
+
187
+ # Use semantic score directly
188
+ combined_score = content_relevance
189
+
190
+ if combined_score > 0:
191
+ category_scores.append(
192
+ {
193
+ "category": category,
194
+ "content": content,
195
+ "score": combined_score,
196
+ "content_relevance": content_relevance,
197
+ "semantic_search_used": self.semantic_search_enabled and self.embedding_client is not None,
198
+ "length": len(content),
199
+ "lines": len(content.split("\n")),
200
+ }
201
+ )
202
+
203
+ # Sort by score and get top-k
204
+ category_scores.sort(key=lambda x: x["score"], reverse=True)
205
+ top_categories = category_scores[:top_k]
206
+
207
+ # Format results
208
+ results = []
209
+ for item in top_categories:
210
+ results.append(
211
+ {
212
+ "category": item["category"],
213
+ "content": item["content"],
214
+ "content_type": "relevant_category",
215
+ "relevance_score": item["score"],
216
+ "content_relevance": item["content_relevance"],
217
+ "semantic_search_used": item["semantic_search_used"],
218
+ "length": item["length"],
219
+ "lines": item["lines"],
220
+ }
221
+ )
222
+
223
+ return {
224
+ "success": True,
225
+ "method": "retrieve_relevant_category",
226
+ "query": query,
227
+ "top_k": top_k,
228
+ "all_categories_found": all_categories,
229
+ "excluded_categories": excluded_categories,
230
+ "available_categories": relevant_categories,
231
+ "semantic_search_enabled": self.semantic_search_enabled,
232
+ "results": results,
233
+ "total_items": len(results),
234
+ "message": f"Retrieved top {len(results)} relevant categories for query '{query}' using semantic search from {len(all_categories)} total categories",
235
+ }
236
+
237
+ except Exception as e:
238
+ logger.error(f"Error in retrieve_relevant_category for {agent_id}:{user_id}: {e}")
239
+ return {
240
+ "success": False,
241
+ "error": str(e),
242
+ "method": "retrieve_relevant_category",
243
+ "query": query,
244
+ }
245
+
246
+ def retrieve_relevant_memories(self, agent_id: str, user_id: str, query: str, top_k: int = 10) -> Dict[str, Any]:
247
+ """
248
+ Method 3: Retrieve Relevant Memories
249
+ Retrieve top-k memories using embedding search across all categories
250
+
251
+ Args:
252
+ agent_id: Agent identifier
253
+ user_id: User identifier
254
+ query: Search query for memory retrieval
255
+ top_k: Number of top memories to return
256
+
257
+ Returns:
258
+ Dict containing relevant memories
259
+ """
260
+ try:
261
+ if not self.semantic_search_enabled:
262
+ return {
263
+ "success": False,
264
+ "error": "Semantic search not available - embedding client not initialized",
265
+ "method": "retrieve_relevant_memories",
266
+ "query": query,
267
+ }
268
+
269
+ # Generate query embedding
270
+ query_embedding = self.embedding_client.embed(query)
271
+
272
+ results = []
273
+
274
+ # Get embeddings directory from storage manager
275
+ embeddings_dir = self.storage_manager.get_char_embeddings_dir()
276
+
277
+ if not embeddings_dir.exists():
278
+ return {
279
+ "success": True,
280
+ "method": "retrieve_relevant_memories",
281
+ "query": query,
282
+ "results": [],
283
+ "total_items": 0,
284
+ "message": f"No embeddings found for {agent_id}:{user_id} in {embeddings_dir}",
285
+ }
286
+
287
+ # Search through all embedding files for this character
288
+ for embeddings_file in embeddings_dir.glob("*_embeddings.json"):
289
+ category = embeddings_file.stem.replace("_embeddings", "")
290
+
291
+ try:
292
+ with open(embeddings_file, "r", encoding="utf-8") as f:
293
+ embeddings_data = json.load(f)
294
+
295
+ # Search through stored embeddings
296
+ for emb_data in embeddings_data.get("embeddings", []):
297
+ similarity = self._cosine_similarity(query_embedding, emb_data["embedding"])
298
+
299
+ if similarity > 0.1: # Minimum threshold for semantic similarity
300
+ results.append(
301
+ {
302
+ "content": emb_data["text"],
303
+ "content_type": "relevant_memory",
304
+ "semantic_score": similarity,
305
+ "category": category,
306
+ "item_id": emb_data.get("item_id", ""),
307
+ "memory_id": emb_data.get("memory_id", ""),
308
+ "line_number": emb_data.get("line_number", 0),
309
+ "length": len(emb_data["text"]),
310
+ "metadata": emb_data.get("metadata", {}),
311
+ }
312
+ )
313
+
314
+ except Exception as e:
315
+ logger.warning(f"Failed to process embeddings for {category}: {e}")
316
+
317
+ # Sort by semantic score and get top-k
318
+ results.sort(key=lambda x: x["semantic_score"], reverse=True)
319
+ top_memories = results[:top_k]
320
+
321
+ return {
322
+ "success": True,
323
+ "method": "retrieve_relevant_memories",
324
+ "query": query,
325
+ "top_k": top_k,
326
+ "semantic_search_enabled": self.semantic_search_enabled,
327
+ "results": top_memories,
328
+ "total_items": len(top_memories),
329
+ "total_candidates": len(results),
330
+ "message": f"Retrieved top {len(top_memories)} memories from {len(results)} candidates",
331
+ }
332
+
333
+ except Exception as e:
334
+ logger.error(f"Error in retrieve_relevant_memories for {agent_id}:{user_id}: {e}")
335
+ return {
336
+ "success": False,
337
+ "error": str(e),
338
+ "method": "retrieve_relevant_memories",
339
+ "query": query,
340
+ }
341
+
342
+ def _cosine_similarity(self, vec1: List[float], vec2: List[float]) -> float:
343
+ """Calculate cosine similarity between two vectors"""
344
+ try:
345
+ if len(vec1) != len(vec2):
346
+ return 0.0
347
+
348
+ dot_product = sum(a * b for a, b in zip(vec1, vec2))
349
+ magnitude1 = math.sqrt(sum(a * a for a in vec1))
350
+ magnitude2 = math.sqrt(sum(a * a for a in vec2))
351
+
352
+ if magnitude1 == 0 or magnitude2 == 0:
353
+ return 0.0
354
+
355
+ return dot_product / (magnitude1 * magnitude2)
356
+
357
+ except Exception as e:
358
+ logger.warning(f"Cosine similarity calculation failed: {e}")
359
+ return 0.0
360
+
361
+ def get_status(self) -> Dict[str, Any]:
362
+ """Get status information about the recall agent"""
363
+ return {
364
+ "agent_name": "recall_agent",
365
+ "agent_type": "enhanced_retrieval",
366
+ "memory_types": list(self.memory_types.keys()),
367
+ "memory_dir": str(self.memory_dir),
368
+ "semantic_search_enabled": self.semantic_search_enabled,
369
+ "default_categories": self.default_categories,
370
+ "config_source": "markdown_config.py",
371
+ "retrieval_methods": [
372
+ "retrieve_default_category(agent_id, user_id)",
373
+ "retrieve_relevant_category(agent_id, user_id, query, top_k) - excludes profile/event/activity",
374
+ "retrieve_relevant_memories(agent_id, user_id, query, top_k)",
375
+ ],
376
+ }