fast-agent-mcp 0.3.6__py3-none-any.whl → 0.3.8__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/__init__.py +9 -1
- fast_agent/agents/agent_types.py +11 -11
- fast_agent/agents/llm_agent.py +60 -40
- fast_agent/agents/llm_decorator.py +351 -7
- fast_agent/agents/mcp_agent.py +87 -65
- fast_agent/agents/tool_agent.py +50 -4
- fast_agent/cli/commands/auth.py +14 -1
- fast_agent/cli/commands/go.py +3 -3
- fast_agent/constants.py +2 -0
- fast_agent/core/agent_app.py +2 -0
- fast_agent/core/direct_factory.py +39 -120
- fast_agent/core/fastagent.py +2 -2
- fast_agent/core/logging/listeners.py +2 -1
- fast_agent/history/history_exporter.py +3 -3
- fast_agent/interfaces.py +2 -2
- fast_agent/llm/fastagent_llm.py +3 -3
- fast_agent/llm/model_database.py +7 -1
- fast_agent/llm/model_factory.py +2 -3
- fast_agent/llm/provider/bedrock/llm_bedrock.py +1 -1
- fast_agent/llm/provider/google/llm_google_native.py +1 -3
- fast_agent/llm/provider/openai/llm_azure.py +1 -1
- fast_agent/llm/provider/openai/llm_openai.py +57 -8
- fast_agent/llm/provider/openai/llm_tensorzero_openai.py +1 -1
- fast_agent/llm/request_params.py +1 -1
- fast_agent/mcp/__init__.py +1 -2
- fast_agent/mcp/mcp_aggregator.py +6 -3
- fast_agent/mcp/prompt_message_extended.py +2 -0
- fast_agent/mcp/prompt_serialization.py +124 -39
- fast_agent/mcp/prompts/prompt_load.py +34 -32
- fast_agent/mcp/prompts/prompt_server.py +26 -11
- fast_agent/resources/setup/fastagent.config.yaml +2 -2
- fast_agent/types/__init__.py +3 -1
- fast_agent/ui/enhanced_prompt.py +111 -64
- fast_agent/ui/interactive_prompt.py +13 -41
- fast_agent/ui/rich_progress.py +12 -8
- {fast_agent_mcp-0.3.6.dist-info → fast_agent_mcp-0.3.8.dist-info}/METADATA +4 -4
- {fast_agent_mcp-0.3.6.dist-info → fast_agent_mcp-0.3.8.dist-info}/RECORD +40 -40
- {fast_agent_mcp-0.3.6.dist-info → fast_agent_mcp-0.3.8.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.3.6.dist-info → fast_agent_mcp-0.3.8.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.3.6.dist-info → fast_agent_mcp-0.3.8.dist-info}/licenses/LICENSE +0 -0
fast_agent/agents/mcp_agent.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Base Agent class that implements the AgentProtocol interface.
|
|
3
3
|
|
|
4
4
|
This class provides default implementations of the standard agent methods
|
|
5
|
-
and delegates operations to an attached
|
|
5
|
+
and delegates operations to an attached FastAgentLLMProtocol instance.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import asyncio
|
|
@@ -16,8 +16,6 @@ from typing import (
|
|
|
16
16
|
Mapping,
|
|
17
17
|
Optional,
|
|
18
18
|
Sequence,
|
|
19
|
-
Tuple,
|
|
20
|
-
Type,
|
|
21
19
|
TypeVar,
|
|
22
20
|
Union,
|
|
23
21
|
)
|
|
@@ -54,7 +52,6 @@ from fast_agent.types import PromptMessageExtended, RequestParams
|
|
|
54
52
|
# Define a TypeVar for models
|
|
55
53
|
ModelT = TypeVar("ModelT", bound=BaseModel)
|
|
56
54
|
|
|
57
|
-
# Define a TypeVar for AugmentedLLM and its subclasses
|
|
58
55
|
LLM = TypeVar("LLM", bound=FastAgentLLMProtocol)
|
|
59
56
|
|
|
60
57
|
if TYPE_CHECKING:
|
|
@@ -69,7 +66,7 @@ class McpAgent(ABC, ToolAgent):
|
|
|
69
66
|
A base Agent class that implements the AgentProtocol interface.
|
|
70
67
|
|
|
71
68
|
This class provides default implementations of the standard agent methods
|
|
72
|
-
and delegates LLM operations to an attached
|
|
69
|
+
and delegates LLM operations to an attached FastAgentLLMProtocol instance.
|
|
73
70
|
"""
|
|
74
71
|
|
|
75
72
|
def __init__(
|
|
@@ -195,8 +192,9 @@ class McpAgent(ABC, ToolAgent):
|
|
|
195
192
|
server_instructions = ""
|
|
196
193
|
|
|
197
194
|
# Replace the template variable
|
|
198
|
-
self.instruction = self.instruction.replace(
|
|
199
|
-
|
|
195
|
+
self.instruction = self.instruction.replace(
|
|
196
|
+
"{{serverInstructions}}", server_instructions
|
|
197
|
+
)
|
|
200
198
|
|
|
201
199
|
# Update default request params to match
|
|
202
200
|
if self._default_request_params:
|
|
@@ -204,7 +202,9 @@ class McpAgent(ABC, ToolAgent):
|
|
|
204
202
|
|
|
205
203
|
self.logger.debug(f"Applied instruction templates for agent {self._name}")
|
|
206
204
|
|
|
207
|
-
def _format_server_instructions(
|
|
205
|
+
def _format_server_instructions(
|
|
206
|
+
self, instructions_data: Dict[str, tuple[str | None, List[str]]]
|
|
207
|
+
) -> str:
|
|
208
208
|
"""
|
|
209
209
|
Format server instructions with XML tags and tool lists.
|
|
210
210
|
|
|
@@ -228,7 +228,7 @@ class McpAgent(ABC, ToolAgent):
|
|
|
228
228
|
tools_list = ", ".join(prefixed_tools) if prefixed_tools else "No tools available"
|
|
229
229
|
|
|
230
230
|
formatted_parts.append(
|
|
231
|
-
f
|
|
231
|
+
f'<mcp-server name="{server_name}">\n'
|
|
232
232
|
f"<tools>{tools_list}</tools>\n"
|
|
233
233
|
f"<instructions>\n{instructions}\n</instructions>\n"
|
|
234
234
|
f"</mcp-server>"
|
|
@@ -249,31 +249,31 @@ class McpAgent(ABC, ToolAgent):
|
|
|
249
249
|
) -> str:
|
|
250
250
|
return await self.send(message)
|
|
251
251
|
|
|
252
|
-
async def send(
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
) -> str:
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
252
|
+
# async def send(
|
|
253
|
+
# self,
|
|
254
|
+
# message: Union[
|
|
255
|
+
# str,
|
|
256
|
+
# PromptMessage,
|
|
257
|
+
# PromptMessageExtended,
|
|
258
|
+
# Sequence[Union[str, PromptMessage, PromptMessageExtended]],
|
|
259
|
+
# ],
|
|
260
|
+
# request_params: RequestParams | None = None,
|
|
261
|
+
# ) -> str:
|
|
262
|
+
# """
|
|
263
|
+
# Send a message to the agent and get a response.
|
|
264
|
+
|
|
265
|
+
# Args:
|
|
266
|
+
# message: Message content in various formats:
|
|
267
|
+
# - String: Converted to a user PromptMessageExtended
|
|
268
|
+
# - PromptMessage: Converted to PromptMessageExtended
|
|
269
|
+
# - PromptMessageExtended: Used directly
|
|
270
|
+
# - request_params: Optional request parameters
|
|
271
|
+
|
|
272
|
+
# Returns:
|
|
273
|
+
# The agent's response as a string
|
|
274
|
+
# """
|
|
275
|
+
# response = await self.generate(message, request_params)
|
|
276
|
+
# return response.last_text() or ""
|
|
277
277
|
|
|
278
278
|
def _matches_pattern(self, name: str, pattern: str, server_name: str) -> bool:
|
|
279
279
|
"""
|
|
@@ -597,6 +597,7 @@ class McpAgent(ABC, ToolAgent):
|
|
|
597
597
|
return PromptMessageExtended(role="user", tool_results={})
|
|
598
598
|
|
|
599
599
|
tool_results: dict[str, CallToolResult] = {}
|
|
600
|
+
self._tool_loop_error = None
|
|
600
601
|
|
|
601
602
|
# Cache available tool names (original, not namespaced) for display
|
|
602
603
|
available_tools = [
|
|
@@ -613,6 +614,27 @@ class McpAgent(ABC, ToolAgent):
|
|
|
613
614
|
namespaced_tool = self._aggregator._namespaced_tool_map.get(tool_name)
|
|
614
615
|
display_tool_name = namespaced_tool.tool.name if namespaced_tool else tool_name
|
|
615
616
|
|
|
617
|
+
tool_available = False
|
|
618
|
+
if tool_name == HUMAN_INPUT_TOOL_NAME:
|
|
619
|
+
tool_available = True
|
|
620
|
+
elif namespaced_tool:
|
|
621
|
+
tool_available = True
|
|
622
|
+
else:
|
|
623
|
+
tool_available = any(
|
|
624
|
+
candidate.tool.name == tool_name
|
|
625
|
+
for candidate in self._aggregator._namespaced_tool_map.values()
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
if not tool_available:
|
|
629
|
+
error_message = f"Tool '{display_tool_name}' is not available"
|
|
630
|
+
self.logger.error(error_message)
|
|
631
|
+
self._mark_tool_loop_error(
|
|
632
|
+
correlation_id=correlation_id,
|
|
633
|
+
error_message=error_message,
|
|
634
|
+
tool_results=tool_results,
|
|
635
|
+
)
|
|
636
|
+
break
|
|
637
|
+
|
|
616
638
|
# Find the index of the current tool in available_tools for highlighting
|
|
617
639
|
highlight_index = None
|
|
618
640
|
try:
|
|
@@ -650,7 +672,7 @@ class McpAgent(ABC, ToolAgent):
|
|
|
650
672
|
# Show error result too
|
|
651
673
|
self.display.show_tool_result(name=self._name, result=error_result)
|
|
652
674
|
|
|
653
|
-
return
|
|
675
|
+
return self._finalize_tool_results(tool_results)
|
|
654
676
|
|
|
655
677
|
async def apply_prompt_template(self, prompt_result: GetPromptResult, prompt_name: str) -> str:
|
|
656
678
|
"""
|
|
@@ -668,36 +690,36 @@ class McpAgent(ABC, ToolAgent):
|
|
|
668
690
|
with self._tracer.start_as_current_span(f"Agent: '{self._name}' apply_prompt_template"):
|
|
669
691
|
return await self._llm.apply_prompt_template(prompt_result, prompt_name)
|
|
670
692
|
|
|
671
|
-
async def structured(
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
) -> Tuple[ModelT | None, PromptMessageExtended]:
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
693
|
+
# async def structured(
|
|
694
|
+
# self,
|
|
695
|
+
# messages: Union[
|
|
696
|
+
# str,
|
|
697
|
+
# PromptMessage,
|
|
698
|
+
# PromptMessageExtended,
|
|
699
|
+
# Sequence[Union[str, PromptMessage, PromptMessageExtended]],
|
|
700
|
+
# ],
|
|
701
|
+
# model: Type[ModelT],
|
|
702
|
+
# request_params: RequestParams | None = None,
|
|
703
|
+
# ) -> Tuple[ModelT | None, PromptMessageExtended]:
|
|
704
|
+
# """
|
|
705
|
+
# Apply the prompt and return the result as a Pydantic model.
|
|
706
|
+
# Normalizes input messages and delegates to the attached LLM.
|
|
707
|
+
|
|
708
|
+
# Args:
|
|
709
|
+
# messages: Message(s) in various formats:
|
|
710
|
+
# - String: Converted to a user PromptMessageExtended
|
|
711
|
+
# - PromptMessage: Converted to PromptMessageExtended
|
|
712
|
+
# - PromptMessageExtended: Used directly
|
|
713
|
+
# - List of any combination of the above
|
|
714
|
+
# model: The Pydantic model class to parse the result into
|
|
715
|
+
# request_params: Optional parameters to configure the LLM request
|
|
716
|
+
|
|
717
|
+
# Returns:
|
|
718
|
+
# An instance of the specified model, or None if coercion fails
|
|
719
|
+
# """
|
|
720
|
+
|
|
721
|
+
# with self._tracer.start_as_current_span(f"Agent: '{self._name}' structured"):
|
|
722
|
+
# return await super().structured(messages, model, request_params)
|
|
701
723
|
|
|
702
724
|
async def apply_prompt_messages(
|
|
703
725
|
self, prompts: List[PromptMessageExtended], request_params: RequestParams | None = None
|
fast_agent/agents/tool_agent.py
CHANGED
|
@@ -5,7 +5,7 @@ from mcp.types import CallToolResult, ListToolsResult, Tool
|
|
|
5
5
|
|
|
6
6
|
from fast_agent.agents.agent_types import AgentConfig
|
|
7
7
|
from fast_agent.agents.llm_agent import LlmAgent
|
|
8
|
-
from fast_agent.constants import HUMAN_INPUT_TOOL_NAME
|
|
8
|
+
from fast_agent.constants import FAST_AGENT_ERROR_CHANNEL, HUMAN_INPUT_TOOL_NAME
|
|
9
9
|
from fast_agent.context import Context
|
|
10
10
|
from fast_agent.core.logging.logger import get_logger
|
|
11
11
|
from fast_agent.mcp.helpers.content_helpers import text_content
|
|
@@ -42,6 +42,7 @@ class ToolAgent(LlmAgent):
|
|
|
42
42
|
|
|
43
43
|
self._execution_tools: dict[str, FastMCPTool] = {}
|
|
44
44
|
self._tool_schemas: list[Tool] = []
|
|
45
|
+
self._tool_loop_error: str | None = None
|
|
45
46
|
|
|
46
47
|
# Build a working list of tools and auto-inject human-input tool if missing
|
|
47
48
|
working_tools: list[FastMCPTool | Callable] = list(tools) if tools else []
|
|
@@ -97,10 +98,19 @@ class ToolAgent(LlmAgent):
|
|
|
97
98
|
)
|
|
98
99
|
|
|
99
100
|
if LlmStopReason.TOOL_USE == result.stop_reason:
|
|
101
|
+
self._tool_loop_error = None
|
|
100
102
|
if self.config.use_history:
|
|
101
|
-
|
|
103
|
+
tool_message = await self.run_tools(result)
|
|
104
|
+
if self._tool_loop_error:
|
|
105
|
+
result.stop_reason = LlmStopReason.ERROR
|
|
106
|
+
break
|
|
107
|
+
messages = [tool_message]
|
|
102
108
|
else:
|
|
103
|
-
|
|
109
|
+
tool_message = await self.run_tools(result)
|
|
110
|
+
if self._tool_loop_error:
|
|
111
|
+
result.stop_reason = LlmStopReason.ERROR
|
|
112
|
+
break
|
|
113
|
+
messages.extend([result, tool_message])
|
|
104
114
|
else:
|
|
105
115
|
break
|
|
106
116
|
|
|
@@ -123,12 +133,23 @@ class ToolAgent(LlmAgent):
|
|
|
123
133
|
return PromptMessageExtended(role="user", tool_results={})
|
|
124
134
|
|
|
125
135
|
tool_results: dict[str, CallToolResult] = {}
|
|
136
|
+
self._tool_loop_error = None
|
|
126
137
|
# TODO -- use gather() for parallel results, update display
|
|
127
138
|
available_tools = [t.name for t in (await self.list_tools()).tools]
|
|
128
139
|
for correlation_id, tool_request in request.tool_calls.items():
|
|
129
140
|
tool_name = tool_request.params.name
|
|
130
141
|
tool_args = tool_request.params.arguments or {}
|
|
131
142
|
|
|
143
|
+
if tool_name not in self._execution_tools:
|
|
144
|
+
error_message = f"Tool '{tool_name}' is not available"
|
|
145
|
+
logger.error(error_message)
|
|
146
|
+
self._mark_tool_loop_error(
|
|
147
|
+
correlation_id=correlation_id,
|
|
148
|
+
error_message=error_message,
|
|
149
|
+
tool_results=tool_results,
|
|
150
|
+
)
|
|
151
|
+
break
|
|
152
|
+
|
|
132
153
|
# Find the index of the current tool in available_tools for highlighting
|
|
133
154
|
highlight_index = None
|
|
134
155
|
try:
|
|
@@ -151,7 +172,32 @@ class ToolAgent(LlmAgent):
|
|
|
151
172
|
tool_results[correlation_id] = result
|
|
152
173
|
self.display.show_tool_result(name=self.name, result=result)
|
|
153
174
|
|
|
154
|
-
return
|
|
175
|
+
return self._finalize_tool_results(tool_results)
|
|
176
|
+
|
|
177
|
+
def _mark_tool_loop_error(
|
|
178
|
+
self,
|
|
179
|
+
*,
|
|
180
|
+
correlation_id: str,
|
|
181
|
+
error_message: str,
|
|
182
|
+
tool_results: dict[str, CallToolResult],
|
|
183
|
+
) -> None:
|
|
184
|
+
error_result = CallToolResult(
|
|
185
|
+
content=[text_content(error_message)],
|
|
186
|
+
isError=True,
|
|
187
|
+
)
|
|
188
|
+
tool_results[correlation_id] = error_result
|
|
189
|
+
self.display.show_tool_result(name=self.name, result=error_result)
|
|
190
|
+
self._tool_loop_error = error_message
|
|
191
|
+
|
|
192
|
+
def _finalize_tool_results(
|
|
193
|
+
self, tool_results: dict[str, CallToolResult]
|
|
194
|
+
) -> PromptMessageExtended:
|
|
195
|
+
channels = None
|
|
196
|
+
if self._tool_loop_error:
|
|
197
|
+
channels = {
|
|
198
|
+
FAST_AGENT_ERROR_CHANNEL: [text_content(self._tool_loop_error)],
|
|
199
|
+
}
|
|
200
|
+
return PromptMessageExtended(role="user", tool_results=tool_results, channels=channels)
|
|
155
201
|
|
|
156
202
|
async def list_tools(self) -> ListToolsResult:
|
|
157
203
|
"""Return available tools for this agent. Overridable by subclasses."""
|
fast_agent/cli/commands/auth.py
CHANGED
|
@@ -293,7 +293,9 @@ def main(
|
|
|
293
293
|
|
|
294
294
|
@app.command()
|
|
295
295
|
def login(
|
|
296
|
-
target: str = typer.Argument(
|
|
296
|
+
target: Optional[str] = typer.Argument(
|
|
297
|
+
None, help="Server name (from config) or identity (base URL)"
|
|
298
|
+
),
|
|
297
299
|
transport: Optional[str] = typer.Option(
|
|
298
300
|
None, "--transport", help="Transport for identity mode: http or sse"
|
|
299
301
|
),
|
|
@@ -311,6 +313,17 @@ def login(
|
|
|
311
313
|
cfg = None
|
|
312
314
|
resolved_transport = None
|
|
313
315
|
|
|
316
|
+
if target is None or not target.strip():
|
|
317
|
+
typer.echo("Provide a server name or identity URL to log in.")
|
|
318
|
+
typer.echo(
|
|
319
|
+
"Example: `fast-agent auth login my-server` "
|
|
320
|
+
"or `fast-agent auth login https://example.com`."
|
|
321
|
+
)
|
|
322
|
+
typer.echo("Run `fast-agent auth login --help` for more details.")
|
|
323
|
+
raise typer.Exit(1)
|
|
324
|
+
|
|
325
|
+
target = target.strip()
|
|
326
|
+
|
|
314
327
|
if "://" in target:
|
|
315
328
|
# Identity mode
|
|
316
329
|
base = _derive_base_server_url(target)
|
fast_agent/cli/commands/go.py
CHANGED
|
@@ -40,7 +40,7 @@ async def _run_agent(
|
|
|
40
40
|
"""Async implementation to run an interactive agent."""
|
|
41
41
|
from pathlib import Path
|
|
42
42
|
|
|
43
|
-
from fast_agent.mcp.prompts.prompt_load import
|
|
43
|
+
from fast_agent.mcp.prompts.prompt_load import load_prompt
|
|
44
44
|
|
|
45
45
|
# Create the FastAgent instance
|
|
46
46
|
|
|
@@ -110,7 +110,7 @@ async def _run_agent(
|
|
|
110
110
|
display = ConsoleDisplay(config=None)
|
|
111
111
|
display.show_parallel_results(agent.parallel)
|
|
112
112
|
elif prompt_file:
|
|
113
|
-
prompt =
|
|
113
|
+
prompt = load_prompt(Path(prompt_file))
|
|
114
114
|
await agent.parallel.generate(prompt)
|
|
115
115
|
display = ConsoleDisplay(config=None)
|
|
116
116
|
display.show_parallel_results(agent.parallel)
|
|
@@ -135,7 +135,7 @@ async def _run_agent(
|
|
|
135
135
|
# Print the response and exit
|
|
136
136
|
print(response)
|
|
137
137
|
elif prompt_file:
|
|
138
|
-
prompt =
|
|
138
|
+
prompt = load_prompt(Path(prompt_file))
|
|
139
139
|
response = await agent.agent.generate(prompt)
|
|
140
140
|
print(f"\nLoaded {len(prompt)} messages from prompt file '{prompt_file}'")
|
|
141
141
|
await agent.interactive()
|
fast_agent/constants.py
CHANGED
|
@@ -6,3 +6,5 @@ Global constants for fast_agent with minimal dependencies to avoid circular impo
|
|
|
6
6
|
HUMAN_INPUT_TOOL_NAME = "__human_input"
|
|
7
7
|
MCP_UI = "mcp-ui"
|
|
8
8
|
REASONING = "reasoning"
|
|
9
|
+
FAST_AGENT_ERROR_CHANNEL = "fast-agent-error"
|
|
10
|
+
FAST_AGENT_REMOVED_METADATA_CHANNEL = "fast-agent-removed-meta"
|
fast_agent/core/agent_app.py
CHANGED
|
@@ -3,7 +3,8 @@ Direct factory functions for creating agent and workflow instances without proxi
|
|
|
3
3
|
Implements type-safe factories with improved error handling.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from
|
|
6
|
+
from functools import partial
|
|
7
|
+
from typing import Any, Dict, List, Optional, Protocol, TypeVar
|
|
7
8
|
|
|
8
9
|
from fast_agent.agents import McpAgent
|
|
9
10
|
from fast_agent.agents.agent_types import AgentConfig, AgentType
|
|
@@ -379,6 +380,35 @@ async def create_agents_by_type(
|
|
|
379
380
|
return result_agents
|
|
380
381
|
|
|
381
382
|
|
|
383
|
+
async def active_agents_in_dependency_group(
|
|
384
|
+
app_instance: Core,
|
|
385
|
+
agents_dict: AgentConfigDict,
|
|
386
|
+
model_factory_func: ModelFactoryFunctionProtocol,
|
|
387
|
+
group: List[str],
|
|
388
|
+
active_agents: AgentDict,
|
|
389
|
+
):
|
|
390
|
+
"""
|
|
391
|
+
For each of the possible agent types, create agents and update the active agents dictionary.
|
|
392
|
+
|
|
393
|
+
Notice: This function modifies the active_agents dictionary in-place which is a feature (no copies).
|
|
394
|
+
"""
|
|
395
|
+
type_of_agents = list(map(lambda c: (c, c.value), AgentType))
|
|
396
|
+
for agent_type, agent_type_value in type_of_agents:
|
|
397
|
+
agents_dict_local = {
|
|
398
|
+
name: agents_dict[name]
|
|
399
|
+
for name in group
|
|
400
|
+
if agents_dict[name]["type"] == agent_type_value
|
|
401
|
+
}
|
|
402
|
+
agents = await create_agents_by_type(
|
|
403
|
+
app_instance,
|
|
404
|
+
agents_dict_local,
|
|
405
|
+
agent_type,
|
|
406
|
+
model_factory_func,
|
|
407
|
+
active_agents,
|
|
408
|
+
)
|
|
409
|
+
active_agents.update(agents)
|
|
410
|
+
|
|
411
|
+
|
|
382
412
|
async def create_agents_in_dependency_order(
|
|
383
413
|
app_instance: Core,
|
|
384
414
|
agents_dict: AgentConfigDict,
|
|
@@ -403,127 +433,16 @@ async def create_agents_in_dependency_order(
|
|
|
403
433
|
# Create a dictionary to store all active agents/workflows
|
|
404
434
|
active_agents: AgentDict = {}
|
|
405
435
|
|
|
436
|
+
active_agents_in_dependency_group_partial = partial(
|
|
437
|
+
active_agents_in_dependency_group,
|
|
438
|
+
app_instance,
|
|
439
|
+
agents_dict,
|
|
440
|
+
model_factory_func,
|
|
441
|
+
)
|
|
442
|
+
|
|
406
443
|
# Create agent proxies for each group in dependency order
|
|
407
444
|
for group in dependencies:
|
|
408
|
-
|
|
409
|
-
# Note: We compare string values from config with the Enum's string value
|
|
410
|
-
if AgentType.BASIC.value in [agents_dict[name]["type"] for name in group]:
|
|
411
|
-
basic_agents = await create_agents_by_type(
|
|
412
|
-
app_instance,
|
|
413
|
-
{
|
|
414
|
-
name: agents_dict[name]
|
|
415
|
-
for name in group
|
|
416
|
-
if agents_dict[name]["type"] == AgentType.BASIC.value
|
|
417
|
-
},
|
|
418
|
-
AgentType.BASIC,
|
|
419
|
-
model_factory_func,
|
|
420
|
-
active_agents,
|
|
421
|
-
)
|
|
422
|
-
active_agents.update(basic_agents)
|
|
423
|
-
|
|
424
|
-
# Create custom agents first
|
|
425
|
-
if AgentType.CUSTOM.value in [agents_dict[name]["type"] for name in group]:
|
|
426
|
-
basic_agents = await create_agents_by_type(
|
|
427
|
-
app_instance,
|
|
428
|
-
{
|
|
429
|
-
name: agents_dict[name]
|
|
430
|
-
for name in group
|
|
431
|
-
if agents_dict[name]["type"] == AgentType.CUSTOM.value
|
|
432
|
-
},
|
|
433
|
-
AgentType.CUSTOM,
|
|
434
|
-
model_factory_func,
|
|
435
|
-
active_agents,
|
|
436
|
-
)
|
|
437
|
-
active_agents.update(basic_agents)
|
|
438
|
-
|
|
439
|
-
# Create parallel agents
|
|
440
|
-
if AgentType.PARALLEL.value in [agents_dict[name]["type"] for name in group]:
|
|
441
|
-
parallel_agents = await create_agents_by_type(
|
|
442
|
-
app_instance,
|
|
443
|
-
{
|
|
444
|
-
name: agents_dict[name]
|
|
445
|
-
for name in group
|
|
446
|
-
if agents_dict[name]["type"] == AgentType.PARALLEL.value
|
|
447
|
-
},
|
|
448
|
-
AgentType.PARALLEL,
|
|
449
|
-
model_factory_func,
|
|
450
|
-
active_agents,
|
|
451
|
-
)
|
|
452
|
-
active_agents.update(parallel_agents)
|
|
453
|
-
|
|
454
|
-
# Create router agents
|
|
455
|
-
if AgentType.ROUTER.value in [agents_dict[name]["type"] for name in group]:
|
|
456
|
-
router_agents = await create_agents_by_type(
|
|
457
|
-
app_instance,
|
|
458
|
-
{
|
|
459
|
-
name: agents_dict[name]
|
|
460
|
-
for name in group
|
|
461
|
-
if agents_dict[name]["type"] == AgentType.ROUTER.value
|
|
462
|
-
},
|
|
463
|
-
AgentType.ROUTER,
|
|
464
|
-
model_factory_func,
|
|
465
|
-
active_agents,
|
|
466
|
-
)
|
|
467
|
-
active_agents.update(router_agents)
|
|
468
|
-
|
|
469
|
-
# Create chain agents
|
|
470
|
-
if AgentType.CHAIN.value in [agents_dict[name]["type"] for name in group]:
|
|
471
|
-
chain_agents = await create_agents_by_type(
|
|
472
|
-
app_instance,
|
|
473
|
-
{
|
|
474
|
-
name: agents_dict[name]
|
|
475
|
-
for name in group
|
|
476
|
-
if agents_dict[name]["type"] == AgentType.CHAIN.value
|
|
477
|
-
},
|
|
478
|
-
AgentType.CHAIN,
|
|
479
|
-
model_factory_func,
|
|
480
|
-
active_agents,
|
|
481
|
-
)
|
|
482
|
-
active_agents.update(chain_agents)
|
|
483
|
-
|
|
484
|
-
# Create evaluator-optimizer agents
|
|
485
|
-
if AgentType.EVALUATOR_OPTIMIZER.value in [agents_dict[name]["type"] for name in group]:
|
|
486
|
-
evaluator_agents = await create_agents_by_type(
|
|
487
|
-
app_instance,
|
|
488
|
-
{
|
|
489
|
-
name: agents_dict[name]
|
|
490
|
-
for name in group
|
|
491
|
-
if agents_dict[name]["type"] == AgentType.EVALUATOR_OPTIMIZER.value
|
|
492
|
-
},
|
|
493
|
-
AgentType.EVALUATOR_OPTIMIZER,
|
|
494
|
-
model_factory_func,
|
|
495
|
-
active_agents,
|
|
496
|
-
)
|
|
497
|
-
active_agents.update(evaluator_agents)
|
|
498
|
-
|
|
499
|
-
if AgentType.ORCHESTRATOR.value in [agents_dict[name]["type"] for name in group]:
|
|
500
|
-
orchestrator_agents = await create_agents_by_type(
|
|
501
|
-
app_instance,
|
|
502
|
-
{
|
|
503
|
-
name: agents_dict[name]
|
|
504
|
-
for name in group
|
|
505
|
-
if agents_dict[name]["type"] == AgentType.ORCHESTRATOR.value
|
|
506
|
-
},
|
|
507
|
-
AgentType.ORCHESTRATOR,
|
|
508
|
-
model_factory_func,
|
|
509
|
-
active_agents,
|
|
510
|
-
)
|
|
511
|
-
active_agents.update(orchestrator_agents)
|
|
512
|
-
|
|
513
|
-
# Create orchestrator2 agents last since they might depend on other agents
|
|
514
|
-
if AgentType.ITERATIVE_PLANNER.value in [agents_dict[name]["type"] for name in group]:
|
|
515
|
-
orchestrator2_agents = await create_agents_by_type(
|
|
516
|
-
app_instance,
|
|
517
|
-
{
|
|
518
|
-
name: agents_dict[name]
|
|
519
|
-
for name in group
|
|
520
|
-
if agents_dict[name]["type"] == AgentType.ITERATIVE_PLANNER.value
|
|
521
|
-
},
|
|
522
|
-
AgentType.ITERATIVE_PLANNER,
|
|
523
|
-
model_factory_func,
|
|
524
|
-
active_agents,
|
|
525
|
-
)
|
|
526
|
-
active_agents.update(orchestrator2_agents)
|
|
445
|
+
await active_agents_in_dependency_group_partial(group, active_agents)
|
|
527
446
|
|
|
528
447
|
return active_agents
|
|
529
448
|
|
fast_agent/core/fastagent.py
CHANGED
|
@@ -75,7 +75,7 @@ from fast_agent.core.validation import (
|
|
|
75
75
|
validate_server_references,
|
|
76
76
|
validate_workflow_references,
|
|
77
77
|
)
|
|
78
|
-
from fast_agent.mcp.prompts.prompt_load import
|
|
78
|
+
from fast_agent.mcp.prompts.prompt_load import load_prompt
|
|
79
79
|
from fast_agent.ui.usage_display import display_usage_report
|
|
80
80
|
|
|
81
81
|
if TYPE_CHECKING:
|
|
@@ -543,7 +543,7 @@ class FastAgent:
|
|
|
543
543
|
|
|
544
544
|
if hasattr(self.args, "prompt_file") and self.args.prompt_file:
|
|
545
545
|
agent_name = self.args.agent
|
|
546
|
-
prompt: List[PromptMessageExtended] =
|
|
546
|
+
prompt: List[PromptMessageExtended] = load_prompt(
|
|
547
547
|
Path(self.args.prompt_file)
|
|
548
548
|
)
|
|
549
549
|
if agent_name not in active_agents:
|
|
@@ -55,7 +55,8 @@ def convert_log_event(event: Event) -> "ProgressEvent | None":
|
|
|
55
55
|
if progress_message: # Only override if message is non-empty
|
|
56
56
|
details = progress_message
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
# TODO: there must be a better way :D?!
|
|
59
|
+
elif "llm" in namespace:
|
|
59
60
|
model = event_data.get("model", "")
|
|
60
61
|
|
|
61
62
|
# For all augmented_llm events, put model info in details column
|
|
@@ -10,7 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
from typing import TYPE_CHECKING, Optional
|
|
12
12
|
|
|
13
|
-
from fast_agent.mcp.prompt_serialization import
|
|
13
|
+
from fast_agent.mcp.prompt_serialization import save_messages
|
|
14
14
|
|
|
15
15
|
if TYPE_CHECKING:
|
|
16
16
|
from fast_agent.interfaces import AgentProtocol
|
|
@@ -35,10 +35,10 @@ class HistoryExporter:
|
|
|
35
35
|
The path that was written to.
|
|
36
36
|
"""
|
|
37
37
|
# Determine a default filename when not provided
|
|
38
|
-
target = filename or f"{getattr(agent, 'name', 'assistant')}
|
|
38
|
+
target = filename or f"{getattr(agent, 'name', 'assistant')}.json"
|
|
39
39
|
|
|
40
40
|
messages = agent.message_history
|
|
41
|
-
|
|
41
|
+
save_messages(messages, target)
|
|
42
42
|
|
|
43
43
|
# Return and optionally print a small confirmation
|
|
44
44
|
return target
|
fast_agent/interfaces.py
CHANGED
|
@@ -47,7 +47,7 @@ ModelT = TypeVar("ModelT", bound=BaseModel)
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
class LLMFactoryProtocol(Protocol):
|
|
50
|
-
"""Protocol for LLM factory functions that create
|
|
50
|
+
"""Protocol for LLM factory functions that create FastAgentLLM instances."""
|
|
51
51
|
|
|
52
52
|
def __call__(self, agent: "LlmAgentProtocol", **kwargs: Any) -> "FastAgentLLMProtocol": ...
|
|
53
53
|
|
|
@@ -59,7 +59,7 @@ class ModelFactoryFunctionProtocol(Protocol):
|
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
class FastAgentLLMProtocol(Protocol):
|
|
62
|
-
"""Protocol defining the interface for
|
|
62
|
+
"""Protocol defining the interface for LLMs"""
|
|
63
63
|
|
|
64
64
|
async def structured(
|
|
65
65
|
self,
|