fast-agent-mcp 0.2.58__py3-none-any.whl → 0.3.1__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 +127 -0
- fast_agent/agents/__init__.py +36 -0
- {mcp_agent/core → fast_agent/agents}/agent_types.py +2 -1
- fast_agent/agents/llm_agent.py +217 -0
- fast_agent/agents/llm_decorator.py +486 -0
- mcp_agent/agents/base_agent.py → fast_agent/agents/mcp_agent.py +377 -385
- fast_agent/agents/tool_agent.py +168 -0
- {mcp_agent → fast_agent}/agents/workflow/chain_agent.py +43 -33
- {mcp_agent → fast_agent}/agents/workflow/evaluator_optimizer.py +31 -35
- {mcp_agent → fast_agent}/agents/workflow/iterative_planner.py +56 -47
- {mcp_agent → fast_agent}/agents/workflow/orchestrator_models.py +4 -4
- {mcp_agent → fast_agent}/agents/workflow/parallel_agent.py +34 -41
- {mcp_agent → fast_agent}/agents/workflow/router_agent.py +54 -39
- {mcp_agent → fast_agent}/cli/__main__.py +5 -3
- {mcp_agent → fast_agent}/cli/commands/check_config.py +95 -66
- {mcp_agent → fast_agent}/cli/commands/go.py +20 -11
- {mcp_agent → fast_agent}/cli/commands/quickstart.py +4 -4
- {mcp_agent → fast_agent}/cli/commands/server_helpers.py +1 -1
- {mcp_agent → fast_agent}/cli/commands/setup.py +75 -134
- {mcp_agent → fast_agent}/cli/commands/url_parser.py +9 -8
- {mcp_agent → fast_agent}/cli/main.py +36 -16
- {mcp_agent → fast_agent}/cli/terminal.py +2 -2
- {mcp_agent → fast_agent}/config.py +10 -2
- fast_agent/constants.py +8 -0
- {mcp_agent → fast_agent}/context.py +24 -19
- {mcp_agent → fast_agent}/context_dependent.py +9 -5
- fast_agent/core/__init__.py +52 -0
- {mcp_agent → fast_agent}/core/agent_app.py +39 -36
- fast_agent/core/core_app.py +135 -0
- {mcp_agent → fast_agent}/core/direct_decorators.py +12 -26
- {mcp_agent → fast_agent}/core/direct_factory.py +95 -73
- {mcp_agent → fast_agent/core}/executor/executor.py +4 -5
- {mcp_agent → fast_agent}/core/fastagent.py +32 -32
- fast_agent/core/logging/__init__.py +5 -0
- {mcp_agent → fast_agent/core}/logging/events.py +3 -3
- {mcp_agent → fast_agent/core}/logging/json_serializer.py +1 -1
- {mcp_agent → fast_agent/core}/logging/listeners.py +85 -7
- {mcp_agent → fast_agent/core}/logging/logger.py +7 -7
- {mcp_agent → fast_agent/core}/logging/transport.py +10 -11
- fast_agent/core/prompt.py +9 -0
- {mcp_agent → fast_agent}/core/validation.py +4 -4
- fast_agent/event_progress.py +61 -0
- fast_agent/history/history_exporter.py +44 -0
- {mcp_agent → fast_agent}/human_input/__init__.py +9 -12
- {mcp_agent → fast_agent}/human_input/elicitation_handler.py +26 -8
- {mcp_agent → fast_agent}/human_input/elicitation_state.py +7 -7
- {mcp_agent → fast_agent}/human_input/simple_form.py +6 -4
- {mcp_agent → fast_agent}/human_input/types.py +1 -18
- fast_agent/interfaces.py +228 -0
- fast_agent/llm/__init__.py +9 -0
- mcp_agent/llm/augmented_llm.py → fast_agent/llm/fastagent_llm.py +127 -218
- fast_agent/llm/internal/passthrough.py +137 -0
- mcp_agent/llm/augmented_llm_playback.py → fast_agent/llm/internal/playback.py +29 -25
- mcp_agent/llm/augmented_llm_silent.py → fast_agent/llm/internal/silent.py +10 -17
- fast_agent/llm/internal/slow.py +38 -0
- {mcp_agent → fast_agent}/llm/memory.py +40 -30
- {mcp_agent → fast_agent}/llm/model_database.py +35 -2
- {mcp_agent → fast_agent}/llm/model_factory.py +103 -77
- fast_agent/llm/model_info.py +126 -0
- {mcp_agent/llm/providers → fast_agent/llm/provider/anthropic}/anthropic_utils.py +7 -7
- fast_agent/llm/provider/anthropic/llm_anthropic.py +603 -0
- {mcp_agent/llm/providers → fast_agent/llm/provider/anthropic}/multipart_converter_anthropic.py +79 -86
- {mcp_agent/llm/providers → fast_agent/llm/provider/bedrock}/bedrock_utils.py +3 -1
- mcp_agent/llm/providers/augmented_llm_bedrock.py → fast_agent/llm/provider/bedrock/llm_bedrock.py +833 -717
- {mcp_agent/llm/providers → fast_agent/llm/provider/google}/google_converter.py +66 -14
- fast_agent/llm/provider/google/llm_google_native.py +431 -0
- mcp_agent/llm/providers/augmented_llm_aliyun.py → fast_agent/llm/provider/openai/llm_aliyun.py +6 -7
- mcp_agent/llm/providers/augmented_llm_azure.py → fast_agent/llm/provider/openai/llm_azure.py +4 -4
- mcp_agent/llm/providers/augmented_llm_deepseek.py → fast_agent/llm/provider/openai/llm_deepseek.py +10 -11
- mcp_agent/llm/providers/augmented_llm_generic.py → fast_agent/llm/provider/openai/llm_generic.py +4 -4
- mcp_agent/llm/providers/augmented_llm_google_oai.py → fast_agent/llm/provider/openai/llm_google_oai.py +4 -4
- mcp_agent/llm/providers/augmented_llm_groq.py → fast_agent/llm/provider/openai/llm_groq.py +14 -16
- mcp_agent/llm/providers/augmented_llm_openai.py → fast_agent/llm/provider/openai/llm_openai.py +133 -207
- mcp_agent/llm/providers/augmented_llm_openrouter.py → fast_agent/llm/provider/openai/llm_openrouter.py +6 -6
- mcp_agent/llm/providers/augmented_llm_tensorzero_openai.py → fast_agent/llm/provider/openai/llm_tensorzero_openai.py +17 -16
- mcp_agent/llm/providers/augmented_llm_xai.py → fast_agent/llm/provider/openai/llm_xai.py +6 -6
- {mcp_agent/llm/providers → fast_agent/llm/provider/openai}/multipart_converter_openai.py +125 -63
- {mcp_agent/llm/providers → fast_agent/llm/provider/openai}/openai_multipart.py +12 -12
- {mcp_agent/llm/providers → fast_agent/llm/provider/openai}/openai_utils.py +18 -16
- {mcp_agent → fast_agent}/llm/provider_key_manager.py +2 -2
- {mcp_agent → fast_agent}/llm/provider_types.py +2 -0
- {mcp_agent → fast_agent}/llm/sampling_converter.py +15 -12
- {mcp_agent → fast_agent}/llm/usage_tracking.py +23 -5
- fast_agent/mcp/__init__.py +54 -0
- {mcp_agent → fast_agent}/mcp/elicitation_factory.py +3 -3
- {mcp_agent → fast_agent}/mcp/elicitation_handlers.py +19 -10
- {mcp_agent → fast_agent}/mcp/gen_client.py +3 -3
- fast_agent/mcp/helpers/__init__.py +36 -0
- fast_agent/mcp/helpers/content_helpers.py +183 -0
- {mcp_agent → fast_agent}/mcp/helpers/server_config_helpers.py +8 -8
- {mcp_agent → fast_agent}/mcp/hf_auth.py +25 -23
- fast_agent/mcp/interfaces.py +93 -0
- {mcp_agent → fast_agent}/mcp/logger_textio.py +4 -4
- {mcp_agent → fast_agent}/mcp/mcp_agent_client_session.py +49 -44
- {mcp_agent → fast_agent}/mcp/mcp_aggregator.py +66 -115
- {mcp_agent → fast_agent}/mcp/mcp_connection_manager.py +16 -23
- {mcp_agent/core → fast_agent/mcp}/mcp_content.py +23 -15
- {mcp_agent → fast_agent}/mcp/mime_utils.py +39 -0
- fast_agent/mcp/prompt.py +159 -0
- mcp_agent/mcp/prompt_message_multipart.py → fast_agent/mcp/prompt_message_extended.py +27 -20
- {mcp_agent → fast_agent}/mcp/prompt_render.py +21 -19
- {mcp_agent → fast_agent}/mcp/prompt_serialization.py +46 -46
- fast_agent/mcp/prompts/__main__.py +7 -0
- {mcp_agent → fast_agent}/mcp/prompts/prompt_helpers.py +31 -30
- {mcp_agent → fast_agent}/mcp/prompts/prompt_load.py +8 -8
- {mcp_agent → fast_agent}/mcp/prompts/prompt_server.py +11 -19
- {mcp_agent → fast_agent}/mcp/prompts/prompt_template.py +18 -18
- {mcp_agent → fast_agent}/mcp/resource_utils.py +1 -1
- {mcp_agent → fast_agent}/mcp/sampling.py +31 -26
- {mcp_agent/mcp_server → fast_agent/mcp/server}/__init__.py +1 -1
- {mcp_agent/mcp_server → fast_agent/mcp/server}/agent_server.py +5 -6
- fast_agent/mcp/ui_agent.py +48 -0
- fast_agent/mcp/ui_mixin.py +209 -0
- fast_agent/mcp_server_registry.py +90 -0
- {mcp_agent → fast_agent}/resources/examples/data-analysis/analysis-campaign.py +5 -4
- {mcp_agent → fast_agent}/resources/examples/data-analysis/analysis.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/forms_demo.py +3 -3
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/game_character.py +2 -2
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/game_character_handler.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/tool_call.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/agent_one.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/agent_two.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/researcher/researcher-eval.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/researcher/researcher-imp.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/researcher/researcher.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/tensorzero/agent.py +2 -2
- {mcp_agent → fast_agent}/resources/examples/tensorzero/image_demo.py +3 -3
- {mcp_agent → fast_agent}/resources/examples/tensorzero/simple_agent.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/workflows/chaining.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/workflows/evaluator.py +3 -3
- {mcp_agent → fast_agent}/resources/examples/workflows/human_input.py +5 -3
- {mcp_agent → fast_agent}/resources/examples/workflows/orchestrator.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/workflows/parallel.py +2 -2
- {mcp_agent → fast_agent}/resources/examples/workflows/router.py +5 -2
- fast_agent/resources/setup/.gitignore +24 -0
- fast_agent/resources/setup/agent.py +18 -0
- fast_agent/resources/setup/fastagent.config.yaml +44 -0
- fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
- fast_agent/resources/setup/pyproject.toml.tmpl +17 -0
- fast_agent/tools/elicitation.py +369 -0
- fast_agent/types/__init__.py +32 -0
- fast_agent/types/llm_stop_reason.py +77 -0
- fast_agent/ui/__init__.py +38 -0
- fast_agent/ui/console_display.py +1005 -0
- {mcp_agent/human_input → fast_agent/ui}/elicitation_form.py +17 -12
- mcp_agent/human_input/elicitation_forms.py → fast_agent/ui/elicitation_style.py +1 -1
- {mcp_agent/core → fast_agent/ui}/enhanced_prompt.py +96 -25
- {mcp_agent/core → fast_agent/ui}/interactive_prompt.py +330 -125
- fast_agent/ui/mcp_ui_utils.py +224 -0
- {mcp_agent → fast_agent/ui}/progress_display.py +2 -2
- {mcp_agent/logging → fast_agent/ui}/rich_progress.py +4 -4
- {mcp_agent/core → fast_agent/ui}/usage_display.py +3 -8
- {fast_agent_mcp-0.2.58.dist-info → fast_agent_mcp-0.3.1.dist-info}/METADATA +7 -7
- fast_agent_mcp-0.3.1.dist-info/RECORD +203 -0
- fast_agent_mcp-0.3.1.dist-info/entry_points.txt +5 -0
- fast_agent_mcp-0.2.58.dist-info/RECORD +0 -193
- fast_agent_mcp-0.2.58.dist-info/entry_points.txt +0 -6
- mcp_agent/__init__.py +0 -114
- mcp_agent/agents/agent.py +0 -92
- mcp_agent/agents/workflow/__init__.py +0 -1
- mcp_agent/agents/workflow/orchestrator_agent.py +0 -597
- mcp_agent/app.py +0 -175
- mcp_agent/core/__init__.py +0 -26
- mcp_agent/core/prompt.py +0 -191
- mcp_agent/event_progress.py +0 -134
- mcp_agent/human_input/handler.py +0 -81
- mcp_agent/llm/__init__.py +0 -2
- mcp_agent/llm/augmented_llm_passthrough.py +0 -232
- mcp_agent/llm/augmented_llm_slow.py +0 -53
- mcp_agent/llm/providers/__init__.py +0 -8
- mcp_agent/llm/providers/augmented_llm_anthropic.py +0 -718
- mcp_agent/llm/providers/augmented_llm_google_native.py +0 -496
- mcp_agent/llm/providers/sampling_converter_anthropic.py +0 -57
- mcp_agent/llm/providers/sampling_converter_openai.py +0 -26
- mcp_agent/llm/sampling_format_converter.py +0 -37
- mcp_agent/logging/__init__.py +0 -0
- mcp_agent/mcp/__init__.py +0 -50
- mcp_agent/mcp/helpers/__init__.py +0 -25
- mcp_agent/mcp/helpers/content_helpers.py +0 -187
- mcp_agent/mcp/interfaces.py +0 -266
- mcp_agent/mcp/prompts/__init__.py +0 -0
- mcp_agent/mcp/prompts/__main__.py +0 -10
- mcp_agent/mcp_server_registry.py +0 -343
- mcp_agent/tools/tool_definition.py +0 -14
- mcp_agent/ui/console_display.py +0 -790
- mcp_agent/ui/console_display_legacy.py +0 -401
- {mcp_agent → fast_agent}/agents/workflow/orchestrator_prompts.py +0 -0
- {mcp_agent/agents → fast_agent/cli}/__init__.py +0 -0
- {mcp_agent → fast_agent}/cli/constants.py +0 -0
- {mcp_agent → fast_agent}/core/error_handling.py +0 -0
- {mcp_agent → fast_agent}/core/exceptions.py +0 -0
- {mcp_agent/cli → fast_agent/core/executor}/__init__.py +0 -0
- {mcp_agent → fast_agent/core}/executor/task_registry.py +0 -0
- {mcp_agent → fast_agent/core}/executor/workflow_signal.py +0 -0
- {mcp_agent → fast_agent}/human_input/form_fields.py +0 -0
- {mcp_agent → fast_agent}/llm/prompt_utils.py +0 -0
- {mcp_agent/core → fast_agent/llm}/request_params.py +0 -0
- {mcp_agent → fast_agent}/mcp/common.py +0 -0
- {mcp_agent/executor → fast_agent/mcp/prompts}/__init__.py +0 -0
- {mcp_agent → fast_agent}/mcp/prompts/prompt_constants.py +0 -0
- {mcp_agent → fast_agent}/py.typed +0 -0
- {mcp_agent → fast_agent}/resources/examples/data-analysis/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/elicitation_account_server.py +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/elicitation_forms_server.py +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/elicitation_game_server.py +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +0 -0
- {mcp_agent → fast_agent}/resources/examples/researcher/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/.env.sample +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/Makefile +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/README.md +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/demo_images/crab.png +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/docker-compose.yml +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/Dockerfile +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/entrypoint.sh +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/mcp_server.py +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/pyproject.toml +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/tensorzero_config/system_schema.json +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +0 -0
- {mcp_agent → fast_agent}/resources/examples/workflows/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/workflows/graded_report.md +0 -0
- {mcp_agent → fast_agent}/resources/examples/workflows/short_story.md +0 -0
- {mcp_agent → fast_agent}/resources/examples/workflows/short_story.txt +0 -0
- {mcp_agent → fast_agent/ui}/console.py +0 -0
- {mcp_agent/core → fast_agent/ui}/mermaid_utils.py +0 -0
- {fast_agent_mcp-0.2.58.dist-info → fast_agent_mcp-0.3.1.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.2.58.dist-info → fast_agent_mcp-0.3.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -12,6 +12,7 @@ from typing import Any, List, Literal, Optional, Union
|
|
|
12
12
|
from mcp.types import (
|
|
13
13
|
Annotations,
|
|
14
14
|
BlobResourceContents,
|
|
15
|
+
ContentBlock,
|
|
15
16
|
EmbeddedResource,
|
|
16
17
|
ImageContent,
|
|
17
18
|
ReadResourceResult,
|
|
@@ -21,15 +22,12 @@ from mcp.types import (
|
|
|
21
22
|
)
|
|
22
23
|
from pydantic import AnyUrl
|
|
23
24
|
|
|
24
|
-
from
|
|
25
|
+
from fast_agent.mcp.mime_utils import (
|
|
25
26
|
guess_mime_type,
|
|
26
27
|
is_binary_content,
|
|
27
28
|
is_image_mime_type,
|
|
28
29
|
)
|
|
29
30
|
|
|
30
|
-
# Type for all MCP content types
|
|
31
|
-
MCPContentType = Union[TextContent, ImageContent, EmbeddedResource, ResourceContents]
|
|
32
|
-
|
|
33
31
|
|
|
34
32
|
def MCPText(
|
|
35
33
|
text: str,
|
|
@@ -152,8 +150,8 @@ def MCPFile(
|
|
|
152
150
|
|
|
153
151
|
|
|
154
152
|
def MCPPrompt(
|
|
155
|
-
*content_items: Union[dict, str, Path, bytes,
|
|
156
|
-
role: Literal["user", "assistant"] = "user"
|
|
153
|
+
*content_items: Union[dict, str, Path, bytes, ContentBlock, ReadResourceResult],
|
|
154
|
+
role: Literal["user", "assistant"] = "user",
|
|
157
155
|
) -> List[dict]:
|
|
158
156
|
"""
|
|
159
157
|
Create one or more prompt messages with various content types.
|
|
@@ -209,19 +207,25 @@ def MCPPrompt(
|
|
|
209
207
|
elif isinstance(item, EmbeddedResource):
|
|
210
208
|
# Already an EmbeddedResource, wrap in a message
|
|
211
209
|
result.append({"role": role, "content": item})
|
|
212
|
-
elif hasattr(item,
|
|
210
|
+
elif hasattr(item, "type") and item.type == "resource" and hasattr(item, "resource"):
|
|
213
211
|
# Looks like an EmbeddedResource but may not be the exact class
|
|
214
|
-
result.append(
|
|
212
|
+
result.append(
|
|
213
|
+
{"role": role, "content": EmbeddedResource(type="resource", resource=item.resource)}
|
|
214
|
+
)
|
|
215
215
|
elif isinstance(item, ResourceContents):
|
|
216
216
|
# It's a ResourceContents, wrap it in an EmbeddedResource
|
|
217
|
-
result.append(
|
|
217
|
+
result.append(
|
|
218
|
+
{"role": role, "content": EmbeddedResource(type="resource", resource=item)}
|
|
219
|
+
)
|
|
218
220
|
elif isinstance(item, ReadResourceResult):
|
|
219
221
|
# It's a ReadResourceResult, convert each resource content
|
|
220
222
|
for resource_content in item.contents:
|
|
221
|
-
result.append(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
223
|
+
result.append(
|
|
224
|
+
{
|
|
225
|
+
"role": role,
|
|
226
|
+
"content": EmbeddedResource(type="resource", resource=resource_content),
|
|
227
|
+
}
|
|
228
|
+
)
|
|
225
229
|
else:
|
|
226
230
|
# Try to convert to string
|
|
227
231
|
result.append(MCPText(str(item), role=role))
|
|
@@ -229,12 +233,16 @@ def MCPPrompt(
|
|
|
229
233
|
return result
|
|
230
234
|
|
|
231
235
|
|
|
232
|
-
def User(
|
|
236
|
+
def User(
|
|
237
|
+
*content_items: Union[dict, str, Path, bytes, ContentBlock, ReadResourceResult],
|
|
238
|
+
) -> List[dict]:
|
|
233
239
|
"""Create user message(s) with various content types."""
|
|
234
240
|
return MCPPrompt(*content_items, role="user")
|
|
235
241
|
|
|
236
242
|
|
|
237
|
-
def Assistant(
|
|
243
|
+
def Assistant(
|
|
244
|
+
*content_items: Union[dict, str, Path, bytes, ContentBlock, ReadResourceResult],
|
|
245
|
+
) -> List[dict]:
|
|
238
246
|
"""Create assistant message(s) with various content types."""
|
|
239
247
|
return MCPPrompt(*content_items, role="assistant")
|
|
240
248
|
|
|
@@ -67,3 +67,42 @@ def is_binary_content(mime_type: str) -> bool:
|
|
|
67
67
|
def is_image_mime_type(mime_type: str) -> bool:
|
|
68
68
|
"""Check if a MIME type represents an image."""
|
|
69
69
|
return mime_type.startswith("image/") and mime_type != "image/svg+xml"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# Common alias mapping and normalization helpers
|
|
73
|
+
_MIME_ALIASES = {
|
|
74
|
+
# Friendly or non-standard labels
|
|
75
|
+
"document/pdf": "application/pdf",
|
|
76
|
+
"image/jpg": "image/jpeg",
|
|
77
|
+
# Some providers sometimes return these variants
|
|
78
|
+
"application/x-pdf": "application/pdf",
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def normalize_mime_type(mime: str | None) -> str | None:
|
|
83
|
+
"""
|
|
84
|
+
Normalize a MIME-like string to a canonical MIME type.
|
|
85
|
+
|
|
86
|
+
- Lowercases and trims
|
|
87
|
+
- Resolves common aliases (e.g. image/jpg -> image/jpeg, document/pdf -> application/pdf)
|
|
88
|
+
- If input looks like a bare extension (e.g. "pdf", "png"), map via mimetypes
|
|
89
|
+
- Returns None for falsy inputs
|
|
90
|
+
"""
|
|
91
|
+
if not mime:
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
m = mime.strip().lower()
|
|
95
|
+
|
|
96
|
+
# If it's an alias we know about
|
|
97
|
+
if m in _MIME_ALIASES:
|
|
98
|
+
return _MIME_ALIASES[m]
|
|
99
|
+
|
|
100
|
+
# If it already looks like a full MIME type
|
|
101
|
+
if "/" in m:
|
|
102
|
+
# image/jpg -> image/jpeg etc.
|
|
103
|
+
return _MIME_ALIASES.get(m, m)
|
|
104
|
+
|
|
105
|
+
# Treat as a bare file extension (e.g. "pdf", "png")
|
|
106
|
+
if not m.startswith("."):
|
|
107
|
+
m = "." + m
|
|
108
|
+
return mimetypes.types_map.get(m, None)
|
fast_agent/mcp/prompt.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Prompt class for easily creating and working with MCP prompt content.
|
|
3
|
+
|
|
4
|
+
This implementation lives in the fast_agent namespace as part of the
|
|
5
|
+
migration away from fast_agent. A compatibility shim remains at
|
|
6
|
+
fast_agent.core.prompt importing this Prompt.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, List, Literal, Union
|
|
11
|
+
|
|
12
|
+
from mcp import CallToolRequest
|
|
13
|
+
from mcp.types import ContentBlock, PromptMessage
|
|
14
|
+
|
|
15
|
+
from fast_agent.mcp.mcp_content import Assistant, MCPPrompt, User
|
|
16
|
+
from fast_agent.types import LlmStopReason, PromptMessageExtended
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Prompt:
|
|
20
|
+
"""
|
|
21
|
+
A helper class for working with MCP prompt content.
|
|
22
|
+
|
|
23
|
+
This class provides static methods to create:
|
|
24
|
+
- PromptMessage instances
|
|
25
|
+
- PromptMessageExtended instances
|
|
26
|
+
- Lists of messages for conversations
|
|
27
|
+
|
|
28
|
+
All methods intelligently handle various content types:
|
|
29
|
+
- Strings become TextContent
|
|
30
|
+
- Image file paths become ImageContent
|
|
31
|
+
- Other file paths become EmbeddedResource
|
|
32
|
+
- TextContent objects are used directly
|
|
33
|
+
- ImageContent objects are used directly
|
|
34
|
+
- EmbeddedResource objects are used directly
|
|
35
|
+
- Pre-formatted messages pass through unchanged
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def user(
|
|
40
|
+
cls,
|
|
41
|
+
*content_items: Union[
|
|
42
|
+
str, Path, bytes, dict, ContentBlock, PromptMessage, PromptMessageExtended
|
|
43
|
+
],
|
|
44
|
+
) -> PromptMessageExtended:
|
|
45
|
+
"""
|
|
46
|
+
Create a user PromptMessageExtended with various content items.
|
|
47
|
+
"""
|
|
48
|
+
# Handle PromptMessage and PromptMessageExtended directly
|
|
49
|
+
if len(content_items) == 1:
|
|
50
|
+
item = content_items[0]
|
|
51
|
+
if isinstance(item, PromptMessage):
|
|
52
|
+
return PromptMessageExtended(role="user", content=[item.content])
|
|
53
|
+
elif isinstance(item, PromptMessageExtended):
|
|
54
|
+
# Keep the content but change role to user
|
|
55
|
+
return PromptMessageExtended(role="user", content=item.content)
|
|
56
|
+
|
|
57
|
+
# Use the content factory for other types
|
|
58
|
+
messages = User(*content_items)
|
|
59
|
+
return PromptMessageExtended(role="user", content=[msg["content"] for msg in messages])
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def assistant(
|
|
63
|
+
cls,
|
|
64
|
+
*content_items: Union[
|
|
65
|
+
str, Path, bytes, dict, ContentBlock, PromptMessage, PromptMessageExtended
|
|
66
|
+
],
|
|
67
|
+
stop_reason: LlmStopReason | None = None,
|
|
68
|
+
tool_calls: Dict[str, CallToolRequest] | None = None,
|
|
69
|
+
) -> PromptMessageExtended:
|
|
70
|
+
"""
|
|
71
|
+
Create an assistant PromptMessageExtended with various content items.
|
|
72
|
+
"""
|
|
73
|
+
# Handle PromptMessage and PromptMessageExtended directly
|
|
74
|
+
if len(content_items) == 1:
|
|
75
|
+
item = content_items[0]
|
|
76
|
+
if isinstance(item, PromptMessage):
|
|
77
|
+
return PromptMessageExtended(
|
|
78
|
+
role="assistant",
|
|
79
|
+
content=[item.content],
|
|
80
|
+
stop_reason=stop_reason,
|
|
81
|
+
tool_calls=tool_calls,
|
|
82
|
+
)
|
|
83
|
+
elif isinstance(item, PromptMessageExtended):
|
|
84
|
+
# Keep the content but change role to assistant
|
|
85
|
+
return PromptMessageExtended(
|
|
86
|
+
role="assistant",
|
|
87
|
+
content=item.content,
|
|
88
|
+
stop_reason=stop_reason,
|
|
89
|
+
tool_calls=tool_calls,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Use the content factory for other types
|
|
93
|
+
messages = Assistant(*content_items)
|
|
94
|
+
return PromptMessageExtended(
|
|
95
|
+
role="assistant",
|
|
96
|
+
content=[msg["content"] for msg in messages],
|
|
97
|
+
stop_reason=stop_reason,
|
|
98
|
+
tool_calls=tool_calls,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def message(
|
|
103
|
+
cls,
|
|
104
|
+
*content_items: Union[
|
|
105
|
+
str, Path, bytes, dict, ContentBlock, PromptMessage, PromptMessageExtended
|
|
106
|
+
],
|
|
107
|
+
role: Literal["user", "assistant"] = "user",
|
|
108
|
+
) -> PromptMessageExtended:
|
|
109
|
+
"""
|
|
110
|
+
Create a PromptMessageExtended with the specified role and content items.
|
|
111
|
+
"""
|
|
112
|
+
# Handle PromptMessage and PromptMessageExtended directly
|
|
113
|
+
if len(content_items) == 1:
|
|
114
|
+
item = content_items[0]
|
|
115
|
+
if isinstance(item, PromptMessage):
|
|
116
|
+
return PromptMessageExtended(role=role, content=[item.content])
|
|
117
|
+
elif isinstance(item, PromptMessageExtended):
|
|
118
|
+
# Keep the content but change role as specified
|
|
119
|
+
return PromptMessageExtended(role=role, content=item.content)
|
|
120
|
+
|
|
121
|
+
# Use the content factory for other types
|
|
122
|
+
messages = MCPPrompt(*content_items, role=role)
|
|
123
|
+
return PromptMessageExtended(
|
|
124
|
+
role=messages[0]["role"] if messages else role,
|
|
125
|
+
content=[msg["content"] for msg in messages],
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
@classmethod
|
|
129
|
+
def conversation(cls, *messages) -> List[PromptMessage]:
|
|
130
|
+
"""
|
|
131
|
+
Create a list of PromptMessages from various inputs.
|
|
132
|
+
"""
|
|
133
|
+
result = []
|
|
134
|
+
|
|
135
|
+
for item in messages:
|
|
136
|
+
if isinstance(item, PromptMessageExtended):
|
|
137
|
+
# Convert PromptMessageExtended to a list of PromptMessages
|
|
138
|
+
result.extend(item.from_multipart())
|
|
139
|
+
elif isinstance(item, dict) and "role" in item and "content" in item:
|
|
140
|
+
# Convert a single message dict to PromptMessage
|
|
141
|
+
result.append(PromptMessage(**item))
|
|
142
|
+
elif isinstance(item, list):
|
|
143
|
+
# Process each item in the list
|
|
144
|
+
for msg in item:
|
|
145
|
+
if isinstance(msg, dict) and "role" in msg and "content" in msg:
|
|
146
|
+
result.append(PromptMessage(**msg))
|
|
147
|
+
# Ignore other types
|
|
148
|
+
|
|
149
|
+
return result
|
|
150
|
+
|
|
151
|
+
@classmethod
|
|
152
|
+
def from_multipart(cls, multipart: List[PromptMessageExtended]) -> List[PromptMessage]:
|
|
153
|
+
"""
|
|
154
|
+
Convert a list of PromptMessageExtended objects to PromptMessages.
|
|
155
|
+
"""
|
|
156
|
+
result = []
|
|
157
|
+
for mp in multipart:
|
|
158
|
+
result.extend(mp.from_multipart())
|
|
159
|
+
return result
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
from typing import List, Optional
|
|
1
|
+
from typing import Dict, List, Optional
|
|
2
2
|
|
|
3
3
|
from mcp.types import (
|
|
4
|
+
CallToolRequest,
|
|
5
|
+
CallToolResult,
|
|
4
6
|
ContentBlock,
|
|
5
7
|
GetPromptResult,
|
|
6
8
|
PromptMessage,
|
|
@@ -9,21 +11,26 @@ from mcp.types import (
|
|
|
9
11
|
)
|
|
10
12
|
from pydantic import BaseModel
|
|
11
13
|
|
|
12
|
-
from
|
|
14
|
+
from fast_agent.mcp.helpers.content_helpers import get_text
|
|
15
|
+
from fast_agent.types.llm_stop_reason import LlmStopReason
|
|
13
16
|
|
|
14
17
|
|
|
15
|
-
class
|
|
18
|
+
class PromptMessageExtended(BaseModel):
|
|
16
19
|
"""
|
|
17
20
|
Extension of PromptMessage that handles multiple content parts.
|
|
18
21
|
Internally converts to/from a sequence of standard PromptMessages.
|
|
19
22
|
"""
|
|
20
23
|
|
|
21
24
|
role: Role
|
|
22
|
-
content: List[ContentBlock]
|
|
25
|
+
content: List[ContentBlock] = []
|
|
26
|
+
tool_calls: Dict[str, CallToolRequest] | None = None
|
|
27
|
+
tool_results: Dict[str, CallToolResult] | None = None
|
|
28
|
+
channels: Dict[str, List[ContentBlock]] | None = None
|
|
29
|
+
stop_reason: LlmStopReason | None = None
|
|
23
30
|
|
|
24
31
|
@classmethod
|
|
25
|
-
def
|
|
26
|
-
"""Convert a sequence of PromptMessages into
|
|
32
|
+
def to_extended(cls, messages: List[PromptMessage]) -> List["PromptMessageExtended"]:
|
|
33
|
+
"""Convert a sequence of PromptMessages into PromptMessageExtended objects."""
|
|
27
34
|
if not messages:
|
|
28
35
|
return []
|
|
29
36
|
|
|
@@ -50,7 +57,7 @@ class PromptMessageMultipart(BaseModel):
|
|
|
50
57
|
return result
|
|
51
58
|
|
|
52
59
|
def from_multipart(self) -> List[PromptMessage]:
|
|
53
|
-
"""Convert this
|
|
60
|
+
"""Convert this PromptMessageExtended to a sequence of standard PromptMessages."""
|
|
54
61
|
return [
|
|
55
62
|
PromptMessage(role=self.role, content=content_part) for content_part in self.content
|
|
56
63
|
]
|
|
@@ -60,7 +67,7 @@ class PromptMessageMultipart(BaseModel):
|
|
|
60
67
|
Get the first available text content from a message. Note this could be tool content etc.
|
|
61
68
|
|
|
62
69
|
Args:
|
|
63
|
-
message: A PromptMessage or
|
|
70
|
+
message: A PromptMessage or PromptMessageExtended
|
|
64
71
|
|
|
65
72
|
Returns:
|
|
66
73
|
First text content or None if no text content exists
|
|
@@ -72,13 +79,13 @@ class PromptMessageMultipart(BaseModel):
|
|
|
72
79
|
|
|
73
80
|
return "<no text>"
|
|
74
81
|
|
|
75
|
-
def last_text(self) -> str:
|
|
82
|
+
def last_text(self) -> str | None:
|
|
76
83
|
"""
|
|
77
84
|
Get the last available text content from a message. This will usually be the final
|
|
78
85
|
generation from the Assistant.
|
|
79
86
|
|
|
80
87
|
Args:
|
|
81
|
-
message: A PromptMessage or
|
|
88
|
+
message: A PromptMessage or PromptMessageExtended
|
|
82
89
|
|
|
83
90
|
Returns:
|
|
84
91
|
First text content or None if no text content exists
|
|
@@ -88,14 +95,14 @@ class PromptMessageMultipart(BaseModel):
|
|
|
88
95
|
if text is not None:
|
|
89
96
|
return text
|
|
90
97
|
|
|
91
|
-
return
|
|
98
|
+
return None
|
|
92
99
|
|
|
93
100
|
def all_text(self) -> str:
|
|
94
101
|
"""
|
|
95
102
|
Get all the text available.
|
|
96
103
|
|
|
97
104
|
Args:
|
|
98
|
-
message: A PromptMessage or
|
|
105
|
+
message: A PromptMessage or PromptMessageExtended
|
|
99
106
|
|
|
100
107
|
Returns:
|
|
101
108
|
First text content or None if no text content exists
|
|
@@ -114,32 +121,32 @@ class PromptMessageMultipart(BaseModel):
|
|
|
114
121
|
return text
|
|
115
122
|
|
|
116
123
|
@classmethod
|
|
117
|
-
def parse_get_prompt_result(cls, result: GetPromptResult) -> List["
|
|
124
|
+
def parse_get_prompt_result(cls, result: GetPromptResult) -> List["PromptMessageExtended"]:
|
|
118
125
|
"""
|
|
119
|
-
Parse a GetPromptResult into
|
|
126
|
+
Parse a GetPromptResult into PromptMessageExtended objects.
|
|
120
127
|
|
|
121
128
|
Args:
|
|
122
129
|
result: GetPromptResult from MCP server
|
|
123
130
|
|
|
124
131
|
Returns:
|
|
125
|
-
List of
|
|
132
|
+
List of PromptMessageExtended objects
|
|
126
133
|
"""
|
|
127
|
-
return cls.
|
|
134
|
+
return cls.to_extended(result.messages)
|
|
128
135
|
|
|
129
136
|
@classmethod
|
|
130
137
|
def from_get_prompt_result(
|
|
131
138
|
cls, result: Optional[GetPromptResult]
|
|
132
|
-
) -> List["
|
|
139
|
+
) -> List["PromptMessageExtended"]:
|
|
133
140
|
"""
|
|
134
|
-
Convert a GetPromptResult to
|
|
141
|
+
Convert a GetPromptResult to PromptMessageExtended objects with error handling.
|
|
135
142
|
This method safely handles None values and empty results.
|
|
136
143
|
|
|
137
144
|
Args:
|
|
138
145
|
result: GetPromptResult from MCP server or None
|
|
139
146
|
|
|
140
147
|
Returns:
|
|
141
|
-
List of
|
|
148
|
+
List of PromptMessageExtended objects or empty list if result is None/empty
|
|
142
149
|
"""
|
|
143
150
|
if not result or not result.messages:
|
|
144
151
|
return []
|
|
145
|
-
return cls.
|
|
152
|
+
return cls.to_extended(result.messages)
|
|
@@ -1,43 +1,43 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Utilities for rendering
|
|
2
|
+
Utilities for rendering PromptMessageExtended objects for display.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from typing import List
|
|
6
6
|
|
|
7
7
|
from mcp.types import BlobResourceContents, TextResourceContents
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from fast_agent.mcp.helpers.content_helpers import (
|
|
10
10
|
get_resource_uri,
|
|
11
11
|
get_text,
|
|
12
12
|
is_image_content,
|
|
13
13
|
is_resource_content,
|
|
14
14
|
is_text_content,
|
|
15
15
|
)
|
|
16
|
-
from
|
|
16
|
+
from fast_agent.types import PromptMessageExtended
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
def render_multipart_message(message:
|
|
19
|
+
def render_multipart_message(message: PromptMessageExtended) -> str:
|
|
20
20
|
"""
|
|
21
21
|
Render a multipart message for display purposes.
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
This function formats the message content for user-friendly display,
|
|
24
24
|
handling different content types appropriately.
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
Args:
|
|
27
|
-
message: A
|
|
28
|
-
|
|
27
|
+
message: A PromptMessageExtended object to render
|
|
28
|
+
|
|
29
29
|
Returns:
|
|
30
30
|
A string representation of the message's content
|
|
31
31
|
"""
|
|
32
32
|
rendered_parts: List[str] = []
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
for content in message.content:
|
|
35
35
|
if is_text_content(content):
|
|
36
36
|
# Handle text content
|
|
37
37
|
text = get_text(content)
|
|
38
38
|
if text:
|
|
39
39
|
rendered_parts.append(text)
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
elif is_image_content(content):
|
|
42
42
|
# Format details about the image
|
|
43
43
|
image_data = getattr(content, "data", "")
|
|
@@ -45,36 +45,38 @@ def render_multipart_message(message: PromptMessageMultipart) -> str:
|
|
|
45
45
|
mime_type = getattr(content, "mimeType", "unknown")
|
|
46
46
|
image_info = f"[IMAGE: {mime_type}, {data_size} bytes]"
|
|
47
47
|
rendered_parts.append(image_info)
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
elif is_resource_content(content):
|
|
50
50
|
# Handle embedded resources
|
|
51
51
|
uri = get_resource_uri(content)
|
|
52
52
|
resource = getattr(content, "resource", None)
|
|
53
|
-
|
|
53
|
+
|
|
54
54
|
if resource and isinstance(resource, TextResourceContents):
|
|
55
55
|
# Handle text resources
|
|
56
56
|
text = resource.text
|
|
57
57
|
text_length = len(text)
|
|
58
58
|
mime_type = getattr(resource, "mimeType", "text/plain")
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
# Preview with truncation for long content
|
|
61
61
|
preview = text[:300] + ("..." if text_length > 300 else "")
|
|
62
|
-
resource_info =
|
|
62
|
+
resource_info = (
|
|
63
|
+
f"[EMBEDDED TEXT RESOURCE: {mime_type}, {uri}, {text_length} chars]\n{preview}"
|
|
64
|
+
)
|
|
63
65
|
rendered_parts.append(resource_info)
|
|
64
|
-
|
|
66
|
+
|
|
65
67
|
elif resource and isinstance(resource, BlobResourceContents):
|
|
66
68
|
# Handle blob resources (binary data)
|
|
67
69
|
blob = getattr(resource, "blob", "")
|
|
68
70
|
blob_length = len(blob) if blob else 0
|
|
69
71
|
mime_type = getattr(resource, "mimeType", "application/octet-stream")
|
|
70
|
-
|
|
72
|
+
|
|
71
73
|
resource_info = f"[EMBEDDED BLOB RESOURCE: {mime_type}, {uri}, {blob_length} bytes]"
|
|
72
74
|
rendered_parts.append(resource_info)
|
|
73
|
-
|
|
75
|
+
|
|
74
76
|
else:
|
|
75
77
|
# Fallback for other content types
|
|
76
78
|
text = get_text(content)
|
|
77
79
|
if text is not None:
|
|
78
80
|
rendered_parts.append(text)
|
|
79
|
-
|
|
80
|
-
return "\n".join(rendered_parts)
|
|
81
|
+
|
|
82
|
+
return "\n".join(rendered_parts)
|