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,462 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Sequence, Union
|
|
3
|
+
|
|
4
|
+
from anthropic.types import (
|
|
5
|
+
Base64ImageSourceParam,
|
|
6
|
+
Base64PDFSourceParam,
|
|
7
|
+
ContentBlockParam,
|
|
8
|
+
DocumentBlockParam,
|
|
9
|
+
ImageBlockParam,
|
|
10
|
+
MessageParam,
|
|
11
|
+
PlainTextSourceParam,
|
|
12
|
+
TextBlockParam,
|
|
13
|
+
ToolResultBlockParam,
|
|
14
|
+
ToolUseBlockParam,
|
|
15
|
+
URLImageSourceParam,
|
|
16
|
+
URLPDFSourceParam,
|
|
17
|
+
)
|
|
18
|
+
from mcp.types import (
|
|
19
|
+
BlobResourceContents,
|
|
20
|
+
CallToolResult,
|
|
21
|
+
ContentBlock,
|
|
22
|
+
EmbeddedResource,
|
|
23
|
+
ImageContent,
|
|
24
|
+
PromptMessage,
|
|
25
|
+
TextContent,
|
|
26
|
+
TextResourceContents,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
from fast_agent.core.logging.logger import get_logger
|
|
30
|
+
from fast_agent.mcp.helpers.content_helpers import (
|
|
31
|
+
get_image_data,
|
|
32
|
+
get_resource_uri,
|
|
33
|
+
get_text,
|
|
34
|
+
is_image_content,
|
|
35
|
+
is_resource_content,
|
|
36
|
+
is_text_content,
|
|
37
|
+
)
|
|
38
|
+
from fast_agent.mcp.mime_utils import (
|
|
39
|
+
guess_mime_type,
|
|
40
|
+
is_image_mime_type,
|
|
41
|
+
is_text_mime_type,
|
|
42
|
+
)
|
|
43
|
+
from fast_agent.types import PromptMessageExtended
|
|
44
|
+
|
|
45
|
+
_logger = get_logger("multipart_converter_anthropic")
|
|
46
|
+
|
|
47
|
+
# List of image MIME types supported by Anthropic API
|
|
48
|
+
SUPPORTED_IMAGE_MIME_TYPES = {"image/jpeg", "image/png", "image/gif", "image/webp"}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AnthropicConverter:
|
|
52
|
+
"""Converts MCP message types to Anthropic API format."""
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def _is_supported_image_type(mime_type: str) -> bool:
|
|
56
|
+
"""Check if the given MIME type is supported by Anthropic's image API.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
mime_type: The MIME type to check
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
True if the MIME type is supported, False otherwise
|
|
63
|
+
"""
|
|
64
|
+
return mime_type in SUPPORTED_IMAGE_MIME_TYPES
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def convert_to_anthropic(multipart_msg: PromptMessageExtended) -> MessageParam:
|
|
68
|
+
"""
|
|
69
|
+
Convert a PromptMessageExtended message to Anthropic API format.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
multipart_msg: The PromptMessageExtended message to convert
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
An Anthropic API MessageParam object
|
|
76
|
+
"""
|
|
77
|
+
role = multipart_msg.role
|
|
78
|
+
all_content_blocks = []
|
|
79
|
+
|
|
80
|
+
# If this is an assistant message that contains tool_calls, convert
|
|
81
|
+
# those into Anthropic tool_use blocks so the next user message can
|
|
82
|
+
# legally include corresponding tool_result blocks.
|
|
83
|
+
if role == "assistant" and multipart_msg.tool_calls:
|
|
84
|
+
for tool_use_id, req in multipart_msg.tool_calls.items():
|
|
85
|
+
sanitized_id = AnthropicConverter._sanitize_tool_id(tool_use_id)
|
|
86
|
+
name = None
|
|
87
|
+
args = None
|
|
88
|
+
try:
|
|
89
|
+
params = getattr(req, "params", None)
|
|
90
|
+
if params is not None:
|
|
91
|
+
name = getattr(params, "name", None)
|
|
92
|
+
args = getattr(params, "arguments", None)
|
|
93
|
+
except Exception:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
all_content_blocks.append(
|
|
97
|
+
ToolUseBlockParam(
|
|
98
|
+
type="tool_use",
|
|
99
|
+
id=sanitized_id,
|
|
100
|
+
name=name or "unknown_tool",
|
|
101
|
+
input=args or {},
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return MessageParam(role=role, content=all_content_blocks)
|
|
106
|
+
|
|
107
|
+
# Handle tool_results if present (for user messages with tool results)
|
|
108
|
+
# Tool results must come FIRST in the content array per Anthropic API requirements
|
|
109
|
+
if multipart_msg.tool_results:
|
|
110
|
+
# Convert dict to list of tuples for create_tool_results_message
|
|
111
|
+
tool_results_list = list(multipart_msg.tool_results.items())
|
|
112
|
+
tool_msg = AnthropicConverter.create_tool_results_message(tool_results_list)
|
|
113
|
+
# Extract the content blocks from the tool results message
|
|
114
|
+
all_content_blocks.extend(tool_msg["content"])
|
|
115
|
+
|
|
116
|
+
# Then handle regular content blocks if present
|
|
117
|
+
if multipart_msg.content:
|
|
118
|
+
# Convert content blocks
|
|
119
|
+
anthropic_blocks = AnthropicConverter._convert_content_items(
|
|
120
|
+
multipart_msg.content, document_mode=True
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Filter blocks based on role (assistant can only have text blocks)
|
|
124
|
+
if role == "assistant":
|
|
125
|
+
text_blocks = []
|
|
126
|
+
for block in anthropic_blocks:
|
|
127
|
+
if block.get("type") == "text":
|
|
128
|
+
text_blocks.append(block)
|
|
129
|
+
else:
|
|
130
|
+
_logger.warning(
|
|
131
|
+
f"Removing non-text block from assistant message: {block.get('type')}"
|
|
132
|
+
)
|
|
133
|
+
anthropic_blocks = text_blocks
|
|
134
|
+
|
|
135
|
+
all_content_blocks.extend(anthropic_blocks)
|
|
136
|
+
|
|
137
|
+
# Handle empty content case
|
|
138
|
+
if not all_content_blocks:
|
|
139
|
+
return MessageParam(role=role, content=[])
|
|
140
|
+
|
|
141
|
+
# Create the Anthropic message
|
|
142
|
+
return MessageParam(role=role, content=all_content_blocks)
|
|
143
|
+
|
|
144
|
+
@staticmethod
|
|
145
|
+
def convert_prompt_message_to_anthropic(message: PromptMessage) -> MessageParam:
|
|
146
|
+
"""
|
|
147
|
+
Convert a standard PromptMessage to Anthropic API format.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
message: The PromptMessage to convert
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
An Anthropic API MessageParam object
|
|
154
|
+
"""
|
|
155
|
+
# Convert the PromptMessage to a PromptMessageExtended containing a single content item
|
|
156
|
+
multipart = PromptMessageExtended(role=message.role, content=[message.content])
|
|
157
|
+
|
|
158
|
+
# Use the existing conversion method
|
|
159
|
+
return AnthropicConverter.convert_to_anthropic(multipart)
|
|
160
|
+
|
|
161
|
+
@staticmethod
|
|
162
|
+
def _convert_content_items(
|
|
163
|
+
content_items: Sequence[ContentBlock],
|
|
164
|
+
document_mode: bool = True,
|
|
165
|
+
) -> list[ContentBlockParam]:
|
|
166
|
+
"""
|
|
167
|
+
Convert a list of content items to Anthropic content blocks.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
content_items: Sequence of MCP content items
|
|
171
|
+
document_mode: Whether to convert text resources to document blocks (True) or text blocks (False)
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
List of Anthropic content blocks
|
|
175
|
+
"""
|
|
176
|
+
anthropic_blocks: list[ContentBlockParam] = []
|
|
177
|
+
|
|
178
|
+
for content_item in content_items:
|
|
179
|
+
if is_text_content(content_item):
|
|
180
|
+
# Handle text content
|
|
181
|
+
text = get_text(content_item)
|
|
182
|
+
anthropic_blocks.append(TextBlockParam(type="text", text=text))
|
|
183
|
+
|
|
184
|
+
elif is_image_content(content_item):
|
|
185
|
+
# Handle image content
|
|
186
|
+
image_content = content_item # type: ImageContent
|
|
187
|
+
# Check if image MIME type is supported
|
|
188
|
+
if not AnthropicConverter._is_supported_image_type(image_content.mimeType):
|
|
189
|
+
data_size = len(image_content.data) if image_content.data else 0
|
|
190
|
+
anthropic_blocks.append(
|
|
191
|
+
TextBlockParam(
|
|
192
|
+
type="text",
|
|
193
|
+
text=f"Image with unsupported format '{image_content.mimeType}' ({data_size} bytes)",
|
|
194
|
+
)
|
|
195
|
+
)
|
|
196
|
+
else:
|
|
197
|
+
image_data = get_image_data(image_content)
|
|
198
|
+
anthropic_blocks.append(
|
|
199
|
+
ImageBlockParam(
|
|
200
|
+
type="image",
|
|
201
|
+
source=Base64ImageSourceParam(
|
|
202
|
+
type="base64",
|
|
203
|
+
media_type=image_content.mimeType,
|
|
204
|
+
data=image_data,
|
|
205
|
+
),
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
elif is_resource_content(content_item):
|
|
210
|
+
# Handle embedded resource
|
|
211
|
+
block = AnthropicConverter._convert_embedded_resource(content_item, document_mode)
|
|
212
|
+
anthropic_blocks.append(block)
|
|
213
|
+
|
|
214
|
+
return anthropic_blocks
|
|
215
|
+
|
|
216
|
+
@staticmethod
|
|
217
|
+
def _convert_embedded_resource(
|
|
218
|
+
resource: EmbeddedResource,
|
|
219
|
+
document_mode: bool = True,
|
|
220
|
+
) -> ContentBlockParam:
|
|
221
|
+
"""
|
|
222
|
+
Convert EmbeddedResource to appropriate Anthropic block type.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
resource: The embedded resource to convert
|
|
226
|
+
document_mode: Whether to convert text resources to Document blocks (True) or Text blocks (False)
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
An appropriate ContentBlockParam for the resource
|
|
230
|
+
"""
|
|
231
|
+
resource_content = resource.resource
|
|
232
|
+
uri_str = get_resource_uri(resource)
|
|
233
|
+
uri = getattr(resource_content, "uri", None)
|
|
234
|
+
is_url: bool = uri and uri.scheme in ("http", "https")
|
|
235
|
+
|
|
236
|
+
# Determine MIME type
|
|
237
|
+
mime_type = AnthropicConverter._determine_mime_type(resource_content)
|
|
238
|
+
|
|
239
|
+
# Extract title from URI
|
|
240
|
+
from fast_agent.mcp.resource_utils import extract_title_from_uri
|
|
241
|
+
|
|
242
|
+
title = extract_title_from_uri(uri) if uri else "resource"
|
|
243
|
+
|
|
244
|
+
# Convert based on MIME type
|
|
245
|
+
if mime_type == "image/svg+xml":
|
|
246
|
+
return AnthropicConverter._convert_svg_resource(resource_content)
|
|
247
|
+
|
|
248
|
+
elif is_image_mime_type(mime_type):
|
|
249
|
+
if not AnthropicConverter._is_supported_image_type(mime_type):
|
|
250
|
+
return AnthropicConverter._create_fallback_text(
|
|
251
|
+
f"Image with unsupported format '{mime_type}'", resource
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
if is_url and uri_str:
|
|
255
|
+
return ImageBlockParam(
|
|
256
|
+
type="image", source=URLImageSourceParam(type="url", url=uri_str)
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
# Try to get image data
|
|
260
|
+
image_data = get_image_data(resource)
|
|
261
|
+
if image_data:
|
|
262
|
+
return ImageBlockParam(
|
|
263
|
+
type="image",
|
|
264
|
+
source=Base64ImageSourceParam(
|
|
265
|
+
type="base64", media_type=mime_type, data=image_data
|
|
266
|
+
),
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
return AnthropicConverter._create_fallback_text("Image missing data", resource)
|
|
270
|
+
|
|
271
|
+
elif mime_type == "application/pdf":
|
|
272
|
+
if is_url and uri_str:
|
|
273
|
+
return DocumentBlockParam(
|
|
274
|
+
type="document",
|
|
275
|
+
title=title,
|
|
276
|
+
source=URLPDFSourceParam(type="url", url=uri_str),
|
|
277
|
+
)
|
|
278
|
+
elif isinstance(resource_content, BlobResourceContents):
|
|
279
|
+
return DocumentBlockParam(
|
|
280
|
+
type="document",
|
|
281
|
+
title=title,
|
|
282
|
+
source=Base64PDFSourceParam(
|
|
283
|
+
type="base64",
|
|
284
|
+
media_type="application/pdf",
|
|
285
|
+
data=resource_content.blob,
|
|
286
|
+
),
|
|
287
|
+
)
|
|
288
|
+
return TextBlockParam(type="text", text=f"[PDF resource missing data: {title}]")
|
|
289
|
+
|
|
290
|
+
elif is_text_mime_type(mime_type):
|
|
291
|
+
text = get_text(resource)
|
|
292
|
+
if not text:
|
|
293
|
+
return TextBlockParam(
|
|
294
|
+
type="text",
|
|
295
|
+
text=f"[Text content could not be extracted from {title}]",
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# Create document block when in document mode
|
|
299
|
+
if document_mode:
|
|
300
|
+
return DocumentBlockParam(
|
|
301
|
+
type="document",
|
|
302
|
+
title=title,
|
|
303
|
+
source=PlainTextSourceParam(
|
|
304
|
+
type="text",
|
|
305
|
+
media_type="text/plain",
|
|
306
|
+
data=text,
|
|
307
|
+
),
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
# Return as simple text block when not in document mode
|
|
311
|
+
return TextBlockParam(type="text", text=text)
|
|
312
|
+
|
|
313
|
+
# Default fallback - convert to text if possible
|
|
314
|
+
text = get_text(resource)
|
|
315
|
+
if text:
|
|
316
|
+
return TextBlockParam(type="text", text=text)
|
|
317
|
+
|
|
318
|
+
# This is for binary resources - match the format expected by the test
|
|
319
|
+
if isinstance(resource.resource, BlobResourceContents) and hasattr(
|
|
320
|
+
resource.resource, "blob"
|
|
321
|
+
):
|
|
322
|
+
blob_length = len(resource.resource.blob)
|
|
323
|
+
return TextBlockParam(
|
|
324
|
+
type="text",
|
|
325
|
+
text=f"Embedded Resource {uri._url} with unsupported format {mime_type} ({blob_length} characters)",
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
return AnthropicConverter._create_fallback_text(
|
|
329
|
+
f"Unsupported resource ({mime_type})", resource
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
@staticmethod
|
|
333
|
+
def _determine_mime_type(
|
|
334
|
+
resource: Union[TextResourceContents, BlobResourceContents],
|
|
335
|
+
) -> str:
|
|
336
|
+
"""
|
|
337
|
+
Determine the MIME type of a resource.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
resource: The resource to check
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
The MIME type as a string
|
|
344
|
+
"""
|
|
345
|
+
if getattr(resource, "mimeType", None):
|
|
346
|
+
return resource.mimeType
|
|
347
|
+
|
|
348
|
+
if getattr(resource, "uri", None):
|
|
349
|
+
return guess_mime_type(resource.uri.serialize_url)
|
|
350
|
+
|
|
351
|
+
if hasattr(resource, "blob"):
|
|
352
|
+
return "application/octet-stream"
|
|
353
|
+
|
|
354
|
+
return "text/plain"
|
|
355
|
+
|
|
356
|
+
@staticmethod
|
|
357
|
+
def _convert_svg_resource(resource_content) -> TextBlockParam:
|
|
358
|
+
"""
|
|
359
|
+
Convert SVG resource to text block with XML code formatting.
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
resource_content: The resource content containing SVG data
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
A TextBlockParam with formatted SVG content
|
|
366
|
+
"""
|
|
367
|
+
# Use get_text helper to extract text from various content types
|
|
368
|
+
svg_content = get_text(resource_content)
|
|
369
|
+
if svg_content:
|
|
370
|
+
return TextBlockParam(type="text", text=f"```xml\n{svg_content}\n```")
|
|
371
|
+
return TextBlockParam(type="text", text="[SVG content could not be extracted]")
|
|
372
|
+
|
|
373
|
+
@staticmethod
|
|
374
|
+
def _create_fallback_text(
|
|
375
|
+
message: str, resource: Union[TextContent, ImageContent, EmbeddedResource]
|
|
376
|
+
) -> TextBlockParam:
|
|
377
|
+
"""
|
|
378
|
+
Create a fallback text block for unsupported resource types.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
message: The fallback message
|
|
382
|
+
resource: The resource that couldn't be converted
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
A TextBlockParam with the fallback message
|
|
386
|
+
"""
|
|
387
|
+
if isinstance(resource, EmbeddedResource) and hasattr(resource.resource, "uri"):
|
|
388
|
+
uri = resource.resource.uri
|
|
389
|
+
return TextBlockParam(type="text", text=f"[{message}: {uri._url}]")
|
|
390
|
+
|
|
391
|
+
return TextBlockParam(type="text", text=f"[{message}]")
|
|
392
|
+
|
|
393
|
+
@staticmethod
|
|
394
|
+
def create_tool_results_message(
|
|
395
|
+
tool_results: list[tuple[str, CallToolResult]],
|
|
396
|
+
) -> MessageParam:
|
|
397
|
+
"""
|
|
398
|
+
Create a user message containing tool results.
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
tool_results: List of (tool_use_id, tool_result) tuples
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
A MessageParam with role='user' containing all tool results
|
|
405
|
+
"""
|
|
406
|
+
content_blocks = []
|
|
407
|
+
|
|
408
|
+
for tool_use_id, result in tool_results:
|
|
409
|
+
sanitized_id = AnthropicConverter._sanitize_tool_id(tool_use_id)
|
|
410
|
+
# Process each tool result
|
|
411
|
+
tool_result_blocks = []
|
|
412
|
+
|
|
413
|
+
# Process each content item in the result
|
|
414
|
+
for item in result.content:
|
|
415
|
+
if isinstance(item, (TextContent, ImageContent)):
|
|
416
|
+
blocks = AnthropicConverter._convert_content_items([item], document_mode=False)
|
|
417
|
+
tool_result_blocks.extend(blocks)
|
|
418
|
+
elif isinstance(item, EmbeddedResource):
|
|
419
|
+
resource_content = item.resource
|
|
420
|
+
document_mode: bool = not isinstance(resource_content, TextResourceContents)
|
|
421
|
+
# With Anthropic SDK 0.66, documents can be inside tool results
|
|
422
|
+
# Text resources remain inline within the tool_result
|
|
423
|
+
block = AnthropicConverter._convert_embedded_resource(
|
|
424
|
+
item, document_mode=document_mode
|
|
425
|
+
)
|
|
426
|
+
tool_result_blocks.append(block)
|
|
427
|
+
|
|
428
|
+
# Create the tool result block if we have content
|
|
429
|
+
if tool_result_blocks:
|
|
430
|
+
content_blocks.append(
|
|
431
|
+
ToolResultBlockParam(
|
|
432
|
+
type="tool_result",
|
|
433
|
+
tool_use_id=sanitized_id,
|
|
434
|
+
content=tool_result_blocks,
|
|
435
|
+
is_error=result.isError,
|
|
436
|
+
)
|
|
437
|
+
)
|
|
438
|
+
else:
|
|
439
|
+
# If there's no content, still create a placeholder
|
|
440
|
+
content_blocks.append(
|
|
441
|
+
ToolResultBlockParam(
|
|
442
|
+
type="tool_result",
|
|
443
|
+
tool_use_id=sanitized_id,
|
|
444
|
+
content=[TextBlockParam(type="text", text="[No content in tool result]")],
|
|
445
|
+
is_error=result.isError,
|
|
446
|
+
)
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
# All content is now included within the tool_result block.
|
|
450
|
+
|
|
451
|
+
return MessageParam(role="user", content=content_blocks)
|
|
452
|
+
|
|
453
|
+
@staticmethod
|
|
454
|
+
def _sanitize_tool_id(tool_id: str | None) -> str:
|
|
455
|
+
"""
|
|
456
|
+
Anthropic tool_use ids must match ^[a-zA-Z0-9_-]+$.
|
|
457
|
+
Clean any other characters to underscores and provide a stable fallback.
|
|
458
|
+
"""
|
|
459
|
+
if not tool_id:
|
|
460
|
+
return "tool"
|
|
461
|
+
cleaned = re.sub(r"[^a-zA-Z0-9_-]", "_", tool_id)
|
|
462
|
+
return cleaned or "tool"
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Collection, Literal, TypedDict, cast
|
|
4
|
+
|
|
5
|
+
# Lightweight, runtime-only loader for AWS Bedrock models.
|
|
6
|
+
# - Fetches once per process via boto3 (region from session; env override supported)
|
|
7
|
+
# - Memory cache only; no disk persistence
|
|
8
|
+
# - Provides filtering and optional prefixing (default 'bedrock.') for model IDs
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
import boto3
|
|
12
|
+
except Exception: # pragma: no cover - import error path
|
|
13
|
+
boto3 = None # type: ignore[assignment]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
Modality = Literal["TEXT", "IMAGE", "VIDEO", "SPEECH", "EMBEDDING"]
|
|
17
|
+
Lifecycle = Literal["ACTIVE", "LEGACY"]
|
|
18
|
+
InferenceType = Literal["ON_DEMAND", "PROVISIONED", "INFERENCE_PROFILE"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ModelSummary(TypedDict, total=False):
|
|
22
|
+
modelId: str
|
|
23
|
+
modelName: str
|
|
24
|
+
providerName: str
|
|
25
|
+
inputModalities: list[Modality]
|
|
26
|
+
outputModalities: list[Modality]
|
|
27
|
+
responseStreamingSupported: bool
|
|
28
|
+
customizationsSupported: list[str]
|
|
29
|
+
inferenceTypesSupported: list[InferenceType]
|
|
30
|
+
modelLifecycle: dict[str, Lifecycle]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
_MODELS_CACHE_BY_REGION: dict[str, dict[str, ModelSummary]] = {}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _resolve_region(region: str | None) -> str:
|
|
37
|
+
if region:
|
|
38
|
+
return region
|
|
39
|
+
import os
|
|
40
|
+
|
|
41
|
+
env_region = os.getenv("BEDROCK_REGION")
|
|
42
|
+
if env_region:
|
|
43
|
+
return env_region
|
|
44
|
+
if boto3 is None:
|
|
45
|
+
raise RuntimeError(
|
|
46
|
+
"boto3 is required to load Bedrock models. Install boto3 or provide a static list."
|
|
47
|
+
)
|
|
48
|
+
session = boto3.Session()
|
|
49
|
+
if not session.region_name:
|
|
50
|
+
raise RuntimeError(
|
|
51
|
+
"AWS region could not be resolved. Configure your AWS SSO/profile or set BEDROCK_REGION."
|
|
52
|
+
)
|
|
53
|
+
return session.region_name
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _strip_prefix(model_id: str, prefix: str) -> str:
|
|
57
|
+
return model_id[len(prefix) :] if prefix and model_id.startswith(prefix) else model_id
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _ensure_loaded(region: str | None = None) -> dict[str, ModelSummary]:
|
|
61
|
+
resolved_region = _resolve_region(region)
|
|
62
|
+
cache = _MODELS_CACHE_BY_REGION.get(resolved_region)
|
|
63
|
+
if cache is not None:
|
|
64
|
+
return cache
|
|
65
|
+
|
|
66
|
+
if boto3 is None:
|
|
67
|
+
raise RuntimeError("boto3 is required to load Bedrock models. Install boto3.")
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
client = boto3.client("bedrock", region_name=resolved_region)
|
|
71
|
+
resp = client.list_foundation_models()
|
|
72
|
+
summaries: list[ModelSummary] = resp.get("modelSummaries", []) # type: ignore[assignment]
|
|
73
|
+
except Exception as exc: # keep error simple and actionable
|
|
74
|
+
raise RuntimeError(
|
|
75
|
+
f"Failed to list Bedrock foundation models in region '{resolved_region}'. "
|
|
76
|
+
f"Ensure AWS credentials (SSO) and permissions (bedrock:ListFoundationModels) are configured. "
|
|
77
|
+
f"Original error: {exc}"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
cache = {s.get("modelId", ""): s for s in summaries if s.get("modelId")}
|
|
81
|
+
_MODELS_CACHE_BY_REGION[resolved_region] = cache
|
|
82
|
+
return cache
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def refresh_bedrock_models(region: str | None = None) -> None:
|
|
86
|
+
resolved_region = _resolve_region(region)
|
|
87
|
+
# drop and reload on next access
|
|
88
|
+
_MODELS_CACHE_BY_REGION.pop(resolved_region, None)
|
|
89
|
+
_ensure_loaded(resolved_region)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _matches_modalities(model_modalities: list[Modality], requested: Collection[Modality]) -> bool:
|
|
93
|
+
# include if all requested are present in the model's modalities
|
|
94
|
+
return set(requested).issubset(set(model_modalities))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def all_model_summaries(
|
|
98
|
+
input_modalities: Collection[Modality] | None = None,
|
|
99
|
+
output_modalities: Collection[Modality] | None = None,
|
|
100
|
+
include_legacy: bool = False,
|
|
101
|
+
providers: Collection[str] | None = None,
|
|
102
|
+
inference_types: Collection[InferenceType] | None = None,
|
|
103
|
+
direct_invocation_only: bool = True,
|
|
104
|
+
region: str | None = None,
|
|
105
|
+
) -> list[ModelSummary]:
|
|
106
|
+
"""Return filtered Bedrock model summaries.
|
|
107
|
+
|
|
108
|
+
Defaults: input_modalities={"TEXT"}, output_modalities={"TEXT"}, include_legacy=False,
|
|
109
|
+
inference_types={"ON_DEMAND"}, direct_invocation_only=True.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
cache = _ensure_loaded(region)
|
|
113
|
+
results: list[ModelSummary] = []
|
|
114
|
+
|
|
115
|
+
effective_output: set[Modality] = (
|
|
116
|
+
set(output_modalities) if output_modalities is not None else {cast("Modality", "TEXT")}
|
|
117
|
+
)
|
|
118
|
+
effective_input: set[Modality] | None = (
|
|
119
|
+
set(input_modalities) if input_modalities is not None else {cast("Modality", "TEXT")}
|
|
120
|
+
)
|
|
121
|
+
provider_filter: set[str] | None = set(providers) if providers is not None else None
|
|
122
|
+
effective_inference: set[InferenceType] = (
|
|
123
|
+
set(inference_types)
|
|
124
|
+
if inference_types is not None
|
|
125
|
+
else {cast("InferenceType", "ON_DEMAND")}
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
for summary in cache.values():
|
|
129
|
+
lifecycle = (summary.get("modelLifecycle") or {}).get("status")
|
|
130
|
+
if not include_legacy and lifecycle == "LEGACY":
|
|
131
|
+
continue
|
|
132
|
+
|
|
133
|
+
if provider_filter is not None and summary.get("providerName") not in provider_filter:
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
# direct invocation only: exclude profile variants like :0:24k or :mm
|
|
137
|
+
if direct_invocation_only:
|
|
138
|
+
mid = summary.get("modelId") or ""
|
|
139
|
+
if mid.count(":") > 1:
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
# modalities
|
|
143
|
+
model_inputs: list[Modality] = summary.get("inputModalities", []) # type: ignore[assignment]
|
|
144
|
+
model_outputs: list[Modality] = summary.get("outputModalities", []) # type: ignore[assignment]
|
|
145
|
+
|
|
146
|
+
if effective_input is not None and not _matches_modalities(model_inputs, effective_input):
|
|
147
|
+
continue
|
|
148
|
+
if effective_output and not _matches_modalities(model_outputs, effective_output):
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
# inference types
|
|
152
|
+
model_inference: list[InferenceType] = summary.get("inferenceTypesSupported", []) # type: ignore[assignment]
|
|
153
|
+
if effective_inference and not set(effective_inference).issubset(set(model_inference)):
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
results.append(summary)
|
|
157
|
+
|
|
158
|
+
return results
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def all_bedrock_models(
|
|
162
|
+
input_modalities: Collection[Modality] | None = None,
|
|
163
|
+
output_modalities: Collection[Modality] | None = None,
|
|
164
|
+
include_legacy: bool = False,
|
|
165
|
+
providers: Collection[str] | None = None,
|
|
166
|
+
prefix: str = "bedrock.",
|
|
167
|
+
inference_types: Collection[InferenceType] | None = None,
|
|
168
|
+
direct_invocation_only: bool = True,
|
|
169
|
+
region: str | None = None,
|
|
170
|
+
) -> list[str]:
|
|
171
|
+
"""Return model IDs (optionally prefixed) filtered by the given criteria.
|
|
172
|
+
|
|
173
|
+
Defaults: output_modalities={"TEXT"}, exclude LEGACY,
|
|
174
|
+
inference_types={"ON_DEMAND"}, direct_invocation_only=True.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
summaries = all_model_summaries(
|
|
178
|
+
input_modalities=input_modalities,
|
|
179
|
+
output_modalities=output_modalities,
|
|
180
|
+
include_legacy=include_legacy,
|
|
181
|
+
providers=providers,
|
|
182
|
+
inference_types=inference_types,
|
|
183
|
+
direct_invocation_only=direct_invocation_only,
|
|
184
|
+
region=region,
|
|
185
|
+
)
|
|
186
|
+
ids: list[str] = []
|
|
187
|
+
for s in summaries:
|
|
188
|
+
mid = s.get("modelId")
|
|
189
|
+
if mid:
|
|
190
|
+
ids.append(mid)
|
|
191
|
+
if prefix:
|
|
192
|
+
return [f"{prefix}{mid}" for mid in ids]
|
|
193
|
+
return ids
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def get_model_metadata(model_id: str, region: str | None = None) -> ModelSummary | None:
|
|
197
|
+
cache = _ensure_loaded(region)
|
|
198
|
+
# Accept either prefixed or plain model IDs
|
|
199
|
+
plain_id = _strip_prefix(model_id, "bedrock.")
|
|
200
|
+
return cache.get(plain_id)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def list_providers(region: str | None = None) -> list[str]:
|
|
204
|
+
cache = _ensure_loaded(region)
|
|
205
|
+
providers = {s.get("providerName") for s in cache.values() if s.get("providerName")}
|
|
206
|
+
return sorted(providers) # type: ignore[arg-type]
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
__all__ = [
|
|
210
|
+
"Modality",
|
|
211
|
+
"Lifecycle",
|
|
212
|
+
"ModelSummary",
|
|
213
|
+
"all_bedrock_models",
|
|
214
|
+
"all_model_summaries",
|
|
215
|
+
"get_model_metadata",
|
|
216
|
+
"list_providers",
|
|
217
|
+
"refresh_bedrock_models",
|
|
218
|
+
]
|