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.
- {fast_agent_mcp-0.0.11.dist-info → fast_agent_mcp-0.0.13.dist-info}/METADATA +9 -1
- {fast_agent_mcp-0.0.11.dist-info → fast_agent_mcp-0.0.13.dist-info}/RECORD +30 -25
- mcp_agent/agents/agent.py +48 -8
- mcp_agent/cli/commands/bootstrap.py +2 -5
- mcp_agent/cli/commands/setup.py +1 -1
- mcp_agent/cli/main.py +6 -6
- mcp_agent/core/enhanced_prompt.py +358 -0
- mcp_agent/core/exceptions.py +17 -0
- mcp_agent/core/fastagent.py +108 -34
- mcp_agent/human_input/handler.py +43 -18
- mcp_agent/mcp/mcp_connection_manager.py +14 -12
- mcp_agent/resources/examples/internal/agent.py +17 -0
- mcp_agent/resources/examples/internal/job.py +1 -1
- mcp_agent/resources/examples/mcp_researcher/researcher-eval.py +1 -1
- mcp_agent/resources/examples/researcher/fastagent.config.yaml +53 -0
- mcp_agent/resources/examples/researcher/researcher-eval.py +53 -0
- mcp_agent/resources/examples/workflows/chaining.py +5 -1
- mcp_agent/resources/examples/workflows/evaluator.py +7 -4
- mcp_agent/resources/examples/workflows/fastagent.config.yaml +24 -0
- mcp_agent/resources/examples/workflows/orchestrator.py +3 -2
- mcp_agent/resources/examples/workflows/parallel.py +2 -1
- mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +31 -30
- mcp_agent/workflows/llm/augmented_llm.py +8 -2
- mcp_agent/workflows/llm/augmented_llm_anthropic.py +3 -1
- mcp_agent/workflows/llm/augmented_llm_openai.py +20 -9
- mcp_agent/workflows/llm/model_factory.py +7 -4
- {fast_agent_mcp-0.0.11.dist-info → fast_agent_mcp-0.0.13.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.0.11.dist-info → fast_agent_mcp-0.0.13.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.0.11.dist-info → fast_agent_mcp-0.0.13.dist-info}/licenses/LICENSE +0 -0
- /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
|
mcp_agent/core/exceptions.py
CHANGED
|
@@ -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)
|
mcp_agent/core/fastagent.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
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
|
|
408
|
-
missing.append(f"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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=
|
|
851
|
-
|
|
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
|
-
|
|
857
|
-
active_agents[agent_data["
|
|
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
|
|
918
|
+
if not generator or not evaluator:
|
|
864
919
|
raise ValueError(
|
|
865
920
|
f"Missing agents for workflow {name}: "
|
|
866
|
-
f"
|
|
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
|
-
|
|
928
|
+
generator.config.model if isinstance(generator, Agent) else None
|
|
874
929
|
)
|
|
875
930
|
instance = EvaluatorOptimizerLLM(
|
|
876
|
-
|
|
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(
|