Skip to content

feat: support all model providers in JSON agent configuration#2109

Draft
Unshure wants to merge 4 commits intomainfrom
agent-tasks/1064
Draft

feat: support all model providers in JSON agent configuration#2109
Unshure wants to merge 4 commits intomainfrom
agent-tasks/1064

Conversation

@Unshure
Copy link
Copy Markdown
Member

@Unshure Unshure commented Apr 10, 2026

Motivation

The experimental agent_config.py feature currently only accepts a simple string for the model field, which is always interpreted as a Bedrock model_id. This limits JSON-based agent configuration to a single provider, preventing use cases like agent-builder tools and the use_agent tool from working with any of the SDK's 12 model providers.

Resolves #1064

Public API Changes

The model field in agent JSON configuration now supports two formats:

# Before: string only (Bedrock model_id)
config = {"model": "us.anthropic.claude-sonnet-4-20250514-v1:0"}
agent = config_to_agent(config)

# After: string still works (backward compatible)
agent = config_to_agent(config)

# After: object format for any provider
config = {
    "model": {
        "provider": "openai",
        "model_id": "gpt-4o",
        "client_args": {"api_key": "..."}
    }
}
agent = config_to_agent(config)

All 12 SDK providers are supported: bedrock, anthropic, openai, gemini, ollama, litellm, mistral, llamaapi, llamacpp, sagemaker, writer, openai_responses. Each provider's constructor parameters are correctly routed — for example, boto_client_config dicts are converted to BotocoreConfig objects for Bedrock/SageMaker, Ollama's client_args maps to ollama_client_args, and Mistral's api_key is extracted as a separate parameter.

The factory logic is co-located with each provider via from_dict classmethods: the base Model.from_dict handles the common client_args + **model_config pattern (used by 7 providers), while Bedrock, Ollama, Mistral, LlamaCpp, and SageMaker override it for their non-standard constructors. agent_config.py is a thin orchestration layer that validates the schema, resolves the provider class via strands.models lazy __getattr__, and delegates to from_dict.

All provider imports are lazy to avoid requiring optional dependencies that aren't installed. Non-serializable parameters (boto_session, client, gemini_tools) cannot be specified from JSON and are documented as such.

Use Cases

  • Agent-builder tools: Create agents from JSON templates with any model provider, not just Bedrock
  • Multi-provider configs: Store agent configurations in files that use OpenAI, Anthropic, or local models

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 10, 2026

Codecov Report

❌ Patch coverage is 93.45794% with 7 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/strands/models/sagemaker.py 76.92% 2 Missing and 1 partial ⚠️
src/strands/models/llamacpp.py 80.00% 0 Missing and 2 partials ⚠️
src/strands/models/mistral.py 83.33% 0 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

Comment thread src/strands/experimental/agent_config.py
Comment thread src/strands/experimental/agent_config.py Outdated
Comment thread src/strands/experimental/agent_config.py Outdated
Comment thread src/strands/experimental/agent_config.py Outdated
Comment thread src/strands/experimental/agent_config.py Outdated
Comment thread src/strands/experimental/agent_config.py Outdated
@github-actions
Copy link
Copy Markdown

Assessment: Comment

Solid implementation that extends the experimental JSON agent configuration to support all 12 SDK model providers with backward compatibility. The main areas for improvement are reducing code duplication in the provider factory functions and replacing the fragile globals() dispatch pattern.

Review Categories
  • Code Duplication: 7 of 12 factory functions are identical — a shared helper would significantly reduce boilerplate and make adding future providers trivial.
  • Dispatch Pattern: PROVIDER_MAP stores string names resolved via globals(), which is fragile and opaque to static analysis. Direct function references are more idiomatic and safer.
  • Silent Failures: The SageMaker factory silently discards unrecognized config keys, unlike all other factories that pass them through. This inconsistency could silently swallow user config errors.
  • Unused Code: logging import and logger are defined but never called — should either add debug logging for provider instantiation or remove.
  • Type Annotation: **kwargs: dict[str, Any] on config_to_agent is incorrect — should be **kwargs: Any.
  • API Review: This PR modifies experimental public API surface (new object model format). Consider adding the needs-api-review label per the API bar raising process.
  • Documentation PR: The PR adds a new public configuration format (object model). Since this is within the experimental module, a docs PR is suggested but non-blocking. Consider adding a "Documentation PR" section to the PR description.

Good backward compatibility preservation, thorough test coverage, and clean lazy-import pattern across all providers.

- Fix **kwargs type annotation (dict[str, Any] -> Any) in config_to_agent
- Add defensive copy in all from_dict methods to avoid mutating caller's dict
- Raise ValueError on unsupported config keys in SageMaker from_dict
- Improve _create_model_from_dict return type to Model
- Document env var pattern full-string-only matching
Comment thread tests/strands/experimental/test_agent_config.py Outdated
Comment thread src/strands/models/sagemaker.py
Comment thread tests/strands/experimental/test_agent_config.py Outdated
@github-actions
Copy link
Copy Markdown

Assessment: Comment

The rework since the last review is excellent — all 6 prior issues have been addressed. Moving factory logic to from_dict classmethods on each model class is the right design: it keeps provider-specific knowledge co-located with the provider, the base Model.from_dict eliminates duplication for the 7 common providers, and agent_config.py is now a clean thin orchestration layer.

Review Details
  • Previous review items: All 6 addressed — factory duplication eliminated via base class from_dict, unused logging removed, **kwargs type fixed, env var pattern documented, SageMaker validates unexpected keys, dispatch uses established lazy __getattr__ pattern.
  • Test placement: TestModelFromConfig tests from_dict methods on individual model classes but lives in test_agent_config.py — these should be moved to their respective model test files per the project's test mirroring convention.
  • Missing test: SageMakerAIModel.from_dict now raises on unexpected keys, but this error path lacks a test.
  • Dead code: Unused BedrockModel import and patch block in test_default_from_dict_client_args_pattern.

Clean architecture, solid backward compatibility, and thorough test coverage overall.

Comment thread src/strands/experimental/agent_config.py Outdated
Comment thread src/strands/experimental/agent_config.py Outdated
@Unshure
Copy link
Copy Markdown
Member Author

Unshure commented Apr 15, 2026

/strands address the open comments

- Remove env var resolution per maintainer request (Unshure)
- Move from_dict tests to respective model test files per project
  test mirroring convention (test_bedrock, test_ollama, test_mistral,
  test_llamacpp, test_sagemaker, test_model)
- Add missing test for SageMaker unexpected config keys rejection
- Remove dead code (unused BedrockModel import/patch block)
- Fix test mocking to avoid triggering lazy imports of optional deps
- Fix mypy type annotation on _create_model_from_dict return type
"openai_responses",
]

def test_all_providers_registered(self):
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove this test, it doesn't add much value

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed TestProviderMap entirely. The parametrized test_all_providers_dispatch already covers dispatch correctness for every provider.

# Should not raise
config_to_agent(config)

def test_null_model_still_valid(self):
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this already tested elsewhere? Can you also check and make sure there aren't duplicated tests?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked — test_null_model_still_valid is unique (tests model: null specifically), while test_config_to_agent_ignores_none_values tests prompt: null, name: null with a valid string model. Kept it. Also removed two actual duplicates: test_string_model_backward_compat (same as test_string_model_valid) and test_unknown_provider_error_message (same as test_unknown_provider_raises).

@Unshure
Copy link
Copy Markdown
Member Author

Unshure commented Apr 15, 2026

/strands

- Remove TestProviderMap class (test_all_providers_dispatch already
  covers all provider dispatch correctness)
- Remove test_string_model_backward_compat (duplicate of
  test_string_model_valid)
- Remove test_unknown_provider_error_message (duplicate of
  test_unknown_provider_raises)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Support Every model provider in Agent Json feature

2 participants