Guide — Custom model providers
Scope
This document summarises the steps and criteria for implementing custom model providers with the Strands Agents SDK inside Shell Sentinel. It serves as an internal checklist and complements the official documentation.
Prerequisites
- Know the
strands.models.Modelhierarchy (review examples such asBedrockModel). - Understand
Messages,StreamEventandToolSpectypes. - Python client for the proprietary LLM service (sync or async).
- Declarative configuration in
conf/and credentials via environment variables.
Implementation flow
- Define configuration: create a typed
ModelConfigand exposeget_config/update_config. - Initialise the client: resolve credentials securely, instantiate the remote client and register logging.
- Implement
stream(...): convert inputs, adapt toStreamEvent, handle errors; useasyncio.to_threadfor sync SDKs. - Support tools: reuse
streaminstructured_output(...)with PydanticToolSpecconversion. - Register the provider in
smart_ai_sys_admin.agentandconf/agent.conf.
Additional considerations
- Use
DEBUGlogging for troubleshooting. - Document new parameters in user manuals when operators are affected.
- Never hardcode tokens or endpoints.
- Run smoke tests before TUI integration.
Practical case: OpenAI Responses API
providers.openai.api accepts chat_completions (default,
/v1/chat/completions) and responses (/v1/responses).
max_tokens is normalized to max_completion_tokens for Chat Completions
or max_output_tokens for Responses; reasoning_effort and
reasoning.effort are normalized for the selected endpoint too.
The migration addresses an HTTP 400 when gpt-5.6-sol receives function tools and
active reasoning through Chat Completions. reasoning_effort: "none" avoids the error
only by disabling reasoning (reasoning_tokens=0). Responses supports function tools
with medium reasoning without that degradation.
The current example uses GPT-6.1
Sol. This model requires Responses for function tools and supports medium
reasoning; none is not supported. The GPT-5.6 workaround above does not apply to
GPT-6.1 Sol.
{
"model_id": "gpt-6.1-sol",
"api": "responses",
"params": {"reasoning_effort": "medium", "max_tokens": 32768}
}
The effective request uses reasoning.effort and max_output_tokens; the
real configuration uses 65536. Export OPENAI_API_KEY from the environment.
Optional stateful mode can use previous_response_id, but reconstructed
multi-turn reasoningContent does not yet retain full reasoning continuity.
Practical case: LM Studio
- OpenAI-compatible local server (
/v1/*); configurebase_url,api_keyandmodel_id. - Start with
lms server start; tuneclient_argsfor timeouts. - Native REST API (
/api/v0/*) exposes metrics andmax_context_length.
Practical case: Mistral AI (Path A+)
ShellMistralModelwraps StrandsMistralModel(officialmistralaiv2 SDK).- Configure
providers.mistralwithreasoning_effort: highandmax_tokens: 16184by default. - Run
make test-mistralwhenMISTRAL_API_KEYis available.
For Mistral Large 4, set provider: "mistral", providers.mistral.model_id:
"mistral-large-4" and reasoning_effort: "high"; keep max_tokens:
16184 and MISTRAL_API_KEY in the environment. Mistral Large 4 reasoning does not
support none. Run the live TUI checks with RUN_E2E=1
SHELL_SENTINEL_E2E_PROVIDERS=mistral-large-4 make test-e2e-live.
Practical case: Google Gemini
- Strands'
GeminiModel(geminiextra, officialgoogle-genaiSDK) is used directly; no custom wrapper. - Configure
providers.geminiwithmodel_id(gemini-3.8-flash),params(max_tokensbecomesmax_output_tokens) andclient_args. - Key from
api_key_env(GEMINI_API_KEY, thenGOOGLE_API_KEY);api_keyin JSON is rejected.client_args.vertexai: trueuses Google Cloud credentials instead. - Thoughts stream as
reasoningContent(show_thinking);cached_content_token_countmaps tocacheReadInputTokens.CacheConfigis not supported, so thecacheblock defaults to disabled. - The live e2e registry includes a
geminirow:make test-e2e-livecovers it whenGEMINI_API_KEYis set.
Prompt caching
- Optional
cacheblock per provider (enabled,strategy,ttl,system_prompt_ttl,tools_ttl,cache_key) mapped to Strands'CacheConfig. - Bedrock:
enabled: true+strategy: "auto"by default; cache points are injected only for Claude model ids. - OpenAI and Mistral:
enabled: trueby default; only a stableprompt_cache_keyderived from the configuration path andmodel_idis sent (cache_key: falseopts out). - LM Studio is disabled by default; Ollama, Cerebras and Gemini ignore the block with a logged warning.
/statusreports cached vs total input tokens of the last turn.
Turn limit per instruction
agent.max_turns(default25,null/0disables) is validated as a positive integer and passed byAgentRuntime.invokeaslimits={"turns": N}to the Strands agent.- When the cap fires Strands returns
stop_reason == "limit_turns"instead of recursing; the runtime appends the localizedagent.invoke.turn_limitwarning to the model text and logs a WARNING. No provider-specific work is needed.
Practical case: Cerebras
- Integrate
cerebras_cloud_sdkwith SSE streaming. - Configure
providers.cerebraswithmodel_id,params,client_argsandapi_key_env. - Convert
ChatChunkResponseto native events withmetadatafor usage and timing. - For structured output, generate a
ToolSpecand parsetool_call.function.arguments.