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.
- noesium/core/__init__.py +4 -0
- noesium/core/agent/__init__.py +14 -0
- noesium/core/agent/base.py +227 -0
- noesium/core/consts.py +6 -0
- noesium/core/goalith/conflict/conflict.py +104 -0
- noesium/core/goalith/conflict/detector.py +53 -0
- noesium/core/goalith/decomposer/__init__.py +6 -0
- noesium/core/goalith/decomposer/base.py +46 -0
- noesium/core/goalith/decomposer/callable_decomposer.py +65 -0
- noesium/core/goalith/decomposer/llm_decomposer.py +326 -0
- noesium/core/goalith/decomposer/prompts.py +140 -0
- noesium/core/goalith/decomposer/simple_decomposer.py +61 -0
- noesium/core/goalith/errors.py +22 -0
- noesium/core/goalith/goalgraph/graph.py +526 -0
- noesium/core/goalith/goalgraph/node.py +179 -0
- noesium/core/goalith/replanner/base.py +31 -0
- noesium/core/goalith/replanner/replanner.py +36 -0
- noesium/core/goalith/service.py +26 -0
- noesium/core/llm/__init__.py +154 -0
- noesium/core/llm/base.py +152 -0
- noesium/core/llm/litellm.py +528 -0
- noesium/core/llm/llamacpp.py +487 -0
- noesium/core/llm/message.py +184 -0
- noesium/core/llm/ollama.py +459 -0
- noesium/core/llm/openai.py +520 -0
- noesium/core/llm/openrouter.py +89 -0
- noesium/core/llm/prompt.py +551 -0
- noesium/core/memory/__init__.py +11 -0
- noesium/core/memory/base.py +464 -0
- noesium/core/memory/memu/__init__.py +24 -0
- noesium/core/memory/memu/config/__init__.py +26 -0
- noesium/core/memory/memu/config/activity/config.py +46 -0
- noesium/core/memory/memu/config/event/config.py +46 -0
- noesium/core/memory/memu/config/markdown_config.py +241 -0
- noesium/core/memory/memu/config/profile/config.py +48 -0
- noesium/core/memory/memu/llm_adapter.py +129 -0
- noesium/core/memory/memu/memory/__init__.py +31 -0
- noesium/core/memory/memu/memory/actions/__init__.py +40 -0
- noesium/core/memory/memu/memory/actions/add_activity_memory.py +299 -0
- noesium/core/memory/memu/memory/actions/base_action.py +342 -0
- noesium/core/memory/memu/memory/actions/cluster_memories.py +262 -0
- noesium/core/memory/memu/memory/actions/generate_suggestions.py +198 -0
- noesium/core/memory/memu/memory/actions/get_available_categories.py +66 -0
- noesium/core/memory/memu/memory/actions/link_related_memories.py +515 -0
- noesium/core/memory/memu/memory/actions/run_theory_of_mind.py +254 -0
- noesium/core/memory/memu/memory/actions/update_memory_with_suggestions.py +514 -0
- noesium/core/memory/memu/memory/embeddings.py +130 -0
- noesium/core/memory/memu/memory/file_manager.py +306 -0
- noesium/core/memory/memu/memory/memory_agent.py +578 -0
- noesium/core/memory/memu/memory/recall_agent.py +376 -0
- noesium/core/memory/memu/memory_store.py +628 -0
- noesium/core/memory/models.py +149 -0
- noesium/core/msgbus/__init__.py +12 -0
- noesium/core/msgbus/base.py +395 -0
- noesium/core/orchestrix/__init__.py +0 -0
- noesium/core/py.typed +0 -0
- noesium/core/routing/__init__.py +20 -0
- noesium/core/routing/base.py +66 -0
- noesium/core/routing/router.py +241 -0
- noesium/core/routing/strategies/__init__.py +9 -0
- noesium/core/routing/strategies/dynamic_complexity.py +361 -0
- noesium/core/routing/strategies/self_assessment.py +147 -0
- noesium/core/routing/types.py +38 -0
- noesium/core/toolify/__init__.py +39 -0
- noesium/core/toolify/base.py +360 -0
- noesium/core/toolify/config.py +138 -0
- noesium/core/toolify/mcp_integration.py +275 -0
- noesium/core/toolify/registry.py +214 -0
- noesium/core/toolify/toolkits/__init__.py +1 -0
- noesium/core/tracing/__init__.py +37 -0
- noesium/core/tracing/langgraph_hooks.py +308 -0
- noesium/core/tracing/opik_tracing.py +144 -0
- noesium/core/tracing/token_tracker.py +166 -0
- noesium/core/utils/__init__.py +10 -0
- noesium/core/utils/logging.py +172 -0
- noesium/core/utils/statistics.py +12 -0
- noesium/core/utils/typing.py +17 -0
- noesium/core/vector_store/__init__.py +79 -0
- noesium/core/vector_store/base.py +94 -0
- noesium/core/vector_store/pgvector.py +304 -0
- noesium/core/vector_store/weaviate.py +383 -0
- noesium-0.1.0.dist-info/METADATA +525 -0
- noesium-0.1.0.dist-info/RECORD +86 -0
- noesium-0.1.0.dist-info/WHEEL +5 -0
- noesium-0.1.0.dist-info/licenses/LICENSE +21 -0
- noesium-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MemU Memory Agent - Action-Based Architecture
|
|
3
|
+
|
|
4
|
+
Modern memory management system with function calling interface.
|
|
5
|
+
Each operation is implemented as a separate action module for modularity and maintainability.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
import threading
|
|
11
|
+
import time
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
15
|
+
|
|
16
|
+
from ..config.markdown_config import get_config_manager
|
|
17
|
+
from ..llm_adapter import BaseLLMClient
|
|
18
|
+
from .actions import ACTION_REGISTRY
|
|
19
|
+
from .embeddings import create_embedding_client
|
|
20
|
+
from .file_manager import MemoryFileManager
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MemoryCore:
|
|
26
|
+
"""
|
|
27
|
+
Core memory functionality shared across all actions
|
|
28
|
+
|
|
29
|
+
Provides the shared resources and utilities that actions need:
|
|
30
|
+
- LLM client
|
|
31
|
+
- Storage manager
|
|
32
|
+
- Embedding client
|
|
33
|
+
- Configuration
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
llm_client: BaseLLMClient = None,
|
|
39
|
+
memory_dir: str = "memu/server/memory",
|
|
40
|
+
enable_embeddings: bool = True,
|
|
41
|
+
agent_id: str = "",
|
|
42
|
+
user_id: str = "",
|
|
43
|
+
):
|
|
44
|
+
self.llm_client = llm_client
|
|
45
|
+
self.memory_dir = Path(memory_dir)
|
|
46
|
+
self._stop_flag = threading.Event()
|
|
47
|
+
|
|
48
|
+
# Initialize config manager and processing order (basic only)
|
|
49
|
+
self.config_manager = get_config_manager()
|
|
50
|
+
self.processing_order = self.config_manager.get_processing_order()
|
|
51
|
+
|
|
52
|
+
# Initialize file-based storage manager with context (shared by actions)
|
|
53
|
+
self.storage_manager = MemoryFileManager(memory_dir, agent_id=agent_id, user_id=user_id)
|
|
54
|
+
# Initialize memory types from storage manager (includes cluster for this context)
|
|
55
|
+
self.memory_types = self.storage_manager.memory_types
|
|
56
|
+
|
|
57
|
+
# Initialize embedding client using the LLM client
|
|
58
|
+
self.enable_embeddings = enable_embeddings
|
|
59
|
+
if enable_embeddings and llm_client:
|
|
60
|
+
try:
|
|
61
|
+
self.embedding_client = create_embedding_client(llm_client)
|
|
62
|
+
self.embeddings_enabled = True
|
|
63
|
+
logger.info("Embeddings enabled for semantic retrieval using LLM client")
|
|
64
|
+
except Exception as e:
|
|
65
|
+
logger.warning(f"Failed to initialize embedding client with LLM client: {e}. Embeddings disabled.")
|
|
66
|
+
self.embedding_client = None
|
|
67
|
+
self.embeddings_enabled = False
|
|
68
|
+
else:
|
|
69
|
+
self.embedding_client = None
|
|
70
|
+
self.embeddings_enabled = False
|
|
71
|
+
if enable_embeddings and not llm_client:
|
|
72
|
+
logger.warning("Embeddings requested but no LLM client provided. Embeddings disabled.")
|
|
73
|
+
|
|
74
|
+
# Create storage directories
|
|
75
|
+
self.embeddings_dir = self.memory_dir / "embeddings"
|
|
76
|
+
self.embeddings_dir.mkdir(exist_ok=True)
|
|
77
|
+
|
|
78
|
+
logger.info(
|
|
79
|
+
f"Memory Core initialized: {len(self.memory_types)} memory types, embeddings: {self.embeddings_enabled}"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class MemoryAgent:
|
|
84
|
+
"""
|
|
85
|
+
Modern Memory Agent with Action-Based Architecture
|
|
86
|
+
|
|
87
|
+
Uses independent action modules for each memory operation:
|
|
88
|
+
- add_activity_memory: Add new activity memory content with strict formatting
|
|
89
|
+
- get_available_categories: Get available categories (excluding activity)
|
|
90
|
+
- link_related_memories: Find and link related memories using embedding search
|
|
91
|
+
- generate_memory_suggestions: Generate suggestions for memory categories
|
|
92
|
+
- update_memory_with_suggestions: Update memory categories based on suggestions
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
Each action is implemented as a separate module in the actions/ directory.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
def __init__(
|
|
99
|
+
self,
|
|
100
|
+
*,
|
|
101
|
+
llm_client: Optional[BaseLLMClient] = None,
|
|
102
|
+
agent_id: str = "default_agent",
|
|
103
|
+
user_id: str = "default_user",
|
|
104
|
+
memory_dir: str = "default_memory",
|
|
105
|
+
enable_embeddings: bool = True,
|
|
106
|
+
):
|
|
107
|
+
"""
|
|
108
|
+
Initialize Memory Agent
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
memory_dir: Directory to store memory files
|
|
112
|
+
enable_embeddings: Whether to generate embeddings for semantic search
|
|
113
|
+
"""
|
|
114
|
+
# Initialize LLM client if not provided
|
|
115
|
+
if llm_client is None:
|
|
116
|
+
try:
|
|
117
|
+
from ..llm_adapter import _get_llm_client_memu_compatible
|
|
118
|
+
|
|
119
|
+
llm_client = _get_llm_client_memu_compatible()
|
|
120
|
+
except Exception as e:
|
|
121
|
+
logger.warning(f"Failed to initialize default LLM client: {e}")
|
|
122
|
+
|
|
123
|
+
self.llm_client = llm_client
|
|
124
|
+
|
|
125
|
+
# Initialize memory core
|
|
126
|
+
self.memory_core = MemoryCore(
|
|
127
|
+
llm_client=llm_client,
|
|
128
|
+
memory_dir=memory_dir,
|
|
129
|
+
enable_embeddings=enable_embeddings,
|
|
130
|
+
agent_id=agent_id,
|
|
131
|
+
user_id=user_id,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Initialize actions
|
|
135
|
+
self.actions = {}
|
|
136
|
+
self._load_actions()
|
|
137
|
+
|
|
138
|
+
# Build function registry for compatibility
|
|
139
|
+
self.function_registry = self._build_function_registry()
|
|
140
|
+
|
|
141
|
+
logger.info(f"Memory Agent initialized: {len(self.actions)} actions available")
|
|
142
|
+
|
|
143
|
+
def _load_actions(self):
|
|
144
|
+
"""Load all available actions from the registry"""
|
|
145
|
+
for action_name, action_class in ACTION_REGISTRY.items():
|
|
146
|
+
try:
|
|
147
|
+
action_instance = action_class(self.memory_core)
|
|
148
|
+
self.actions[action_name] = action_instance
|
|
149
|
+
logger.debug(f"Loaded action: {action_name}")
|
|
150
|
+
except Exception as e:
|
|
151
|
+
logger.error(f"Failed to load action {action_name}: {e}")
|
|
152
|
+
|
|
153
|
+
def _build_function_registry(self) -> Dict[str, Callable]:
|
|
154
|
+
"""Build registry of callable functions from actions"""
|
|
155
|
+
registry = {}
|
|
156
|
+
for action_name, action in self.actions.items():
|
|
157
|
+
registry[action_name] = action.execute
|
|
158
|
+
return registry
|
|
159
|
+
|
|
160
|
+
# ================================
|
|
161
|
+
# Smart Conversation Processing
|
|
162
|
+
# ================================
|
|
163
|
+
|
|
164
|
+
def run(
|
|
165
|
+
self,
|
|
166
|
+
conversation: List[Dict[str, str]],
|
|
167
|
+
character_name: str,
|
|
168
|
+
max_iterations: int = 20,
|
|
169
|
+
session_date: str = None,
|
|
170
|
+
) -> Dict[str, Any]:
|
|
171
|
+
"""
|
|
172
|
+
Intelligent conversation processing using iterative function calling
|
|
173
|
+
|
|
174
|
+
This function allows the LLM to autonomously decide which memory operations to perform
|
|
175
|
+
through function calling, iterating until the LLM decides it's complete or max iterations reached.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
conversation: List of conversation messages
|
|
179
|
+
character_name: Name of the character to store memories for
|
|
180
|
+
max_iterations: Maximum number of function calling iterations (default: 20)
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Dict containing processing results and file paths
|
|
184
|
+
"""
|
|
185
|
+
try:
|
|
186
|
+
if not conversation or not isinstance(conversation, list):
|
|
187
|
+
return {
|
|
188
|
+
"success": False,
|
|
189
|
+
"error": "Invalid conversation format. Expected list of message dictionaries.",
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if not character_name:
|
|
193
|
+
return {"success": False, "error": "Character name is required."}
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
session_date = datetime.fromisoformat(session_date).strftime("%Y-%m-%d")
|
|
197
|
+
except Exception:
|
|
198
|
+
logger.info(f"session date unavaiable, use system datetime")
|
|
199
|
+
session_date = datetime.now().strftime("%Y-%m-%d")
|
|
200
|
+
|
|
201
|
+
logger.info(f"🚀 Starting iterative conversation processing for {character_name}")
|
|
202
|
+
|
|
203
|
+
# Convert conversation to text for processing
|
|
204
|
+
conversation_text = self._convert_conversation_to_text(conversation)
|
|
205
|
+
|
|
206
|
+
# Initialize results tracking
|
|
207
|
+
results = {
|
|
208
|
+
"success": True,
|
|
209
|
+
"character_name": character_name,
|
|
210
|
+
"session_date": session_date,
|
|
211
|
+
"conversation_length": len(conversation),
|
|
212
|
+
"iterations": 0,
|
|
213
|
+
"function_calls": [],
|
|
214
|
+
# "files_generated": [],
|
|
215
|
+
"processing_log": [],
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
# Get function schemas for LLM
|
|
219
|
+
function_schemas = self.get_functions_schema()
|
|
220
|
+
|
|
221
|
+
# Build initial system message
|
|
222
|
+
system_message = f"""You are a memory processing agent. Follow this structured process to analyze and store conversation information for "{character_name}":
|
|
223
|
+
|
|
224
|
+
CONVERSATION TO PROCESS:
|
|
225
|
+
{conversation_text}
|
|
226
|
+
|
|
227
|
+
CHARACTER: {character_name}
|
|
228
|
+
SESSION DATE: {session_date}
|
|
229
|
+
|
|
230
|
+
PROCESSING WORKFLOW:
|
|
231
|
+
1. STORE TO ACTIVITY: Call add_activity_memory with the COMPLETE RAW CONVERSATION TEXT as the 'content' parameter. This will automatically append to existing activity memories. DO NOT extract, modify, or summarize the conversation - pass the entire original conversation text exactly as shown above.
|
|
232
|
+
|
|
233
|
+
2. THEORY OF MIND: Call run_theory_of_mind to analyze the subtle information behind the conversation and extract the theory of mind of the characters.
|
|
234
|
+
|
|
235
|
+
3. GENERATE SUGGESTIONS: Call generate_memory_suggestions with the available memory items to get suggestions for what should be added to each category.
|
|
236
|
+
|
|
237
|
+
4. UPDATE CATEGORIES: For each category that should be updated (based on suggestions), call update_memory_with_suggestions to update that category with the new memory items and suggestions. This will return structured modifications.
|
|
238
|
+
|
|
239
|
+
5. LINK MEMORIES: For each category that was modified, call link_related_memories with link_all_items=true and write_to_memory=true to add relevant links between ALL memories in that category.
|
|
240
|
+
|
|
241
|
+
6. CLUSTER MEMORIES: Call cluster_memories to cluster the memories into different categories.
|
|
242
|
+
|
|
243
|
+
IMPORTANT GUIDELINES:
|
|
244
|
+
- Step 1: CRITICAL: For add_activity_memory, the 'content' parameter MUST be the complete original conversation text exactly as shown above. Do NOT modify, extract, or summarize it.
|
|
245
|
+
- Step 2: Use both the original conversation and the extracted activity memoryitems from step 1 for the theory of mind analysis
|
|
246
|
+
- Step 3: Use BOTH the extracted memory items from step 1 and theory-of-mind items from step 2 for generating suggestions. You can simply concatenate the two lists of memory items and pass them to the subsequent function.
|
|
247
|
+
- Step 4: Use the memory suggestions from step 3 to update EVERY memory categories in suggestions.
|
|
248
|
+
- Step 5-6: Use the new memory items returned from step 4 for linking and clustering memories. DO NOT include the memory items returned from step 1 and 2.
|
|
249
|
+
- Each memory item should have its own memory_id and focused content
|
|
250
|
+
- Follow the suggestions when updating categories
|
|
251
|
+
- The update_memory_with_suggestions function will return structured format with memory_id and content
|
|
252
|
+
- Always link related memories after updating categories by setting link_all_items=true and write_to_memory=true
|
|
253
|
+
|
|
254
|
+
Start with step 1 and work through the process systematically. When you complete all steps, respond with "PROCESSING_COMPLETE"."""
|
|
255
|
+
|
|
256
|
+
# Start iterative function calling
|
|
257
|
+
messages = [{"role": "system", "content": system_message}]
|
|
258
|
+
|
|
259
|
+
for iteration in range(max_iterations):
|
|
260
|
+
results["iterations"] = iteration + 1
|
|
261
|
+
logger.info(f"🔄 Iteration {iteration + 1}/{max_iterations}")
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
# Call LLM with function calling enabled
|
|
265
|
+
response = self.memory_core.llm_client.chat_completion(
|
|
266
|
+
messages=messages,
|
|
267
|
+
tools=[{"type": "function", "function": schema} for schema in function_schemas],
|
|
268
|
+
tool_choice="auto",
|
|
269
|
+
temperature=0.3,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
if not response.success:
|
|
273
|
+
logger.error(f"LLM call failed: {response.error}")
|
|
274
|
+
break
|
|
275
|
+
|
|
276
|
+
# Add assistant response to conversation
|
|
277
|
+
assistant_message = {
|
|
278
|
+
"role": "assistant",
|
|
279
|
+
"content": response.content or "",
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
# Check if processing is complete
|
|
283
|
+
if response.content and "PROCESSING_COMPLETE" in response.content:
|
|
284
|
+
logger.info("✅ LLM indicated processing is complete")
|
|
285
|
+
results["processing_log"].append(f"Iteration {iteration + 1}: Processing completed")
|
|
286
|
+
break
|
|
287
|
+
|
|
288
|
+
# Handle tool calls if present
|
|
289
|
+
if response.tool_calls:
|
|
290
|
+
assistant_message["tool_calls"] = response.tool_calls
|
|
291
|
+
messages.append(assistant_message)
|
|
292
|
+
|
|
293
|
+
# Execute each tool call
|
|
294
|
+
for tool_call in response.tool_calls:
|
|
295
|
+
function_name = tool_call.function.name
|
|
296
|
+
|
|
297
|
+
try:
|
|
298
|
+
arguments = json.loads(tool_call.function.arguments)
|
|
299
|
+
except json.JSONDecodeError as e:
|
|
300
|
+
logger.error(f"Failed to parse function arguments: {e}")
|
|
301
|
+
logger.error(f"Function name: {function_name}")
|
|
302
|
+
logger.error(f"Arguments raw: {repr(tool_call.function.arguments)}")
|
|
303
|
+
continue
|
|
304
|
+
|
|
305
|
+
logger.info(f"🔧 Calling function: {function_name}")
|
|
306
|
+
|
|
307
|
+
# Execute the function call
|
|
308
|
+
time_start = time.time()
|
|
309
|
+
function_result = self.call_function(function_name, arguments)
|
|
310
|
+
time_end = time.time()
|
|
311
|
+
|
|
312
|
+
logger.info(f" Function time used: {time_end - time_start:.2f} seconds")
|
|
313
|
+
|
|
314
|
+
# Track function call
|
|
315
|
+
call_record = {
|
|
316
|
+
"iteration": iteration + 1,
|
|
317
|
+
"function_name": function_name,
|
|
318
|
+
"arguments": arguments,
|
|
319
|
+
"result": function_result,
|
|
320
|
+
}
|
|
321
|
+
results["function_calls"].append(call_record)
|
|
322
|
+
|
|
323
|
+
# Add tool result to conversation
|
|
324
|
+
tool_message = {
|
|
325
|
+
"role": "tool",
|
|
326
|
+
"tool_call_id": getattr(tool_call, "id", f"call_{iteration}_{function_name}"),
|
|
327
|
+
"content": json.dumps(function_result, ensure_ascii=False),
|
|
328
|
+
}
|
|
329
|
+
messages.append(tool_message)
|
|
330
|
+
|
|
331
|
+
results["processing_log"].append(
|
|
332
|
+
f"Iteration {iteration + 1}: Called {function_name} - "
|
|
333
|
+
+ (
|
|
334
|
+
"Success"
|
|
335
|
+
if function_result.get("success")
|
|
336
|
+
else f"Failed: {function_result.get('error', 'Unknown error')}"
|
|
337
|
+
)
|
|
338
|
+
)
|
|
339
|
+
else:
|
|
340
|
+
# No tool calls, add response and continue
|
|
341
|
+
messages.append(assistant_message)
|
|
342
|
+
if response.content:
|
|
343
|
+
results["processing_log"].append(f"Iteration {iteration + 1}: {response.content[:100]}...")
|
|
344
|
+
|
|
345
|
+
except Exception as e:
|
|
346
|
+
logger.error(f"Error in iteration {iteration + 1}: {e}")
|
|
347
|
+
results["processing_log"].append(f"Iteration {iteration + 1}: Error - {str(e)}")
|
|
348
|
+
break
|
|
349
|
+
|
|
350
|
+
# Finalize results
|
|
351
|
+
if results["iterations"] >= max_iterations:
|
|
352
|
+
logger.warning(f"⚠️ Reached maximum iterations ({max_iterations})")
|
|
353
|
+
results["processing_log"].append(f"Reached maximum iterations ({max_iterations})")
|
|
354
|
+
|
|
355
|
+
logger.info(f"🎉 Conversation processing completed after {results['iterations']} iterations")
|
|
356
|
+
# logger.info(f"📁 Generated {len(results['files_generated'])} files")
|
|
357
|
+
logger.info(f"🔧 Made {len(results['function_calls'])} function calls")
|
|
358
|
+
|
|
359
|
+
return results
|
|
360
|
+
|
|
361
|
+
except Exception as e:
|
|
362
|
+
logger.error(f"Error in conversation processing: {e}")
|
|
363
|
+
return {
|
|
364
|
+
"success": False,
|
|
365
|
+
"error": str(e),
|
|
366
|
+
"character_name": character_name,
|
|
367
|
+
"timestamp": datetime.now().isoformat(),
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
def _convert_conversation_to_text(self, conversation: List[Dict]) -> str:
|
|
371
|
+
"""Convert conversation list to text format for LLM processing"""
|
|
372
|
+
if not conversation or not isinstance(conversation, list):
|
|
373
|
+
return ""
|
|
374
|
+
|
|
375
|
+
text_parts = []
|
|
376
|
+
for message in conversation:
|
|
377
|
+
role = message.get("role", "unknown")
|
|
378
|
+
content = message.get("content", "")
|
|
379
|
+
text_parts.append(f"{role.upper()}: {content.strip()}")
|
|
380
|
+
|
|
381
|
+
return "\n".join(text_parts)
|
|
382
|
+
|
|
383
|
+
# ================================
|
|
384
|
+
# Function Calling Interface
|
|
385
|
+
# ================================
|
|
386
|
+
|
|
387
|
+
def get_functions_schema(self) -> List[Dict[str, Any]]:
|
|
388
|
+
"""
|
|
389
|
+
Get OpenAI-compatible function schemas for all memory functions
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
List of function schemas that can be used with OpenAI function calling
|
|
393
|
+
"""
|
|
394
|
+
schemas = []
|
|
395
|
+
for action in self.actions.values():
|
|
396
|
+
try:
|
|
397
|
+
schema = action.get_schema()
|
|
398
|
+
schemas.append(schema)
|
|
399
|
+
except Exception as e:
|
|
400
|
+
logger.error(f"Failed to get schema for action {action.action_name}: {e}")
|
|
401
|
+
return schemas
|
|
402
|
+
|
|
403
|
+
def call_function(self, function_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
404
|
+
"""
|
|
405
|
+
Call a memory function with the provided arguments
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
function_name: Name of the function to call
|
|
409
|
+
arguments: Arguments to pass to the function
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
Dict containing the function result
|
|
413
|
+
"""
|
|
414
|
+
try:
|
|
415
|
+
if function_name not in self.actions:
|
|
416
|
+
return {
|
|
417
|
+
"success": False,
|
|
418
|
+
"error": f"Unknown function: {function_name}",
|
|
419
|
+
"available_functions": list(self.actions.keys()),
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
# Get the action instance
|
|
423
|
+
action = self.actions[function_name]
|
|
424
|
+
|
|
425
|
+
# Execute the action with arguments
|
|
426
|
+
result = action.execute(**arguments)
|
|
427
|
+
|
|
428
|
+
logger.debug(f"Function call successful: {function_name}")
|
|
429
|
+
return result
|
|
430
|
+
|
|
431
|
+
except Exception as e:
|
|
432
|
+
error_result = {
|
|
433
|
+
"success": False,
|
|
434
|
+
"error": str(e),
|
|
435
|
+
"function_name": function_name,
|
|
436
|
+
"timestamp": datetime.now().isoformat(),
|
|
437
|
+
}
|
|
438
|
+
logger.error(f"Function call failed: {function_name} - {repr(e)}")
|
|
439
|
+
import traceback
|
|
440
|
+
|
|
441
|
+
traceback.print_exc()
|
|
442
|
+
return error_result
|
|
443
|
+
|
|
444
|
+
def validate_function_call(self, function_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
445
|
+
"""
|
|
446
|
+
Validate a function call before execution
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
function_name: Name of the function
|
|
450
|
+
arguments: Arguments for the function
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
Dict with validation result
|
|
454
|
+
"""
|
|
455
|
+
try:
|
|
456
|
+
if function_name not in self.actions:
|
|
457
|
+
return {
|
|
458
|
+
"valid": False,
|
|
459
|
+
"error": f"Unknown function: {function_name}",
|
|
460
|
+
"available_functions": list(self.actions.keys()),
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
# Use the action's validation method
|
|
464
|
+
action = self.actions[function_name]
|
|
465
|
+
return action.validate_arguments(arguments)
|
|
466
|
+
|
|
467
|
+
except Exception as e:
|
|
468
|
+
return {"valid": False, "error": f"Validation error: {str(e)}"}
|
|
469
|
+
|
|
470
|
+
# ================================
|
|
471
|
+
# Direct Method Access (Compatibility)
|
|
472
|
+
# ================================
|
|
473
|
+
|
|
474
|
+
def get_available_categories(self) -> Dict[str, Any]:
|
|
475
|
+
"""Get available memory categories"""
|
|
476
|
+
return self.actions["get_available_categories"].execute()
|
|
477
|
+
|
|
478
|
+
def link_related_memories(
|
|
479
|
+
self,
|
|
480
|
+
character_name: str,
|
|
481
|
+
category: str,
|
|
482
|
+
memory_id: str = None,
|
|
483
|
+
top_k: int = 5,
|
|
484
|
+
min_similarity: float = 0.3,
|
|
485
|
+
search_categories: List[str] = None,
|
|
486
|
+
link_all_items: bool = False,
|
|
487
|
+
write_to_memory: bool = False,
|
|
488
|
+
) -> Dict[str, Any]:
|
|
489
|
+
"""Find and link related memories using embedding search"""
|
|
490
|
+
return self.actions["link_related_memories"].execute(
|
|
491
|
+
character_name=character_name,
|
|
492
|
+
category=category,
|
|
493
|
+
memory_id=memory_id,
|
|
494
|
+
top_k=top_k,
|
|
495
|
+
min_similarity=min_similarity,
|
|
496
|
+
search_categories=search_categories,
|
|
497
|
+
link_all_items=link_all_items,
|
|
498
|
+
write_to_memory=write_to_memory,
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
# ================================
|
|
502
|
+
# Utility Methods
|
|
503
|
+
# ================================
|
|
504
|
+
|
|
505
|
+
def get_function_list(self) -> List[str]:
|
|
506
|
+
"""Get list of available function names"""
|
|
507
|
+
return list(self.actions.keys())
|
|
508
|
+
|
|
509
|
+
def get_function_description(self, function_name: str) -> str:
|
|
510
|
+
"""Get description for a specific function"""
|
|
511
|
+
if function_name in self.actions:
|
|
512
|
+
try:
|
|
513
|
+
schema = self.actions[function_name].get_schema()
|
|
514
|
+
return schema.get("description", "No description available")
|
|
515
|
+
except Exception:
|
|
516
|
+
return "Description not available"
|
|
517
|
+
return "Function not found"
|
|
518
|
+
|
|
519
|
+
def get_action_instance(self, action_name: str):
|
|
520
|
+
"""Get a specific action instance (for advanced usage)"""
|
|
521
|
+
return self.actions.get(action_name)
|
|
522
|
+
|
|
523
|
+
def stop_action(self) -> Dict[str, Any]:
|
|
524
|
+
"""
|
|
525
|
+
Stop current operations
|
|
526
|
+
|
|
527
|
+
Returns:
|
|
528
|
+
Dict containing stop result
|
|
529
|
+
"""
|
|
530
|
+
try:
|
|
531
|
+
self.memory_core._stop_flag.set()
|
|
532
|
+
logger.info("Memory Agent: Stop flag set")
|
|
533
|
+
|
|
534
|
+
return {
|
|
535
|
+
"success": True,
|
|
536
|
+
"message": "Stop signal sent to Memory Agent operations",
|
|
537
|
+
"timestamp": datetime.now().isoformat(),
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
except Exception as e:
|
|
541
|
+
logger.error(f"Error stopping operations: {e}")
|
|
542
|
+
return {"success": False, "error": str(e)}
|
|
543
|
+
|
|
544
|
+
def reset_stop_flag(self):
|
|
545
|
+
"""Reset the stop flag to allow new operations"""
|
|
546
|
+
self.memory_core._stop_flag.clear()
|
|
547
|
+
logger.debug("Memory Agent: Stop flag reset")
|
|
548
|
+
|
|
549
|
+
def get_status(self) -> Dict[str, Any]:
|
|
550
|
+
"""Get status information about the memory agent"""
|
|
551
|
+
return {
|
|
552
|
+
"agent_name": "memory_agent",
|
|
553
|
+
"architecture": "action_based",
|
|
554
|
+
"memory_types": list(self.memory_core.memory_types.keys()),
|
|
555
|
+
"processing_order": self.memory_core.processing_order,
|
|
556
|
+
"storage_type": "file_system",
|
|
557
|
+
"memory_dir": str(self.memory_core.memory_dir),
|
|
558
|
+
"config_source": "memory_cat_config.yaml (system + custom)",
|
|
559
|
+
"total_actions": len(self.actions),
|
|
560
|
+
"available_actions": list(self.actions.keys()),
|
|
561
|
+
"total_functions": len(self.function_registry),
|
|
562
|
+
"available_functions": list(self.function_registry.keys()),
|
|
563
|
+
"function_calling_enabled": True,
|
|
564
|
+
"stop_flag_set": self.memory_core._stop_flag.is_set(),
|
|
565
|
+
"embedding_capabilities": {
|
|
566
|
+
"embeddings_enabled": self.memory_core.embeddings_enabled,
|
|
567
|
+
"embedding_client": (
|
|
568
|
+
str(type(self.memory_core.embedding_client)) if self.memory_core.embedding_client else None
|
|
569
|
+
),
|
|
570
|
+
"embeddings_directory": str(self.memory_core.embeddings_dir),
|
|
571
|
+
},
|
|
572
|
+
"config_details": {
|
|
573
|
+
"total_file_types": len(self.memory_core.memory_types),
|
|
574
|
+
"categories_from_config": True,
|
|
575
|
+
"config_structure": "Dynamic folder configuration",
|
|
576
|
+
},
|
|
577
|
+
"last_updated": datetime.now().isoformat(),
|
|
578
|
+
}
|