ambivo-agents 1.0.1__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.
- ambivo_agents/__init__.py +91 -0
- ambivo_agents/agents/__init__.py +21 -0
- ambivo_agents/agents/assistant.py +203 -0
- ambivo_agents/agents/code_executor.py +133 -0
- ambivo_agents/agents/code_executor2.py +222 -0
- ambivo_agents/agents/knowledge_base.py +935 -0
- ambivo_agents/agents/media_editor.py +992 -0
- ambivo_agents/agents/moderator.py +617 -0
- ambivo_agents/agents/simple_web_search.py +404 -0
- ambivo_agents/agents/web_scraper.py +1027 -0
- ambivo_agents/agents/web_search.py +933 -0
- ambivo_agents/agents/youtube_download.py +784 -0
- ambivo_agents/cli.py +699 -0
- ambivo_agents/config/__init__.py +4 -0
- ambivo_agents/config/loader.py +301 -0
- ambivo_agents/core/__init__.py +33 -0
- ambivo_agents/core/base.py +1024 -0
- ambivo_agents/core/history.py +606 -0
- ambivo_agents/core/llm.py +333 -0
- ambivo_agents/core/memory.py +640 -0
- ambivo_agents/executors/__init__.py +8 -0
- ambivo_agents/executors/docker_executor.py +108 -0
- ambivo_agents/executors/media_executor.py +237 -0
- ambivo_agents/executors/youtube_executor.py +404 -0
- ambivo_agents/services/__init__.py +6 -0
- ambivo_agents/services/agent_service.py +605 -0
- ambivo_agents/services/factory.py +370 -0
- ambivo_agents-1.0.1.dist-info/METADATA +1090 -0
- ambivo_agents-1.0.1.dist-info/RECORD +33 -0
- ambivo_agents-1.0.1.dist-info/WHEEL +5 -0
- ambivo_agents-1.0.1.dist-info/entry_points.txt +3 -0
- ambivo_agents-1.0.1.dist-info/licenses/LICENSE +21 -0
- ambivo_agents-1.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,617 @@
|
|
1
|
+
# ambivo_agents/agents/moderator.py
|
2
|
+
"""
|
3
|
+
ModeratorAgent: Complete intelligent orchestrator that routes queries to specialized agents
|
4
|
+
FIXED VERSION - All methods implemented correctly
|
5
|
+
"""
|
6
|
+
|
7
|
+
import asyncio
|
8
|
+
import json
|
9
|
+
import uuid
|
10
|
+
import time
|
11
|
+
import logging
|
12
|
+
from typing import Dict, List, Any, Optional, Union
|
13
|
+
from datetime import datetime
|
14
|
+
from dataclasses import dataclass
|
15
|
+
|
16
|
+
from ..core.base import BaseAgent, AgentRole, AgentMessage, MessageType, ExecutionContext
|
17
|
+
from ..config.loader import load_config, get_config_section
|
18
|
+
from ..core.history import BaseAgentHistoryMixin, ContextType
|
19
|
+
|
20
|
+
|
21
|
+
@dataclass
|
22
|
+
class AgentResponse:
|
23
|
+
"""Response from an individual agent"""
|
24
|
+
agent_type: str
|
25
|
+
content: str
|
26
|
+
success: bool
|
27
|
+
execution_time: float
|
28
|
+
metadata: Dict[str, Any]
|
29
|
+
error: Optional[str] = None
|
30
|
+
|
31
|
+
|
32
|
+
class ModeratorAgent(BaseAgent, BaseAgentHistoryMixin):
|
33
|
+
"""
|
34
|
+
Complete moderator agent that intelligently routes queries to specialized agents
|
35
|
+
Users only interact with this agent, which handles everything behind the scenes
|
36
|
+
"""
|
37
|
+
|
38
|
+
def __init__(self, agent_id: str = None, memory_manager=None, llm_service=None,
|
39
|
+
enabled_agents: List[str] = None, **kwargs):
|
40
|
+
if agent_id is None:
|
41
|
+
agent_id = f"moderator_{str(uuid.uuid4())[:8]}"
|
42
|
+
|
43
|
+
super().__init__(
|
44
|
+
agent_id=agent_id,
|
45
|
+
role=AgentRole.COORDINATOR,
|
46
|
+
memory_manager=memory_manager,
|
47
|
+
llm_service=llm_service,
|
48
|
+
name="Moderator Agent",
|
49
|
+
description="Intelligent orchestrator that routes queries to specialized agents",
|
50
|
+
**kwargs
|
51
|
+
)
|
52
|
+
|
53
|
+
# Initialize history mixin
|
54
|
+
self.setup_history_mixin()
|
55
|
+
|
56
|
+
# Load configuration
|
57
|
+
self.config = load_config()
|
58
|
+
self.capabilities = self.config.get('agent_capabilities', {})
|
59
|
+
self.moderator_config = self.config.get('moderator', {})
|
60
|
+
|
61
|
+
# Initialize available agents based on config and enabled list
|
62
|
+
self.enabled_agents = enabled_agents or self._get_default_enabled_agents()
|
63
|
+
self.specialized_agents = {}
|
64
|
+
self.agent_routing_patterns = {}
|
65
|
+
|
66
|
+
# Initialize specialized agents
|
67
|
+
self._initialize_specialized_agents()
|
68
|
+
|
69
|
+
# Setup routing intelligence
|
70
|
+
self._setup_routing_patterns()
|
71
|
+
|
72
|
+
logging.info(f"ModeratorAgent initialized with agents: {list(self.specialized_agents.keys())}")
|
73
|
+
|
74
|
+
def _get_default_enabled_agents(self) -> List[str]:
|
75
|
+
"""Get default enabled agents from configuration"""
|
76
|
+
# Check moderator config first
|
77
|
+
if 'default_enabled_agents' in self.moderator_config:
|
78
|
+
return self.moderator_config['default_enabled_agents']
|
79
|
+
|
80
|
+
# Otherwise check capabilities config
|
81
|
+
enabled = []
|
82
|
+
|
83
|
+
if self.capabilities.get('enable_knowledge_base', False):
|
84
|
+
enabled.append('knowledge_base')
|
85
|
+
if self.capabilities.get('enable_web_search', False):
|
86
|
+
enabled.append('web_search')
|
87
|
+
if self.capabilities.get('enable_code_execution', False):
|
88
|
+
enabled.append('code_executor')
|
89
|
+
if self.capabilities.get('enable_media_editor', False):
|
90
|
+
enabled.append('media_editor')
|
91
|
+
if self.capabilities.get('enable_youtube_download', False):
|
92
|
+
enabled.append('youtube_download')
|
93
|
+
if self.capabilities.get('enable_web_scraping', False):
|
94
|
+
enabled.append('web_scraper')
|
95
|
+
|
96
|
+
# Always include assistant for general queries
|
97
|
+
enabled.append('assistant')
|
98
|
+
|
99
|
+
return enabled
|
100
|
+
|
101
|
+
def _is_agent_enabled(self, agent_type: str) -> bool:
|
102
|
+
"""Check if an agent type is enabled"""
|
103
|
+
if agent_type in self.enabled_agents:
|
104
|
+
return True
|
105
|
+
|
106
|
+
# Double-check against capabilities config
|
107
|
+
capability_map = {
|
108
|
+
'knowledge_base': 'enable_knowledge_base',
|
109
|
+
'web_search': 'enable_web_search',
|
110
|
+
'code_executor': 'enable_code_execution',
|
111
|
+
'media_editor': 'enable_media_editor',
|
112
|
+
'youtube_download': 'enable_youtube_download',
|
113
|
+
'web_scraper': 'enable_web_scraping',
|
114
|
+
'assistant': True # Always enabled
|
115
|
+
}
|
116
|
+
|
117
|
+
if agent_type == 'assistant':
|
118
|
+
return True
|
119
|
+
|
120
|
+
capability_key = capability_map.get(agent_type)
|
121
|
+
if capability_key and isinstance(capability_key, str):
|
122
|
+
return self.capabilities.get(capability_key, False)
|
123
|
+
|
124
|
+
return False
|
125
|
+
|
126
|
+
def _get_agent_config(self, agent_type: str) -> Dict[str, Any]:
|
127
|
+
"""Get configuration for specific agent type"""
|
128
|
+
agent_config = {}
|
129
|
+
|
130
|
+
if agent_type == 'knowledge_base':
|
131
|
+
agent_config = self.config.get('knowledge_base', {})
|
132
|
+
elif agent_type == 'web_search':
|
133
|
+
agent_config = self.config.get('web_search', {})
|
134
|
+
elif agent_type == 'media_editor':
|
135
|
+
agent_config = self.config.get('media_editor', {})
|
136
|
+
elif agent_type == 'youtube_download':
|
137
|
+
agent_config = self.config.get('youtube_download', {})
|
138
|
+
elif agent_type == 'web_scraper':
|
139
|
+
agent_config = self.config.get('web_scraping', {})
|
140
|
+
elif agent_type == 'code_executor':
|
141
|
+
agent_config = self.config.get('docker', {})
|
142
|
+
|
143
|
+
return agent_config
|
144
|
+
|
145
|
+
def _initialize_specialized_agents(self):
|
146
|
+
"""Initialize all enabled specialized agents"""
|
147
|
+
# Import agents dynamically to avoid circular imports
|
148
|
+
try:
|
149
|
+
from . import (
|
150
|
+
KnowledgeBaseAgent, WebSearchAgent, CodeExecutorAgent,
|
151
|
+
MediaEditorAgent, YouTubeDownloadAgent, WebScraperAgent, AssistantAgent
|
152
|
+
)
|
153
|
+
except ImportError:
|
154
|
+
# Fallback individual imports
|
155
|
+
try:
|
156
|
+
from .knowledge_base import KnowledgeBaseAgent
|
157
|
+
except ImportError:
|
158
|
+
KnowledgeBaseAgent = None
|
159
|
+
try:
|
160
|
+
from .web_search import WebSearchAgent
|
161
|
+
except ImportError:
|
162
|
+
WebSearchAgent = None
|
163
|
+
try:
|
164
|
+
from .code_executor import CodeExecutorAgent
|
165
|
+
except ImportError:
|
166
|
+
CodeExecutorAgent = None
|
167
|
+
try:
|
168
|
+
from .media_editor import MediaEditorAgent
|
169
|
+
except ImportError:
|
170
|
+
MediaEditorAgent = None
|
171
|
+
try:
|
172
|
+
from .youtube_download import YouTubeDownloadAgent
|
173
|
+
except ImportError:
|
174
|
+
YouTubeDownloadAgent = None
|
175
|
+
try:
|
176
|
+
from .web_scraper import WebScraperAgent
|
177
|
+
except ImportError:
|
178
|
+
WebScraperAgent = None
|
179
|
+
try:
|
180
|
+
from .assistant import AssistantAgent
|
181
|
+
except ImportError:
|
182
|
+
AssistantAgent = None
|
183
|
+
|
184
|
+
agent_classes = {
|
185
|
+
'knowledge_base': KnowledgeBaseAgent,
|
186
|
+
'web_search': WebSearchAgent,
|
187
|
+
'code_executor': CodeExecutorAgent,
|
188
|
+
'media_editor': MediaEditorAgent,
|
189
|
+
'youtube_download': YouTubeDownloadAgent,
|
190
|
+
'web_scraper': WebScraperAgent,
|
191
|
+
'assistant': AssistantAgent
|
192
|
+
}
|
193
|
+
|
194
|
+
for agent_type in self.enabled_agents:
|
195
|
+
if not self._is_agent_enabled(agent_type):
|
196
|
+
continue
|
197
|
+
|
198
|
+
agent_class = agent_classes.get(agent_type)
|
199
|
+
if agent_class is None:
|
200
|
+
logging.warning(f"Agent class for {agent_type} not available")
|
201
|
+
continue
|
202
|
+
|
203
|
+
try:
|
204
|
+
# Create agent with shared context
|
205
|
+
agent_instance = agent_class.create_simple(
|
206
|
+
user_id=self.context.user_id,
|
207
|
+
tenant_id=self.context.tenant_id,
|
208
|
+
session_metadata={
|
209
|
+
'parent_moderator': self.agent_id,
|
210
|
+
'agent_type': agent_type
|
211
|
+
}
|
212
|
+
)
|
213
|
+
self.specialized_agents[agent_type] = agent_instance
|
214
|
+
logging.info(f"Initialized {agent_type} agent: {agent_instance.agent_id}")
|
215
|
+
|
216
|
+
except Exception as e:
|
217
|
+
logging.error(f"Failed to initialize {agent_type} agent: {e}")
|
218
|
+
|
219
|
+
def _setup_routing_patterns(self):
|
220
|
+
"""Setup intelligent routing patterns for different query types"""
|
221
|
+
self.agent_routing_patterns = {
|
222
|
+
'knowledge_base': {
|
223
|
+
'keywords': ['search knowledge', 'query kb', 'knowledge base', 'find in documents',
|
224
|
+
'search documents', 'what do you know about', 'from my files'],
|
225
|
+
'patterns': [r'search\s+(?:in\s+)?(?:kb|knowledge|documents?)',
|
226
|
+
r'query\s+(?:the\s+)?(?:kb|knowledge|database)',
|
227
|
+
r'find\s+(?:in\s+)?(?:my\s+)?(?:files|documents?)'],
|
228
|
+
'indicators': ['kb_name', 'collection_table', 'document', 'file'],
|
229
|
+
'priority': 1
|
230
|
+
},
|
231
|
+
|
232
|
+
'web_search': {
|
233
|
+
'keywords': ['search web', 'google', 'find online', 'search for', 'look up',
|
234
|
+
'search internet', 'web search', 'find information'],
|
235
|
+
'patterns': [r'search\s+(?:the\s+)?(?:web|internet|online)',
|
236
|
+
r'(?:google|look\s+up|find)\s+(?:information\s+)?(?:about|on)',
|
237
|
+
r'what\'s\s+happening\s+with', r'latest\s+news'],
|
238
|
+
'indicators': ['search', 'web', 'online', 'internet', 'news'],
|
239
|
+
'priority': 2
|
240
|
+
},
|
241
|
+
|
242
|
+
'youtube_download': {
|
243
|
+
'keywords': ['download youtube', 'youtube video', 'download video', 'get from youtube'],
|
244
|
+
'patterns': [r'download\s+(?:from\s+)?youtube', r'youtube\.com/watch', r'youtu\.be/',
|
245
|
+
r'get\s+(?:video|audio)\s+from\s+youtube'],
|
246
|
+
'indicators': ['youtube.com', 'youtu.be', 'download video', 'download audio'],
|
247
|
+
'priority': 1
|
248
|
+
},
|
249
|
+
|
250
|
+
'media_editor': {
|
251
|
+
'keywords': ['convert video', 'edit media', 'extract audio', 'resize video',
|
252
|
+
'media processing', 'ffmpeg'],
|
253
|
+
'patterns': [r'convert\s+(?:video|audio)', r'extract\s+audio', r'resize\s+video',
|
254
|
+
r'trim\s+(?:video|audio)', r'media\s+(?:processing|editing)'],
|
255
|
+
'indicators': ['.mp4', '.avi', '.mp3', '.wav', 'video', 'audio'],
|
256
|
+
'priority': 1
|
257
|
+
},
|
258
|
+
|
259
|
+
'web_scraper': {
|
260
|
+
'keywords': ['scrape website', 'extract from site', 'crawl web', 'scrape data'],
|
261
|
+
'patterns': [r'scrape\s+(?:website|site|web)', r'extract\s+(?:data\s+)?from\s+(?:website|site)',
|
262
|
+
r'crawl\s+(?:website|web)'],
|
263
|
+
'indicators': ['scrape', 'crawl', 'extract data', 'website'],
|
264
|
+
'priority': 1
|
265
|
+
},
|
266
|
+
|
267
|
+
'code_executor': {
|
268
|
+
'keywords': ['run code', 'execute python', 'run script', 'code execution'],
|
269
|
+
'patterns': [r'run\s+(?:this\s+)?(?:code|script|python)', r'execute\s+(?:code|script)',
|
270
|
+
r'```(?:python|bash)'],
|
271
|
+
'indicators': ['```', 'def ', 'import ', 'python', 'bash'],
|
272
|
+
'priority': 1
|
273
|
+
},
|
274
|
+
|
275
|
+
'assistant': {
|
276
|
+
'keywords': ['help', 'explain', 'how to', 'what is', 'tell me'],
|
277
|
+
'patterns': [r'(?:help|explain|tell)\s+me', r'what\s+is', r'how\s+(?:do\s+)?(?:I|to)',
|
278
|
+
r'can\s+you\s+(?:help|explain)'],
|
279
|
+
'indicators': ['help', 'explain', 'question', 'general'],
|
280
|
+
'priority': 3 # Lowest priority - fallback
|
281
|
+
}
|
282
|
+
}
|
283
|
+
|
284
|
+
async def _analyze_query_intent(self, user_message: str) -> Dict[str, Any]:
|
285
|
+
"""Analyze user query to determine which agent(s) should handle it"""
|
286
|
+
message_lower = user_message.lower()
|
287
|
+
|
288
|
+
# Extract context from conversation history
|
289
|
+
conversation_context = self._get_conversation_context_summary()
|
290
|
+
|
291
|
+
# Score each agent type
|
292
|
+
agent_scores = {}
|
293
|
+
|
294
|
+
for agent_type, patterns in self.agent_routing_patterns.items():
|
295
|
+
if agent_type not in self.specialized_agents:
|
296
|
+
continue
|
297
|
+
|
298
|
+
score = 0
|
299
|
+
|
300
|
+
# Keyword matching
|
301
|
+
keyword_matches = sum(1 for keyword in patterns['keywords']
|
302
|
+
if keyword in message_lower)
|
303
|
+
score += keyword_matches * 2
|
304
|
+
|
305
|
+
# Pattern matching
|
306
|
+
import re
|
307
|
+
pattern_matches = sum(1 for pattern in patterns['patterns']
|
308
|
+
if re.search(pattern, message_lower))
|
309
|
+
score += pattern_matches * 3
|
310
|
+
|
311
|
+
# Indicator matching
|
312
|
+
indicator_matches = sum(1 for indicator in patterns['indicators']
|
313
|
+
if indicator in message_lower)
|
314
|
+
score += indicator_matches * 1
|
315
|
+
|
316
|
+
# Context matching from conversation history
|
317
|
+
if conversation_context and agent_type in conversation_context.lower():
|
318
|
+
score += 2
|
319
|
+
|
320
|
+
# Apply priority weighting (lower priority number = higher weight)
|
321
|
+
priority_weight = 4 - patterns.get('priority', 3)
|
322
|
+
score *= priority_weight
|
323
|
+
|
324
|
+
agent_scores[agent_type] = score
|
325
|
+
|
326
|
+
# Determine primary agent (highest score)
|
327
|
+
if agent_scores:
|
328
|
+
primary_agent = max(agent_scores.items(), key=lambda x: x[1])[0]
|
329
|
+
confidence = agent_scores[primary_agent] / sum(agent_scores.values()) if sum(
|
330
|
+
agent_scores.values()) > 0 else 0
|
331
|
+
else:
|
332
|
+
primary_agent = 'assistant' # fallback
|
333
|
+
confidence = 0.5
|
334
|
+
|
335
|
+
# Determine if multiple agents needed
|
336
|
+
high_scoring_agents = [agent for agent, score in agent_scores.items() if score > 3]
|
337
|
+
requires_multiple = len(high_scoring_agents) > 1
|
338
|
+
|
339
|
+
return {
|
340
|
+
'primary_agent': primary_agent,
|
341
|
+
'confidence': confidence,
|
342
|
+
'agent_scores': agent_scores,
|
343
|
+
'requires_multiple_agents': requires_multiple,
|
344
|
+
'high_scoring_agents': high_scoring_agents,
|
345
|
+
'context_detected': bool(conversation_context)
|
346
|
+
}
|
347
|
+
|
348
|
+
async def _route_to_agent(self, agent_type: str, user_message: str,
|
349
|
+
context: ExecutionContext = None) -> AgentResponse:
|
350
|
+
"""Route query to specific agent and get response"""
|
351
|
+
if agent_type not in self.specialized_agents:
|
352
|
+
return AgentResponse(
|
353
|
+
agent_type=agent_type,
|
354
|
+
content=f"Agent {agent_type} not available",
|
355
|
+
success=False,
|
356
|
+
execution_time=0.0,
|
357
|
+
metadata={},
|
358
|
+
error=f"Agent {agent_type} not initialized"
|
359
|
+
)
|
360
|
+
|
361
|
+
start_time = time.time()
|
362
|
+
|
363
|
+
try:
|
364
|
+
agent = self.specialized_agents[agent_type]
|
365
|
+
|
366
|
+
# Use the agent's chat interface
|
367
|
+
response_content = await agent.chat(user_message)
|
368
|
+
|
369
|
+
execution_time = time.time() - start_time
|
370
|
+
|
371
|
+
return AgentResponse(
|
372
|
+
agent_type=agent_type,
|
373
|
+
content=response_content,
|
374
|
+
success=True,
|
375
|
+
execution_time=execution_time,
|
376
|
+
metadata={
|
377
|
+
'agent_id': agent.agent_id,
|
378
|
+
'session_id': agent.context.session_id
|
379
|
+
}
|
380
|
+
)
|
381
|
+
|
382
|
+
except Exception as e:
|
383
|
+
execution_time = time.time() - start_time
|
384
|
+
logging.error(f"Error routing to {agent_type} agent: {e}")
|
385
|
+
|
386
|
+
return AgentResponse(
|
387
|
+
agent_type=agent_type,
|
388
|
+
content=f"Error processing request with {agent_type} agent",
|
389
|
+
success=False,
|
390
|
+
execution_time=execution_time,
|
391
|
+
metadata={},
|
392
|
+
error=str(e)
|
393
|
+
)
|
394
|
+
|
395
|
+
async def _coordinate_multiple_agents(self, agents: List[str], user_message: str,
|
396
|
+
context: ExecutionContext = None) -> str:
|
397
|
+
"""Coordinate multiple agents for complex queries"""
|
398
|
+
responses = []
|
399
|
+
|
400
|
+
# Execute agents concurrently
|
401
|
+
tasks = [self._route_to_agent(agent_type, user_message, context)
|
402
|
+
for agent_type in agents]
|
403
|
+
|
404
|
+
agent_responses = await asyncio.gather(*tasks, return_exceptions=True)
|
405
|
+
|
406
|
+
successful_responses = []
|
407
|
+
for response in agent_responses:
|
408
|
+
if isinstance(response, AgentResponse) and response.success:
|
409
|
+
successful_responses.append(response)
|
410
|
+
|
411
|
+
if not successful_responses:
|
412
|
+
return "I wasn't able to process your request with any of the available agents."
|
413
|
+
|
414
|
+
# Combine multiple responses intelligently
|
415
|
+
if len(successful_responses) == 1:
|
416
|
+
return successful_responses[0].content
|
417
|
+
|
418
|
+
combined_response = "Here's what I found from multiple sources:\n\n"
|
419
|
+
|
420
|
+
for i, response in enumerate(successful_responses, 1):
|
421
|
+
combined_response += f"**From {response.agent_type.replace('_', ' ').title()}:**\n"
|
422
|
+
combined_response += f"{response.content}\n\n"
|
423
|
+
|
424
|
+
return combined_response.strip()
|
425
|
+
|
426
|
+
def _get_conversation_context_summary(self) -> str:
|
427
|
+
"""Get conversation context summary (simplified for now)"""
|
428
|
+
try:
|
429
|
+
recent_history = self.get_conversation_history_with_context(limit=3)
|
430
|
+
context_summary = []
|
431
|
+
|
432
|
+
for msg in recent_history:
|
433
|
+
if msg.get('message_type') == 'user_input':
|
434
|
+
content = msg.get('content', '')
|
435
|
+
context_summary.append(content[:50])
|
436
|
+
|
437
|
+
return " ".join(context_summary) if context_summary else ""
|
438
|
+
except:
|
439
|
+
return ""
|
440
|
+
|
441
|
+
async def process_message(self, message: AgentMessage, context: ExecutionContext = None) -> AgentMessage:
|
442
|
+
"""Main processing method - routes to appropriate agents"""
|
443
|
+
self.memory.store_message(message)
|
444
|
+
|
445
|
+
try:
|
446
|
+
user_message = message.content
|
447
|
+
|
448
|
+
# Update conversation state
|
449
|
+
self.update_conversation_state(user_message)
|
450
|
+
|
451
|
+
# Analyze intent to determine routing
|
452
|
+
intent_analysis = await self._analyze_query_intent(user_message)
|
453
|
+
|
454
|
+
logging.info(f"Intent analysis: Primary={intent_analysis['primary_agent']}, "
|
455
|
+
f"Confidence={intent_analysis['confidence']:.2f}")
|
456
|
+
|
457
|
+
# Route based on analysis
|
458
|
+
if intent_analysis['requires_multiple_agents'] and len(intent_analysis['high_scoring_agents']) > 1:
|
459
|
+
# Use multiple agents
|
460
|
+
response_content = await self._coordinate_multiple_agents(
|
461
|
+
intent_analysis['high_scoring_agents'],
|
462
|
+
user_message,
|
463
|
+
context
|
464
|
+
)
|
465
|
+
else:
|
466
|
+
# Use single primary agent
|
467
|
+
primary_response = await self._route_to_agent(
|
468
|
+
intent_analysis['primary_agent'],
|
469
|
+
user_message,
|
470
|
+
context
|
471
|
+
)
|
472
|
+
|
473
|
+
if primary_response.success:
|
474
|
+
response_content = primary_response.content
|
475
|
+
else:
|
476
|
+
# Fallback to assistant if primary agent fails
|
477
|
+
if intent_analysis['primary_agent'] != 'assistant' and 'assistant' in self.specialized_agents:
|
478
|
+
fallback_response = await self._route_to_agent('assistant', user_message, context)
|
479
|
+
response_content = fallback_response.content
|
480
|
+
else:
|
481
|
+
response_content = f"I encountered an error processing your request: {primary_response.error}"
|
482
|
+
|
483
|
+
# Add routing metadata to response
|
484
|
+
agent_name = intent_analysis['primary_agent'].replace('_', ' ').title()
|
485
|
+
response_content += f"\n\n*Processed by: {agent_name} (confidence: {intent_analysis['confidence']:.2f})*"
|
486
|
+
|
487
|
+
response = self.create_response(
|
488
|
+
content=response_content,
|
489
|
+
recipient_id=message.sender_id,
|
490
|
+
session_id=message.session_id,
|
491
|
+
conversation_id=message.conversation_id
|
492
|
+
)
|
493
|
+
|
494
|
+
self.memory.store_message(response)
|
495
|
+
return response
|
496
|
+
|
497
|
+
except Exception as e:
|
498
|
+
logging.error(f"ModeratorAgent error: {e}")
|
499
|
+
error_response = self.create_response(
|
500
|
+
content=f"I encountered an error processing your request: {str(e)}",
|
501
|
+
recipient_id=message.sender_id,
|
502
|
+
message_type=MessageType.ERROR,
|
503
|
+
session_id=message.session_id,
|
504
|
+
conversation_id=message.conversation_id
|
505
|
+
)
|
506
|
+
return error_response
|
507
|
+
|
508
|
+
async def get_agent_status(self) -> Dict[str, Any]:
|
509
|
+
"""Get status of all managed agents - FIXED METHOD"""
|
510
|
+
status = {
|
511
|
+
'moderator_id': self.agent_id,
|
512
|
+
'enabled_agents': self.enabled_agents,
|
513
|
+
'active_agents': {},
|
514
|
+
'total_agents': len(self.specialized_agents),
|
515
|
+
'routing_patterns': len(self.agent_routing_patterns)
|
516
|
+
}
|
517
|
+
|
518
|
+
for agent_type, agent in self.specialized_agents.items():
|
519
|
+
try:
|
520
|
+
# Simple status check
|
521
|
+
status['active_agents'][agent_type] = {
|
522
|
+
'agent_id': agent.agent_id,
|
523
|
+
'status': 'active',
|
524
|
+
'session_id': agent.context.session_id if hasattr(agent, 'context') else 'unknown'
|
525
|
+
}
|
526
|
+
except Exception as e:
|
527
|
+
status['active_agents'][agent_type] = {
|
528
|
+
'agent_id': getattr(agent, 'agent_id', 'unknown'),
|
529
|
+
'status': 'error',
|
530
|
+
'error': str(e)
|
531
|
+
}
|
532
|
+
|
533
|
+
return status
|
534
|
+
|
535
|
+
async def cleanup_session(self) -> bool:
|
536
|
+
"""Cleanup all managed agents"""
|
537
|
+
success = True
|
538
|
+
|
539
|
+
# Cleanup all specialized agents
|
540
|
+
for agent_type, agent in self.specialized_agents.items():
|
541
|
+
try:
|
542
|
+
await agent.cleanup_session()
|
543
|
+
logging.info(f"Cleaned up {agent_type} agent")
|
544
|
+
except Exception as e:
|
545
|
+
logging.error(f"Error cleaning up {agent_type} agent: {e}")
|
546
|
+
success = False
|
547
|
+
|
548
|
+
# Cleanup moderator itself
|
549
|
+
moderator_cleanup = await super().cleanup_session()
|
550
|
+
|
551
|
+
return success and moderator_cleanup
|
552
|
+
|
553
|
+
@classmethod
|
554
|
+
def create(cls,
|
555
|
+
agent_id: str = None,
|
556
|
+
user_id: str = None,
|
557
|
+
tenant_id: str = "default",
|
558
|
+
enabled_agents: List[str] = None,
|
559
|
+
session_metadata: Dict[str, Any] = None,
|
560
|
+
**kwargs):
|
561
|
+
"""
|
562
|
+
Create ModeratorAgent with specified enabled agents
|
563
|
+
|
564
|
+
Args:
|
565
|
+
agent_id: Optional agent ID
|
566
|
+
user_id: User ID for context
|
567
|
+
tenant_id: Tenant ID for context
|
568
|
+
enabled_agents: List of agent types to enable. If None, uses config defaults
|
569
|
+
session_metadata: Additional session metadata
|
570
|
+
**kwargs: Additional arguments
|
571
|
+
|
572
|
+
Returns:
|
573
|
+
Tuple of (ModeratorAgent, AgentContext)
|
574
|
+
"""
|
575
|
+
if agent_id is None:
|
576
|
+
agent_id = f"moderator_{str(uuid.uuid4())[:8]}"
|
577
|
+
|
578
|
+
agent = cls(
|
579
|
+
agent_id=agent_id,
|
580
|
+
user_id=user_id,
|
581
|
+
tenant_id=tenant_id,
|
582
|
+
enabled_agents=enabled_agents,
|
583
|
+
session_metadata=session_metadata,
|
584
|
+
auto_configure=True,
|
585
|
+
**kwargs
|
586
|
+
)
|
587
|
+
|
588
|
+
return agent, agent.context
|
589
|
+
|
590
|
+
@classmethod
|
591
|
+
def create_simple(cls,
|
592
|
+
user_id: str = None,
|
593
|
+
tenant_id: str = "default",
|
594
|
+
enabled_agents: List[str] = None,
|
595
|
+
**kwargs):
|
596
|
+
"""
|
597
|
+
Simple factory method for ModeratorAgent
|
598
|
+
|
599
|
+
Args:
|
600
|
+
user_id: User ID for context
|
601
|
+
tenant_id: Tenant ID for context
|
602
|
+
enabled_agents: List of agent types to enable
|
603
|
+
**kwargs: Additional arguments
|
604
|
+
|
605
|
+
Returns:
|
606
|
+
ModeratorAgent instance
|
607
|
+
"""
|
608
|
+
agent_id = f"moderator_{str(uuid.uuid4())[:8]}"
|
609
|
+
|
610
|
+
return cls(
|
611
|
+
agent_id=agent_id,
|
612
|
+
user_id=user_id,
|
613
|
+
tenant_id=tenant_id,
|
614
|
+
enabled_agents=enabled_agents,
|
615
|
+
auto_configure=True,
|
616
|
+
**kwargs
|
617
|
+
)
|