fast-agent-mcp 0.3.11__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.

@@ -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[
@@ -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: list[FastMCPTool | Callable] = [],
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
- EXAMPLE_TYPES = {
20
- "workflow": {
21
- "description": "Example workflows, demonstrating each of the patterns in Anthropic's\n"
22
- "'Building Effective Agents' paper. Some agents use the 'fetch'\n"
23
- "and filesystem MCP Servers.",
24
- "files": [
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
- "create_subdir": True,
35
- },
36
- "researcher": {
37
- "description": "Research agent example with additional evaluation/optimization\n"
38
- "example. Uses Brave Search and Docker MCP Servers.\n"
39
- "Creates examples in a 'researcher' subdirectory.",
40
- "files": ["researcher.py", "researcher-eval.py", "fastagent.config.yaml"],
41
- "create_subdir": True,
42
- },
43
- "data-analysis": {
44
- "description": "Data analysis agent examples that demonstrate working with\n"
45
- "datasets, performing statistical analysis, and generating visualizations.\n"
46
- "Creates examples in a 'data-analysis' subdirectory with mount-point for data.\n"
47
- "Uses MCP 'roots' feature for mapping",
48
- "files": ["analysis.py", "fastagent.config.yaml"],
49
- "mount_point_files": ["WA_Fn-UseC_-HR-Employee-Attrition.csv"],
50
- "create_subdir": True,
51
- },
52
- "state-transfer": {
53
- "description": "Example demonstrating state transfer between multiple agents.\n"
54
- "Shows how state can be passed between agent runs to maintain context.\n"
55
- "Creates examples in a 'state-transfer' subdirectory.",
56
- "files": [
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
- "create_subdir": True,
63
- },
64
- "elicitations": {
65
- "description": "Interactive form examples using MCP elicitations feature.\n"
66
- "Demonstrates collecting structured data with forms, AI-guided workflows,\n"
67
- "and custom handlers. Creates examples in an 'elicitations' subdirectory.",
68
- "files": [
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
- "create_subdir": True,
80
- },
81
- "tensorzero": {
82
- "description": "A complete example showcasing the TensorZero integration.\n"
83
- "Includes the T0 Gateway, an MCP server, an interactive agent, and \n"
84
- "multi-modal functionality.",
85
- "files": [
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
- "create_subdir": True,
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 = EXAMPLE_TYPES[example_type]
109
- if example_info["create_subdir"]:
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
- if example_type == "state-transfer":
129
- # The state-transfer example is in the mcp subdirectory
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}. Falling back to development mode.[/yellow]"
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
- package_dir = Path(__file__).parent.parent.parent.parent.parent
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
- # Fall back to the top-level directory if the resource finding fails
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 created
182
+ return []
193
183
 
194
- for filename in example_info["files"]:
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] {created[-1]}")
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
- source_mount_point = source_dir / "mount-point"
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 = source_mount_point / filename
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(f"[yellow]--force specified. Removing existing directory: {dest_dir}[/yellow]")
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(f"[bold yellow]Directory '{dest_dir.name}' already exists.[/bold yellow] Use --force to overwrite.")
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 EXAMPLE_TYPES.items():
283
+ for name, info in _EXAMPLE_CONFIGS.items():
282
284
  # Just show file count instead of listing all files
283
- file_count = len(info["files"])
285
+ file_count = len(info.files)
284
286
  files_summary = f"{file_count} files"
285
- if "mount_point_files" in info:
286
- mount_count = len(info["mount_point_files"])
287
- files_summary += f"\n+ {mount_count} data files"
288
- table.add_row(f"[green]{name}[/green]", info["description"], files_summary)
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(False, "--force", "-f", help="Force overwrite if project directory exists"),
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 = files("fast_agent").joinpath("resources").joinpath("examples").joinpath("tensorzero")
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("[yellow]Package resources not found. Falling back to development mode.[/yellow]")
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("\n3. [bold]Start the required services (TensorZero Gateway & MCP Server):[/bold]")
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(Path("."), help="Directory for the 'tensorzero' project folder."),
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."""
@@ -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 functools import wraps
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[[AgentCallable[P, R]], AgentCallable[P, R]]:
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: AgentCallable[P, R]) -> AgentCallable[P, R]:
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 on the wrapper function
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(wrapper, "_agent_type", agent_type)
248
- setattr(wrapper, "_agent_config", config)
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(wrapper, f"_{key}", value)
242
+ setattr(func, f"_{key}", value)
251
243
 
252
- return cast("AgentCallable[P, R]", wrapper)
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[[AgentCallable[P, R]], AgentCallable[P, R]]:
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[[AgentCallable[P, R]], AgentCallable[P, R]]:
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[[AgentCallable[P, R]], AgentCallable[P, R]]:
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[[AgentCallable[P, R]], AgentCallable[P, R]]:
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[[AgentCallable[P, R]], AgentCallable[P, R]]:
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[[AgentCallable[P, R]], AgentCallable[P, R]]:
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[[AgentCallable[P, R]], AgentCallable[P, R]]:
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[[AgentCallable[P, R]], AgentCallable[P, R]]:
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
 
@@ -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, Awaitable[R]]], Callable[P, Awaitable[R]]]: ...
296
+ ) -> Callable[[Callable[P, Coroutine[Any, Any, R]]], Callable[P, Coroutine[Any, Any, R]]]: ...
296
297
 
297
298
  def custom(
298
299
  self,
fast_agent/interfaces.py CHANGED
@@ -94,6 +94,8 @@ class FastAgentLLMProtocol(Protocol):
94
94
  @property
95
95
  def model_info(self) -> "ModelInfo | None": ...
96
96
 
97
+ def clear(self, *, clear_prompts: bool = False) -> None: ...
98
+
97
99
 
98
100
  class LlmAgentProtocol(Protocol):
99
101
  """Protocol defining the minimal interface for LLM agents."""
@@ -111,6 +113,8 @@ class LlmAgentProtocol(Protocol):
111
113
 
112
114
  async def shutdown(self) -> None: ...
113
115
 
116
+ def clear(self, *, clear_prompts: bool = False) -> None: ...
117
+
114
118
 
115
119
  class AgentProtocol(LlmAgentProtocol):
116
120
  """Standard agent interface with flexible input types."""
@@ -129,6 +129,7 @@ class FastAgentLLM(ContextDependent, FastAgentLLMProtocol, Generic[MessageParamT
129
129
  self.history: Memory[MessageParamT] = SimpleMemory[MessageParamT]()
130
130
 
131
131
  self._message_history: List[PromptMessageExtended] = []
132
+ self._template_messages: List[PromptMessageExtended] = []
132
133
 
133
134
  # Initialize the display component
134
135
  from fast_agent.ui.console_display import ConsoleDisplay
@@ -575,11 +576,15 @@ class FastAgentLLM(ContextDependent, FastAgentLLMProtocol, Generic[MessageParamT
575
576
 
576
577
  # Convert to PromptMessageExtended objects
577
578
  multipart_messages = PromptMessageExtended.parse_get_prompt_result(prompt_result)
579
+ # Store a local copy of template messages so we can retain them across clears
580
+ self._template_messages = [msg.model_copy(deep=True) for msg in multipart_messages]
578
581
 
579
582
  # Delegate to the provider-specific implementation
580
583
  result = await self._apply_prompt_provider_specific(
581
584
  multipart_messages, None, is_template=True
582
585
  )
586
+ # Ensure message history always includes the stored template when applied
587
+ self._message_history = [msg.model_copy(deep=True) for msg in self._template_messages]
583
588
  return result.first_text()
584
589
 
585
590
  async def _save_history(self, filename: str) -> None:
@@ -607,6 +612,18 @@ class FastAgentLLM(ContextDependent, FastAgentLLMProtocol, Generic[MessageParamT
607
612
  """
608
613
  return self._message_history
609
614
 
615
+ def clear(self, *, clear_prompts: bool = False) -> None:
616
+ """Reset stored message history while optionally retaining prompt templates."""
617
+
618
+ self.history.clear(clear_prompts=clear_prompts)
619
+ if clear_prompts:
620
+ self._template_messages = []
621
+ self._message_history = []
622
+ return
623
+
624
+ # Restore message history to template messages only; new turns will append as normal
625
+ self._message_history = [msg.model_copy(deep=True) for msg in self._template_messages]
626
+
610
627
  def _api_key(self):
611
628
  if self._init_api_key:
612
629
  return self._init_api_key