fast-agent-mcp 0.3.10__py3-none-any.whl → 0.3.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fast-agent-mcp might be problematic. Click here for more details.
- fast_agent/agents/llm_decorator.py +7 -0
- fast_agent/agents/tool_agent.py +2 -2
- fast_agent/cli/commands/quickstart.py +142 -129
- fast_agent/config.py +2 -2
- fast_agent/core/direct_decorators.py +18 -26
- fast_agent/core/fastagent.py +2 -1
- fast_agent/human_input/elicitation_handler.py +71 -54
- fast_agent/interfaces.py +4 -0
- fast_agent/llm/fastagent_llm.py +17 -0
- fast_agent/llm/model_database.py +2 -0
- fast_agent/llm/model_factory.py +4 -1
- fast_agent/mcp/elicitation_factory.py +2 -2
- fast_agent/mcp/mcp_aggregator.py +9 -16
- fast_agent/mcp/prompts/prompt_load.py +2 -3
- fast_agent/mcp/sampling.py +21 -0
- fast_agent/ui/console_display.py +27 -22
- fast_agent/ui/enhanced_prompt.py +61 -14
- fast_agent/ui/history_display.py +555 -0
- fast_agent/ui/interactive_prompt.py +44 -12
- fast_agent/ui/mcp_display.py +31 -11
- fast_agent/ui/notification_tracker.py +206 -0
- fast_agent/ui/rich_progress.py +1 -1
- {fast_agent_mcp-0.3.10.dist-info → fast_agent_mcp-0.3.12.dist-info}/METADATA +4 -4
- {fast_agent_mcp-0.3.10.dist-info → fast_agent_mcp-0.3.12.dist-info}/RECORD +27 -25
- {fast_agent_mcp-0.3.10.dist-info → fast_agent_mcp-0.3.12.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.3.10.dist-info → fast_agent_mcp-0.3.12.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.3.10.dist-info → fast_agent_mcp-0.3.12.dist-info}/licenses/LICENSE +0 -0
|
@@ -314,6 +314,13 @@ class LlmDecorator(AgentProtocol):
|
|
|
314
314
|
# Otherwise treat the string as plain content (ignore arguments here)
|
|
315
315
|
return await self.send(prompt)
|
|
316
316
|
|
|
317
|
+
def clear(self, *, clear_prompts: bool = False) -> None:
|
|
318
|
+
"""Reset conversation state while optionally retaining applied prompt templates."""
|
|
319
|
+
|
|
320
|
+
if not self._llm:
|
|
321
|
+
return
|
|
322
|
+
self._llm.clear(clear_prompts=clear_prompts)
|
|
323
|
+
|
|
317
324
|
async def structured(
|
|
318
325
|
self,
|
|
319
326
|
messages: Union[
|
fast_agent/agents/tool_agent.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any, Callable, Dict, List
|
|
1
|
+
from typing import Any, Callable, Dict, List, Sequence
|
|
2
2
|
|
|
3
3
|
from mcp.server.fastmcp.tools.base import Tool as FastMCPTool
|
|
4
4
|
from mcp.types import CallToolResult, ListToolsResult, Tool
|
|
@@ -35,7 +35,7 @@ class ToolAgent(LlmAgent):
|
|
|
35
35
|
def __init__(
|
|
36
36
|
self,
|
|
37
37
|
config: AgentConfig,
|
|
38
|
-
tools:
|
|
38
|
+
tools: Sequence[FastMCPTool | Callable] = [],
|
|
39
39
|
context: Context | None = None,
|
|
40
40
|
) -> None:
|
|
41
41
|
super().__init__(config=config, context=context)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""Bootstrap command to create example applications."""
|
|
2
2
|
|
|
3
3
|
import shutil
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from importlib.resources import files
|
|
4
6
|
from pathlib import Path
|
|
5
7
|
|
|
6
8
|
import typer
|
|
@@ -16,12 +18,27 @@ app = typer.Typer(
|
|
|
16
18
|
)
|
|
17
19
|
console = shared_console
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
|
|
22
|
+
BASE_EXAMPLES_DIR = files("fast_agent").joinpath("resources").joinpath("examples")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class ExampleConfig:
|
|
27
|
+
description: str
|
|
28
|
+
files: list[str]
|
|
29
|
+
create_subdir: bool
|
|
30
|
+
path_in_examples: list[str]
|
|
31
|
+
mount_point_files: list[str] | None = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
_EXAMPLE_CONFIGS = {
|
|
35
|
+
"workflow": ExampleConfig(
|
|
36
|
+
description=(
|
|
37
|
+
"Example workflows, demonstrating each of the patterns in Anthropic's\n"
|
|
38
|
+
"'Building Effective Agents' paper. Some agents use the 'fetch'\n"
|
|
39
|
+
"and filesystem MCP Servers."
|
|
40
|
+
),
|
|
41
|
+
files=[
|
|
25
42
|
"chaining.py",
|
|
26
43
|
"evaluator.py",
|
|
27
44
|
"human_input.py",
|
|
@@ -31,41 +48,53 @@ EXAMPLE_TYPES = {
|
|
|
31
48
|
"short_story.txt",
|
|
32
49
|
"fastagent.config.yaml",
|
|
33
50
|
],
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
|
|
51
|
+
create_subdir=True,
|
|
52
|
+
path_in_examples=["workflows"],
|
|
53
|
+
),
|
|
54
|
+
"researcher": ExampleConfig(
|
|
55
|
+
description=(
|
|
56
|
+
"Research agent example with additional evaluation/optimization\n"
|
|
57
|
+
"example. Uses Brave Search and Docker MCP Servers.\n"
|
|
58
|
+
"Creates examples in a 'researcher' subdirectory."
|
|
59
|
+
),
|
|
60
|
+
files=["researcher.py", "researcher-eval.py", "fastagent.config.yaml"],
|
|
61
|
+
create_subdir=True,
|
|
62
|
+
path_in_examples=["researcher"],
|
|
63
|
+
),
|
|
64
|
+
"data-analysis": ExampleConfig(
|
|
65
|
+
description=(
|
|
66
|
+
"Data analysis agent examples that demonstrate working with\n"
|
|
67
|
+
"datasets, performing statistical analysis, and generating visualizations.\n"
|
|
68
|
+
"Creates examples in a 'data-analysis' subdirectory with mount-point for data.\n"
|
|
69
|
+
"Uses MCP 'roots' feature for mapping"
|
|
70
|
+
),
|
|
71
|
+
files=["analysis.py", "fastagent.config.yaml"],
|
|
72
|
+
mount_point_files=["WA_Fn-UseC_-HR-Employee-Attrition.csv"],
|
|
73
|
+
create_subdir=True,
|
|
74
|
+
path_in_examples=["data-analysis"],
|
|
75
|
+
),
|
|
76
|
+
"state-transfer": ExampleConfig(
|
|
77
|
+
description=(
|
|
78
|
+
"Example demonstrating state transfer between multiple agents.\n"
|
|
79
|
+
"Shows how state can be passed between agent runs to maintain context.\n"
|
|
80
|
+
"Creates examples in a 'state-transfer' subdirectory."
|
|
81
|
+
),
|
|
82
|
+
files=[
|
|
57
83
|
"agent_one.py",
|
|
58
84
|
"agent_two.py",
|
|
59
85
|
"fastagent.config.yaml",
|
|
60
86
|
"fastagent.secrets.yaml.example",
|
|
61
87
|
],
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
88
|
+
create_subdir=True,
|
|
89
|
+
path_in_examples=["mcp", "state-transfer"],
|
|
90
|
+
),
|
|
91
|
+
"elicitations": ExampleConfig(
|
|
92
|
+
description=(
|
|
93
|
+
"Interactive form examples using MCP elicitations feature.\n"
|
|
94
|
+
"Demonstrates collecting structured data with forms, AI-guided workflows,\n"
|
|
95
|
+
"and custom handlers. Creates examples in an 'elicitations' subdirectory."
|
|
96
|
+
),
|
|
97
|
+
files=[
|
|
69
98
|
"elicitation_account_server.py",
|
|
70
99
|
"elicitation_forms_server.py",
|
|
71
100
|
"elicitation_game_server.py",
|
|
@@ -76,13 +105,16 @@ EXAMPLE_TYPES = {
|
|
|
76
105
|
"game_character_handler.py",
|
|
77
106
|
"tool_call.py",
|
|
78
107
|
],
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
108
|
+
create_subdir=True,
|
|
109
|
+
path_in_examples=["mcp", "elicitations"],
|
|
110
|
+
),
|
|
111
|
+
"tensorzero": ExampleConfig(
|
|
112
|
+
description=(
|
|
113
|
+
"A complete example showcasing the TensorZero integration.\n"
|
|
114
|
+
"Includes the T0 Gateway, an MCP server, an interactive agent, and \n"
|
|
115
|
+
"multi-modal functionality."
|
|
116
|
+
),
|
|
117
|
+
files=[
|
|
86
118
|
".env.sample",
|
|
87
119
|
"Makefile",
|
|
88
120
|
"README.md",
|
|
@@ -93,105 +125,64 @@ EXAMPLE_TYPES = {
|
|
|
93
125
|
"simple_agent.py",
|
|
94
126
|
"mcp_server/",
|
|
95
127
|
"demo_images/",
|
|
96
|
-
"tensorzero_config/"
|
|
128
|
+
"tensorzero_config/",
|
|
97
129
|
],
|
|
98
|
-
|
|
99
|
-
|
|
130
|
+
create_subdir=True,
|
|
131
|
+
path_in_examples=["elicitations"],
|
|
132
|
+
),
|
|
100
133
|
}
|
|
101
134
|
|
|
102
135
|
|
|
136
|
+
def _development_mode_fallback(example_info: ExampleConfig) -> Path:
|
|
137
|
+
"""Fallback function for development mode."""
|
|
138
|
+
package_dir = Path(__file__).parent.parent.parent.parent.parent
|
|
139
|
+
for dir in example_info.path_in_examples:
|
|
140
|
+
package_dir = package_dir / dir
|
|
141
|
+
console.print(f"[blue]Using development directory: {package_dir}[/blue]")
|
|
142
|
+
return package_dir
|
|
143
|
+
|
|
144
|
+
|
|
103
145
|
def copy_example_files(example_type: str, target_dir: Path, force: bool = False) -> list[str]:
|
|
104
146
|
"""Copy example files from resources to target directory."""
|
|
105
|
-
created = []
|
|
106
|
-
|
|
107
147
|
# Determine if we should create a subdirectory for this example type
|
|
108
|
-
example_info =
|
|
109
|
-
if example_info
|
|
148
|
+
example_info = _EXAMPLE_CONFIGS.get(example_type, None)
|
|
149
|
+
if example_info is None:
|
|
150
|
+
console.print(f"Example type '{example_type}' not found.")
|
|
151
|
+
return []
|
|
152
|
+
|
|
153
|
+
if example_info.create_subdir:
|
|
110
154
|
target_dir = target_dir / example_type
|
|
111
155
|
if not target_dir.exists():
|
|
112
156
|
target_dir.mkdir(parents=True)
|
|
113
157
|
console.print(f"Created subdirectory: {target_dir}")
|
|
114
158
|
|
|
115
|
-
# Create mount-point directory if needed
|
|
116
|
-
mount_point_files = example_info.get("mount_point_files", [])
|
|
117
|
-
if mount_point_files:
|
|
118
|
-
mount_point_dir = target_dir / "mount-point"
|
|
119
|
-
if not mount_point_dir.exists():
|
|
120
|
-
mount_point_dir.mkdir(parents=True)
|
|
121
|
-
console.print(f"Created mount-point directory: {mount_point_dir}")
|
|
122
|
-
|
|
123
159
|
# Try to use examples from the installed package first, or fall back to the top-level directory
|
|
124
|
-
from importlib.resources import files
|
|
125
|
-
|
|
126
160
|
try:
|
|
127
161
|
# First try to find examples in the package resources
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
source_dir = (
|
|
131
|
-
files("fast_agent")
|
|
132
|
-
.joinpath("resources")
|
|
133
|
-
.joinpath("examples")
|
|
134
|
-
.joinpath("mcp")
|
|
135
|
-
.joinpath("state-transfer")
|
|
136
|
-
)
|
|
137
|
-
elif example_type == "elicitations":
|
|
138
|
-
# The elicitations example is in the mcp subdirectory
|
|
139
|
-
source_dir = (
|
|
140
|
-
files("fast_agent")
|
|
141
|
-
.joinpath("resources")
|
|
142
|
-
.joinpath("examples")
|
|
143
|
-
.joinpath("mcp")
|
|
144
|
-
.joinpath("elicitations")
|
|
145
|
-
)
|
|
146
|
-
else:
|
|
147
|
-
# Other examples are at the top level of examples
|
|
148
|
-
source_dir = (
|
|
149
|
-
files("fast_agent")
|
|
150
|
-
.joinpath("resources")
|
|
151
|
-
.joinpath("examples")
|
|
152
|
-
.joinpath("workflows" if example_type == "workflow" else f"{example_type}")
|
|
153
|
-
)
|
|
162
|
+
source_dir = BASE_EXAMPLES_DIR
|
|
163
|
+
for dir in example_info.path_in_examples:
|
|
164
|
+
source_dir = source_dir.joinpath(dir)
|
|
154
165
|
|
|
155
166
|
# Check if we found a valid directory
|
|
156
167
|
if not source_dir.is_dir():
|
|
157
168
|
console.print(
|
|
158
|
-
f"[yellow]Resource directory not found: {source_dir}.
|
|
169
|
+
f"[yellow]Resource directory not found: {source_dir}. "
|
|
170
|
+
"Falling back to development mode.[/yellow]"
|
|
159
171
|
)
|
|
160
172
|
# Fall back to the top-level directory for development mode
|
|
161
|
-
|
|
162
|
-
if example_type == "state-transfer":
|
|
163
|
-
source_dir = package_dir / "examples" / "mcp" / "state-transfer"
|
|
164
|
-
elif example_type == "elicitations":
|
|
165
|
-
source_dir = package_dir / "examples" / "mcp" / "elicitations"
|
|
166
|
-
else:
|
|
167
|
-
source_dir = (
|
|
168
|
-
package_dir
|
|
169
|
-
/ "examples"
|
|
170
|
-
/ ("workflows" if example_type == "workflow" else f"{example_type}")
|
|
171
|
-
)
|
|
172
|
-
console.print(f"[blue]Using development directory: {source_dir}[/blue]")
|
|
173
|
+
source_dir = _development_mode_fallback(example_info)
|
|
173
174
|
except (ImportError, ModuleNotFoundError, ValueError) as e:
|
|
174
175
|
console.print(
|
|
175
176
|
f"[yellow]Error accessing resources: {e}. Falling back to development mode.[/yellow]"
|
|
176
177
|
)
|
|
177
|
-
|
|
178
|
-
package_dir = Path(__file__).parent.parent.parent.parent.parent
|
|
179
|
-
if example_type == "state-transfer":
|
|
180
|
-
source_dir = package_dir / "examples" / "mcp" / "state-transfer"
|
|
181
|
-
elif example_type == "elicitations":
|
|
182
|
-
source_dir = package_dir / "examples" / "mcp" / "elicitations"
|
|
183
|
-
else:
|
|
184
|
-
source_dir = (
|
|
185
|
-
package_dir
|
|
186
|
-
/ "examples"
|
|
187
|
-
/ ("workflows" if example_type == "workflow" else f"{example_type}")
|
|
188
|
-
)
|
|
178
|
+
source_dir = _development_mode_fallback(example_info)
|
|
189
179
|
|
|
190
180
|
if not source_dir.exists():
|
|
191
181
|
console.print(f"[red]Error: Source directory not found: {source_dir}[/red]")
|
|
192
|
-
return
|
|
182
|
+
return []
|
|
193
183
|
|
|
194
|
-
|
|
184
|
+
created = []
|
|
185
|
+
for filename in example_info.files:
|
|
195
186
|
source = source_dir / filename
|
|
196
187
|
target = target_dir / filename
|
|
197
188
|
|
|
@@ -213,16 +204,23 @@ def copy_example_files(example_type: str, target_dir: Path, force: bool = False)
|
|
|
213
204
|
rel_path = f"{example_type}/{filename}"
|
|
214
205
|
|
|
215
206
|
created.append(rel_path)
|
|
216
|
-
console.print(f"[green]Created[/green] {
|
|
207
|
+
console.print(f"[green]Created[/green] {rel_path}")
|
|
217
208
|
|
|
218
209
|
except Exception as e:
|
|
219
210
|
console.print(f"[red]Error copying {filename}: {str(e)}[/red]")
|
|
220
211
|
|
|
221
212
|
# Copy mount-point files if any
|
|
213
|
+
mount_point_files = example_info.mount_point_files or []
|
|
222
214
|
if mount_point_files:
|
|
223
|
-
|
|
215
|
+
mount_point_dir = target_dir / "mount-point"
|
|
216
|
+
|
|
217
|
+
# Create mount-point directory if needed
|
|
218
|
+
if not mount_point_dir.exists():
|
|
219
|
+
mount_point_dir.mkdir(parents=True)
|
|
220
|
+
console.print(f"Created mount-point directory: {mount_point_dir}")
|
|
221
|
+
|
|
224
222
|
for filename in mount_point_files:
|
|
225
|
-
source =
|
|
223
|
+
source = source_dir / "mount-point" / filename
|
|
226
224
|
target = mount_point_dir / filename
|
|
227
225
|
|
|
228
226
|
try:
|
|
@@ -253,10 +251,14 @@ def copy_project_template(source_dir: Path, dest_dir: Path, console: Console, fo
|
|
|
253
251
|
"""
|
|
254
252
|
if dest_dir.exists():
|
|
255
253
|
if force:
|
|
256
|
-
console.print(
|
|
254
|
+
console.print(
|
|
255
|
+
f"[yellow]--force specified. Removing existing directory: {dest_dir}[/yellow]"
|
|
256
|
+
)
|
|
257
257
|
shutil.rmtree(dest_dir)
|
|
258
258
|
else:
|
|
259
|
-
console.print(
|
|
259
|
+
console.print(
|
|
260
|
+
f"[bold yellow]Directory '{dest_dir.name}' already exists.[/bold yellow] Use --force to overwrite."
|
|
261
|
+
)
|
|
260
262
|
return False
|
|
261
263
|
|
|
262
264
|
try:
|
|
@@ -278,14 +280,14 @@ def show_overview() -> None:
|
|
|
278
280
|
table.add_column("Description")
|
|
279
281
|
table.add_column("Files")
|
|
280
282
|
|
|
281
|
-
for name, info in
|
|
283
|
+
for name, info in _EXAMPLE_CONFIGS.items():
|
|
282
284
|
# Just show file count instead of listing all files
|
|
283
|
-
file_count = len(info
|
|
285
|
+
file_count = len(info.files)
|
|
284
286
|
files_summary = f"{file_count} files"
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
files_summary += f"\n+ {
|
|
288
|
-
table.add_row(f"[green]{name}[/green]", info
|
|
287
|
+
mount_files = info.mount_point_files
|
|
288
|
+
if mount_files:
|
|
289
|
+
files_summary += f"\n+ {len(mount_files)} data files"
|
|
290
|
+
table.add_row(f"[green]{name}[/green]", info.description, files_summary)
|
|
289
291
|
|
|
290
292
|
console.print(table)
|
|
291
293
|
|
|
@@ -445,7 +447,9 @@ def tensorzero(
|
|
|
445
447
|
Path("."),
|
|
446
448
|
help="Directory where the 'tensorzero' project folder will be created.",
|
|
447
449
|
),
|
|
448
|
-
force: bool = typer.Option(
|
|
450
|
+
force: bool = typer.Option(
|
|
451
|
+
False, "--force", "-f", help="Force overwrite if project directory exists"
|
|
452
|
+
),
|
|
449
453
|
):
|
|
450
454
|
"""Create the TensorZero project example."""
|
|
451
455
|
console.print("[bold green]Setting up the TensorZero quickstart example...[/bold green]")
|
|
@@ -454,13 +458,18 @@ def tensorzero(
|
|
|
454
458
|
|
|
455
459
|
# --- Find Source Directory ---
|
|
456
460
|
from importlib.resources import files
|
|
461
|
+
|
|
457
462
|
try:
|
|
458
463
|
# This path MUST match the "to" path from hatch_build.py
|
|
459
|
-
source_dir =
|
|
464
|
+
source_dir = (
|
|
465
|
+
files("fast_agent").joinpath("resources").joinpath("examples").joinpath("tensorzero")
|
|
466
|
+
)
|
|
460
467
|
if not source_dir.is_dir():
|
|
461
468
|
raise FileNotFoundError # Fallback to dev mode if resource isn't a dir
|
|
462
469
|
except (ImportError, ModuleNotFoundError, FileNotFoundError):
|
|
463
|
-
console.print(
|
|
470
|
+
console.print(
|
|
471
|
+
"[yellow]Package resources not found. Falling back to development mode.[/yellow]"
|
|
472
|
+
)
|
|
464
473
|
# This path is relative to the project root in a development environment
|
|
465
474
|
source_dir = Path(__file__).parent.parent.parent.parent / "examples" / "tensorzero"
|
|
466
475
|
|
|
@@ -486,7 +495,9 @@ def tensorzero(
|
|
|
486
495
|
" [dim]Then, open the new '.env' file and add your OpenAI or Anthropic API key.[/dim]"
|
|
487
496
|
)
|
|
488
497
|
|
|
489
|
-
console.print(
|
|
498
|
+
console.print(
|
|
499
|
+
"\n3. [bold]Start the required services (TensorZero Gateway & MCP Server):[/bold]"
|
|
500
|
+
)
|
|
490
501
|
console.print(" [cyan]docker compose up --build -d[/cyan]")
|
|
491
502
|
console.print(
|
|
492
503
|
" [dim](This builds and starts the necessary containers in the background)[/dim]"
|
|
@@ -499,7 +510,9 @@ def tensorzero(
|
|
|
499
510
|
|
|
500
511
|
@app.command(name="t0", help="Alias for the TensorZero quickstart.", hidden=True)
|
|
501
512
|
def t0_alias(
|
|
502
|
-
directory: Path = typer.Argument(
|
|
513
|
+
directory: Path = typer.Argument(
|
|
514
|
+
Path("."), help="Directory for the 'tensorzero' project folder."
|
|
515
|
+
),
|
|
503
516
|
force: bool = typer.Option(False, "--force", "-f", help="Force overwrite"),
|
|
504
517
|
):
|
|
505
518
|
"""Alias for the `tensorzero` command."""
|
fast_agent/config.py
CHANGED
|
@@ -43,8 +43,8 @@ class MCPSamplingSettings(BaseModel):
|
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
class MCPElicitationSettings(BaseModel):
|
|
46
|
-
mode: Literal["forms", "
|
|
47
|
-
"""Elicitation mode: 'forms' (default UI), '
|
|
46
|
+
mode: Literal["forms", "auto-cancel", "none"] = "none"
|
|
47
|
+
"""Elicitation mode: 'forms' (default UI), 'auto-cancel', 'none' (no capability)"""
|
|
48
48
|
|
|
49
49
|
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
50
50
|
|
|
@@ -4,9 +4,10 @@ These decorators provide type-safe function signatures and IDE support
|
|
|
4
4
|
for creating agents in the DirectFastAgent framework.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from
|
|
7
|
+
from collections.abc import Coroutine
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import (
|
|
10
|
+
Any,
|
|
10
11
|
Awaitable,
|
|
11
12
|
Callable,
|
|
12
13
|
Dict,
|
|
@@ -16,7 +17,6 @@ from typing import (
|
|
|
16
17
|
ParamSpec,
|
|
17
18
|
Protocol,
|
|
18
19
|
TypeVar,
|
|
19
|
-
cast,
|
|
20
20
|
)
|
|
21
21
|
|
|
22
22
|
from mcp.client.session import ElicitationFnT
|
|
@@ -33,9 +33,6 @@ from fast_agent.types import RequestParams
|
|
|
33
33
|
P = ParamSpec("P") # Parameters
|
|
34
34
|
R = TypeVar("R", covariant=True) # Return type
|
|
35
35
|
|
|
36
|
-
# Type for agent functions - can be either async or sync
|
|
37
|
-
AgentCallable = Callable[P, Awaitable[R]]
|
|
38
|
-
|
|
39
36
|
|
|
40
37
|
# Protocol for decorated agent functions
|
|
41
38
|
class DecoratedAgentProtocol(Protocol[P, R]):
|
|
@@ -186,7 +183,7 @@ def _decorator_impl(
|
|
|
186
183
|
resources: Optional[Dict[str, List[str]]] = None,
|
|
187
184
|
prompts: Optional[Dict[str, List[str]]] = None,
|
|
188
185
|
**extra_kwargs,
|
|
189
|
-
) -> Callable[[
|
|
186
|
+
) -> Callable[[Callable[P, Coroutine[Any, Any, R]]], Callable[P, Coroutine[Any, Any, R]]]:
|
|
190
187
|
"""
|
|
191
188
|
Core implementation for agent decorators with common behavior and type safety.
|
|
192
189
|
|
|
@@ -203,12 +200,7 @@ def _decorator_impl(
|
|
|
203
200
|
**extra_kwargs: Additional agent/workflow-specific parameters
|
|
204
201
|
"""
|
|
205
202
|
|
|
206
|
-
def decorator(func:
|
|
207
|
-
@wraps(func)
|
|
208
|
-
def wrapper(*args: P.args, **kwargs: P.kwargs) -> Awaitable[R]:
|
|
209
|
-
# Call the original function
|
|
210
|
-
return func(*args, **kwargs)
|
|
211
|
-
|
|
203
|
+
def decorator(func: Callable[P, Coroutine[Any, Any, R]]) -> Callable[P, Coroutine[Any, Any, R]]:
|
|
212
204
|
# Create agent configuration
|
|
213
205
|
config = AgentConfig(
|
|
214
206
|
name=name,
|
|
@@ -229,7 +221,7 @@ def _decorator_impl(
|
|
|
229
221
|
if request_params:
|
|
230
222
|
config.default_request_params = request_params
|
|
231
223
|
|
|
232
|
-
# Store metadata
|
|
224
|
+
# Store metadata in the registry
|
|
233
225
|
agent_data = {
|
|
234
226
|
"config": config,
|
|
235
227
|
"type": agent_type.value,
|
|
@@ -243,13 +235,13 @@ def _decorator_impl(
|
|
|
243
235
|
# Store the configuration in the FastAgent instance
|
|
244
236
|
self.agents[name] = agent_data
|
|
245
237
|
|
|
246
|
-
# Store type information for IDE support
|
|
247
|
-
setattr(
|
|
248
|
-
setattr(
|
|
238
|
+
# Store type information on the function for IDE support
|
|
239
|
+
setattr(func, "_agent_type", agent_type)
|
|
240
|
+
setattr(func, "_agent_config", config)
|
|
249
241
|
for key, value in extra_kwargs.items():
|
|
250
|
-
setattr(
|
|
242
|
+
setattr(func, f"_{key}", value)
|
|
251
243
|
|
|
252
|
-
return
|
|
244
|
+
return func
|
|
253
245
|
|
|
254
246
|
return decorator
|
|
255
247
|
|
|
@@ -271,7 +263,7 @@ def agent(
|
|
|
271
263
|
default: bool = False,
|
|
272
264
|
elicitation_handler: Optional[ElicitationFnT] = None,
|
|
273
265
|
api_key: str | None = None,
|
|
274
|
-
) -> Callable[[
|
|
266
|
+
) -> Callable[[Callable[P, Coroutine[Any, Any, R]]], Callable[P, Coroutine[Any, Any, R]]]:
|
|
275
267
|
"""
|
|
276
268
|
Decorator to create and register a standard agent with type-safe signature.
|
|
277
269
|
|
|
@@ -336,7 +328,7 @@ def custom(
|
|
|
336
328
|
default: bool = False,
|
|
337
329
|
elicitation_handler: Optional[ElicitationFnT] = None,
|
|
338
330
|
api_key: str | None = None,
|
|
339
|
-
) -> Callable[[
|
|
331
|
+
) -> Callable[[Callable[P, Coroutine[Any, Any, R]]], Callable[P, Coroutine[Any, Any, R]]]:
|
|
340
332
|
"""
|
|
341
333
|
Decorator to create and register a standard agent with type-safe signature.
|
|
342
334
|
|
|
@@ -400,7 +392,7 @@ def orchestrator(
|
|
|
400
392
|
plan_iterations: int = 5,
|
|
401
393
|
default: bool = False,
|
|
402
394
|
api_key: str | None = None,
|
|
403
|
-
) -> Callable[[
|
|
395
|
+
) -> Callable[[Callable[P, Coroutine[Any, Any, R]]], Callable[P, Coroutine[Any, Any, R]]]:
|
|
404
396
|
"""
|
|
405
397
|
Decorator to create and register an orchestrator agent with type-safe signature.
|
|
406
398
|
|
|
@@ -452,7 +444,7 @@ def iterative_planner(
|
|
|
452
444
|
plan_iterations: int = -1,
|
|
453
445
|
default: bool = False,
|
|
454
446
|
api_key: str | None = None,
|
|
455
|
-
) -> Callable[[
|
|
447
|
+
) -> Callable[[Callable[P, Coroutine[Any, Any, R]]], Callable[P, Coroutine[Any, Any, R]]]:
|
|
456
448
|
"""
|
|
457
449
|
Decorator to create and register an orchestrator agent with type-safe signature.
|
|
458
450
|
|
|
@@ -510,7 +502,7 @@ def router(
|
|
|
510
502
|
ElicitationFnT
|
|
511
503
|
] = None, ## exclude from docs, decide whether allowable
|
|
512
504
|
api_key: str | None = None,
|
|
513
|
-
) -> Callable[[
|
|
505
|
+
) -> Callable[[Callable[P, Coroutine[Any, Any, R]]], Callable[P, Coroutine[Any, Any, R]]]:
|
|
514
506
|
"""
|
|
515
507
|
Decorator to create and register a router agent with type-safe signature.
|
|
516
508
|
|
|
@@ -558,7 +550,7 @@ def chain(
|
|
|
558
550
|
instruction: Optional[str | Path | AnyUrl] = None,
|
|
559
551
|
cumulative: bool = False,
|
|
560
552
|
default: bool = False,
|
|
561
|
-
) -> Callable[[
|
|
553
|
+
) -> Callable[[Callable[P, Coroutine[Any, Any, R]]], Callable[P, Coroutine[Any, Any, R]]]:
|
|
562
554
|
"""
|
|
563
555
|
Decorator to create and register a chain agent with type-safe signature.
|
|
564
556
|
|
|
@@ -604,7 +596,7 @@ def parallel(
|
|
|
604
596
|
instruction: Optional[str | Path | AnyUrl] = None,
|
|
605
597
|
include_request: bool = True,
|
|
606
598
|
default: bool = False,
|
|
607
|
-
) -> Callable[[
|
|
599
|
+
) -> Callable[[Callable[P, Coroutine[Any, Any, R]]], Callable[P, Coroutine[Any, Any, R]]]:
|
|
608
600
|
"""
|
|
609
601
|
Decorator to create and register a parallel agent with type-safe signature.
|
|
610
602
|
|
|
@@ -648,7 +640,7 @@ def evaluator_optimizer(
|
|
|
648
640
|
min_rating: str = "GOOD",
|
|
649
641
|
max_refinements: int = 3,
|
|
650
642
|
default: bool = False,
|
|
651
|
-
) -> Callable[[
|
|
643
|
+
) -> Callable[[Callable[P, Coroutine[Any, Any, R]]], Callable[P, Coroutine[Any, Any, R]]]:
|
|
652
644
|
"""
|
|
653
645
|
Decorator to create and register an evaluator-optimizer agent with type-safe signature.
|
|
654
646
|
|
fast_agent/core/fastagent.py
CHANGED
|
@@ -268,6 +268,7 @@ class FastAgent:
|
|
|
268
268
|
# Decorator methods with precise signatures for IDE completion
|
|
269
269
|
# Provide annotations so IDEs can discover these attributes on instances
|
|
270
270
|
if TYPE_CHECKING: # pragma: no cover - typing aid only
|
|
271
|
+
from collections.abc import Coroutine
|
|
271
272
|
from pathlib import Path
|
|
272
273
|
|
|
273
274
|
from fast_agent.types import RequestParams
|
|
@@ -292,7 +293,7 @@ class FastAgent:
|
|
|
292
293
|
default: bool = False,
|
|
293
294
|
elicitation_handler: Optional[ElicitationFnT] = None,
|
|
294
295
|
api_key: str | None = None,
|
|
295
|
-
) -> Callable[[Callable[P,
|
|
296
|
+
) -> Callable[[Callable[P, Coroutine[Any, Any, R]]], Callable[P, Coroutine[Any, Any, R]]]: ...
|
|
296
297
|
|
|
297
298
|
def custom(
|
|
298
299
|
self,
|