fast-agent-mcp 0.0.11__py3-none-any.whl → 0.0.13__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 (30) hide show
  1. {fast_agent_mcp-0.0.11.dist-info → fast_agent_mcp-0.0.13.dist-info}/METADATA +9 -1
  2. {fast_agent_mcp-0.0.11.dist-info → fast_agent_mcp-0.0.13.dist-info}/RECORD +30 -25
  3. mcp_agent/agents/agent.py +48 -8
  4. mcp_agent/cli/commands/bootstrap.py +2 -5
  5. mcp_agent/cli/commands/setup.py +1 -1
  6. mcp_agent/cli/main.py +6 -6
  7. mcp_agent/core/enhanced_prompt.py +358 -0
  8. mcp_agent/core/exceptions.py +17 -0
  9. mcp_agent/core/fastagent.py +108 -34
  10. mcp_agent/human_input/handler.py +43 -18
  11. mcp_agent/mcp/mcp_connection_manager.py +14 -12
  12. mcp_agent/resources/examples/internal/agent.py +17 -0
  13. mcp_agent/resources/examples/internal/job.py +1 -1
  14. mcp_agent/resources/examples/mcp_researcher/researcher-eval.py +1 -1
  15. mcp_agent/resources/examples/researcher/fastagent.config.yaml +53 -0
  16. mcp_agent/resources/examples/researcher/researcher-eval.py +53 -0
  17. mcp_agent/resources/examples/workflows/chaining.py +5 -1
  18. mcp_agent/resources/examples/workflows/evaluator.py +7 -4
  19. mcp_agent/resources/examples/workflows/fastagent.config.yaml +24 -0
  20. mcp_agent/resources/examples/workflows/orchestrator.py +3 -2
  21. mcp_agent/resources/examples/workflows/parallel.py +2 -1
  22. mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +31 -30
  23. mcp_agent/workflows/llm/augmented_llm.py +8 -2
  24. mcp_agent/workflows/llm/augmented_llm_anthropic.py +3 -1
  25. mcp_agent/workflows/llm/augmented_llm_openai.py +20 -9
  26. mcp_agent/workflows/llm/model_factory.py +7 -4
  27. {fast_agent_mcp-0.0.11.dist-info → fast_agent_mcp-0.0.13.dist-info}/WHEEL +0 -0
  28. {fast_agent_mcp-0.0.11.dist-info → fast_agent_mcp-0.0.13.dist-info}/entry_points.txt +0 -0
  29. {fast_agent_mcp-0.0.11.dist-info → fast_agent_mcp-0.0.13.dist-info}/licenses/LICENSE +0 -0
  30. /mcp_agent/resources/examples/{mcp_researcher → researcher}/researcher.py +0 -0
@@ -0,0 +1,358 @@
1
+ """
2
+ Enhanced prompt functionality with advanced prompt_toolkit features.
3
+ """
4
+
5
+ from typing import List
6
+ from prompt_toolkit import PromptSession
7
+ from prompt_toolkit.formatted_text import HTML
8
+ from prompt_toolkit.history import InMemoryHistory
9
+ from prompt_toolkit.key_binding import KeyBindings
10
+ from prompt_toolkit.completion import Completer, Completion
11
+ from prompt_toolkit.lexers import PygmentsLexer
12
+ from prompt_toolkit.filters import Condition
13
+ from pygments.lexers.python import PythonLexer
14
+ from rich import print as rich_print
15
+
16
+ from mcp_agent.core.exceptions import PromptExitError
17
+
18
+ # Map of agent names to their history
19
+ agent_histories = {}
20
+
21
+ # Store available agents for auto-completion
22
+ available_agents = set()
23
+
24
+ # Keep track of multi-line mode state
25
+ in_multiline_mode = False
26
+
27
+ # Track which agents have already shown welcome messages
28
+ agent_messages_shown = set()
29
+
30
+
31
+ class AgentCompleter(Completer):
32
+ """Provide completion for agent names and common commands."""
33
+
34
+ def __init__(
35
+ self,
36
+ agents: List[str],
37
+ commands: List[str] = None,
38
+ agent_types: dict = None,
39
+ is_human_input: bool = False,
40
+ ):
41
+ self.agents = agents
42
+ # Map commands to their descriptions for better completion hints
43
+ self.commands = {
44
+ "help": "Show available commands",
45
+ "clear": "Clear the screen",
46
+ "agents": "List available agents",
47
+ "STOP": "Stop this prompting session and move to next workflow step",
48
+ "EXIT": "Exit FastAgent, terminating any running workflows",
49
+ **(commands or {}), # Allow custom commands to be passed in
50
+ }
51
+ if is_human_input:
52
+ self.commands.pop("agents")
53
+ self.agent_types = agent_types or {}
54
+
55
+ def get_completions(self, document, complete_event):
56
+ """Synchronous completions method - this is what prompt_toolkit expects by default"""
57
+ text = document.text_before_cursor.lower()
58
+
59
+ # Complete commands
60
+ if text.startswith("/"):
61
+ cmd = text[1:]
62
+ for command, description in self.commands.items():
63
+ if command.lower().startswith(cmd):
64
+ yield Completion(
65
+ command,
66
+ start_position=-len(cmd),
67
+ display=command,
68
+ display_meta=description,
69
+ )
70
+
71
+ # Complete agent names for agent-related commands
72
+ elif text.startswith("@"):
73
+ agent_name = text[1:]
74
+ for agent in self.agents:
75
+ if agent.lower().startswith(agent_name.lower()):
76
+ # Get agent type or default to "Agent"
77
+ agent_type = self.agent_types.get(agent, "Agent")
78
+ yield Completion(
79
+ agent,
80
+ start_position=-len(agent_name),
81
+ display=agent,
82
+ display_meta=agent_type,
83
+ style="bg:ansiblack fg:ansiblue",
84
+ )
85
+
86
+
87
+ def create_keybindings(on_toggle_multiline=None, app=None):
88
+ """Create custom key bindings."""
89
+ kb = KeyBindings()
90
+
91
+ @kb.add("c-m", filter=Condition(lambda: not in_multiline_mode))
92
+ def _(event):
93
+ """Enter: accept input when not in multiline mode."""
94
+ event.current_buffer.validate_and_handle()
95
+
96
+ @kb.add("c-m", filter=Condition(lambda: in_multiline_mode))
97
+ def _(event):
98
+ """Enter: insert newline when in multiline mode."""
99
+ event.current_buffer.insert_text("\n")
100
+
101
+ # Use c-j (Ctrl+J) as an alternative to represent Ctrl+Enter in multiline mode
102
+ @kb.add("c-j", filter=Condition(lambda: in_multiline_mode))
103
+ def _(event):
104
+ """Ctrl+J (equivalent to Ctrl+Enter): Submit in multiline mode."""
105
+ event.current_buffer.validate_and_handle()
106
+
107
+ @kb.add("c-t")
108
+ def _(event):
109
+ """Ctrl+T: Toggle multiline mode."""
110
+ global in_multiline_mode
111
+ in_multiline_mode = not in_multiline_mode
112
+
113
+ # Force redraw the app to update toolbar
114
+ if event.app:
115
+ event.app.invalidate()
116
+ elif app:
117
+ app.invalidate()
118
+
119
+ # Call the toggle callback if provided
120
+ if on_toggle_multiline:
121
+ on_toggle_multiline(in_multiline_mode)
122
+
123
+ # Instead of printing, we'll just update the toolbar
124
+ # The toolbar will show the current mode
125
+
126
+ @kb.add("c-l")
127
+ def _(event):
128
+ """Ctrl+L: Clear the input buffer."""
129
+ event.current_buffer.text = ""
130
+
131
+ return kb
132
+
133
+
134
+ async def get_enhanced_input(
135
+ agent_name: str,
136
+ default: str = "",
137
+ show_default: bool = False,
138
+ show_stop_hint: bool = False,
139
+ multiline: bool = False,
140
+ available_agent_names: List[str] = None,
141
+ syntax: str = None,
142
+ agent_types: dict = None,
143
+ is_human_input: bool = False,
144
+ toolbar_color: str = "ansiblue",
145
+ ) -> str:
146
+ """
147
+ Enhanced input with advanced prompt_toolkit features.
148
+
149
+ Args:
150
+ agent_name: Name of the agent (used for prompt and history)
151
+ default: Default value if user presses enter
152
+ show_default: Whether to show the default value in the prompt
153
+ show_stop_hint: Whether to show the STOP hint
154
+ multiline: Start in multiline mode
155
+ available_agent_names: List of agent names for auto-completion
156
+ syntax: Syntax highlighting (e.g., 'python', 'sql')
157
+ agent_types: Dictionary mapping agent names to their types for display
158
+ is_human_input: Whether this is a human input request (disables agent selection features)
159
+ toolbar_color: Color to use for the agent name in the toolbar (default: "ansiblue")
160
+
161
+ Returns:
162
+ User input string
163
+ """
164
+ global in_multiline_mode, available_agents
165
+
166
+ # Update global state
167
+ in_multiline_mode = multiline
168
+ if available_agent_names:
169
+ available_agents = set(available_agent_names)
170
+
171
+ # Get or create history object for this agent
172
+ if agent_name not in agent_histories:
173
+ agent_histories[agent_name] = InMemoryHistory()
174
+
175
+ # Define callback for multiline toggle
176
+ def on_multiline_toggle(enabled):
177
+ nonlocal session
178
+ if hasattr(session, "app") and session.app:
179
+ session.app.invalidate()
180
+
181
+ # Define toolbar function that will update dynamically
182
+ def get_toolbar():
183
+ if in_multiline_mode:
184
+ mode_style = "ansired" # More noticeable for multiline mode
185
+ mode_text = "MULTILINE"
186
+ toggle_text = "Normal Editing"
187
+ else:
188
+ mode_style = "ansigreen"
189
+ mode_text = "NORMAL"
190
+ toggle_text = "Multiline Editing"
191
+
192
+ shortcuts = [
193
+ ("Ctrl+T", toggle_text),
194
+ ("Ctrl+L", "Clear"),
195
+ ("↑/↓", "History"),
196
+ ]
197
+
198
+ newline = (
199
+ "Ctrl+<Enter>:Submit" if in_multiline_mode else "<Enter>:Submit"
200
+ )
201
+
202
+ # Only show relevant shortcuts based on mode
203
+ shortcuts = [(k, v) for k, v in shortcuts if v]
204
+
205
+ shortcut_text = " | ".join(f"{key}:{action}" for key, action in shortcuts)
206
+ return HTML(
207
+ f" <{toolbar_color}> {agent_name} </{toolbar_color}> | <b>Mode:</b> <{mode_style}> {mode_text} </{mode_style}> {newline} | {shortcut_text}"
208
+ )
209
+
210
+ # Create session with history and completions
211
+ session = PromptSession(
212
+ history=agent_histories[agent_name],
213
+ completer=AgentCompleter(
214
+ agents=list(available_agents) if available_agents else [],
215
+ agent_types=agent_types or {},
216
+ is_human_input=is_human_input,
217
+ ),
218
+ complete_while_typing=True,
219
+ lexer=PygmentsLexer(PythonLexer) if syntax == "python" else None,
220
+ multiline=Condition(lambda: in_multiline_mode),
221
+ complete_in_thread=True,
222
+ mouse_support=False,
223
+ bottom_toolbar=get_toolbar, # Pass the function here
224
+ )
225
+
226
+ # Create key bindings with a reference to the app
227
+ bindings = create_keybindings(
228
+ on_toggle_multiline=on_multiline_toggle, app=session.app
229
+ )
230
+ session.app.key_bindings = bindings
231
+
232
+ # Create formatted prompt text
233
+ prompt_text = f"<ansicyan>{agent_name}</ansicyan> > "
234
+
235
+ # Add default value display if requested
236
+ if show_default and default and default != "STOP":
237
+ prompt_text = f"{prompt_text} [<ansigreen>{default}</ansigreen>] "
238
+
239
+ # Only show hints at startup if requested
240
+ if show_stop_hint:
241
+ if default == "STOP":
242
+ rich_print("[yellow]Press <ENTER> to finish.[/yellow]")
243
+ else:
244
+ rich_print("Enter a prompt, or [red]STOP[/red] to finish")
245
+ if default:
246
+ rich_print(
247
+ f"Press <ENTER> to use the default prompt:\n[cyan]{default}[/cyan]"
248
+ )
249
+
250
+ # Mention available features but only on first usage for this agent
251
+ if agent_name not in agent_messages_shown:
252
+ if is_human_input:
253
+ rich_print(
254
+ "[dim]Tip: Type /help for commands. Ctrl+T toggles multiline mode. Ctrl+Enter to submit in multiline mode.[/dim]"
255
+ )
256
+ else:
257
+ rich_print(
258
+ "[dim]Tip: Type /help for commands, @Agent to switch agent. Ctrl+T toggles multiline mode. [/dim]"
259
+ )
260
+ agent_messages_shown.add(agent_name)
261
+
262
+ # Process special commands
263
+ def pre_process_input(text):
264
+ # Command processing
265
+ if text and text.startswith("/"):
266
+ cmd = text[1:].strip().lower()
267
+ if cmd == "help":
268
+ return "HELP"
269
+ elif cmd == "clear":
270
+ return "CLEAR"
271
+ elif cmd == "agents":
272
+ return "LIST_AGENTS"
273
+ elif cmd == "exit":
274
+ return "EXIT"
275
+ elif cmd == "stop":
276
+ return "STOP"
277
+
278
+ # Agent switching
279
+ if text and text.startswith("@"):
280
+ return f"SWITCH:{text[1:].strip()}"
281
+
282
+ return text
283
+
284
+ # Get the input - using async version
285
+ try:
286
+ result = await session.prompt_async(HTML(prompt_text), default=default)
287
+ return pre_process_input(result)
288
+ except KeyboardInterrupt:
289
+ # Handle Ctrl+C gracefully
290
+ return "STOP"
291
+ except EOFError:
292
+ # Handle Ctrl+D gracefully
293
+ return "STOP"
294
+ except Exception as e:
295
+ # Log and gracefully handle other exceptions
296
+ print(f"\nInput error: {type(e).__name__}: {e}")
297
+ return "STOP"
298
+
299
+
300
+ async def handle_special_commands(command, agent_app=None):
301
+ """Handle special input commands."""
302
+ # Quick guard for empty or None commands
303
+ if not command:
304
+ return False
305
+
306
+ # Check for special commands
307
+ if command == "HELP":
308
+ rich_print("\n[bold]Available Commands:[/bold]")
309
+ rich_print(" /help - Show this help")
310
+ rich_print(" /clear - Clear screen")
311
+ rich_print(" /agents - List available agents")
312
+ rich_print(" @agent_name - Switch to agent")
313
+ rich_print(" STOP - Return control back to the workflow")
314
+ rich_print(
315
+ " EXIT - Exit FastAgent, terminating any running workflows"
316
+ )
317
+ rich_print("\n[bold]Keyboard Shortcuts:[/bold]")
318
+ rich_print(
319
+ " Enter - Submit (normal mode) / New line (multiline mode)"
320
+ )
321
+ rich_print(" Ctrl+Enter - Always submit (even in multiline mode)")
322
+ rich_print(" Ctrl+T - Toggle multiline mode")
323
+ rich_print(" Ctrl+L - Clear input")
324
+ rich_print(" Up/Down - Navigate history")
325
+ return True
326
+
327
+ elif command == "CLEAR":
328
+ # Clear screen (ANSI escape sequence)
329
+ print("\033c", end="")
330
+ return True
331
+
332
+ elif command == "EXIT":
333
+ raise PromptExitError("User requested to exit FastAgent session")
334
+
335
+ elif command == "LIST_AGENTS":
336
+ if available_agents:
337
+ rich_print("\n[bold]Available Agents:[/bold]")
338
+ for agent in sorted(available_agents):
339
+ rich_print(f" @{agent}")
340
+ else:
341
+ rich_print("[yellow]No agents available[/yellow]")
342
+ return True
343
+
344
+ elif isinstance(command, str) and command.startswith("SWITCH:"):
345
+ agent_name = command.split(":", 1)[1]
346
+ if agent_name in available_agents:
347
+ if agent_app:
348
+ rich_print(f"[green]Switching to agent: {agent_name}[/green]")
349
+ return {"switch_agent": agent_name}
350
+ else:
351
+ rich_print(
352
+ "[yellow]Agent switching not available in this context[/yellow]"
353
+ )
354
+ else:
355
+ rich_print(f"[red]Unknown agent: {agent_name}[/red]")
356
+ return True
357
+
358
+ return False
@@ -45,3 +45,20 @@ class ServerInitializationError(FastAgentError):
45
45
 
46
46
  def __init__(self, message: str, details: str = ""):
47
47
  super().__init__(message, details)
48
+
49
+
50
+ class ModelConfigError(FastAgentError):
51
+ """Raised when there are issues with LLM model configuration
52
+ Example: Unknown model name in model specification string
53
+ """
54
+
55
+ def __init__(self, message: str, details: str = ""):
56
+ super().__init__(message, details)
57
+
58
+
59
+ class PromptExitError(FastAgentError):
60
+ """Raised from enhanced_prompt when the user requests hard exits"""
61
+
62
+ # TODO an exception for flow control :(
63
+ def __init__(self, message: str, details: str = ""):
64
+ super().__init__(message, details)
@@ -3,7 +3,17 @@ Decorator-based interface for MCP Agent applications.
3
3
  Provides a simplified way to create and manage agents using decorators.
4
4
  """
5
5
 
6
- from typing import List, Optional, Dict, Callable, TypeVar, Any, Union, TypeAlias
6
+ from typing import (
7
+ List,
8
+ Optional,
9
+ Dict,
10
+ Callable,
11
+ TypeVar,
12
+ Any,
13
+ Union,
14
+ TypeAlias,
15
+ Literal,
16
+ )
7
17
  from enum import Enum
8
18
  import yaml
9
19
  import argparse
@@ -11,6 +21,8 @@ from contextlib import asynccontextmanager
11
21
 
12
22
  from mcp_agent.core.exceptions import (
13
23
  AgentConfigError,
24
+ ModelConfigError,
25
+ PromptExitError,
14
26
  ServerConfigError,
15
27
  ProviderKeyError,
16
28
  ServerInitializationError,
@@ -28,7 +40,6 @@ from mcp_agent.workflows.evaluator_optimizer.evaluator_optimizer import (
28
40
  )
29
41
  from mcp_agent.workflows.router.router_llm import LLMRouter
30
42
  from mcp_agent.config import Settings
31
- from rich.prompt import Prompt
32
43
  from rich import print
33
44
  from mcp_agent.progress_display import progress_display
34
45
  from mcp_agent.workflows.llm.model_factory import ModelFactory
@@ -161,34 +172,73 @@ class AgentApp:
161
172
 
162
173
  async def prompt(self, agent_name: Optional[str] = None, default: str = "") -> str:
163
174
  """
164
- Interactive prompt for sending messages.
175
+ Interactive prompt for sending messages with advanced features.
165
176
 
166
177
  Args:
167
178
  agent_name: Optional target agent name (uses default if not specified)
168
- default_prompt: Default message to use when user presses enter
179
+ default: Default message to use when user presses enter
169
180
  """
181
+ from .enhanced_prompt import get_enhanced_input, handle_special_commands
170
182
 
171
183
  agent = agent_name or self._default
172
184
 
173
185
  if agent not in self._agents:
174
186
  raise ValueError(f"No agent named '{agent}'")
187
+
188
+ # Pass all available agent names for auto-completion
189
+ available_agents = list(self._agents.keys())
190
+
191
+ # Create agent_types dictionary mapping agent names to their types
192
+ agent_types = {}
193
+ for name, proxy in self._agents.items():
194
+ # Determine agent type based on the proxy type
195
+ if isinstance(proxy, LLMAgentProxy):
196
+ # Convert AgentType.BASIC.value ("agent") to "Agent"
197
+ agent_types[name] = "Agent"
198
+ elif isinstance(proxy, RouterProxy):
199
+ agent_types[name] = "Router"
200
+ elif isinstance(proxy, WorkflowProxy):
201
+ # For workflow proxies, check the workflow type
202
+ workflow = proxy._workflow
203
+ if isinstance(workflow, Orchestrator):
204
+ agent_types[name] = "Orchestrator"
205
+ elif isinstance(workflow, ParallelLLM):
206
+ agent_types[name] = "Parallel"
207
+ elif isinstance(workflow, EvaluatorOptimizerLLM):
208
+ agent_types[name] = "Evaluator"
209
+ else:
210
+ agent_types[name] = "Workflow"
211
+
175
212
  result = ""
176
213
  while True:
177
214
  with progress_display.paused():
178
- if default == "STOP":
179
- print("Press <ENTER> to finish.")
180
- elif default != "":
181
- print("Enter a prompt, or [red]STOP[/red] to finish.")
182
- print(
183
- f"Press <ENTER> to use the default prompt:\n[cyan]{default}[/cyan]"
184
- )
185
- else:
186
- print("Enter a prompt, or [red]STOP[/red] to finish")
187
-
188
- prompt_text = f"[blue]{agent}[/blue] >"
189
- user_input = Prompt.ask(
190
- prompt=prompt_text, default=default, show_default=False
215
+ # Use the enhanced input method with advanced features
216
+ user_input = await get_enhanced_input(
217
+ agent_name=agent,
218
+ default=default,
219
+ show_default=(default != ""),
220
+ show_stop_hint=True,
221
+ multiline=False, # Default to single-line mode
222
+ available_agent_names=available_agents,
223
+ syntax=None, # Can enable syntax highlighting for code input
224
+ agent_types=agent_types, # Pass agent types for display
191
225
  )
226
+
227
+ # Handle special commands
228
+ command_result = await handle_special_commands(user_input, self)
229
+
230
+ # Check if we should switch agents
231
+ if (
232
+ isinstance(command_result, dict)
233
+ and "switch_agent" in command_result
234
+ ):
235
+ agent = command_result["switch_agent"]
236
+ continue
237
+
238
+ # Skip further processing if command was handled
239
+ if command_result:
240
+ continue
241
+
192
242
  if user_input.upper() == "STOP":
193
243
  return
194
244
  if user_input == "":
@@ -400,12 +450,12 @@ class FastAgent(ContextDependent):
400
450
  elif agent_type == AgentType.EVALUATOR_OPTIMIZER.value:
401
451
  # Check both evaluator and optimizer exist
402
452
  evaluator = agent_data["evaluator"]
403
- optimizer = agent_data["optimizer"]
453
+ generator = agent_data["generator"]
404
454
  missing = []
405
455
  if evaluator not in available_components:
406
456
  missing.append(f"evaluator: {evaluator}")
407
- if optimizer not in available_components:
408
- missing.append(f"optimizer: {optimizer}")
457
+ if generator not in available_components:
458
+ missing.append(f"generator: {generator}")
409
459
  if missing:
410
460
  raise AgentConfigError(
411
461
  f"Evaluator-Optimizer '{name}' references non-existent components: {', '.join(missing)}"
@@ -595,6 +645,7 @@ class FastAgent(ContextDependent):
595
645
  use_history: bool = False,
596
646
  request_params: Optional[Dict] = None,
597
647
  human_input: bool = False,
648
+ plan_type: Literal["full", "iterative"] = "full",
598
649
  ) -> Callable:
599
650
  """
600
651
  Decorator to create and register an orchestrator.
@@ -607,6 +658,7 @@ class FastAgent(ContextDependent):
607
658
  use_history: Whether to maintain conversation history (forced false)
608
659
  request_params: Additional request parameters for the LLM
609
660
  human_input: Whether to enable human input capabilities
661
+ plan_type: Planning approach - "full" generates entire plan first, "iterative" plans one step at a time
610
662
  """
611
663
  default_instruction = """
612
664
  You are an expert planner. Given an objective task and a list of MCP servers (which are collections of tools)
@@ -628,6 +680,7 @@ class FastAgent(ContextDependent):
628
680
  use_history=use_history,
629
681
  request_params=request_params,
630
682
  human_input=human_input,
683
+ plan_type=plan_type,
631
684
  )
632
685
  return decorator
633
686
 
@@ -672,7 +725,7 @@ class FastAgent(ContextDependent):
672
725
  def evaluator_optimizer(
673
726
  self,
674
727
  name: str,
675
- optimizer: str,
728
+ generator: str,
676
729
  evaluator: str,
677
730
  min_rating: str = "GOOD",
678
731
  max_refinements: int = 3,
@@ -684,7 +737,7 @@ class FastAgent(ContextDependent):
684
737
 
685
738
  Args:
686
739
  name: Name of the workflow
687
- optimizer: Name of the optimizer agent
740
+ generator: Name of the generator agent
688
741
  evaluator: Name of the evaluator agent
689
742
  min_rating: Minimum acceptable quality rating (EXCELLENT, GOOD, FAIR, POOR)
690
743
  max_refinements: Maximum number of refinement iterations
@@ -699,7 +752,7 @@ class FastAgent(ContextDependent):
699
752
  wrapper_needed=True,
700
753
  )(
701
754
  name=name,
702
- optimizer=optimizer,
755
+ generator=generator,
703
756
  evaluator=evaluator,
704
757
  min_rating=min_rating,
705
758
  max_refinements=max_refinements,
@@ -826,7 +879,7 @@ class FastAgent(ContextDependent):
826
879
  which can be performed by LLMs with access to the servers or agents.
827
880
  """,
828
881
  servers=[], # Planner doesn't need server access
829
- model=config.model, # Use same model as orchestrator
882
+ model=config.model,
830
883
  default_request_params=base_params,
831
884
  )
832
885
  planner_agent = Agent(
@@ -847,33 +900,35 @@ class FastAgent(ContextDependent):
847
900
  available_agents=child_agents,
848
901
  context=agent_app.context,
849
902
  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
903
+ plan_type=agent_data.get(
904
+ "plan_type", "full"
905
+ ), # Get plan_type from agent_data
906
+ verb=ProgressAction.PLANNING,
852
907
  )
853
908
 
854
909
  elif agent_type == AgentType.EVALUATOR_OPTIMIZER:
855
910
  # Get the referenced agents - unwrap from proxies
856
- optimizer = self._unwrap_proxy(
857
- active_agents[agent_data["optimizer"]]
911
+ generator = self._unwrap_proxy(
912
+ active_agents[agent_data["generator"]]
858
913
  )
859
914
  evaluator = self._unwrap_proxy(
860
915
  active_agents[agent_data["evaluator"]]
861
916
  )
862
917
 
863
- if not optimizer or not evaluator:
918
+ if not generator or not evaluator:
864
919
  raise ValueError(
865
920
  f"Missing agents for workflow {name}: "
866
- f"optimizer={agent_data['optimizer']}, "
921
+ f"generator={agent_data['generator']}, "
867
922
  f"evaluator={agent_data['evaluator']}"
868
923
  )
869
924
 
870
925
  # TODO: Remove legacy - factory usage is only needed for str evaluators
871
926
  # Later this should only be passed when evaluator is a string
872
927
  optimizer_model = (
873
- optimizer.config.model if isinstance(optimizer, Agent) else None
928
+ generator.config.model if isinstance(generator, Agent) else None
874
929
  )
875
930
  instance = EvaluatorOptimizerLLM(
876
- optimizer=optimizer,
931
+ generator=generator,
877
932
  evaluator=evaluator,
878
933
  min_rating=QualityRating[agent_data["min_rating"]],
879
934
  max_refinements=agent_data["max_refinements"],
@@ -1190,10 +1245,28 @@ class FastAgent(ContextDependent):
1190
1245
  had_error = True
1191
1246
  self._handle_error(
1192
1247
  e,
1193
- "Server Startup Error",
1248
+ "MCP Server Startup Error",
1194
1249
  "There was an error starting up the MCP Server.",
1195
1250
  )
1196
1251
  raise SystemExit(1)
1252
+
1253
+ except ModelConfigError as e:
1254
+ had_error = True
1255
+ self._handle_error(
1256
+ e,
1257
+ "Model Configuration Error",
1258
+ "Common models: gpt-4o, o3-mini, sonnet, haiku. for o3, set reasoning effort with o3-mini.high",
1259
+ )
1260
+ raise SystemExit(1)
1261
+
1262
+ except PromptExitError as e:
1263
+ had_error = True
1264
+ self._handle_error(
1265
+ e,
1266
+ "User requested exit",
1267
+ )
1268
+ raise SystemExit(1)
1269
+
1197
1270
  finally:
1198
1271
  # Clean up any active agents without re-raising errors
1199
1272
  if active_agents and not had_error:
@@ -1201,7 +1274,8 @@ class FastAgent(ContextDependent):
1201
1274
  if isinstance(proxy, LLMAgentProxy):
1202
1275
  try:
1203
1276
  await proxy._agent.__aexit__(None, None, None)
1204
- except Exception:
1277
+ except Exception as e:
1278
+ print(f"DEBUG {e.message}")
1205
1279
  pass # Ignore cleanup errors
1206
1280
 
1207
1281
  def _handle_error(