praisonaiagents 0.0.127__tar.gz → 0.0.129__tar.gz

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 (77) hide show
  1. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/PKG-INFO +1 -1
  2. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/agent/__init__.py +2 -1
  3. praisonaiagents-0.0.129/praisonaiagents/agent/router_agent.py +334 -0
  4. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/agents/agents.py +15 -17
  5. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/agents/autoagents.py +1 -1
  6. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/llm/__init__.py +11 -1
  7. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/llm/llm.py +240 -274
  8. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/llm/model_capabilities.py +20 -3
  9. praisonaiagents-0.0.129/praisonaiagents/llm/model_router.py +348 -0
  10. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/process/process.py +71 -61
  11. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/task/task.py +17 -4
  12. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents.egg-info/PKG-INFO +1 -1
  13. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents.egg-info/SOURCES.txt +2 -0
  14. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/pyproject.toml +1 -1
  15. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/README.md +0 -0
  16. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/__init__.py +0 -0
  17. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/agent/agent.py +0 -0
  18. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/agent/handoff.py +0 -0
  19. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/agent/image_agent.py +0 -0
  20. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/agents/__init__.py +0 -0
  21. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/approval.py +0 -0
  22. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/guardrails/__init__.py +0 -0
  23. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/guardrails/guardrail_result.py +0 -0
  24. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/guardrails/llm_guardrail.py +0 -0
  25. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/knowledge/__init__.py +0 -0
  26. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/knowledge/chunking.py +0 -0
  27. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/knowledge/knowledge.py +0 -0
  28. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/llm/openai_client.py +0 -0
  29. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/main.py +0 -0
  30. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/mcp/__init__.py +0 -0
  31. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/mcp/mcp.py +0 -0
  32. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/mcp/mcp_http_stream.py +0 -0
  33. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/mcp/mcp_sse.py +0 -0
  34. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/memory/__init__.py +0 -0
  35. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/memory/memory.py +0 -0
  36. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/process/__init__.py +0 -0
  37. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/session.py +0 -0
  38. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/task/__init__.py +0 -0
  39. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/telemetry/__init__.py +0 -0
  40. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/telemetry/integration.py +0 -0
  41. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/telemetry/telemetry.py +0 -0
  42. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/README.md +0 -0
  43. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/__init__.py +0 -0
  44. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/arxiv_tools.py +0 -0
  45. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/calculator_tools.py +0 -0
  46. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/csv_tools.py +0 -0
  47. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/duckdb_tools.py +0 -0
  48. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/duckduckgo_tools.py +0 -0
  49. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/excel_tools.py +0 -0
  50. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/file_tools.py +0 -0
  51. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/json_tools.py +0 -0
  52. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/newspaper_tools.py +0 -0
  53. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/pandas_tools.py +0 -0
  54. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/python_tools.py +0 -0
  55. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/searxng_tools.py +0 -0
  56. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/shell_tools.py +0 -0
  57. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/spider_tools.py +0 -0
  58. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/test.py +0 -0
  59. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/tools.py +0 -0
  60. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/train/data/generatecot.py +0 -0
  61. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/wikipedia_tools.py +0 -0
  62. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/xml_tools.py +0 -0
  63. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/yaml_tools.py +0 -0
  64. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents/tools/yfinance_tools.py +0 -0
  65. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents.egg-info/dependency_links.txt +0 -0
  66. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents.egg-info/requires.txt +0 -0
  67. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/praisonaiagents.egg-info/top_level.txt +0 -0
  68. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/setup.cfg +0 -0
  69. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/tests/test-graph-memory.py +0 -0
  70. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/tests/test.py +0 -0
  71. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/tests/test_fix_comprehensive.py +0 -0
  72. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/tests/test_handoff_compatibility.py +0 -0
  73. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/tests/test_http_stream_basic.py +0 -0
  74. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/tests/test_ollama_async_fix.py +0 -0
  75. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/tests/test_ollama_fix.py +0 -0
  76. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/tests/test_posthog_fixed.py +0 -0
  77. {praisonaiagents-0.0.127 → praisonaiagents-0.0.129}/tests/test_validation_feedback.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: praisonaiagents
3
- Version: 0.0.127
3
+ Version: 0.0.129
4
4
  Summary: Praison AI agents for completing complex tasks with Self Reflection Agents
5
5
  Author: Mervin Praison
6
6
  Requires-Python: >=3.10
@@ -2,5 +2,6 @@
2
2
  from .agent import Agent
3
3
  from .image_agent import ImageAgent
4
4
  from .handoff import Handoff, handoff, handoff_filters, RECOMMENDED_PROMPT_PREFIX, prompt_with_handoff_instructions
5
+ from .router_agent import RouterAgent
5
6
 
6
- __all__ = ['Agent', 'ImageAgent', 'Handoff', 'handoff', 'handoff_filters', 'RECOMMENDED_PROMPT_PREFIX', 'prompt_with_handoff_instructions']
7
+ __all__ = ['Agent', 'ImageAgent', 'Handoff', 'handoff', 'handoff_filters', 'RECOMMENDED_PROMPT_PREFIX', 'prompt_with_handoff_instructions', 'RouterAgent']
@@ -0,0 +1,334 @@
1
+ """
2
+ Router Agent that can use different LLM models based on task requirements.
3
+
4
+ This module extends the base Agent class to support multiple models and intelligent
5
+ model selection based on task characteristics.
6
+ """
7
+
8
+ import os
9
+ import logging
10
+ from typing import Dict, List, Optional, Any, Union
11
+ from .agent import Agent
12
+ from ..llm.model_router import ModelRouter
13
+ from ..llm import LLM
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class RouterAgent(Agent):
19
+ """
20
+ An enhanced agent that can dynamically select and use different LLM models
21
+ based on task requirements, optimizing for cost and performance.
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ models: Optional[Union[List[str], Dict[str, Any]]] = None,
27
+ model_router: Optional[ModelRouter] = None,
28
+ routing_strategy: str = "auto", # "auto", "manual", "cost-optimized", "performance-optimized"
29
+ primary_model: Optional[str] = None,
30
+ fallback_model: Optional[str] = None,
31
+ **kwargs
32
+ ):
33
+ """
34
+ Initialize a RouterAgent.
35
+
36
+ Args:
37
+ models: List of model names or dict mapping model names to configurations
38
+ model_router: Custom ModelRouter instance for model selection
39
+ routing_strategy: Strategy for model selection
40
+ primary_model: Primary model to use (overrides routing for simple tasks)
41
+ fallback_model: Fallback model if selected model fails
42
+ **kwargs: Additional arguments passed to parent Agent class
43
+ """
44
+ # Initialize model router
45
+ self.model_router = model_router or ModelRouter()
46
+ self.routing_strategy = routing_strategy
47
+ self.fallback_model = fallback_model or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o-mini')
48
+
49
+ # Process models configuration
50
+ self.available_models = self._process_models_config(models)
51
+
52
+ # Set primary model for parent class initialization
53
+ if primary_model:
54
+ kwargs['llm'] = primary_model
55
+ elif self.available_models:
56
+ # Use the most cost-effective model as default
57
+ cheapest_model = min(
58
+ self.available_models.keys(),
59
+ key=lambda m: self.model_router.get_model_info(m).cost_per_1k_tokens
60
+ if self.model_router.get_model_info(m) else float('inf')
61
+ )
62
+ kwargs['llm'] = cheapest_model
63
+
64
+ # Store the original llm parameter for later use
65
+ self._llm_config = kwargs.get('llm')
66
+
67
+ # Store api_key and base_url for LLM initialization
68
+ self._base_url = kwargs.get('base_url')
69
+ self._api_key = kwargs.get('api_key')
70
+
71
+ # Initialize parent Agent class
72
+ super().__init__(**kwargs)
73
+
74
+ # Initialize LLM instances for each model
75
+ self._llm_instances: Dict[str, LLM] = {}
76
+ self._initialize_llm_instances()
77
+
78
+ # Track usage statistics
79
+ self.model_usage_stats = {model: {'calls': 0, 'tokens': 0, 'cost': 0.0}
80
+ for model in self.available_models}
81
+
82
+ def _process_models_config(self, models: Optional[Union[List[str], Dict[str, Any]]]) -> Dict[str, Any]:
83
+ """Process the models configuration into a standardized format."""
84
+ if not models:
85
+ # Use default models from router
86
+ return {m.name: {} for m in self.model_router.models}
87
+
88
+ if isinstance(models, list):
89
+ # Simple list of model names
90
+ return {model: {} for model in models}
91
+
92
+ # Already a dict with model configurations
93
+ return models
94
+
95
+ def _initialize_llm_instances(self):
96
+ """Initialize LLM instances for each available model."""
97
+ base_url = self._base_url
98
+ api_key = self._api_key
99
+
100
+ for model_name, config in self.available_models.items():
101
+ try:
102
+ # Merge base configuration with model-specific config
103
+ llm_config = {
104
+ 'model': model_name,
105
+ 'base_url': config.get('base_url', base_url),
106
+ 'api_key': config.get('api_key', api_key),
107
+ 'verbose': self.verbose,
108
+ 'markdown': self.markdown,
109
+ 'stream': self.stream
110
+ }
111
+
112
+ # Add any model-specific parameters
113
+ llm_config.update(config)
114
+
115
+ # Create LLM instance
116
+ self._llm_instances[model_name] = LLM(**llm_config)
117
+ logger.debug(f"Initialized LLM instance for model: {model_name}")
118
+
119
+ except Exception as e:
120
+ logger.warning(f"Failed to initialize LLM for model {model_name}: {e}")
121
+
122
+ def _select_model_for_task(
123
+ self,
124
+ task_description: str,
125
+ tools: Optional[List[Any]] = None,
126
+ context_size: Optional[int] = None
127
+ ) -> str:
128
+ """
129
+ Select the most appropriate model for a given task.
130
+
131
+ Args:
132
+ task_description: Description of the task
133
+ tools: Tools required for the task
134
+ context_size: Estimated context size
135
+
136
+ Returns:
137
+ Selected model name
138
+ """
139
+ if self.routing_strategy == "manual":
140
+ # Use the configured primary model from llm_model property
141
+ llm_model = self.llm_model
142
+ if hasattr(llm_model, 'model'):
143
+ # If it's an LLM instance, get the model name
144
+ return llm_model.model
145
+ elif isinstance(llm_model, str):
146
+ # If it's a string, use it directly
147
+ return llm_model
148
+ # Fallback if no model is configured
149
+ return self.fallback_model
150
+
151
+ # Determine required capabilities
152
+ required_capabilities = []
153
+ if tools:
154
+ required_capabilities.append("function-calling")
155
+
156
+ # Determine budget consciousness based on strategy
157
+ budget_conscious = self.routing_strategy in ["auto", "cost-optimized"]
158
+
159
+ # Get tool names for analysis
160
+ tool_names = []
161
+ if tools:
162
+ tool_names = [t.__name__ if hasattr(t, '__name__') else str(t) for t in tools]
163
+
164
+ # Use router to select model
165
+ selected_model = self.model_router.select_model(
166
+ task_description=task_description,
167
+ required_capabilities=required_capabilities,
168
+ tools_required=tool_names,
169
+ context_size=context_size,
170
+ budget_conscious=budget_conscious
171
+ )
172
+
173
+ # Ensure selected model is available
174
+ if selected_model not in self._llm_instances:
175
+ logger.warning(f"Selected model {selected_model} not available, using fallback")
176
+ return self.fallback_model
177
+
178
+ return selected_model
179
+
180
+ def _execute_with_model(
181
+ self,
182
+ model_name: str,
183
+ prompt: str,
184
+ context: Optional[str] = None,
185
+ tools: Optional[List[Any]] = None,
186
+ **kwargs
187
+ ) -> str:
188
+ """
189
+ Execute a task with a specific model.
190
+
191
+ Args:
192
+ model_name: Name of the model to use
193
+ prompt: The prompt to send to the model
194
+ context: Additional context
195
+ tools: Tools to make available
196
+ **kwargs: Additional arguments for the LLM
197
+
198
+ Returns:
199
+ Model response
200
+ """
201
+ llm_instance = self._llm_instances.get(model_name)
202
+ if not llm_instance:
203
+ logger.error(f"Model {model_name} not initialized, using fallback")
204
+ llm_instance = self._llm_instances.get(self.fallback_model)
205
+ model_name = self.fallback_model
206
+
207
+ if not llm_instance:
208
+ raise ValueError("No LLM instance available for execution")
209
+
210
+ # Prepare the full prompt
211
+ full_prompt = prompt
212
+ if context:
213
+ full_prompt = f"{context}\n\n{prompt}"
214
+
215
+ try:
216
+ # Execute with the selected model
217
+ response = llm_instance.get_response(
218
+ prompt=full_prompt,
219
+ system_prompt=self._build_system_prompt(),
220
+ tools=tools,
221
+ verbose=self.verbose,
222
+ markdown=self.markdown,
223
+ stream=self.stream,
224
+ agent_name=self.name,
225
+ agent_role=self.role,
226
+ agent_tools=[t.__name__ if hasattr(t, '__name__') else str(t) for t in (tools or [])],
227
+ execute_tool_fn=self.execute_tool if tools else None,
228
+ **kwargs
229
+ )
230
+
231
+ # Update usage statistics
232
+ self.model_usage_stats[model_name]['calls'] += 1
233
+
234
+ # TODO: Implement token tracking when LLM.get_response() is updated to return token usage
235
+ # The LLM response currently returns only text, but litellm provides usage info in:
236
+ # response.get("usage") with prompt_tokens, completion_tokens, and total_tokens
237
+ # This would require modifying the LLM class to return both text and metadata
238
+
239
+ return response
240
+
241
+ except Exception as e:
242
+ logger.error(f"Error executing with model {model_name}: {e}")
243
+
244
+ # Try fallback model if different
245
+ if model_name != self.fallback_model and self.fallback_model in self._llm_instances:
246
+ logger.info(f"Attempting with fallback model: {self.fallback_model}")
247
+ return self._execute_with_model(
248
+ self.fallback_model, prompt, context, tools, **kwargs
249
+ )
250
+
251
+ raise
252
+
253
+ def execute(
254
+ self,
255
+ task_description: str,
256
+ context: Optional[str] = None,
257
+ tools: Optional[List[Any]] = None,
258
+ **kwargs
259
+ ) -> str:
260
+ """
261
+ Execute a task with automatic model selection.
262
+
263
+ This method overrides the parent Agent's execute method to add
264
+ intelligent model selection.
265
+
266
+ Args:
267
+ task_description: Description of the task to execute
268
+ context: Optional context for the task
269
+ tools: Optional tools to use
270
+ **kwargs: Additional arguments
271
+
272
+ Returns:
273
+ Task execution result
274
+ """
275
+ # Estimate context size in tokens (rough estimate: ~4 chars per token)
276
+ # This is a simplified heuristic; actual tokenization varies by model
277
+ text_length = len(task_description) + (len(context) if context else 0)
278
+ context_size = text_length // 4 # Approximate token count
279
+
280
+ # Select the best model for this task
281
+ selected_model = self._select_model_for_task(
282
+ task_description=task_description,
283
+ tools=tools,
284
+ context_size=context_size
285
+ )
286
+
287
+ logger.info(f"RouterAgent '{self.name}' selected model: {selected_model} for task")
288
+
289
+ # Execute with the selected model
290
+ return self._execute_with_model(
291
+ model_name=selected_model,
292
+ prompt=task_description,
293
+ context=context,
294
+ tools=tools,
295
+ **kwargs
296
+ )
297
+
298
+ def get_usage_report(self) -> Dict[str, Any]:
299
+ """
300
+ Get a report of model usage statistics.
301
+
302
+ Returns:
303
+ Dictionary containing usage statistics and cost estimates
304
+ """
305
+ total_cost = 0.0
306
+ report = {
307
+ 'agent_name': self.name,
308
+ 'routing_strategy': self.routing_strategy,
309
+ 'model_usage': {}
310
+ }
311
+
312
+ for model, stats in self.model_usage_stats.items():
313
+ model_info = self.model_router.get_model_info(model)
314
+ if model_info and stats['tokens'] > 0:
315
+ cost = self.model_router.estimate_cost(model, stats['tokens'])
316
+ stats['cost'] = cost
317
+ total_cost += cost
318
+
319
+ report['model_usage'][model] = stats
320
+
321
+ report['total_cost_estimate'] = total_cost
322
+ report['total_calls'] = sum(s['calls'] for s in self.model_usage_stats.values())
323
+
324
+ return report
325
+
326
+ def _build_system_prompt(self) -> str:
327
+ """Build system prompt (inherited from parent but can be customized)."""
328
+ base_prompt = super()._build_system_prompt()
329
+
330
+ # Add multi-model context if needed
331
+ if self.routing_strategy == "auto":
332
+ base_prompt += "\n\nNote: You are part of a multi-model system. Focus on your specific task."
333
+
334
+ return base_prompt
@@ -480,24 +480,22 @@ Context:
480
480
  )
481
481
 
482
482
  if self.process == "workflow":
483
- # Collect all tasks that should run in parallel
484
- parallel_tasks = []
483
+ tasks_to_run = []
485
484
  async for task_id in process.aworkflow():
486
- if self.tasks[task_id].async_execution and self.tasks[task_id].is_start:
487
- parallel_tasks.append(task_id)
488
- elif parallel_tasks:
489
- # Execute collected parallel tasks
490
- await asyncio.gather(*[self.arun_task(t) for t in parallel_tasks])
491
- parallel_tasks = []
492
- # Run the current non-parallel task
493
- if self.tasks[task_id].async_execution:
494
- await self.arun_task(task_id)
495
- else:
496
- self.run_task(task_id)
497
-
498
- # Execute any remaining parallel tasks
499
- if parallel_tasks:
500
- await asyncio.gather(*[self.arun_task(t) for t in parallel_tasks])
485
+ if self.tasks[task_id].async_execution:
486
+ tasks_to_run.append(self.arun_task(task_id))
487
+ else:
488
+ # If we encounter a sync task, we must wait for the previous async tasks to finish.
489
+ if tasks_to_run:
490
+ await asyncio.gather(*tasks_to_run)
491
+ tasks_to_run = []
492
+
493
+ # Run sync task in an executor to avoid blocking the event loop
494
+ loop = asyncio.get_event_loop()
495
+ await loop.run_in_executor(None, self.run_task, task_id)
496
+
497
+ if tasks_to_run:
498
+ await asyncio.gather(*tasks_to_run)
501
499
 
502
500
  elif self.process == "sequential":
503
501
  async for task_id in process.asequential():
@@ -136,7 +136,7 @@ class AutoAgents(PraisonAIAgents):
136
136
  completion_checker=completion_checker,
137
137
  max_retries=max_retries,
138
138
  process=process,
139
- manager_llm=manager_llm
139
+ manager_llm=manager_llm or self.llm
140
140
  )
141
141
 
142
142
  def _display_agents_and_tasks(self, agents: List[Agent], tasks: List[Task]):
@@ -33,6 +33,12 @@ from .model_capabilities import (
33
33
  supports_structured_outputs,
34
34
  supports_streaming_with_tools
35
35
  )
36
+ from .model_router import (
37
+ ModelRouter,
38
+ ModelProfile,
39
+ TaskComplexity,
40
+ create_routing_agent
41
+ )
36
42
 
37
43
  # Ensure telemetry is disabled after import as well
38
44
  try:
@@ -55,5 +61,9 @@ __all__ = [
55
61
  "ToolCall",
56
62
  "process_stream_chunks",
57
63
  "supports_structured_outputs",
58
- "supports_streaming_with_tools"
64
+ "supports_streaming_with_tools",
65
+ "ModelRouter",
66
+ "ModelProfile",
67
+ "TaskComplexity",
68
+ "create_routing_agent"
59
69
  ]