Skip to content

Chapter 2: Models — Choosing Your LLM

← Back to Table of Contents | ← Previous: The Basics


The models section defines named LLM configurations. Each model has a provider, a model_id, and optional params.

models:
  fast:
    provider: bedrock
    model_id: us.anthropic.claude-sonnet-4-6-v1:0

  creative:
    provider: openai
    model_id: gpt-4o
    params:
      temperature: 0.9

  local:
    provider: ollama
    model_id: llama3.2
    params:
      ctx_size: 8192

Built-in Providers

Provider Package Required Example model_id
bedrock (included) us.anthropic.claude-sonnet-4-6-v1:0
openai pip install kaboo-workflows[openai] gpt-4o
ollama pip install kaboo-workflows[ollama] llama3.2
gemini pip install kaboo-workflows[gemini] gemini-2.0-flash

How Agents Reference Models

Agents reference models by name — the key you defined in the models section:

models:
  smart:
    provider: bedrock
    model_id: us.anthropic.claude-sonnet-4-6-v1:0

agents:
  analyst:
    model: smart     # <-- references the "smart" model above
    system_prompt: "You analyze data."

Inline Models

Don't want to name a model? Define it inline directly on the agent:

agents:
  analyst:
    model:
      provider: bedrock
      model_id: us.anthropic.claude-sonnet-4-6-v1:0
    system_prompt: "You analyze data."

This is handy when only one agent uses a specific model — no need to pollute the models section. But if two agents share the same model config, use a named model to avoid duplication.

An inline model is also the simplest choice in a config submitted per run (Chapter 13): the agents in an overlay replace the base's, but a named model has to exist somewhere, either in the base's models or in the overlay's own. Inline needs neither.

Custom Model Providers

If the built-in four providers aren't enough, you can point provider to a custom Model subclass:

models:
  my_custom:
    provider: my_package.models:CustomModel
    model_id: my-model-v1
    params:
      api_key: ${API_KEY}

The class must be a subclass of strands.models.Model. The model_id and params are passed to its constructor.

The params Dict

params is a pass-through dictionary — whatever you put in it gets forwarded as **kwargs to the model constructor. This means you can set any provider-specific parameter:

models:
  tuned:
    provider: bedrock
    model_id: us.anthropic.claude-sonnet-4-6-v1:0
    params:
      max_tokens: 4096
      temperature: 0.7
      top_p: 0.9

OpenAI client defaults: timeout and max_retries

For the openai provider, params.client_args is forwarded to the OpenAI SDK client. kaboo fills two keys when you don't set them: timeout: 180 (seconds) and max_retries: 2. Without a timeout, a stalled connection to the provider (or a router like OpenRouter) hangs the run forever with no error; with these defaults it fails visibly and retries. Override either key to tune:

models:
  default:
    provider: openai
    model_id: anthropic/claude-sonnet-4-6
    params:
      client_args:
        api_key: ${OPENROUTER_API_KEY}
        base_url: https://openrouter.ai/api/v1
        timeout: 300        # long-running document analysis
        max_retries: 1

Bedrock client settings: client_config

For the bedrock provider, params.client_config sets the timeouts and retry policy of the boto client the model reaches Bedrock through. kaboo turns it into a botocore Config and passes it to strands' BedrockModel as boto_client_config. Only these keys are accepted; a misspelt one fails when the config loads rather than being ignored.

Key Value Meaning
read_timeout seconds, > 0 How long to wait for data on the response stream. Unset, it stays at strands' Bedrock default of 120 (botocore's own is 60).
connect_timeout seconds, > 0 How long to wait for a connection. Unset, botocore's 60.
retries.max_attempts integer, ≥ 0 Retries after the first attempt.
retries.mode legacy | standard | adaptive botocore's retry mode.
models:
  claude:
    provider: bedrock
    model_id: us.anthropic.claude-sonnet-4-6-v1:0
    params:
      client_config:
        read_timeout: 840        # long generations on a slow stream
        connect_timeout: 10
        retries: {max_attempts: 4, mode: adaptive}

Thinking

Claude models on Bedrock can think before they answer. thinking: says whether they do: on a model, as the default for every agent that uses it, and on an agent, as an override of its model's.

models:
  sonnet:
    provider: bedrock
    model_id: us.anthropic.claude-sonnet-5
    thinking: {effort: high, display: summarized}

agents:
  planner:
    model: sonnet              # thinks, as its model does
  worker:
    model: sonnet
    thinking: false            # answers without thinking
  checker:
    model: sonnet
    thinking: {effort: low}    # thinks less, with the model's display
Value Sent to Bedrock
false (or off) thinking: {type: disabled}
true thinking: {type: adaptive}
{effort, display} thinking: {type: adaptive, display} and output_config: {effort}, each only when set
unset nothing, so the model's own default applies
  • effort is low, medium, high, xhigh or max.
  • display: summarized returns a readable summary of the thinking, which streams as REASONING events; omitted returns none of it. Thinking is billed the same either way.

An agent's false wins over its model's setting, a mapping replaces only the fields it sets, and true takes the model's settings. The fields reach strands' BedrockModel as additional_request_fields, so a model takes thinking: or params.additional_request_fields.thinking, not both. Other providers reject thinking: when the config loads.

Agents whose setting differs from their named model's run on a variant of it, one per setting, built for the first such agent and reused by every agent and session after it.

Which settings a model accepts is the provider's business: some Claude models think by default, some reject type: disabled, and older ones have no adaptive thinking. Check the model's documentation.

Tips & Tricks

  • When combined with vars, you can swap models at runtime: MODEL=gpt-4o python main.py. See Chapter 3.
  • If you omit model on an agent entirely, strands picks its built-in default (Bedrock). This is fine for quick tests but explicit is better for production.
  • The params dict preserves types from YAML — integers stay integers, floats stay floats. This matters for parameters like max_tokens that must be an int.

Next: Chapter 3 — Variables →