fast-agent-mcp 0.0.9__py3-none-any.whl → 0.0.11__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.

@@ -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 (
@@ -128,7 +129,9 @@ class RouterProxy(BaseAgentProxy):
128
129
  top_result = results[0]
129
130
  if isinstance(top_result.result, Agent):
130
131
  # Agent route - delegate to the agent
131
- return await top_result.result._llm.generate_str(message)
132
+ agent = top_result.result
133
+
134
+ return await agent._llm.generate_str(message)
132
135
  elif isinstance(top_result.result, str):
133
136
  # Server route - use the router directly
134
137
  return "Tool call requested by router - not yet supported"
@@ -223,6 +226,34 @@ class FastAgent(ContextDependent):
223
226
  Provides a simplified way to create and manage agents using decorators.
224
227
  """
225
228
 
229
+ def __init__(self, name: str, config_path: Optional[str] = None):
230
+ """
231
+ Initialize the decorator interface.
232
+
233
+ Args:
234
+ name: Name of the application
235
+ config_path: Optional path to config file
236
+ """
237
+ # Initialize ContextDependent
238
+ super().__init__()
239
+
240
+ # Setup command line argument parsing
241
+ parser = argparse.ArgumentParser(description="MCP Agent Application")
242
+ parser.add_argument(
243
+ "--model",
244
+ help="Override the default model for all agents. Precedence is default < config_file < command line < constructor",
245
+ )
246
+ self.args = parser.parse_args()
247
+
248
+ self.name = name
249
+ self.config_path = config_path
250
+ self._load_config()
251
+ self.app = MCPApp(
252
+ name=name,
253
+ settings=Settings(**self.config) if hasattr(self, "config") else None,
254
+ )
255
+ self.agents: Dict[str, Dict[str, Any]] = {}
256
+
226
257
  def _create_proxy(
227
258
  self, name: str, instance: AgentOrWorkflow, agent_type: str
228
259
  ) -> BaseAgentProxy:
@@ -239,6 +270,11 @@ class FastAgent(ContextDependent):
239
270
  Raises:
240
271
  TypeError: If instance type doesn't match expected type for agent_type
241
272
  """
273
+ if agent_type not in [
274
+ AgentType.PARALLEL.value,
275
+ AgentType.EVALUATOR_OPTIMIZER.value,
276
+ ]:
277
+ self._log_agent_load(name)
242
278
  if agent_type == AgentType.BASIC.value:
243
279
  if not isinstance(instance, Agent):
244
280
  raise TypeError(
@@ -272,34 +308,6 @@ class FastAgent(ContextDependent):
272
308
  else:
273
309
  raise ValueError(f"Unknown agent type: {agent_type}")
274
310
 
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
311
  @property
304
312
  def context(self):
305
313
  """Access the application context"""
@@ -440,6 +448,104 @@ class FastAgent(ContextDependent):
440
448
  # Let model factory handle the model string parsing and setup
441
449
  return ModelFactory.create_factory(model_spec, request_params=request_params)
442
450
 
451
+ def _create_decorator(
452
+ self,
453
+ agent_type: AgentType,
454
+ default_name: str = None,
455
+ default_instruction: str = None,
456
+ default_servers: List[str] = None,
457
+ default_use_history: bool = True,
458
+ wrapper_needed: bool = False,
459
+ **extra_defaults,
460
+ ) -> Callable:
461
+ """
462
+ Factory method for creating agent decorators with common behavior.
463
+
464
+ Args:
465
+ agent_type: Type of agent/workflow to create
466
+ default_name: Default name to use if not provided
467
+ default_instruction: Default instruction to use if not provided
468
+ default_servers: Default servers list to use if not provided
469
+ default_use_history: Default history setting
470
+ wrapper_needed: Whether to wrap the decorated function
471
+ **extra_defaults: Additional agent/workflow-specific parameters
472
+ """
473
+
474
+ def decorator_wrapper(**kwargs):
475
+ # Apply defaults for common parameters
476
+ name = kwargs.get("name", default_name or f"{agent_type.name.title()}")
477
+ instruction = kwargs.get("instruction", default_instruction or "")
478
+ servers = kwargs.get("servers", default_servers or [])
479
+ model = kwargs.get("model", None)
480
+ use_history = kwargs.get("use_history", default_use_history)
481
+ request_params = kwargs.get("request_params", None)
482
+ human_input = kwargs.get("human_input", False)
483
+
484
+ # Create base request params
485
+ def decorator(func: Callable) -> Callable:
486
+ # Create base request params
487
+ if (
488
+ request_params is not None
489
+ or model is not None
490
+ or use_history != default_use_history
491
+ ):
492
+ max_tokens = 4096 if agent_type == AgentType.BASIC else None
493
+ params_dict = {"use_history": use_history, "model": model}
494
+ if max_tokens:
495
+ params_dict["maxTokens"] = max_tokens
496
+ if request_params:
497
+ params_dict.update(request_params)
498
+ base_params = RequestParams(**params_dict)
499
+ else:
500
+ base_params = RequestParams(use_history=use_history)
501
+
502
+ # Create agent configuration
503
+ config = AgentConfig(
504
+ name=name,
505
+ instruction=instruction,
506
+ servers=servers,
507
+ model=model,
508
+ use_history=use_history,
509
+ default_request_params=base_params,
510
+ human_input=human_input,
511
+ )
512
+
513
+ # Build agent/workflow specific data
514
+ agent_data = {
515
+ "config": config,
516
+ "type": agent_type.value,
517
+ "func": func,
518
+ }
519
+
520
+ # Add extra parameters specific to this agent type
521
+ for key, value in kwargs.items():
522
+ if key not in [
523
+ "name",
524
+ "instruction",
525
+ "servers",
526
+ "model",
527
+ "use_history",
528
+ "request_params",
529
+ "human_input",
530
+ ]:
531
+ agent_data[key] = value
532
+
533
+ # Store the configuration under the agent name
534
+ self.agents[name] = agent_data
535
+
536
+ # Either wrap or return the original function
537
+ if wrapper_needed:
538
+
539
+ async def wrapper(*args, **kwargs):
540
+ return await func(*args, **kwargs)
541
+
542
+ return wrapper
543
+ return func
544
+
545
+ return decorator
546
+
547
+ return decorator_wrapper
548
+
443
549
  def agent(
444
550
  self,
445
551
  name: str = "Agent",
@@ -461,37 +567,22 @@ class FastAgent(ContextDependent):
461
567
  model: Model specification string (highest precedence)
462
568
  use_history: Whether to maintain conversation history
463
569
  request_params: Additional request parameters for the LLM
570
+ human_input: Whether to enable human input capabilities
464
571
  """
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
-
572
+ decorator = self._create_decorator(
573
+ AgentType.BASIC,
574
+ default_name="Agent",
575
+ default_instruction="You are a helpful agent.",
576
+ default_use_history=True,
577
+ )(
578
+ name=name,
579
+ instruction=instruction,
580
+ servers=servers,
581
+ model=model,
582
+ use_history=use_history,
583
+ request_params=request_params,
584
+ human_input=human_input,
585
+ )
495
586
  return decorator
496
587
 
497
588
  def orchestrator(
@@ -515,35 +606,29 @@ class FastAgent(ContextDependent):
515
606
  model: Model specification string (highest precedence)
516
607
  use_history: Whether to maintain conversation history (forced false)
517
608
  request_params: Additional request parameters for the LLM
609
+ human_input: Whether to enable human input capabilities
518
610
  """
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
-
611
+ default_instruction = """
612
+ You are an expert planner. Given an objective task and a list of MCP servers (which are collections of tools)
613
+ or Agents (which are collections of servers), your job is to break down the objective into a series of steps,
614
+ which can be performed by LLMs with access to the servers or agents.
615
+ """
616
+
617
+ decorator = self._create_decorator(
618
+ AgentType.ORCHESTRATOR,
619
+ default_name="Orchestrator",
620
+ default_instruction=default_instruction,
621
+ default_servers=[],
622
+ default_use_history=False,
623
+ )(
624
+ name=name,
625
+ instruction=instruction,
626
+ child_agents=agents,
627
+ model=model,
628
+ use_history=use_history,
629
+ request_params=request_params,
630
+ human_input=human_input,
631
+ )
547
632
  return decorator
548
633
 
549
634
  def parallel(
@@ -568,33 +653,20 @@ class FastAgent(ContextDependent):
568
653
  use_history: Whether to maintain conversation history
569
654
  request_params: Additional request parameters for the LLM
570
655
  """
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
-
656
+ decorator = self._create_decorator(
657
+ AgentType.PARALLEL,
658
+ default_instruction="",
659
+ default_servers=[],
660
+ default_use_history=True,
661
+ )(
662
+ name=name,
663
+ fan_in=fan_in,
664
+ fan_out=fan_out,
665
+ instruction=instruction,
666
+ model=model,
667
+ use_history=use_history,
668
+ request_params=request_params,
669
+ )
598
670
  return decorator
599
671
 
600
672
  def evaluator_optimizer(
@@ -619,33 +691,21 @@ class FastAgent(ContextDependent):
619
691
  use_history: Whether to maintain conversation history
620
692
  request_params: Additional request parameters for the LLM
621
693
  """
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
-
694
+ decorator = self._create_decorator(
695
+ AgentType.EVALUATOR_OPTIMIZER,
696
+ default_instruction="",
697
+ default_servers=[],
698
+ default_use_history=True,
699
+ wrapper_needed=True,
700
+ )(
701
+ name=name,
702
+ optimizer=optimizer,
703
+ evaluator=evaluator,
704
+ min_rating=min_rating,
705
+ max_refinements=max_refinements,
706
+ use_history=use_history,
707
+ request_params=request_params,
708
+ )
649
709
  return decorator
650
710
 
651
711
  def router(
@@ -664,77 +724,229 @@ class FastAgent(ContextDependent):
664
724
  Args:
665
725
  name: Name of the router
666
726
  agents: List of agent names this router can delegate to
667
- servers: List of server names the router can use directly
727
+ servers: List of server names the router can use directly (currently not supported)
668
728
  model: Model specification string
669
729
  use_history: Whether to maintain conversation history
670
730
  request_params: Additional request parameters for the LLM
731
+ human_input: Whether to enable human input capabilities
671
732
  """
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
-
733
+ decorator = self._create_decorator(
734
+ AgentType.ROUTER,
735
+ default_instruction="",
736
+ default_servers=[],
737
+ default_use_history=True,
738
+ wrapper_needed=True,
739
+ )(
740
+ name=name,
741
+ agents=agents,
742
+ model=model,
743
+ use_history=use_history,
744
+ request_params=request_params,
745
+ human_input=human_input,
746
+ )
703
747
  return decorator
704
748
 
705
- async def _create_basic_agents(self, agent_app: MCPApp) -> ProxyDict:
749
+ async def _create_agents_by_type(
750
+ self,
751
+ agent_app: MCPApp,
752
+ agent_type: AgentType,
753
+ active_agents: ProxyDict = None,
754
+ **kwargs,
755
+ ) -> ProxyDict:
706
756
  """
707
- Create and initialize basic agents with their configurations.
757
+ Generic method to create agents of a specific type.
708
758
 
709
759
  Args:
710
760
  agent_app: The main application instance
761
+ agent_type: Type of agents to create
762
+ active_agents: Dictionary of already created agents/proxies (for dependencies)
763
+ **kwargs: Additional type-specific parameters
711
764
 
712
765
  Returns:
713
- Dictionary of initialized basic agents wrapped in appropriate proxies
766
+ Dictionary of initialized agents wrapped in appropriate proxies
714
767
  """
715
- active_agents = {}
768
+ if active_agents is None:
769
+ active_agents = {}
770
+
771
+ # Create a dictionary to store the initialized agents
772
+ result_agents = {}
716
773
 
774
+ # Get all agents of the specified type
717
775
  for name, agent_data in self.agents.items():
718
- if agent_data["type"] == AgentType.BASIC.value:
776
+ if agent_data["type"] == agent_type.value:
777
+ # Get common configuration
719
778
  config = agent_data["config"]
720
779
 
721
- # Create agent with configuration
722
- agent = Agent(config=config, context=agent_app.context)
780
+ # Type-specific initialization
781
+ if agent_type == AgentType.BASIC:
782
+ # Create basic agent with configuration
783
+ agent = Agent(config=config, context=agent_app.context)
784
+
785
+ # Set up LLM with proper configuration
786
+ async with agent:
787
+ llm_factory = self._get_model_factory(
788
+ model=config.model,
789
+ request_params=config.default_request_params,
790
+ )
791
+ agent._llm = await agent.attach_llm(llm_factory)
792
+
793
+ # Store the agent
794
+ instance = agent
723
795
 
724
- # Set up LLM with proper configuration
725
- async with agent:
796
+ elif agent_type == AgentType.ORCHESTRATOR:
797
+ # Get base params configured with model settings
798
+ base_params = (
799
+ config.default_request_params.model_copy()
800
+ if config.default_request_params
801
+ else RequestParams()
802
+ )
803
+ base_params.use_history = False # Force no history for orchestrator
804
+
805
+ # Get the child agents - need to unwrap proxies and validate LLM config
806
+ child_agents = []
807
+ for agent_name in agent_data["child_agents"]:
808
+ proxy = active_agents[agent_name]
809
+ instance = self._unwrap_proxy(proxy)
810
+ # Validate basic agents have LLM
811
+ if isinstance(instance, Agent):
812
+ if not hasattr(instance, "_llm") or not instance._llm:
813
+ raise AgentConfigError(
814
+ f"Agent '{agent_name}' used by orchestrator '{name}' missing LLM configuration",
815
+ "All agents must be fully configured with LLMs before being used in an orchestrator",
816
+ )
817
+ child_agents.append(instance)
818
+
819
+ # Create a properly configured planner agent
820
+ planner_config = AgentConfig(
821
+ name=f"{name}", # Use orchestrator name as prefix
822
+ instruction=config.instruction
823
+ or """
824
+ You are an expert planner. Given an objective task and a list of MCP servers (which are collections of tools)
825
+ or Agents (which are collections of servers), your job is to break down the objective into a series of steps,
826
+ which can be performed by LLMs with access to the servers or agents.
827
+ """,
828
+ servers=[], # Planner doesn't need server access
829
+ model=config.model, # Use same model as orchestrator
830
+ default_request_params=base_params,
831
+ )
832
+ planner_agent = Agent(
833
+ config=planner_config, context=agent_app.context
834
+ )
835
+ planner_factory = self._get_model_factory(
836
+ model=config.model,
837
+ request_params=config.default_request_params,
838
+ )
839
+
840
+ async with planner_agent:
841
+ planner = await planner_agent.attach_llm(planner_factory)
842
+
843
+ # Create the orchestrator with pre-configured planner
844
+ instance = Orchestrator(
845
+ name=config.name,
846
+ planner=planner, # Pass pre-configured planner
847
+ available_agents=child_agents,
848
+ context=agent_app.context,
849
+ request_params=planner.default_request_params, # Base params already include model settings
850
+ plan_type="full", # TODO -- support iterative plan type properly
851
+ verb=ProgressAction.PLANNING, # Using PLANNING instead of ORCHESTRATING
852
+ )
853
+
854
+ elif agent_type == AgentType.EVALUATOR_OPTIMIZER:
855
+ # Get the referenced agents - unwrap from proxies
856
+ optimizer = self._unwrap_proxy(
857
+ active_agents[agent_data["optimizer"]]
858
+ )
859
+ evaluator = self._unwrap_proxy(
860
+ active_agents[agent_data["evaluator"]]
861
+ )
862
+
863
+ if not optimizer or not evaluator:
864
+ raise ValueError(
865
+ f"Missing agents for workflow {name}: "
866
+ f"optimizer={agent_data['optimizer']}, "
867
+ f"evaluator={agent_data['evaluator']}"
868
+ )
869
+
870
+ # TODO: Remove legacy - factory usage is only needed for str evaluators
871
+ # Later this should only be passed when evaluator is a string
872
+ optimizer_model = (
873
+ optimizer.config.model if isinstance(optimizer, Agent) else None
874
+ )
875
+ instance = EvaluatorOptimizerLLM(
876
+ optimizer=optimizer,
877
+ evaluator=evaluator,
878
+ min_rating=QualityRating[agent_data["min_rating"]],
879
+ max_refinements=agent_data["max_refinements"],
880
+ llm_factory=self._get_model_factory(model=optimizer_model),
881
+ context=agent_app.context,
882
+ )
883
+
884
+ elif agent_type == AgentType.ROUTER:
885
+ # Get the router's agents - unwrap proxies
886
+ router_agents = self._get_agent_instances(
887
+ agent_data["agents"], active_agents
888
+ )
889
+
890
+ # Create the router with proper configuration
726
891
  llm_factory = self._get_model_factory(
727
892
  model=config.model,
728
893
  request_params=config.default_request_params,
729
894
  )
730
- agent._llm = await agent.attach_llm(llm_factory)
731
895
 
732
- # Create proxy for the agent
733
- active_agents[name] = self._create_proxy(
734
- name, agent, AgentType.BASIC.value
896
+ instance = LLMRouter(
897
+ name=config.name,
898
+ llm_factory=llm_factory,
899
+ agents=router_agents,
900
+ server_names=config.servers,
901
+ context=agent_app.context,
902
+ default_request_params=config.default_request_params,
903
+ verb=ProgressAction.ROUTING, # Set verb for progress display
904
+ )
905
+
906
+ elif agent_type == AgentType.PARALLEL:
907
+ # Get fan-out agents (could be basic agents or other parallels)
908
+ fan_out_agents = self._get_agent_instances(
909
+ agent_data["fan_out"], active_agents
910
+ )
911
+
912
+ # Get fan-in agent - unwrap proxy
913
+ fan_in_agent = self._unwrap_proxy(
914
+ active_agents[agent_data["fan_in"]]
915
+ )
916
+
917
+ # Create the parallel workflow
918
+ llm_factory = self._get_model_factory(config.model)
919
+ instance = ParallelLLM(
920
+ name=config.name,
921
+ instruction=config.instruction,
922
+ fan_out_agents=fan_out_agents,
923
+ fan_in_agent=fan_in_agent,
924
+ context=agent_app.context,
925
+ llm_factory=llm_factory,
926
+ default_request_params=config.default_request_params,
927
+ )
928
+
929
+ else:
930
+ raise ValueError(f"Unsupported agent type: {agent_type}")
931
+
932
+ # Create the appropriate proxy and store in results
933
+ result_agents[name] = self._create_proxy(
934
+ name, instance, agent_type.value
735
935
  )
736
936
 
737
- return active_agents
937
+ return result_agents
938
+
939
+ async def _create_basic_agents(self, agent_app: MCPApp) -> ProxyDict:
940
+ """
941
+ Create and initialize basic agents with their configurations.
942
+
943
+ Args:
944
+ agent_app: The main application instance
945
+
946
+ Returns:
947
+ Dictionary of initialized basic agents wrapped in appropriate proxies
948
+ """
949
+ return await self._create_agents_by_type(agent_app, AgentType.BASIC)
738
950
 
739
951
  async def _create_orchestrators(
740
952
  self, agent_app: MCPApp, active_agents: ProxyDict
@@ -749,71 +961,9 @@ class FastAgent(ContextDependent):
749
961
  Returns:
750
962
  Dictionary of initialized orchestrator agents wrapped in appropriate proxies
751
963
  """
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
964
+ return await self._create_agents_by_type(
965
+ agent_app, AgentType.ORCHESTRATOR, active_agents
966
+ )
817
967
 
818
968
  async def _create_evaluator_optimizers(
819
969
  self, agent_app: MCPApp, active_agents: ProxyDict
@@ -828,39 +978,9 @@ class FastAgent(ContextDependent):
828
978
  Returns:
829
979
  Dictionary of initialized evaluator-optimizer workflows
830
980
  """
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
981
+ return await self._create_agents_by_type(
982
+ agent_app, AgentType.EVALUATOR_OPTIMIZER, active_agents
983
+ )
864
984
 
865
985
  def _get_parallel_dependencies(
866
986
  self, name: str, visited: set, path: set
@@ -944,34 +1064,15 @@ class FastAgent(ContextDependent):
944
1064
  # Create each agent in order
945
1065
  for agent_name in ordered_agents:
946
1066
  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
1067
+ # Create one parallel agent at a time using the generic method
1068
+ agent_result = await self._create_agents_by_type(
1069
+ agent_app,
1070
+ AgentType.PARALLEL,
1071
+ active_agents,
1072
+ agent_name=agent_name,
974
1073
  )
1074
+ if agent_name in agent_result:
1075
+ parallel_agents[agent_name] = agent_result[agent_name]
975
1076
 
976
1077
  return parallel_agents
977
1078
 
@@ -988,34 +1089,9 @@ class FastAgent(ContextDependent):
988
1089
  Returns:
989
1090
  Dictionary of initialized router agents
990
1091
  """
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
1092
+ return await self._create_agents_by_type(
1093
+ agent_app, AgentType.ROUTER, active_agents
1094
+ )
1019
1095
 
1020
1096
  def _unwrap_proxy(self, proxy: BaseAgentProxy) -> AgentOrWorkflow:
1021
1097
  """
@@ -1085,48 +1161,38 @@ class FastAgent(ContextDependent):
1085
1161
 
1086
1162
  except ServerConfigError as e:
1087
1163
  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."
1164
+ self._handle_error(
1165
+ e,
1166
+ "Server Configuration Error",
1167
+ "Please check your 'fastagent.config.yaml' configuration file and add the missing server definitions.",
1095
1168
  )
1096
1169
  raise SystemExit(1)
1097
1170
 
1098
1171
  except ProviderKeyError as e:
1099
1172
  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."
1173
+ self._handle_error(
1174
+ e,
1175
+ "Provider Configuration Error",
1176
+ "Please check your 'fastagent.secrets.yaml' configuration file and ensure all required API keys are set.",
1107
1177
  )
1108
1178
  raise SystemExit(1)
1109
1179
 
1110
1180
  except AgentConfigError as e:
1111
1181
  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."
1182
+ self._handle_error(
1183
+ e,
1184
+ "Workflow or Agent Configuration Error",
1185
+ "Please check your agent definition and ensure names and references are correct.",
1119
1186
  )
1120
1187
  raise SystemExit(1)
1121
1188
 
1122
1189
  except ServerInitializationError as e:
1123
1190
  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.")
1191
+ self._handle_error(
1192
+ e,
1193
+ "Server Startup Error",
1194
+ "There was an error starting up the MCP Server.",
1195
+ )
1130
1196
  raise SystemExit(1)
1131
1197
  finally:
1132
1198
  # Clean up any active agents without re-raising errors
@@ -1137,3 +1203,31 @@ class FastAgent(ContextDependent):
1137
1203
  await proxy._agent.__aexit__(None, None, None)
1138
1204
  except Exception:
1139
1205
  pass # Ignore cleanup errors
1206
+
1207
+ def _handle_error(
1208
+ self, e: Exception, error_type: str, suggestion: str = None
1209
+ ) -> None:
1210
+ """
1211
+ Handle errors with consistent formatting and messaging.
1212
+
1213
+ Args:
1214
+ e: The exception that was raised
1215
+ error_type: Type of error to display
1216
+ suggestion: Optional suggestion message to display
1217
+ """
1218
+ print(f"\n[bold red]{error_type}:")
1219
+ print(getattr(e, "message", str(e)))
1220
+ if hasattr(e, "details") and e.details:
1221
+ print("\nDetails:")
1222
+ print(e.details)
1223
+ if suggestion:
1224
+ print(f"\n{suggestion}")
1225
+
1226
+ def _log_agent_load(self, agent_name: str) -> None:
1227
+ self.app._logger.info(
1228
+ f"Loaded {agent_name}",
1229
+ data={
1230
+ "progress_action": ProgressAction.LOADED,
1231
+ "agent_name": agent_name,
1232
+ },
1233
+ )