fast-agent-mcp 0.0.9__py3-none-any.whl → 0.0.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fast-agent-mcp might be problematic. Click here for more details.
- {fast_agent_mcp-0.0.9.dist-info → fast_agent_mcp-0.0.12.dist-info}/METADATA +17 -11
- {fast_agent_mcp-0.0.9.dist-info → fast_agent_mcp-0.0.12.dist-info}/RECORD +36 -28
- mcp_agent/app.py +4 -4
- mcp_agent/cli/commands/bootstrap.py +2 -5
- mcp_agent/cli/commands/setup.py +1 -1
- mcp_agent/cli/main.py +4 -4
- mcp_agent/core/enhanced_prompt.py +315 -0
- mcp_agent/core/fastagent.py +520 -388
- mcp_agent/event_progress.py +5 -2
- mcp_agent/human_input/handler.py +6 -2
- mcp_agent/logging/rich_progress.py +10 -5
- mcp_agent/mcp/mcp_aggregator.py +2 -1
- mcp_agent/mcp/mcp_connection_manager.py +67 -37
- mcp_agent/resources/examples/internal/agent.py +17 -0
- mcp_agent/resources/examples/internal/job.py +83 -0
- mcp_agent/resources/examples/mcp_researcher/researcher-eval.py +1 -1
- mcp_agent/resources/examples/researcher/fastagent.config.yaml +53 -0
- mcp_agent/resources/examples/researcher/researcher-eval.py +53 -0
- mcp_agent/resources/examples/researcher/researcher.py +38 -0
- mcp_agent/resources/examples/workflows/agent.py +17 -0
- mcp_agent/resources/examples/workflows/agent_build.py +61 -0
- mcp_agent/resources/examples/workflows/chaining.py +0 -1
- mcp_agent/resources/examples/workflows/evaluator.py +6 -3
- mcp_agent/resources/examples/workflows/fastagent.py +22 -0
- mcp_agent/resources/examples/workflows/orchestrator.py +1 -1
- mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +91 -92
- mcp_agent/workflows/llm/augmented_llm.py +14 -3
- mcp_agent/workflows/llm/augmented_llm_anthropic.py +8 -5
- mcp_agent/workflows/llm/augmented_llm_openai.py +20 -9
- mcp_agent/workflows/llm/model_factory.py +25 -11
- mcp_agent/workflows/orchestrator/orchestrator.py +68 -7
- mcp_agent/workflows/orchestrator/orchestrator_prompts.py +11 -6
- mcp_agent/workflows/router/router_llm.py +13 -2
- mcp_agent/resources/examples/workflows/fastagent.config.yaml +0 -9
- {fast_agent_mcp-0.0.9.dist-info → fast_agent_mcp-0.0.12.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.0.9.dist-info → fast_agent_mcp-0.0.12.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.0.9.dist-info → fast_agent_mcp-0.0.12.dist-info}/licenses/LICENSE +0 -0
mcp_agent/core/fastagent.py
CHANGED
|
@@ -19,6 +19,7 @@ from mcp_agent.core.exceptions import (
|
|
|
19
19
|
from mcp_agent.app import MCPApp
|
|
20
20
|
from mcp_agent.agents.agent import Agent, AgentConfig
|
|
21
21
|
from mcp_agent.context_dependent import ContextDependent
|
|
22
|
+
from mcp_agent.event_progress import ProgressAction
|
|
22
23
|
from mcp_agent.workflows.orchestrator.orchestrator import Orchestrator
|
|
23
24
|
from mcp_agent.workflows.parallel.parallel_llm import ParallelLLM
|
|
24
25
|
from mcp_agent.workflows.evaluator_optimizer.evaluator_optimizer import (
|
|
@@ -27,7 +28,6 @@ from mcp_agent.workflows.evaluator_optimizer.evaluator_optimizer import (
|
|
|
27
28
|
)
|
|
28
29
|
from mcp_agent.workflows.router.router_llm import LLMRouter
|
|
29
30
|
from mcp_agent.config import Settings
|
|
30
|
-
from rich.prompt import Prompt
|
|
31
31
|
from rich import print
|
|
32
32
|
from mcp_agent.progress_display import progress_display
|
|
33
33
|
from mcp_agent.workflows.llm.model_factory import ModelFactory
|
|
@@ -128,7 +128,9 @@ class RouterProxy(BaseAgentProxy):
|
|
|
128
128
|
top_result = results[0]
|
|
129
129
|
if isinstance(top_result.result, Agent):
|
|
130
130
|
# Agent route - delegate to the agent
|
|
131
|
-
|
|
131
|
+
agent = top_result.result
|
|
132
|
+
|
|
133
|
+
return await agent._llm.generate_str(message)
|
|
132
134
|
elif isinstance(top_result.result, str):
|
|
133
135
|
# Server route - use the router directly
|
|
134
136
|
return "Tool call requested by router - not yet supported"
|
|
@@ -158,34 +160,73 @@ class AgentApp:
|
|
|
158
160
|
|
|
159
161
|
async def prompt(self, agent_name: Optional[str] = None, default: str = "") -> str:
|
|
160
162
|
"""
|
|
161
|
-
Interactive prompt for sending messages.
|
|
163
|
+
Interactive prompt for sending messages with advanced features.
|
|
162
164
|
|
|
163
165
|
Args:
|
|
164
166
|
agent_name: Optional target agent name (uses default if not specified)
|
|
165
|
-
|
|
167
|
+
default: Default message to use when user presses enter
|
|
166
168
|
"""
|
|
169
|
+
from .enhanced_prompt import get_enhanced_input, handle_special_commands
|
|
167
170
|
|
|
168
171
|
agent = agent_name or self._default
|
|
169
172
|
|
|
170
173
|
if agent not in self._agents:
|
|
171
174
|
raise ValueError(f"No agent named '{agent}'")
|
|
175
|
+
|
|
176
|
+
# Pass all available agent names for auto-completion
|
|
177
|
+
available_agents = list(self._agents.keys())
|
|
178
|
+
|
|
179
|
+
# Create agent_types dictionary mapping agent names to their types
|
|
180
|
+
agent_types = {}
|
|
181
|
+
for name, proxy in self._agents.items():
|
|
182
|
+
# Determine agent type based on the proxy type
|
|
183
|
+
if isinstance(proxy, LLMAgentProxy):
|
|
184
|
+
# Convert AgentType.BASIC.value ("agent") to "Agent"
|
|
185
|
+
agent_types[name] = "Agent"
|
|
186
|
+
elif isinstance(proxy, RouterProxy):
|
|
187
|
+
agent_types[name] = "Router"
|
|
188
|
+
elif isinstance(proxy, WorkflowProxy):
|
|
189
|
+
# For workflow proxies, check the workflow type
|
|
190
|
+
workflow = proxy._workflow
|
|
191
|
+
if isinstance(workflow, Orchestrator):
|
|
192
|
+
agent_types[name] = "Orchestrator"
|
|
193
|
+
elif isinstance(workflow, ParallelLLM):
|
|
194
|
+
agent_types[name] = "Parallel"
|
|
195
|
+
elif isinstance(workflow, EvaluatorOptimizerLLM):
|
|
196
|
+
agent_types[name] = "Evaluator"
|
|
197
|
+
else:
|
|
198
|
+
agent_types[name] = "Workflow"
|
|
199
|
+
|
|
172
200
|
result = ""
|
|
173
201
|
while True:
|
|
174
202
|
with progress_display.paused():
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
prompt_text = f"[blue]{agent}[/blue] >"
|
|
186
|
-
user_input = Prompt.ask(
|
|
187
|
-
prompt=prompt_text, default=default, show_default=False
|
|
203
|
+
# Use the enhanced input method with advanced features
|
|
204
|
+
user_input = await get_enhanced_input(
|
|
205
|
+
agent_name=agent,
|
|
206
|
+
default=default,
|
|
207
|
+
show_default=(default != ""),
|
|
208
|
+
show_stop_hint=True,
|
|
209
|
+
multiline=False, # Default to single-line mode
|
|
210
|
+
available_agent_names=available_agents,
|
|
211
|
+
syntax=None, # Can enable syntax highlighting for code input
|
|
212
|
+
agent_types=agent_types, # Pass agent types for display
|
|
188
213
|
)
|
|
214
|
+
|
|
215
|
+
# Handle special commands
|
|
216
|
+
command_result = await handle_special_commands(user_input, self)
|
|
217
|
+
|
|
218
|
+
# Check if we should switch agents
|
|
219
|
+
if (
|
|
220
|
+
isinstance(command_result, dict)
|
|
221
|
+
and "switch_agent" in command_result
|
|
222
|
+
):
|
|
223
|
+
agent = command_result["switch_agent"]
|
|
224
|
+
continue
|
|
225
|
+
|
|
226
|
+
# Skip further processing if command was handled
|
|
227
|
+
if command_result:
|
|
228
|
+
continue
|
|
229
|
+
|
|
189
230
|
if user_input.upper() == "STOP":
|
|
190
231
|
return
|
|
191
232
|
if user_input == "":
|
|
@@ -223,6 +264,34 @@ class FastAgent(ContextDependent):
|
|
|
223
264
|
Provides a simplified way to create and manage agents using decorators.
|
|
224
265
|
"""
|
|
225
266
|
|
|
267
|
+
def __init__(self, name: str, config_path: Optional[str] = None):
|
|
268
|
+
"""
|
|
269
|
+
Initialize the decorator interface.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
name: Name of the application
|
|
273
|
+
config_path: Optional path to config file
|
|
274
|
+
"""
|
|
275
|
+
# Initialize ContextDependent
|
|
276
|
+
super().__init__()
|
|
277
|
+
|
|
278
|
+
# Setup command line argument parsing
|
|
279
|
+
parser = argparse.ArgumentParser(description="MCP Agent Application")
|
|
280
|
+
parser.add_argument(
|
|
281
|
+
"--model",
|
|
282
|
+
help="Override the default model for all agents. Precedence is default < config_file < command line < constructor",
|
|
283
|
+
)
|
|
284
|
+
self.args = parser.parse_args()
|
|
285
|
+
|
|
286
|
+
self.name = name
|
|
287
|
+
self.config_path = config_path
|
|
288
|
+
self._load_config()
|
|
289
|
+
self.app = MCPApp(
|
|
290
|
+
name=name,
|
|
291
|
+
settings=Settings(**self.config) if hasattr(self, "config") else None,
|
|
292
|
+
)
|
|
293
|
+
self.agents: Dict[str, Dict[str, Any]] = {}
|
|
294
|
+
|
|
226
295
|
def _create_proxy(
|
|
227
296
|
self, name: str, instance: AgentOrWorkflow, agent_type: str
|
|
228
297
|
) -> BaseAgentProxy:
|
|
@@ -239,6 +308,11 @@ class FastAgent(ContextDependent):
|
|
|
239
308
|
Raises:
|
|
240
309
|
TypeError: If instance type doesn't match expected type for agent_type
|
|
241
310
|
"""
|
|
311
|
+
if agent_type not in [
|
|
312
|
+
AgentType.PARALLEL.value,
|
|
313
|
+
AgentType.EVALUATOR_OPTIMIZER.value,
|
|
314
|
+
]:
|
|
315
|
+
self._log_agent_load(name)
|
|
242
316
|
if agent_type == AgentType.BASIC.value:
|
|
243
317
|
if not isinstance(instance, Agent):
|
|
244
318
|
raise TypeError(
|
|
@@ -272,34 +346,6 @@ class FastAgent(ContextDependent):
|
|
|
272
346
|
else:
|
|
273
347
|
raise ValueError(f"Unknown agent type: {agent_type}")
|
|
274
348
|
|
|
275
|
-
def __init__(self, name: str, config_path: Optional[str] = None):
|
|
276
|
-
"""
|
|
277
|
-
Initialize the decorator interface.
|
|
278
|
-
|
|
279
|
-
Args:
|
|
280
|
-
name: Name of the application
|
|
281
|
-
config_path: Optional path to config file
|
|
282
|
-
"""
|
|
283
|
-
# Initialize ContextDependent
|
|
284
|
-
super().__init__()
|
|
285
|
-
|
|
286
|
-
# Setup command line argument parsing
|
|
287
|
-
parser = argparse.ArgumentParser(description="MCP Agent Application")
|
|
288
|
-
parser.add_argument(
|
|
289
|
-
"--model",
|
|
290
|
-
help="Override the default model for all agents. Precedence is default < config_file < command line < constructor",
|
|
291
|
-
)
|
|
292
|
-
self.args = parser.parse_args()
|
|
293
|
-
|
|
294
|
-
self.name = name
|
|
295
|
-
self.config_path = config_path
|
|
296
|
-
self._load_config()
|
|
297
|
-
self.app = MCPApp(
|
|
298
|
-
name=name,
|
|
299
|
-
settings=Settings(**self.config) if hasattr(self, "config") else None,
|
|
300
|
-
)
|
|
301
|
-
self.agents: Dict[str, Dict[str, Any]] = {}
|
|
302
|
-
|
|
303
349
|
@property
|
|
304
350
|
def context(self):
|
|
305
351
|
"""Access the application context"""
|
|
@@ -392,12 +438,12 @@ class FastAgent(ContextDependent):
|
|
|
392
438
|
elif agent_type == AgentType.EVALUATOR_OPTIMIZER.value:
|
|
393
439
|
# Check both evaluator and optimizer exist
|
|
394
440
|
evaluator = agent_data["evaluator"]
|
|
395
|
-
|
|
441
|
+
generator = agent_data["generator"]
|
|
396
442
|
missing = []
|
|
397
443
|
if evaluator not in available_components:
|
|
398
444
|
missing.append(f"evaluator: {evaluator}")
|
|
399
|
-
if
|
|
400
|
-
missing.append(f"
|
|
445
|
+
if generator not in available_components:
|
|
446
|
+
missing.append(f"generator: {generator}")
|
|
401
447
|
if missing:
|
|
402
448
|
raise AgentConfigError(
|
|
403
449
|
f"Evaluator-Optimizer '{name}' references non-existent components: {', '.join(missing)}"
|
|
@@ -440,6 +486,104 @@ class FastAgent(ContextDependent):
|
|
|
440
486
|
# Let model factory handle the model string parsing and setup
|
|
441
487
|
return ModelFactory.create_factory(model_spec, request_params=request_params)
|
|
442
488
|
|
|
489
|
+
def _create_decorator(
|
|
490
|
+
self,
|
|
491
|
+
agent_type: AgentType,
|
|
492
|
+
default_name: str = None,
|
|
493
|
+
default_instruction: str = None,
|
|
494
|
+
default_servers: List[str] = None,
|
|
495
|
+
default_use_history: bool = True,
|
|
496
|
+
wrapper_needed: bool = False,
|
|
497
|
+
**extra_defaults,
|
|
498
|
+
) -> Callable:
|
|
499
|
+
"""
|
|
500
|
+
Factory method for creating agent decorators with common behavior.
|
|
501
|
+
|
|
502
|
+
Args:
|
|
503
|
+
agent_type: Type of agent/workflow to create
|
|
504
|
+
default_name: Default name to use if not provided
|
|
505
|
+
default_instruction: Default instruction to use if not provided
|
|
506
|
+
default_servers: Default servers list to use if not provided
|
|
507
|
+
default_use_history: Default history setting
|
|
508
|
+
wrapper_needed: Whether to wrap the decorated function
|
|
509
|
+
**extra_defaults: Additional agent/workflow-specific parameters
|
|
510
|
+
"""
|
|
511
|
+
|
|
512
|
+
def decorator_wrapper(**kwargs):
|
|
513
|
+
# Apply defaults for common parameters
|
|
514
|
+
name = kwargs.get("name", default_name or f"{agent_type.name.title()}")
|
|
515
|
+
instruction = kwargs.get("instruction", default_instruction or "")
|
|
516
|
+
servers = kwargs.get("servers", default_servers or [])
|
|
517
|
+
model = kwargs.get("model", None)
|
|
518
|
+
use_history = kwargs.get("use_history", default_use_history)
|
|
519
|
+
request_params = kwargs.get("request_params", None)
|
|
520
|
+
human_input = kwargs.get("human_input", False)
|
|
521
|
+
|
|
522
|
+
# Create base request params
|
|
523
|
+
def decorator(func: Callable) -> Callable:
|
|
524
|
+
# Create base request params
|
|
525
|
+
if (
|
|
526
|
+
request_params is not None
|
|
527
|
+
or model is not None
|
|
528
|
+
or use_history != default_use_history
|
|
529
|
+
):
|
|
530
|
+
max_tokens = 4096 if agent_type == AgentType.BASIC else None
|
|
531
|
+
params_dict = {"use_history": use_history, "model": model}
|
|
532
|
+
if max_tokens:
|
|
533
|
+
params_dict["maxTokens"] = max_tokens
|
|
534
|
+
if request_params:
|
|
535
|
+
params_dict.update(request_params)
|
|
536
|
+
base_params = RequestParams(**params_dict)
|
|
537
|
+
else:
|
|
538
|
+
base_params = RequestParams(use_history=use_history)
|
|
539
|
+
|
|
540
|
+
# Create agent configuration
|
|
541
|
+
config = AgentConfig(
|
|
542
|
+
name=name,
|
|
543
|
+
instruction=instruction,
|
|
544
|
+
servers=servers,
|
|
545
|
+
model=model,
|
|
546
|
+
use_history=use_history,
|
|
547
|
+
default_request_params=base_params,
|
|
548
|
+
human_input=human_input,
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
# Build agent/workflow specific data
|
|
552
|
+
agent_data = {
|
|
553
|
+
"config": config,
|
|
554
|
+
"type": agent_type.value,
|
|
555
|
+
"func": func,
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
# Add extra parameters specific to this agent type
|
|
559
|
+
for key, value in kwargs.items():
|
|
560
|
+
if key not in [
|
|
561
|
+
"name",
|
|
562
|
+
"instruction",
|
|
563
|
+
"servers",
|
|
564
|
+
"model",
|
|
565
|
+
"use_history",
|
|
566
|
+
"request_params",
|
|
567
|
+
"human_input",
|
|
568
|
+
]:
|
|
569
|
+
agent_data[key] = value
|
|
570
|
+
|
|
571
|
+
# Store the configuration under the agent name
|
|
572
|
+
self.agents[name] = agent_data
|
|
573
|
+
|
|
574
|
+
# Either wrap or return the original function
|
|
575
|
+
if wrapper_needed:
|
|
576
|
+
|
|
577
|
+
async def wrapper(*args, **kwargs):
|
|
578
|
+
return await func(*args, **kwargs)
|
|
579
|
+
|
|
580
|
+
return wrapper
|
|
581
|
+
return func
|
|
582
|
+
|
|
583
|
+
return decorator
|
|
584
|
+
|
|
585
|
+
return decorator_wrapper
|
|
586
|
+
|
|
443
587
|
def agent(
|
|
444
588
|
self,
|
|
445
589
|
name: str = "Agent",
|
|
@@ -461,37 +605,22 @@ class FastAgent(ContextDependent):
|
|
|
461
605
|
model: Model specification string (highest precedence)
|
|
462
606
|
use_history: Whether to maintain conversation history
|
|
463
607
|
request_params: Additional request parameters for the LLM
|
|
608
|
+
human_input: Whether to enable human input capabilities
|
|
464
609
|
"""
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
servers=servers,
|
|
480
|
-
model=model, # Highest precedence
|
|
481
|
-
use_history=use_history,
|
|
482
|
-
default_request_params=base_params,
|
|
483
|
-
human_input=human_input,
|
|
484
|
-
)
|
|
485
|
-
|
|
486
|
-
# Store the agent configuration
|
|
487
|
-
self.agents[name] = {
|
|
488
|
-
"config": config,
|
|
489
|
-
"type": AgentType.BASIC.value,
|
|
490
|
-
"func": func,
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
return func # Don't wrap the function, just return it
|
|
494
|
-
|
|
610
|
+
decorator = self._create_decorator(
|
|
611
|
+
AgentType.BASIC,
|
|
612
|
+
default_name="Agent",
|
|
613
|
+
default_instruction="You are a helpful agent.",
|
|
614
|
+
default_use_history=True,
|
|
615
|
+
)(
|
|
616
|
+
name=name,
|
|
617
|
+
instruction=instruction,
|
|
618
|
+
servers=servers,
|
|
619
|
+
model=model,
|
|
620
|
+
use_history=use_history,
|
|
621
|
+
request_params=request_params,
|
|
622
|
+
human_input=human_input,
|
|
623
|
+
)
|
|
495
624
|
return decorator
|
|
496
625
|
|
|
497
626
|
def orchestrator(
|
|
@@ -515,35 +644,29 @@ class FastAgent(ContextDependent):
|
|
|
515
644
|
model: Model specification string (highest precedence)
|
|
516
645
|
use_history: Whether to maintain conversation history (forced false)
|
|
517
646
|
request_params: Additional request parameters for the LLM
|
|
647
|
+
human_input: Whether to enable human input capabilities
|
|
518
648
|
"""
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
"child_agents": agents,
|
|
541
|
-
"type": AgentType.ORCHESTRATOR.value,
|
|
542
|
-
"func": func,
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
return func
|
|
546
|
-
|
|
649
|
+
default_instruction = """
|
|
650
|
+
You are an expert planner. Given an objective task and a list of MCP servers (which are collections of tools)
|
|
651
|
+
or Agents (which are collections of servers), your job is to break down the objective into a series of steps,
|
|
652
|
+
which can be performed by LLMs with access to the servers or agents.
|
|
653
|
+
"""
|
|
654
|
+
|
|
655
|
+
decorator = self._create_decorator(
|
|
656
|
+
AgentType.ORCHESTRATOR,
|
|
657
|
+
default_name="Orchestrator",
|
|
658
|
+
default_instruction=default_instruction,
|
|
659
|
+
default_servers=[],
|
|
660
|
+
default_use_history=False,
|
|
661
|
+
)(
|
|
662
|
+
name=name,
|
|
663
|
+
instruction=instruction,
|
|
664
|
+
child_agents=agents,
|
|
665
|
+
model=model,
|
|
666
|
+
use_history=use_history,
|
|
667
|
+
request_params=request_params,
|
|
668
|
+
human_input=human_input,
|
|
669
|
+
)
|
|
547
670
|
return decorator
|
|
548
671
|
|
|
549
672
|
def parallel(
|
|
@@ -568,39 +691,26 @@ class FastAgent(ContextDependent):
|
|
|
568
691
|
use_history: Whether to maintain conversation history
|
|
569
692
|
request_params: Additional request parameters for the LLM
|
|
570
693
|
"""
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
)
|
|
586
|
-
|
|
587
|
-
# Store the parallel configuration
|
|
588
|
-
self.agents[name] = {
|
|
589
|
-
"config": config,
|
|
590
|
-
"fan_out": fan_out,
|
|
591
|
-
"fan_in": fan_in,
|
|
592
|
-
"type": AgentType.PARALLEL.value,
|
|
593
|
-
"func": func,
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
return func
|
|
597
|
-
|
|
694
|
+
decorator = self._create_decorator(
|
|
695
|
+
AgentType.PARALLEL,
|
|
696
|
+
default_instruction="",
|
|
697
|
+
default_servers=[],
|
|
698
|
+
default_use_history=True,
|
|
699
|
+
)(
|
|
700
|
+
name=name,
|
|
701
|
+
fan_in=fan_in,
|
|
702
|
+
fan_out=fan_out,
|
|
703
|
+
instruction=instruction,
|
|
704
|
+
model=model,
|
|
705
|
+
use_history=use_history,
|
|
706
|
+
request_params=request_params,
|
|
707
|
+
)
|
|
598
708
|
return decorator
|
|
599
709
|
|
|
600
710
|
def evaluator_optimizer(
|
|
601
711
|
self,
|
|
602
712
|
name: str,
|
|
603
|
-
|
|
713
|
+
generator: str,
|
|
604
714
|
evaluator: str,
|
|
605
715
|
min_rating: str = "GOOD",
|
|
606
716
|
max_refinements: int = 3,
|
|
@@ -612,40 +722,28 @@ class FastAgent(ContextDependent):
|
|
|
612
722
|
|
|
613
723
|
Args:
|
|
614
724
|
name: Name of the workflow
|
|
615
|
-
|
|
725
|
+
generator: Name of the generator agent
|
|
616
726
|
evaluator: Name of the evaluator agent
|
|
617
727
|
min_rating: Minimum acceptable quality rating (EXCELLENT, GOOD, FAIR, POOR)
|
|
618
728
|
max_refinements: Maximum number of refinement iterations
|
|
619
729
|
use_history: Whether to maintain conversation history
|
|
620
730
|
request_params: Additional request parameters for the LLM
|
|
621
731
|
"""
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
"evaluator": evaluator,
|
|
638
|
-
"min_rating": min_rating,
|
|
639
|
-
"max_refinements": max_refinements,
|
|
640
|
-
"type": AgentType.EVALUATOR_OPTIMIZER.value,
|
|
641
|
-
"func": func,
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
async def wrapper(*args, **kwargs):
|
|
645
|
-
return await func(*args, **kwargs)
|
|
646
|
-
|
|
647
|
-
return wrapper
|
|
648
|
-
|
|
732
|
+
decorator = self._create_decorator(
|
|
733
|
+
AgentType.EVALUATOR_OPTIMIZER,
|
|
734
|
+
default_instruction="",
|
|
735
|
+
default_servers=[],
|
|
736
|
+
default_use_history=True,
|
|
737
|
+
wrapper_needed=True,
|
|
738
|
+
)(
|
|
739
|
+
name=name,
|
|
740
|
+
generator=generator,
|
|
741
|
+
evaluator=evaluator,
|
|
742
|
+
min_rating=min_rating,
|
|
743
|
+
max_refinements=max_refinements,
|
|
744
|
+
use_history=use_history,
|
|
745
|
+
request_params=request_params,
|
|
746
|
+
)
|
|
649
747
|
return decorator
|
|
650
748
|
|
|
651
749
|
def router(
|
|
@@ -664,77 +762,229 @@ class FastAgent(ContextDependent):
|
|
|
664
762
|
Args:
|
|
665
763
|
name: Name of the router
|
|
666
764
|
agents: List of agent names this router can delegate to
|
|
667
|
-
servers: List of server names the router can use directly
|
|
765
|
+
servers: List of server names the router can use directly (currently not supported)
|
|
668
766
|
model: Model specification string
|
|
669
767
|
use_history: Whether to maintain conversation history
|
|
670
768
|
request_params: Additional request parameters for the LLM
|
|
769
|
+
human_input: Whether to enable human input capabilities
|
|
671
770
|
"""
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
default_request_params=base_params,
|
|
687
|
-
human_input=human_input,
|
|
688
|
-
)
|
|
689
|
-
|
|
690
|
-
# Store the router configuration
|
|
691
|
-
self.agents[name] = {
|
|
692
|
-
"config": config,
|
|
693
|
-
"agents": agents,
|
|
694
|
-
"type": AgentType.ROUTER.value,
|
|
695
|
-
"func": func,
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
async def wrapper(*args, **kwargs):
|
|
699
|
-
return await func(*args, **kwargs)
|
|
700
|
-
|
|
701
|
-
return wrapper
|
|
702
|
-
|
|
771
|
+
decorator = self._create_decorator(
|
|
772
|
+
AgentType.ROUTER,
|
|
773
|
+
default_instruction="",
|
|
774
|
+
default_servers=[],
|
|
775
|
+
default_use_history=True,
|
|
776
|
+
wrapper_needed=True,
|
|
777
|
+
)(
|
|
778
|
+
name=name,
|
|
779
|
+
agents=agents,
|
|
780
|
+
model=model,
|
|
781
|
+
use_history=use_history,
|
|
782
|
+
request_params=request_params,
|
|
783
|
+
human_input=human_input,
|
|
784
|
+
)
|
|
703
785
|
return decorator
|
|
704
786
|
|
|
705
|
-
async def
|
|
787
|
+
async def _create_agents_by_type(
|
|
788
|
+
self,
|
|
789
|
+
agent_app: MCPApp,
|
|
790
|
+
agent_type: AgentType,
|
|
791
|
+
active_agents: ProxyDict = None,
|
|
792
|
+
**kwargs,
|
|
793
|
+
) -> ProxyDict:
|
|
706
794
|
"""
|
|
707
|
-
|
|
795
|
+
Generic method to create agents of a specific type.
|
|
708
796
|
|
|
709
797
|
Args:
|
|
710
798
|
agent_app: The main application instance
|
|
799
|
+
agent_type: Type of agents to create
|
|
800
|
+
active_agents: Dictionary of already created agents/proxies (for dependencies)
|
|
801
|
+
**kwargs: Additional type-specific parameters
|
|
711
802
|
|
|
712
803
|
Returns:
|
|
713
|
-
Dictionary of initialized
|
|
804
|
+
Dictionary of initialized agents wrapped in appropriate proxies
|
|
714
805
|
"""
|
|
715
|
-
active_agents
|
|
806
|
+
if active_agents is None:
|
|
807
|
+
active_agents = {}
|
|
808
|
+
|
|
809
|
+
# Create a dictionary to store the initialized agents
|
|
810
|
+
result_agents = {}
|
|
716
811
|
|
|
812
|
+
# Get all agents of the specified type
|
|
717
813
|
for name, agent_data in self.agents.items():
|
|
718
|
-
if agent_data["type"] ==
|
|
814
|
+
if agent_data["type"] == agent_type.value:
|
|
815
|
+
# Get common configuration
|
|
719
816
|
config = agent_data["config"]
|
|
720
817
|
|
|
721
|
-
#
|
|
722
|
-
|
|
818
|
+
# Type-specific initialization
|
|
819
|
+
if agent_type == AgentType.BASIC:
|
|
820
|
+
# Create basic agent with configuration
|
|
821
|
+
agent = Agent(config=config, context=agent_app.context)
|
|
822
|
+
|
|
823
|
+
# Set up LLM with proper configuration
|
|
824
|
+
async with agent:
|
|
825
|
+
llm_factory = self._get_model_factory(
|
|
826
|
+
model=config.model,
|
|
827
|
+
request_params=config.default_request_params,
|
|
828
|
+
)
|
|
829
|
+
agent._llm = await agent.attach_llm(llm_factory)
|
|
830
|
+
|
|
831
|
+
# Store the agent
|
|
832
|
+
instance = agent
|
|
723
833
|
|
|
724
|
-
|
|
725
|
-
|
|
834
|
+
elif agent_type == AgentType.ORCHESTRATOR:
|
|
835
|
+
# Get base params configured with model settings
|
|
836
|
+
base_params = (
|
|
837
|
+
config.default_request_params.model_copy()
|
|
838
|
+
if config.default_request_params
|
|
839
|
+
else RequestParams()
|
|
840
|
+
)
|
|
841
|
+
base_params.use_history = False # Force no history for orchestrator
|
|
842
|
+
|
|
843
|
+
# Get the child agents - need to unwrap proxies and validate LLM config
|
|
844
|
+
child_agents = []
|
|
845
|
+
for agent_name in agent_data["child_agents"]:
|
|
846
|
+
proxy = active_agents[agent_name]
|
|
847
|
+
instance = self._unwrap_proxy(proxy)
|
|
848
|
+
# Validate basic agents have LLM
|
|
849
|
+
if isinstance(instance, Agent):
|
|
850
|
+
if not hasattr(instance, "_llm") or not instance._llm:
|
|
851
|
+
raise AgentConfigError(
|
|
852
|
+
f"Agent '{agent_name}' used by orchestrator '{name}' missing LLM configuration",
|
|
853
|
+
"All agents must be fully configured with LLMs before being used in an orchestrator",
|
|
854
|
+
)
|
|
855
|
+
child_agents.append(instance)
|
|
856
|
+
|
|
857
|
+
# Create a properly configured planner agent
|
|
858
|
+
planner_config = AgentConfig(
|
|
859
|
+
name=f"{name}", # Use orchestrator name as prefix
|
|
860
|
+
instruction=config.instruction
|
|
861
|
+
or """
|
|
862
|
+
You are an expert planner. Given an objective task and a list of MCP servers (which are collections of tools)
|
|
863
|
+
or Agents (which are collections of servers), your job is to break down the objective into a series of steps,
|
|
864
|
+
which can be performed by LLMs with access to the servers or agents.
|
|
865
|
+
""",
|
|
866
|
+
servers=[], # Planner doesn't need server access
|
|
867
|
+
model=config.model, # Use same model as orchestrator
|
|
868
|
+
default_request_params=base_params,
|
|
869
|
+
)
|
|
870
|
+
planner_agent = Agent(
|
|
871
|
+
config=planner_config, context=agent_app.context
|
|
872
|
+
)
|
|
873
|
+
planner_factory = self._get_model_factory(
|
|
874
|
+
model=config.model,
|
|
875
|
+
request_params=config.default_request_params,
|
|
876
|
+
)
|
|
877
|
+
|
|
878
|
+
async with planner_agent:
|
|
879
|
+
planner = await planner_agent.attach_llm(planner_factory)
|
|
880
|
+
|
|
881
|
+
# Create the orchestrator with pre-configured planner
|
|
882
|
+
instance = Orchestrator(
|
|
883
|
+
name=config.name,
|
|
884
|
+
planner=planner, # Pass pre-configured planner
|
|
885
|
+
available_agents=child_agents,
|
|
886
|
+
context=agent_app.context,
|
|
887
|
+
request_params=planner.default_request_params, # Base params already include model settings
|
|
888
|
+
plan_type="full", # TODO -- support iterative plan type properly
|
|
889
|
+
verb=ProgressAction.PLANNING, # Using PLANNING instead of ORCHESTRATING
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
elif agent_type == AgentType.EVALUATOR_OPTIMIZER:
|
|
893
|
+
# Get the referenced agents - unwrap from proxies
|
|
894
|
+
generator = self._unwrap_proxy(
|
|
895
|
+
active_agents[agent_data["generator"]]
|
|
896
|
+
)
|
|
897
|
+
evaluator = self._unwrap_proxy(
|
|
898
|
+
active_agents[agent_data["evaluator"]]
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
if not generator or not evaluator:
|
|
902
|
+
raise ValueError(
|
|
903
|
+
f"Missing agents for workflow {name}: "
|
|
904
|
+
f"generator={agent_data['generator']}, "
|
|
905
|
+
f"evaluator={agent_data['evaluator']}"
|
|
906
|
+
)
|
|
907
|
+
|
|
908
|
+
# TODO: Remove legacy - factory usage is only needed for str evaluators
|
|
909
|
+
# Later this should only be passed when evaluator is a string
|
|
910
|
+
optimizer_model = (
|
|
911
|
+
generator.config.model if isinstance(generator, Agent) else None
|
|
912
|
+
)
|
|
913
|
+
instance = EvaluatorOptimizerLLM(
|
|
914
|
+
generator=generator,
|
|
915
|
+
evaluator=evaluator,
|
|
916
|
+
min_rating=QualityRating[agent_data["min_rating"]],
|
|
917
|
+
max_refinements=agent_data["max_refinements"],
|
|
918
|
+
llm_factory=self._get_model_factory(model=optimizer_model),
|
|
919
|
+
context=agent_app.context,
|
|
920
|
+
)
|
|
921
|
+
|
|
922
|
+
elif agent_type == AgentType.ROUTER:
|
|
923
|
+
# Get the router's agents - unwrap proxies
|
|
924
|
+
router_agents = self._get_agent_instances(
|
|
925
|
+
agent_data["agents"], active_agents
|
|
926
|
+
)
|
|
927
|
+
|
|
928
|
+
# Create the router with proper configuration
|
|
726
929
|
llm_factory = self._get_model_factory(
|
|
727
930
|
model=config.model,
|
|
728
931
|
request_params=config.default_request_params,
|
|
729
932
|
)
|
|
730
|
-
agent._llm = await agent.attach_llm(llm_factory)
|
|
731
933
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
934
|
+
instance = LLMRouter(
|
|
935
|
+
name=config.name,
|
|
936
|
+
llm_factory=llm_factory,
|
|
937
|
+
agents=router_agents,
|
|
938
|
+
server_names=config.servers,
|
|
939
|
+
context=agent_app.context,
|
|
940
|
+
default_request_params=config.default_request_params,
|
|
941
|
+
verb=ProgressAction.ROUTING, # Set verb for progress display
|
|
942
|
+
)
|
|
943
|
+
|
|
944
|
+
elif agent_type == AgentType.PARALLEL:
|
|
945
|
+
# Get fan-out agents (could be basic agents or other parallels)
|
|
946
|
+
fan_out_agents = self._get_agent_instances(
|
|
947
|
+
agent_data["fan_out"], active_agents
|
|
948
|
+
)
|
|
949
|
+
|
|
950
|
+
# Get fan-in agent - unwrap proxy
|
|
951
|
+
fan_in_agent = self._unwrap_proxy(
|
|
952
|
+
active_agents[agent_data["fan_in"]]
|
|
953
|
+
)
|
|
954
|
+
|
|
955
|
+
# Create the parallel workflow
|
|
956
|
+
llm_factory = self._get_model_factory(config.model)
|
|
957
|
+
instance = ParallelLLM(
|
|
958
|
+
name=config.name,
|
|
959
|
+
instruction=config.instruction,
|
|
960
|
+
fan_out_agents=fan_out_agents,
|
|
961
|
+
fan_in_agent=fan_in_agent,
|
|
962
|
+
context=agent_app.context,
|
|
963
|
+
llm_factory=llm_factory,
|
|
964
|
+
default_request_params=config.default_request_params,
|
|
965
|
+
)
|
|
966
|
+
|
|
967
|
+
else:
|
|
968
|
+
raise ValueError(f"Unsupported agent type: {agent_type}")
|
|
969
|
+
|
|
970
|
+
# Create the appropriate proxy and store in results
|
|
971
|
+
result_agents[name] = self._create_proxy(
|
|
972
|
+
name, instance, agent_type.value
|
|
735
973
|
)
|
|
736
974
|
|
|
737
|
-
return
|
|
975
|
+
return result_agents
|
|
976
|
+
|
|
977
|
+
async def _create_basic_agents(self, agent_app: MCPApp) -> ProxyDict:
|
|
978
|
+
"""
|
|
979
|
+
Create and initialize basic agents with their configurations.
|
|
980
|
+
|
|
981
|
+
Args:
|
|
982
|
+
agent_app: The main application instance
|
|
983
|
+
|
|
984
|
+
Returns:
|
|
985
|
+
Dictionary of initialized basic agents wrapped in appropriate proxies
|
|
986
|
+
"""
|
|
987
|
+
return await self._create_agents_by_type(agent_app, AgentType.BASIC)
|
|
738
988
|
|
|
739
989
|
async def _create_orchestrators(
|
|
740
990
|
self, agent_app: MCPApp, active_agents: ProxyDict
|
|
@@ -749,71 +999,9 @@ class FastAgent(ContextDependent):
|
|
|
749
999
|
Returns:
|
|
750
1000
|
Dictionary of initialized orchestrator agents wrapped in appropriate proxies
|
|
751
1001
|
"""
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
config = agent_data["config"]
|
|
756
|
-
|
|
757
|
-
# Get base params configured with model settings
|
|
758
|
-
base_params = (
|
|
759
|
-
config.default_request_params.model_copy()
|
|
760
|
-
if config.default_request_params
|
|
761
|
-
else RequestParams()
|
|
762
|
-
)
|
|
763
|
-
base_params.use_history = False # Force no history for orchestrator
|
|
764
|
-
|
|
765
|
-
# Get the child agents - need to unwrap proxies and validate LLM config
|
|
766
|
-
child_agents = []
|
|
767
|
-
for agent_name in agent_data["child_agents"]:
|
|
768
|
-
proxy = active_agents[agent_name]
|
|
769
|
-
instance = self._unwrap_proxy(proxy)
|
|
770
|
-
# Validate basic agents have LLM
|
|
771
|
-
if isinstance(instance, Agent):
|
|
772
|
-
if not hasattr(instance, "_llm") or not instance._llm:
|
|
773
|
-
raise AgentConfigError(
|
|
774
|
-
f"Agent '{agent_name}' used by orchestrator '{name}' missing LLM configuration",
|
|
775
|
-
"All agents must be fully configured with LLMs before being used in an orchestrator",
|
|
776
|
-
)
|
|
777
|
-
child_agents.append(instance)
|
|
778
|
-
|
|
779
|
-
# Create a properly configured planner agent
|
|
780
|
-
planner_config = AgentConfig(
|
|
781
|
-
name=f"{name}", # Use orchestrator name as prefix
|
|
782
|
-
instruction=config.instruction
|
|
783
|
-
or """
|
|
784
|
-
You are an expert planner. Given an objective task and a list of MCP servers (which are collections of tools)
|
|
785
|
-
or Agents (which are collections of servers), your job is to break down the objective into a series of steps,
|
|
786
|
-
which can be performed by LLMs with access to the servers or agents.
|
|
787
|
-
""",
|
|
788
|
-
servers=[], # Planner doesn't need server access
|
|
789
|
-
model=config.model, # Use same model as orchestrator
|
|
790
|
-
default_request_params=base_params,
|
|
791
|
-
)
|
|
792
|
-
planner_agent = Agent(config=planner_config, context=agent_app.context)
|
|
793
|
-
planner_factory = self._get_model_factory(
|
|
794
|
-
model=config.model,
|
|
795
|
-
request_params=config.default_request_params,
|
|
796
|
-
)
|
|
797
|
-
|
|
798
|
-
async with planner_agent:
|
|
799
|
-
planner = await planner_agent.attach_llm(planner_factory)
|
|
800
|
-
|
|
801
|
-
# Create the orchestrator with pre-configured planner
|
|
802
|
-
orchestrator = Orchestrator(
|
|
803
|
-
name=config.name,
|
|
804
|
-
planner=planner, # Pass pre-configured planner
|
|
805
|
-
available_agents=child_agents,
|
|
806
|
-
context=agent_app.context,
|
|
807
|
-
request_params=planner.default_request_params, # Base params already include model settings
|
|
808
|
-
plan_type="full",
|
|
809
|
-
)
|
|
810
|
-
|
|
811
|
-
# Use factory to create appropriate proxy
|
|
812
|
-
orchestrators[name] = self._create_proxy(
|
|
813
|
-
name, orchestrator, AgentType.ORCHESTRATOR.value
|
|
814
|
-
)
|
|
815
|
-
|
|
816
|
-
return orchestrators
|
|
1002
|
+
return await self._create_agents_by_type(
|
|
1003
|
+
agent_app, AgentType.ORCHESTRATOR, active_agents
|
|
1004
|
+
)
|
|
817
1005
|
|
|
818
1006
|
async def _create_evaluator_optimizers(
|
|
819
1007
|
self, agent_app: MCPApp, active_agents: ProxyDict
|
|
@@ -828,39 +1016,9 @@ class FastAgent(ContextDependent):
|
|
|
828
1016
|
Returns:
|
|
829
1017
|
Dictionary of initialized evaluator-optimizer workflows
|
|
830
1018
|
"""
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
# Get the referenced agents - unwrap from proxies
|
|
835
|
-
optimizer = self._unwrap_proxy(active_agents[agent_data["optimizer"]])
|
|
836
|
-
evaluator = self._unwrap_proxy(active_agents[agent_data["evaluator"]])
|
|
837
|
-
|
|
838
|
-
if not optimizer or not evaluator:
|
|
839
|
-
raise ValueError(
|
|
840
|
-
f"Missing agents for workflow {name}: "
|
|
841
|
-
f"optimizer={agent_data['optimizer']}, "
|
|
842
|
-
f"evaluator={agent_data['evaluator']}"
|
|
843
|
-
)
|
|
844
|
-
|
|
845
|
-
# TODO: Remove legacy - factory usage is only needed for str evaluators
|
|
846
|
-
# Later this should only be passed when evaluator is a string
|
|
847
|
-
optimizer_model = (
|
|
848
|
-
optimizer.config.model if isinstance(optimizer, Agent) else None
|
|
849
|
-
)
|
|
850
|
-
workflow = EvaluatorOptimizerLLM(
|
|
851
|
-
optimizer=optimizer,
|
|
852
|
-
evaluator=evaluator,
|
|
853
|
-
min_rating=QualityRating[agent_data["min_rating"]],
|
|
854
|
-
max_refinements=agent_data["max_refinements"],
|
|
855
|
-
llm_factory=self._get_model_factory(model=optimizer_model),
|
|
856
|
-
context=agent_app.context,
|
|
857
|
-
)
|
|
858
|
-
|
|
859
|
-
workflows[name] = self._create_proxy(
|
|
860
|
-
name, workflow, AgentType.EVALUATOR_OPTIMIZER.value
|
|
861
|
-
)
|
|
862
|
-
|
|
863
|
-
return workflows
|
|
1019
|
+
return await self._create_agents_by_type(
|
|
1020
|
+
agent_app, AgentType.EVALUATOR_OPTIMIZER, active_agents
|
|
1021
|
+
)
|
|
864
1022
|
|
|
865
1023
|
def _get_parallel_dependencies(
|
|
866
1024
|
self, name: str, visited: set, path: set
|
|
@@ -944,34 +1102,15 @@ class FastAgent(ContextDependent):
|
|
|
944
1102
|
# Create each agent in order
|
|
945
1103
|
for agent_name in ordered_agents:
|
|
946
1104
|
if agent_name not in parallel_agents:
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
)
|
|
954
|
-
|
|
955
|
-
# Get fan-in agent - unwrap proxy
|
|
956
|
-
fan_in_agent = self._unwrap_proxy(
|
|
957
|
-
active_agents[agent_data["fan_in"]]
|
|
958
|
-
)
|
|
959
|
-
|
|
960
|
-
# Create the parallel workflow
|
|
961
|
-
llm_factory = self._get_model_factory(config.model)
|
|
962
|
-
parallel = ParallelLLM(
|
|
963
|
-
name=config.name,
|
|
964
|
-
instruction=config.instruction,
|
|
965
|
-
fan_out_agents=fan_out_agents,
|
|
966
|
-
fan_in_agent=fan_in_agent,
|
|
967
|
-
context=agent_app.context,
|
|
968
|
-
llm_factory=llm_factory,
|
|
969
|
-
default_request_params=config.default_request_params,
|
|
970
|
-
)
|
|
971
|
-
|
|
972
|
-
parallel_agents[agent_name] = self._create_proxy(
|
|
973
|
-
name, parallel, AgentType.PARALLEL.value
|
|
1105
|
+
# Create one parallel agent at a time using the generic method
|
|
1106
|
+
agent_result = await self._create_agents_by_type(
|
|
1107
|
+
agent_app,
|
|
1108
|
+
AgentType.PARALLEL,
|
|
1109
|
+
active_agents,
|
|
1110
|
+
agent_name=agent_name,
|
|
974
1111
|
)
|
|
1112
|
+
if agent_name in agent_result:
|
|
1113
|
+
parallel_agents[agent_name] = agent_result[agent_name]
|
|
975
1114
|
|
|
976
1115
|
return parallel_agents
|
|
977
1116
|
|
|
@@ -988,34 +1127,9 @@ class FastAgent(ContextDependent):
|
|
|
988
1127
|
Returns:
|
|
989
1128
|
Dictionary of initialized router agents
|
|
990
1129
|
"""
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
config = agent_data["config"]
|
|
995
|
-
|
|
996
|
-
# Get the router's agents - unwrap proxies
|
|
997
|
-
router_agents = self._get_agent_instances(
|
|
998
|
-
agent_data["agents"], active_agents
|
|
999
|
-
)
|
|
1000
|
-
|
|
1001
|
-
# Create the router with proper configuration
|
|
1002
|
-
llm_factory = self._get_model_factory(
|
|
1003
|
-
model=config.model,
|
|
1004
|
-
request_params=config.default_request_params,
|
|
1005
|
-
)
|
|
1006
|
-
|
|
1007
|
-
router = LLMRouter(
|
|
1008
|
-
name=config.name, # Add the name parameter
|
|
1009
|
-
llm_factory=llm_factory,
|
|
1010
|
-
agents=router_agents,
|
|
1011
|
-
server_names=config.servers,
|
|
1012
|
-
context=agent_app.context,
|
|
1013
|
-
default_request_params=config.default_request_params,
|
|
1014
|
-
)
|
|
1015
|
-
|
|
1016
|
-
routers[name] = self._create_proxy(name, router, AgentType.ROUTER.value)
|
|
1017
|
-
|
|
1018
|
-
return routers
|
|
1130
|
+
return await self._create_agents_by_type(
|
|
1131
|
+
agent_app, AgentType.ROUTER, active_agents
|
|
1132
|
+
)
|
|
1019
1133
|
|
|
1020
1134
|
def _unwrap_proxy(self, proxy: BaseAgentProxy) -> AgentOrWorkflow:
|
|
1021
1135
|
"""
|
|
@@ -1085,48 +1199,38 @@ class FastAgent(ContextDependent):
|
|
|
1085
1199
|
|
|
1086
1200
|
except ServerConfigError as e:
|
|
1087
1201
|
had_error = True
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
print(e.details)
|
|
1093
|
-
print(
|
|
1094
|
-
"\nPlease check your 'fastagent.config.yaml' configuration file and add the missing server definitions."
|
|
1202
|
+
self._handle_error(
|
|
1203
|
+
e,
|
|
1204
|
+
"Server Configuration Error",
|
|
1205
|
+
"Please check your 'fastagent.config.yaml' configuration file and add the missing server definitions.",
|
|
1095
1206
|
)
|
|
1096
1207
|
raise SystemExit(1)
|
|
1097
1208
|
|
|
1098
1209
|
except ProviderKeyError as e:
|
|
1099
1210
|
had_error = True
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
print(e.details)
|
|
1105
|
-
print(
|
|
1106
|
-
"\nPlease check your 'fastagent.secrets.yaml' configuration file and ensure all required API keys are set."
|
|
1211
|
+
self._handle_error(
|
|
1212
|
+
e,
|
|
1213
|
+
"Provider Configuration Error",
|
|
1214
|
+
"Please check your 'fastagent.secrets.yaml' configuration file and ensure all required API keys are set.",
|
|
1107
1215
|
)
|
|
1108
1216
|
raise SystemExit(1)
|
|
1109
1217
|
|
|
1110
1218
|
except AgentConfigError as e:
|
|
1111
1219
|
had_error = True
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
print(e.details)
|
|
1117
|
-
print(
|
|
1118
|
-
"\nPlease check your agent definition and ensure names and references are correct."
|
|
1220
|
+
self._handle_error(
|
|
1221
|
+
e,
|
|
1222
|
+
"Workflow or Agent Configuration Error",
|
|
1223
|
+
"Please check your agent definition and ensure names and references are correct.",
|
|
1119
1224
|
)
|
|
1120
1225
|
raise SystemExit(1)
|
|
1121
1226
|
|
|
1122
1227
|
except ServerInitializationError as e:
|
|
1123
1228
|
had_error = True
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
print("\nThere was an error starting up the MCP Server.")
|
|
1229
|
+
self._handle_error(
|
|
1230
|
+
e,
|
|
1231
|
+
"Server Startup Error",
|
|
1232
|
+
"There was an error starting up the MCP Server.",
|
|
1233
|
+
)
|
|
1130
1234
|
raise SystemExit(1)
|
|
1131
1235
|
finally:
|
|
1132
1236
|
# Clean up any active agents without re-raising errors
|
|
@@ -1137,3 +1241,31 @@ class FastAgent(ContextDependent):
|
|
|
1137
1241
|
await proxy._agent.__aexit__(None, None, None)
|
|
1138
1242
|
except Exception:
|
|
1139
1243
|
pass # Ignore cleanup errors
|
|
1244
|
+
|
|
1245
|
+
def _handle_error(
|
|
1246
|
+
self, e: Exception, error_type: str, suggestion: str = None
|
|
1247
|
+
) -> None:
|
|
1248
|
+
"""
|
|
1249
|
+
Handle errors with consistent formatting and messaging.
|
|
1250
|
+
|
|
1251
|
+
Args:
|
|
1252
|
+
e: The exception that was raised
|
|
1253
|
+
error_type: Type of error to display
|
|
1254
|
+
suggestion: Optional suggestion message to display
|
|
1255
|
+
"""
|
|
1256
|
+
print(f"\n[bold red]{error_type}:")
|
|
1257
|
+
print(getattr(e, "message", str(e)))
|
|
1258
|
+
if hasattr(e, "details") and e.details:
|
|
1259
|
+
print("\nDetails:")
|
|
1260
|
+
print(e.details)
|
|
1261
|
+
if suggestion:
|
|
1262
|
+
print(f"\n{suggestion}")
|
|
1263
|
+
|
|
1264
|
+
def _log_agent_load(self, agent_name: str) -> None:
|
|
1265
|
+
self.app._logger.info(
|
|
1266
|
+
f"Loaded {agent_name}",
|
|
1267
|
+
data={
|
|
1268
|
+
"progress_action": ProgressAction.LOADED,
|
|
1269
|
+
"agent_name": agent_name,
|
|
1270
|
+
},
|
|
1271
|
+
)
|