-
Notifications
You must be signed in to change notification settings - Fork 103
feat: add host model provider interface in wit contract, host model provider to TS and SageMaker support to PY to enable host model calling for wasm architecture #877
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -279,7 +279,20 @@ def __init__( | |
| sp_blocks = json.dumps(system_prompt) | ||
| sp_str = None | ||
|
|
||
| model_config = self._build_model_config(resolve_model(model)) | ||
| # Detect host-side model providers (have stream() method — e.g. SageMaker, Mistral, Writer). | ||
| # These run on the Python side and are invoked by the WASM guest via model-provider import. | ||
| model_provider_callback = None | ||
| if model is not None and hasattr(model, "stream") and callable(getattr(model, "stream", None)): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Issue: Host-model detection via duck-typing is fragile and may produce false positives. Suggestion: Use a more explicit mechanism to distinguish host-side models. Consider:
This would make the dispatch explicit rather than relying on duck-typing that could be accidentally triggered. |
||
| from strands.models.host_adapter import HostModelAdapter | ||
|
|
||
| model_provider_callback = HostModelAdapter(model) | ||
| model_config = _ModelConfigInput( | ||
| provider="host-model", | ||
| additional_config=json.dumps({"provider_type": type(model).__name__}), | ||
| ) | ||
| else: | ||
| model_config = self._build_model_config(resolve_model(model)) | ||
|
|
||
| tool_specs = ( | ||
| [ | ||
| _ToolSpec( | ||
|
|
@@ -300,6 +313,7 @@ def __init__( | |
| tools=tool_specs, | ||
| tool_dispatcher=self._dispatcher, | ||
| log_handler=_LogHandler(), | ||
| model_provider=model_provider_callback, | ||
| use_callback_relay=False, | ||
| ) | ||
|
|
||
|
|
@@ -766,4 +780,4 @@ def cleanup(self) -> None: | |
| # Re-export for test compatibility | ||
| from strands.agent.conversation_manager import NullConversationManager # noqa: E402 | ||
|
|
||
| __all__ = ["Agent", "AgentResult", "NullConversationManager"] | ||
| __all__ = ["Agent", "AgentResult", "NullConversationManager"] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| """Configuration validation utilities for model providers.""" | ||
|
|
||
| import warnings | ||
| from collections.abc import Mapping | ||
| from typing import Any | ||
|
|
||
| from typing_extensions import get_type_hints | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Issue: Suggestion: Either add |
||
|
|
||
| from ..types.content import ContentBlock | ||
| from ..types.tools import ToolChoice | ||
|
|
||
|
|
||
| def validate_config_keys(config_dict: Mapping[str, Any], config_class: type) -> None: | ||
| """Validate that config keys match the TypedDict fields. | ||
|
|
||
| Args: | ||
| config_dict: Dictionary of configuration parameters | ||
| config_class: TypedDict class to validate against | ||
| """ | ||
| valid_keys = set(get_type_hints(config_class).keys()) | ||
| provided_keys = set(config_dict.keys()) | ||
| invalid_keys = provided_keys - valid_keys | ||
|
|
||
| if invalid_keys: | ||
| warnings.warn( | ||
| f"Invalid configuration parameters: {sorted(invalid_keys)}." | ||
| f"\nValid parameters are: {sorted(valid_keys)}." | ||
| f"\n" | ||
| f"\nSee https://github.com/strands-agents/sdk-python/issues/815", | ||
| stacklevel=4, | ||
| ) | ||
|
|
||
|
|
||
| def warn_on_tool_choice_not_supported(tool_choice: ToolChoice | None) -> None: | ||
| """Emits a warning if a tool choice is provided but not supported by the provider. | ||
|
|
||
| Args: | ||
| tool_choice: the tool_choice provided to the provider | ||
| """ | ||
| if tool_choice: | ||
| warnings.warn( | ||
| "A ToolChoice was provided to this provider but is not supported and will be ignored", | ||
| stacklevel=4, | ||
| ) | ||
|
|
||
|
|
||
| def _has_location_source(content: ContentBlock) -> bool: | ||
| """Check if a content block contains a location source. | ||
|
|
||
| Providers need to explicitly define an implementation to support content locations. | ||
|
|
||
| Args: | ||
| content: Content block to check. | ||
|
|
||
| Returns: | ||
| True if the content block contains an location source, False otherwise. | ||
| """ | ||
| if "image" in content: | ||
| return "location" in content["image"].get("source", {}) | ||
| if "document" in content: | ||
| return "location" in content["document"].get("source", {}) | ||
| if "video" in content: | ||
| return "location" in content["video"].get("source", {}) | ||
| return False | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Issue:
jsonis imported inline twice with aliases (_json,_json2) on lines 514 and 530 instead of using the module-level import. This is unusual and unnecessary —jsonis a stdlib module with negligible import cost.Suggestion: Use the module-level
import jsonthat already exists at the top of the file (or add it if missing). Remove the inline aliases.