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.

Files changed (37) hide show
  1. {fast_agent_mcp-0.0.9.dist-info → fast_agent_mcp-0.0.12.dist-info}/METADATA +17 -11
  2. {fast_agent_mcp-0.0.9.dist-info → fast_agent_mcp-0.0.12.dist-info}/RECORD +36 -28
  3. mcp_agent/app.py +4 -4
  4. mcp_agent/cli/commands/bootstrap.py +2 -5
  5. mcp_agent/cli/commands/setup.py +1 -1
  6. mcp_agent/cli/main.py +4 -4
  7. mcp_agent/core/enhanced_prompt.py +315 -0
  8. mcp_agent/core/fastagent.py +520 -388
  9. mcp_agent/event_progress.py +5 -2
  10. mcp_agent/human_input/handler.py +6 -2
  11. mcp_agent/logging/rich_progress.py +10 -5
  12. mcp_agent/mcp/mcp_aggregator.py +2 -1
  13. mcp_agent/mcp/mcp_connection_manager.py +67 -37
  14. mcp_agent/resources/examples/internal/agent.py +17 -0
  15. mcp_agent/resources/examples/internal/job.py +83 -0
  16. mcp_agent/resources/examples/mcp_researcher/researcher-eval.py +1 -1
  17. mcp_agent/resources/examples/researcher/fastagent.config.yaml +53 -0
  18. mcp_agent/resources/examples/researcher/researcher-eval.py +53 -0
  19. mcp_agent/resources/examples/researcher/researcher.py +38 -0
  20. mcp_agent/resources/examples/workflows/agent.py +17 -0
  21. mcp_agent/resources/examples/workflows/agent_build.py +61 -0
  22. mcp_agent/resources/examples/workflows/chaining.py +0 -1
  23. mcp_agent/resources/examples/workflows/evaluator.py +6 -3
  24. mcp_agent/resources/examples/workflows/fastagent.py +22 -0
  25. mcp_agent/resources/examples/workflows/orchestrator.py +1 -1
  26. mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +91 -92
  27. mcp_agent/workflows/llm/augmented_llm.py +14 -3
  28. mcp_agent/workflows/llm/augmented_llm_anthropic.py +8 -5
  29. mcp_agent/workflows/llm/augmented_llm_openai.py +20 -9
  30. mcp_agent/workflows/llm/model_factory.py +25 -11
  31. mcp_agent/workflows/orchestrator/orchestrator.py +68 -7
  32. mcp_agent/workflows/orchestrator/orchestrator_prompts.py +11 -6
  33. mcp_agent/workflows/router/router_llm.py +13 -2
  34. mcp_agent/resources/examples/workflows/fastagent.config.yaml +0 -9
  35. {fast_agent_mcp-0.0.9.dist-info → fast_agent_mcp-0.0.12.dist-info}/WHEEL +0 -0
  36. {fast_agent_mcp-0.0.9.dist-info → fast_agent_mcp-0.0.12.dist-info}/entry_points.txt +0 -0
  37. {fast_agent_mcp-0.0.9.dist-info → fast_agent_mcp-0.0.12.dist-info}/licenses/LICENSE +0 -0
@@ -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
- return await top_result.result._llm.generate_str(message)
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
- default_prompt: Default message to use when user presses enter
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
- if default == "STOP":
176
- print("Press <ENTER> to finish.")
177
- elif default != "":
178
- print("Enter a prompt, or [red]STOP[/red] to finish.")
179
- print(
180
- f"Press <ENTER> to use the default prompt:\n[cyan]{default}[/cyan]"
181
- )
182
- else:
183
- print("Enter a prompt, or [red]STOP[/red] to finish")
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
- optimizer = agent_data["optimizer"]
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 optimizer not in available_components:
400
- missing.append(f"optimizer: {optimizer}")
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
- def decorator(func: Callable) -> Callable:
467
- # Create base request params
468
- base_params = RequestParams(
469
- use_history=use_history,
470
- model=model, # Include model in initial params
471
- maxTokens=4096, # Default to larger context for agents TODO configurations
472
- **(request_params or {}),
473
- )
474
-
475
- # Create agent configuration
476
- config = AgentConfig(
477
- name=name,
478
- instruction=instruction,
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
- def decorator(func: Callable) -> Callable:
521
- # Create base request params
522
- base_params = RequestParams(
523
- use_history=use_history, model=model, **(request_params or {})
524
- )
525
-
526
- # Create agent configuration
527
- config = AgentConfig(
528
- name=name,
529
- instruction=instruction,
530
- servers=[], # Orchestrators don't need servers
531
- model=model, # Highest precedence
532
- use_history=use_history,
533
- default_request_params=base_params,
534
- human_input=human_input,
535
- )
536
-
537
- # Store the orchestrator configuration
538
- self.agents[name] = {
539
- "config": config,
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
- def decorator(func: Callable) -> Callable:
573
- # Create request params with history setting
574
- params = RequestParams(**(request_params or {}))
575
- params.use_history = use_history
576
-
577
- # Create agent configuration
578
- config = AgentConfig(
579
- name=name,
580
- instruction=instruction,
581
- servers=[], # Parallel agents don't need servers
582
- model=model,
583
- use_history=use_history,
584
- default_request_params=params,
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
- optimizer: str,
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
- optimizer: Name of the optimizer agent
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
- def decorator(func: Callable) -> Callable:
624
- # Create workflow configuration
625
- config = AgentConfig(
626
- name=name,
627
- instruction="", # Uses optimizer's instruction
628
- servers=[], # Uses agents' server access
629
- use_history=use_history,
630
- default_request_params=request_params,
631
- )
632
-
633
- # Store the workflow configuration
634
- self.agents[name] = {
635
- "config": config,
636
- "optimizer": optimizer,
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
- def decorator(func: Callable) -> Callable:
674
- # Create base request params
675
- base_params = RequestParams(
676
- use_history=use_history, **(request_params or {})
677
- )
678
-
679
- # Create agent configuration
680
- config = AgentConfig(
681
- name=name,
682
- instruction="", # Router uses its own routing instruction
683
- servers=[], # , servers are not supported now
684
- model=model,
685
- use_history=use_history,
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 _create_basic_agents(self, agent_app: MCPApp) -> ProxyDict:
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
- Create and initialize basic agents with their configurations.
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 basic agents wrapped in appropriate proxies
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"] == AgentType.BASIC.value:
814
+ if agent_data["type"] == agent_type.value:
815
+ # Get common configuration
719
816
  config = agent_data["config"]
720
817
 
721
- # Create agent with configuration
722
- agent = Agent(config=config, context=agent_app.context)
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
- # Set up LLM with proper configuration
725
- async with agent:
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
- # Create proxy for the agent
733
- active_agents[name] = self._create_proxy(
734
- name, agent, AgentType.BASIC.value
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 active_agents
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
- orchestrators = {}
753
- for name, agent_data in self.agents.items():
754
- if agent_data["type"] == AgentType.ORCHESTRATOR.value:
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
- workflows = {}
832
- for name, agent_data in self.agents.items():
833
- if agent_data["type"] == AgentType.EVALUATOR_OPTIMIZER.value:
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
- agent_data = self.agents[agent_name]
948
- config = agent_data["config"]
949
-
950
- # Get fan-out agents (could be basic agents or other parallels)
951
- fan_out_agents = self._get_agent_instances(
952
- agent_data["fan_out"], active_agents
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
- routers = {}
992
- for name, agent_data in self.agents.items():
993
- if agent_data["type"] == AgentType.ROUTER.value:
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
- print("\n[bold red]Server Configuration Error:")
1089
- print(e.message)
1090
- if e.details:
1091
- print("\nDetails:")
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
- print("\n[bold red]Provider Configuration Error:")
1101
- print(e.message)
1102
- if e.details:
1103
- print("\nDetails:")
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
- print("\n[bold red]Workflow or Agent Configuration Error:")
1113
- print(e.message)
1114
- if e.details:
1115
- print("\nDetails:")
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
- print("\n[bold red]Server Startup Error:")
1125
- print(e.message)
1126
- if e.details:
1127
- print("\nDetails:")
1128
- print(e.details)
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
+ )