fast-agent-mcp 0.4.7__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.
- fast_agent/__init__.py +183 -0
- fast_agent/acp/__init__.py +19 -0
- fast_agent/acp/acp_aware_mixin.py +304 -0
- fast_agent/acp/acp_context.py +437 -0
- fast_agent/acp/content_conversion.py +136 -0
- fast_agent/acp/filesystem_runtime.py +427 -0
- fast_agent/acp/permission_store.py +269 -0
- fast_agent/acp/server/__init__.py +5 -0
- fast_agent/acp/server/agent_acp_server.py +1472 -0
- fast_agent/acp/slash_commands.py +1050 -0
- fast_agent/acp/terminal_runtime.py +408 -0
- fast_agent/acp/tool_permission_adapter.py +125 -0
- fast_agent/acp/tool_permissions.py +474 -0
- fast_agent/acp/tool_progress.py +814 -0
- fast_agent/agents/__init__.py +85 -0
- fast_agent/agents/agent_types.py +64 -0
- fast_agent/agents/llm_agent.py +350 -0
- fast_agent/agents/llm_decorator.py +1139 -0
- fast_agent/agents/mcp_agent.py +1337 -0
- fast_agent/agents/tool_agent.py +271 -0
- fast_agent/agents/workflow/agents_as_tools_agent.py +849 -0
- fast_agent/agents/workflow/chain_agent.py +212 -0
- fast_agent/agents/workflow/evaluator_optimizer.py +380 -0
- fast_agent/agents/workflow/iterative_planner.py +652 -0
- fast_agent/agents/workflow/maker_agent.py +379 -0
- fast_agent/agents/workflow/orchestrator_models.py +218 -0
- fast_agent/agents/workflow/orchestrator_prompts.py +248 -0
- fast_agent/agents/workflow/parallel_agent.py +250 -0
- fast_agent/agents/workflow/router_agent.py +353 -0
- fast_agent/cli/__init__.py +0 -0
- fast_agent/cli/__main__.py +73 -0
- fast_agent/cli/commands/acp.py +159 -0
- fast_agent/cli/commands/auth.py +404 -0
- fast_agent/cli/commands/check_config.py +783 -0
- fast_agent/cli/commands/go.py +514 -0
- fast_agent/cli/commands/quickstart.py +557 -0
- fast_agent/cli/commands/serve.py +143 -0
- fast_agent/cli/commands/server_helpers.py +114 -0
- fast_agent/cli/commands/setup.py +174 -0
- fast_agent/cli/commands/url_parser.py +190 -0
- fast_agent/cli/constants.py +40 -0
- fast_agent/cli/main.py +115 -0
- fast_agent/cli/terminal.py +24 -0
- fast_agent/config.py +798 -0
- fast_agent/constants.py +41 -0
- fast_agent/context.py +279 -0
- fast_agent/context_dependent.py +50 -0
- fast_agent/core/__init__.py +92 -0
- fast_agent/core/agent_app.py +448 -0
- fast_agent/core/core_app.py +137 -0
- fast_agent/core/direct_decorators.py +784 -0
- fast_agent/core/direct_factory.py +620 -0
- fast_agent/core/error_handling.py +27 -0
- fast_agent/core/exceptions.py +90 -0
- fast_agent/core/executor/__init__.py +0 -0
- fast_agent/core/executor/executor.py +280 -0
- fast_agent/core/executor/task_registry.py +32 -0
- fast_agent/core/executor/workflow_signal.py +324 -0
- fast_agent/core/fastagent.py +1186 -0
- fast_agent/core/logging/__init__.py +5 -0
- fast_agent/core/logging/events.py +138 -0
- fast_agent/core/logging/json_serializer.py +164 -0
- fast_agent/core/logging/listeners.py +309 -0
- fast_agent/core/logging/logger.py +278 -0
- fast_agent/core/logging/transport.py +481 -0
- fast_agent/core/prompt.py +9 -0
- fast_agent/core/prompt_templates.py +183 -0
- fast_agent/core/validation.py +326 -0
- fast_agent/event_progress.py +62 -0
- fast_agent/history/history_exporter.py +49 -0
- fast_agent/human_input/__init__.py +47 -0
- fast_agent/human_input/elicitation_handler.py +123 -0
- fast_agent/human_input/elicitation_state.py +33 -0
- fast_agent/human_input/form_elements.py +59 -0
- fast_agent/human_input/form_fields.py +256 -0
- fast_agent/human_input/simple_form.py +113 -0
- fast_agent/human_input/types.py +40 -0
- fast_agent/interfaces.py +310 -0
- fast_agent/llm/__init__.py +9 -0
- fast_agent/llm/cancellation.py +22 -0
- fast_agent/llm/fastagent_llm.py +931 -0
- fast_agent/llm/internal/passthrough.py +161 -0
- fast_agent/llm/internal/playback.py +129 -0
- fast_agent/llm/internal/silent.py +41 -0
- fast_agent/llm/internal/slow.py +38 -0
- fast_agent/llm/memory.py +275 -0
- fast_agent/llm/model_database.py +490 -0
- fast_agent/llm/model_factory.py +388 -0
- fast_agent/llm/model_info.py +102 -0
- fast_agent/llm/prompt_utils.py +155 -0
- fast_agent/llm/provider/anthropic/anthropic_utils.py +84 -0
- fast_agent/llm/provider/anthropic/cache_planner.py +56 -0
- fast_agent/llm/provider/anthropic/llm_anthropic.py +796 -0
- fast_agent/llm/provider/anthropic/multipart_converter_anthropic.py +462 -0
- fast_agent/llm/provider/bedrock/bedrock_utils.py +218 -0
- fast_agent/llm/provider/bedrock/llm_bedrock.py +2207 -0
- fast_agent/llm/provider/bedrock/multipart_converter_bedrock.py +84 -0
- fast_agent/llm/provider/google/google_converter.py +466 -0
- fast_agent/llm/provider/google/llm_google_native.py +681 -0
- fast_agent/llm/provider/openai/llm_aliyun.py +31 -0
- fast_agent/llm/provider/openai/llm_azure.py +143 -0
- fast_agent/llm/provider/openai/llm_deepseek.py +76 -0
- fast_agent/llm/provider/openai/llm_generic.py +35 -0
- fast_agent/llm/provider/openai/llm_google_oai.py +32 -0
- fast_agent/llm/provider/openai/llm_groq.py +42 -0
- fast_agent/llm/provider/openai/llm_huggingface.py +85 -0
- fast_agent/llm/provider/openai/llm_openai.py +1195 -0
- fast_agent/llm/provider/openai/llm_openai_compatible.py +138 -0
- fast_agent/llm/provider/openai/llm_openrouter.py +45 -0
- fast_agent/llm/provider/openai/llm_tensorzero_openai.py +128 -0
- fast_agent/llm/provider/openai/llm_xai.py +38 -0
- fast_agent/llm/provider/openai/multipart_converter_openai.py +561 -0
- fast_agent/llm/provider/openai/openai_multipart.py +169 -0
- fast_agent/llm/provider/openai/openai_utils.py +67 -0
- fast_agent/llm/provider/openai/responses.py +133 -0
- fast_agent/llm/provider_key_manager.py +139 -0
- fast_agent/llm/provider_types.py +34 -0
- fast_agent/llm/request_params.py +61 -0
- fast_agent/llm/sampling_converter.py +98 -0
- fast_agent/llm/stream_types.py +9 -0
- fast_agent/llm/usage_tracking.py +445 -0
- fast_agent/mcp/__init__.py +56 -0
- fast_agent/mcp/common.py +26 -0
- fast_agent/mcp/elicitation_factory.py +84 -0
- fast_agent/mcp/elicitation_handlers.py +164 -0
- fast_agent/mcp/gen_client.py +83 -0
- fast_agent/mcp/helpers/__init__.py +36 -0
- fast_agent/mcp/helpers/content_helpers.py +352 -0
- fast_agent/mcp/helpers/server_config_helpers.py +25 -0
- fast_agent/mcp/hf_auth.py +147 -0
- fast_agent/mcp/interfaces.py +92 -0
- fast_agent/mcp/logger_textio.py +108 -0
- fast_agent/mcp/mcp_agent_client_session.py +411 -0
- fast_agent/mcp/mcp_aggregator.py +2175 -0
- fast_agent/mcp/mcp_connection_manager.py +723 -0
- fast_agent/mcp/mcp_content.py +262 -0
- fast_agent/mcp/mime_utils.py +108 -0
- fast_agent/mcp/oauth_client.py +509 -0
- fast_agent/mcp/prompt.py +159 -0
- fast_agent/mcp/prompt_message_extended.py +155 -0
- fast_agent/mcp/prompt_render.py +84 -0
- fast_agent/mcp/prompt_serialization.py +580 -0
- fast_agent/mcp/prompts/__init__.py +0 -0
- fast_agent/mcp/prompts/__main__.py +7 -0
- fast_agent/mcp/prompts/prompt_constants.py +18 -0
- fast_agent/mcp/prompts/prompt_helpers.py +238 -0
- fast_agent/mcp/prompts/prompt_load.py +186 -0
- fast_agent/mcp/prompts/prompt_server.py +552 -0
- fast_agent/mcp/prompts/prompt_template.py +438 -0
- fast_agent/mcp/resource_utils.py +215 -0
- fast_agent/mcp/sampling.py +200 -0
- fast_agent/mcp/server/__init__.py +4 -0
- fast_agent/mcp/server/agent_server.py +613 -0
- fast_agent/mcp/skybridge.py +44 -0
- fast_agent/mcp/sse_tracking.py +287 -0
- fast_agent/mcp/stdio_tracking_simple.py +59 -0
- fast_agent/mcp/streamable_http_tracking.py +309 -0
- fast_agent/mcp/tool_execution_handler.py +137 -0
- fast_agent/mcp/tool_permission_handler.py +88 -0
- fast_agent/mcp/transport_tracking.py +634 -0
- fast_agent/mcp/types.py +24 -0
- fast_agent/mcp/ui_agent.py +48 -0
- fast_agent/mcp/ui_mixin.py +209 -0
- fast_agent/mcp_server_registry.py +89 -0
- fast_agent/py.typed +0 -0
- fast_agent/resources/examples/data-analysis/analysis-campaign.py +189 -0
- fast_agent/resources/examples/data-analysis/analysis.py +68 -0
- fast_agent/resources/examples/data-analysis/fastagent.config.yaml +41 -0
- fast_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +1471 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_account_server.py +88 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_forms_server.py +297 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_game_server.py +164 -0
- fast_agent/resources/examples/mcp/elicitations/fastagent.config.yaml +35 -0
- fast_agent/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +17 -0
- fast_agent/resources/examples/mcp/elicitations/forms_demo.py +107 -0
- fast_agent/resources/examples/mcp/elicitations/game_character.py +65 -0
- fast_agent/resources/examples/mcp/elicitations/game_character_handler.py +256 -0
- fast_agent/resources/examples/mcp/elicitations/tool_call.py +21 -0
- fast_agent/resources/examples/mcp/state-transfer/agent_one.py +18 -0
- fast_agent/resources/examples/mcp/state-transfer/agent_two.py +18 -0
- fast_agent/resources/examples/mcp/state-transfer/fastagent.config.yaml +27 -0
- fast_agent/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +15 -0
- fast_agent/resources/examples/researcher/fastagent.config.yaml +61 -0
- fast_agent/resources/examples/researcher/researcher-eval.py +53 -0
- fast_agent/resources/examples/researcher/researcher-imp.py +189 -0
- fast_agent/resources/examples/researcher/researcher.py +36 -0
- fast_agent/resources/examples/tensorzero/.env.sample +2 -0
- fast_agent/resources/examples/tensorzero/Makefile +31 -0
- fast_agent/resources/examples/tensorzero/README.md +56 -0
- fast_agent/resources/examples/tensorzero/agent.py +35 -0
- fast_agent/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
- fast_agent/resources/examples/tensorzero/demo_images/crab.png +0 -0
- fast_agent/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
- fast_agent/resources/examples/tensorzero/docker-compose.yml +105 -0
- fast_agent/resources/examples/tensorzero/fastagent.config.yaml +19 -0
- fast_agent/resources/examples/tensorzero/image_demo.py +67 -0
- fast_agent/resources/examples/tensorzero/mcp_server/Dockerfile +25 -0
- fast_agent/resources/examples/tensorzero/mcp_server/entrypoint.sh +35 -0
- fast_agent/resources/examples/tensorzero/mcp_server/mcp_server.py +31 -0
- fast_agent/resources/examples/tensorzero/mcp_server/pyproject.toml +11 -0
- fast_agent/resources/examples/tensorzero/simple_agent.py +25 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/system_schema.json +29 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +11 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +35 -0
- fast_agent/resources/examples/workflows/agents_as_tools_extended.py +73 -0
- fast_agent/resources/examples/workflows/agents_as_tools_simple.py +50 -0
- fast_agent/resources/examples/workflows/chaining.py +37 -0
- fast_agent/resources/examples/workflows/evaluator.py +77 -0
- fast_agent/resources/examples/workflows/fastagent.config.yaml +26 -0
- fast_agent/resources/examples/workflows/graded_report.md +89 -0
- fast_agent/resources/examples/workflows/human_input.py +28 -0
- fast_agent/resources/examples/workflows/maker.py +156 -0
- fast_agent/resources/examples/workflows/orchestrator.py +70 -0
- fast_agent/resources/examples/workflows/parallel.py +56 -0
- fast_agent/resources/examples/workflows/router.py +69 -0
- fast_agent/resources/examples/workflows/short_story.md +13 -0
- fast_agent/resources/examples/workflows/short_story.txt +19 -0
- fast_agent/resources/setup/.gitignore +30 -0
- fast_agent/resources/setup/agent.py +28 -0
- fast_agent/resources/setup/fastagent.config.yaml +65 -0
- fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
- fast_agent/resources/setup/pyproject.toml.tmpl +23 -0
- fast_agent/skills/__init__.py +9 -0
- fast_agent/skills/registry.py +235 -0
- fast_agent/tools/elicitation.py +369 -0
- fast_agent/tools/shell_runtime.py +402 -0
- fast_agent/types/__init__.py +59 -0
- fast_agent/types/conversation_summary.py +294 -0
- fast_agent/types/llm_stop_reason.py +78 -0
- fast_agent/types/message_search.py +249 -0
- fast_agent/ui/__init__.py +38 -0
- fast_agent/ui/console.py +59 -0
- fast_agent/ui/console_display.py +1080 -0
- fast_agent/ui/elicitation_form.py +946 -0
- fast_agent/ui/elicitation_style.py +59 -0
- fast_agent/ui/enhanced_prompt.py +1400 -0
- fast_agent/ui/history_display.py +734 -0
- fast_agent/ui/interactive_prompt.py +1199 -0
- fast_agent/ui/markdown_helpers.py +104 -0
- fast_agent/ui/markdown_truncator.py +1004 -0
- fast_agent/ui/mcp_display.py +857 -0
- fast_agent/ui/mcp_ui_utils.py +235 -0
- fast_agent/ui/mermaid_utils.py +169 -0
- fast_agent/ui/message_primitives.py +50 -0
- fast_agent/ui/notification_tracker.py +205 -0
- fast_agent/ui/plain_text_truncator.py +68 -0
- fast_agent/ui/progress_display.py +10 -0
- fast_agent/ui/rich_progress.py +195 -0
- fast_agent/ui/streaming.py +774 -0
- fast_agent/ui/streaming_buffer.py +449 -0
- fast_agent/ui/tool_display.py +422 -0
- fast_agent/ui/usage_display.py +204 -0
- fast_agent/utils/__init__.py +5 -0
- fast_agent/utils/reasoning_stream_parser.py +77 -0
- fast_agent/utils/time.py +22 -0
- fast_agent/workflow_telemetry.py +261 -0
- fast_agent_mcp-0.4.7.dist-info/METADATA +788 -0
- fast_agent_mcp-0.4.7.dist-info/RECORD +261 -0
- fast_agent_mcp-0.4.7.dist-info/WHEEL +4 -0
- fast_agent_mcp-0.4.7.dist-info/entry_points.txt +7 -0
- fast_agent_mcp-0.4.7.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
from typing import Any, Callable, Dict, List, Sequence
|
|
2
|
+
|
|
3
|
+
from mcp.server.fastmcp.tools.base import Tool as FastMCPTool
|
|
4
|
+
from mcp.types import CallToolResult, ListToolsResult, Tool
|
|
5
|
+
|
|
6
|
+
from fast_agent.agents.agent_types import AgentConfig
|
|
7
|
+
from fast_agent.agents.llm_agent import LlmAgent
|
|
8
|
+
from fast_agent.constants import (
|
|
9
|
+
DEFAULT_MAX_ITERATIONS,
|
|
10
|
+
FAST_AGENT_ERROR_CHANNEL,
|
|
11
|
+
HUMAN_INPUT_TOOL_NAME,
|
|
12
|
+
)
|
|
13
|
+
from fast_agent.context import Context
|
|
14
|
+
from fast_agent.core.logging.logger import get_logger
|
|
15
|
+
from fast_agent.mcp.helpers.content_helpers import text_content
|
|
16
|
+
from fast_agent.tools.elicitation import get_elicitation_fastmcp_tool
|
|
17
|
+
from fast_agent.types import PromptMessageExtended, RequestParams
|
|
18
|
+
from fast_agent.types.llm_stop_reason import LlmStopReason
|
|
19
|
+
|
|
20
|
+
logger = get_logger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ToolAgent(LlmAgent):
|
|
24
|
+
"""
|
|
25
|
+
A Tool Calling agent that uses FastMCP Tools for execution.
|
|
26
|
+
|
|
27
|
+
Pass either:
|
|
28
|
+
- FastMCP Tool objects (created via Tool.from_function)
|
|
29
|
+
- Regular Python functions (will be wrapped as FastMCP Tools)
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
config: AgentConfig,
|
|
35
|
+
tools: Sequence[FastMCPTool | Callable] = [],
|
|
36
|
+
context: Context | None = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
super().__init__(config=config, context=context)
|
|
39
|
+
|
|
40
|
+
self._execution_tools: dict[str, FastMCPTool] = {}
|
|
41
|
+
self._tool_schemas: list[Tool] = []
|
|
42
|
+
|
|
43
|
+
# Build a working list of tools and auto-inject human-input tool if missing
|
|
44
|
+
working_tools: list[FastMCPTool | Callable] = list(tools) if tools else []
|
|
45
|
+
# Only auto-inject if enabled via AgentConfig
|
|
46
|
+
if self.config.human_input:
|
|
47
|
+
existing_names = {
|
|
48
|
+
t.name if isinstance(t, FastMCPTool) else getattr(t, "__name__", "")
|
|
49
|
+
for t in working_tools
|
|
50
|
+
}
|
|
51
|
+
if HUMAN_INPUT_TOOL_NAME not in existing_names:
|
|
52
|
+
try:
|
|
53
|
+
working_tools.append(get_elicitation_fastmcp_tool())
|
|
54
|
+
except Exception as e:
|
|
55
|
+
logger.warning(f"Failed to initialize human-input tool: {e}")
|
|
56
|
+
|
|
57
|
+
for tool in working_tools:
|
|
58
|
+
(tool)
|
|
59
|
+
if isinstance(tool, FastMCPTool):
|
|
60
|
+
fast_tool = tool
|
|
61
|
+
elif callable(tool):
|
|
62
|
+
fast_tool = FastMCPTool.from_function(tool)
|
|
63
|
+
else:
|
|
64
|
+
logger.warning(f"Skipping unknown tool type: {type(tool)}")
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
self._execution_tools[fast_tool.name] = fast_tool
|
|
68
|
+
# Create MCP Tool schema for the LLM interface
|
|
69
|
+
self._tool_schemas.append(
|
|
70
|
+
Tool(
|
|
71
|
+
name=fast_tool.name,
|
|
72
|
+
description=fast_tool.description,
|
|
73
|
+
inputSchema=fast_tool.parameters,
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
async def generate_impl(
|
|
78
|
+
self,
|
|
79
|
+
messages: List[PromptMessageExtended],
|
|
80
|
+
request_params: RequestParams | None = None,
|
|
81
|
+
tools: List[Tool] | None = None,
|
|
82
|
+
) -> PromptMessageExtended:
|
|
83
|
+
"""
|
|
84
|
+
Generate a response using the LLM, and handle tool calls if necessary.
|
|
85
|
+
Messages are already normalized to List[PromptMessageExtended].
|
|
86
|
+
"""
|
|
87
|
+
if tools is None:
|
|
88
|
+
tools = (await self.list_tools()).tools
|
|
89
|
+
|
|
90
|
+
iterations = 0
|
|
91
|
+
max_iterations = request_params.max_iterations if request_params else DEFAULT_MAX_ITERATIONS
|
|
92
|
+
|
|
93
|
+
while True:
|
|
94
|
+
result = await super().generate_impl(
|
|
95
|
+
messages,
|
|
96
|
+
request_params=request_params,
|
|
97
|
+
tools=tools,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if LlmStopReason.TOOL_USE == result.stop_reason:
|
|
101
|
+
tool_message = await self.run_tools(result)
|
|
102
|
+
error_channel_messages = (tool_message.channels or {}).get(FAST_AGENT_ERROR_CHANNEL)
|
|
103
|
+
if error_channel_messages:
|
|
104
|
+
tool_result_contents = [
|
|
105
|
+
content
|
|
106
|
+
for tool_result in (tool_message.tool_results or {}).values()
|
|
107
|
+
for content in tool_result.content
|
|
108
|
+
]
|
|
109
|
+
if tool_result_contents:
|
|
110
|
+
if result.content is None:
|
|
111
|
+
result.content = []
|
|
112
|
+
result.content.extend(tool_result_contents)
|
|
113
|
+
result.stop_reason = LlmStopReason.ERROR
|
|
114
|
+
break
|
|
115
|
+
if self.config.use_history:
|
|
116
|
+
messages = [tool_message]
|
|
117
|
+
else:
|
|
118
|
+
messages.extend([result, tool_message])
|
|
119
|
+
else:
|
|
120
|
+
break
|
|
121
|
+
|
|
122
|
+
iterations += 1
|
|
123
|
+
if iterations > max_iterations:
|
|
124
|
+
logger.warning("Max iterations reached, stopping tool loop")
|
|
125
|
+
break
|
|
126
|
+
return result
|
|
127
|
+
|
|
128
|
+
# we take care of tool results, so skip displaying them
|
|
129
|
+
def show_user_message(self, message: PromptMessageExtended) -> None:
|
|
130
|
+
if message.tool_results:
|
|
131
|
+
return
|
|
132
|
+
super().show_user_message(message)
|
|
133
|
+
|
|
134
|
+
async def run_tools(self, request: PromptMessageExtended) -> PromptMessageExtended:
|
|
135
|
+
"""Runs the tools in the request, and returns a new User message with the results"""
|
|
136
|
+
import time
|
|
137
|
+
|
|
138
|
+
if not request.tool_calls:
|
|
139
|
+
logger.warning("No tool calls found in request", data=request)
|
|
140
|
+
return PromptMessageExtended(role="user", tool_results={})
|
|
141
|
+
|
|
142
|
+
tool_results: dict[str, CallToolResult] = {}
|
|
143
|
+
tool_timings: dict[str, float] = {} # Track timing for each tool call
|
|
144
|
+
tool_loop_error: str | None = None
|
|
145
|
+
# TODO -- use gather() for parallel results, update display
|
|
146
|
+
tool_schemas = (await self.list_tools()).tools
|
|
147
|
+
available_tools = [t.name for t in tool_schemas]
|
|
148
|
+
for correlation_id, tool_request in request.tool_calls.items():
|
|
149
|
+
tool_name = tool_request.params.name
|
|
150
|
+
tool_args = tool_request.params.arguments or {}
|
|
151
|
+
|
|
152
|
+
if tool_name not in available_tools and tool_name not in self._execution_tools:
|
|
153
|
+
error_message = f"Tool '{tool_name}' is not available"
|
|
154
|
+
logger.error(error_message)
|
|
155
|
+
tool_loop_error = self._mark_tool_loop_error(
|
|
156
|
+
correlation_id=correlation_id,
|
|
157
|
+
error_message=error_message,
|
|
158
|
+
tool_results=tool_results,
|
|
159
|
+
)
|
|
160
|
+
break
|
|
161
|
+
|
|
162
|
+
# Find the index of the current tool in available_tools for highlighting
|
|
163
|
+
highlight_index = None
|
|
164
|
+
try:
|
|
165
|
+
highlight_index = available_tools.index(tool_name)
|
|
166
|
+
except ValueError:
|
|
167
|
+
# Tool not found in list, no highlighting
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
self.display.show_tool_call(
|
|
171
|
+
name=self.name,
|
|
172
|
+
tool_args=tool_args,
|
|
173
|
+
bottom_items=available_tools,
|
|
174
|
+
tool_name=tool_name,
|
|
175
|
+
highlight_index=highlight_index,
|
|
176
|
+
max_item_length=12,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Track timing for tool execution
|
|
180
|
+
start_time = time.perf_counter()
|
|
181
|
+
result = await self.call_tool(tool_name, tool_args)
|
|
182
|
+
end_time = time.perf_counter()
|
|
183
|
+
duration_ms = round((end_time - start_time) * 1000, 2)
|
|
184
|
+
|
|
185
|
+
tool_results[correlation_id] = result
|
|
186
|
+
# Store timing info (transport_channel not available for local tools)
|
|
187
|
+
tool_timings[correlation_id] = {
|
|
188
|
+
"timing_ms": duration_ms,
|
|
189
|
+
"transport_channel": None
|
|
190
|
+
}
|
|
191
|
+
self.display.show_tool_result(name=self.name, result=result, tool_name=tool_name, timing_ms=duration_ms)
|
|
192
|
+
|
|
193
|
+
return self._finalize_tool_results(tool_results, tool_timings=tool_timings, tool_loop_error=tool_loop_error)
|
|
194
|
+
|
|
195
|
+
def _mark_tool_loop_error(
|
|
196
|
+
self,
|
|
197
|
+
*,
|
|
198
|
+
correlation_id: str,
|
|
199
|
+
error_message: str,
|
|
200
|
+
tool_results: dict[str, CallToolResult],
|
|
201
|
+
) -> str:
|
|
202
|
+
error_result = CallToolResult(
|
|
203
|
+
content=[text_content(error_message)],
|
|
204
|
+
isError=True,
|
|
205
|
+
)
|
|
206
|
+
tool_results[correlation_id] = error_result
|
|
207
|
+
self.display.show_tool_result(name=self.name, result=error_result)
|
|
208
|
+
return error_message
|
|
209
|
+
|
|
210
|
+
def _finalize_tool_results(
|
|
211
|
+
self,
|
|
212
|
+
tool_results: dict[str, CallToolResult],
|
|
213
|
+
*,
|
|
214
|
+
tool_timings: dict[str, dict[str, float | str | None]] | None = None,
|
|
215
|
+
tool_loop_error: str | None = None,
|
|
216
|
+
) -> PromptMessageExtended:
|
|
217
|
+
import json
|
|
218
|
+
|
|
219
|
+
from mcp.types import TextContent
|
|
220
|
+
|
|
221
|
+
from fast_agent.constants import FAST_AGENT_TOOL_TIMING
|
|
222
|
+
|
|
223
|
+
channels = None
|
|
224
|
+
content = []
|
|
225
|
+
if tool_loop_error:
|
|
226
|
+
content.append(text_content(tool_loop_error))
|
|
227
|
+
channels = {
|
|
228
|
+
FAST_AGENT_ERROR_CHANNEL: [text_content(tool_loop_error)],
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
# Add tool timing data to channels
|
|
232
|
+
if tool_timings:
|
|
233
|
+
if channels is None:
|
|
234
|
+
channels = {}
|
|
235
|
+
channels[FAST_AGENT_TOOL_TIMING] = [
|
|
236
|
+
TextContent(type="text", text=json.dumps(tool_timings))
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
return PromptMessageExtended(
|
|
240
|
+
role="user",
|
|
241
|
+
content=content,
|
|
242
|
+
tool_results=tool_results,
|
|
243
|
+
channels=channels,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
async def list_tools(self) -> ListToolsResult:
|
|
247
|
+
"""Return available tools for this agent. Overridable by subclasses."""
|
|
248
|
+
return ListToolsResult(tools=list(self._tool_schemas))
|
|
249
|
+
|
|
250
|
+
async def call_tool(self, name: str, arguments: Dict[str, Any] | None = None) -> CallToolResult:
|
|
251
|
+
"""Execute a tool by name using local FastMCP tools. Overridable by subclasses."""
|
|
252
|
+
fast_tool = self._execution_tools.get(name)
|
|
253
|
+
if not fast_tool:
|
|
254
|
+
logger.warning(f"Unknown tool: {name}")
|
|
255
|
+
return CallToolResult(
|
|
256
|
+
content=[text_content(f"Unknown tool: {name}")],
|
|
257
|
+
isError=True,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
result = await fast_tool.run(arguments or {}, convert_result=False)
|
|
262
|
+
return CallToolResult(
|
|
263
|
+
content=[text_content(str(result))],
|
|
264
|
+
isError=False,
|
|
265
|
+
)
|
|
266
|
+
except Exception as e:
|
|
267
|
+
logger.error(f"Tool {name} failed: {e}")
|
|
268
|
+
return CallToolResult(
|
|
269
|
+
content=[text_content(f"Error: {str(e)}")],
|
|
270
|
+
isError=True,
|
|
271
|
+
)
|