# Providers and reasoning

> Run one agent definition on OpenAI, Azure, Anthropic, Ollama, AWS Bedrock or any OpenAI-compatible gateway, keep reasoning items across turns, and know how each provider treats tool schemas.

You end up with one agent definition that runs on whichever provider you pick at startup, with the model's reasoning carried from one turn to the next.

```rust
/// Builds the same agent for any chat completion client.
fn changelog_agent(llm: &(impl ChatCompletion + Clone + 'static)) -> anyhow::Result<Agent> {
    let agent = Agent::builder()
        .llm(llm)
        .system_prompt("You summarize git history for release notes.")
        .limit(20)
        .build()?;
    Ok(agent)
}
```

Source: [`snippets/src/agents/providers.rs#L14-L22`](https://github.com/bosun-ai/swiftide-website/blob/master/snippets/src/agents/providers.rs#L14-L22)

```rust
let mut agent = match std::env::var("LLM_PROVIDER").as_deref() {
    Ok("anthropic") => changelog_agent(
        &Anthropic::builder()
            .default_prompt_model("claude-sonnet-4-5")
            .build()?,
    )?,
    Ok("ollama") => changelog_agent(&Ollama::builder().default_prompt_model("qwen3").build()?)?,
    Ok("azure") => {
        let config = AzureConfig::new()
            .with_api_base("https://my-resource.openai.azure.com")
            .with_api_version("2024-10-21")
            .with_deployment_id("gpt-4o-mini")
            .with_api_key(std::env::var("AZURE_OPENAI_API_KEY")?);
        let azure: GenericOpenAI<AzureConfig> = GenericOpenAIBuilder::default()
            .client(async_openai::Client::with_config(config))
            .default_prompt_model("gpt-4o-mini")
            .build()?;
        changelog_agent(&azure)?
    }
    _ => changelog_agent(
        &OpenAI::builder()
            .default_prompt_model("gpt-5-mini")
            .build()?,
    )?,
};

agent
    .query("Summarize the commits since the last tag.")
    .await?;
```

Source: [`snippets/src/agents/providers.rs#L28-L56`](https://github.com/bosun-ai/swiftide-website/blob/master/snippets/src/agents/providers.rs#L28-L56)

`AgentBuilder::llm` takes a reference to any client that implements `ChatCompletion + Clone + 'static`. It stores a boxed clone, so `Agent` has no type parameter for the provider. The tools, hooks and system prompt stay the same whichever client you pass.

The Azure client needs the `async-openai` crate as a direct dependency, because Swiftide does not re-export its client type. `api_base` and `api_key` on the builder only configure the `OpenAI` client, so Azure passes a full client to `client(..)`:

```sh
cargo add async-openai@0.41 --no-default-features
```

Use the same version Swiftide depends on, or the client types do not match.

## Chat providers

Each chat client sits behind its own feature flag. The snippet above needs `swiftide-agents` and three provider flags:

```sh
cargo add swiftide@0.34 --features swiftide-agents,openai,anthropic,ollama
```

Every chat client implements `ChatCompletion`. [Providers](/models/overview/#providers) lists each client with its feature flag, and [OpenAI-compatible endpoints](/models/overview/#openai-compatible-endpoints) covers gateways and base URLs.

The ask-your-codebase showcase builds its client from a base URL, a key and a flag for the Responses API, so the same binary runs against OpenAI or a gateway. It sets them with `api_base` and `api_key`:

```rust title="examples/ask-your-codebase/src/llm.rs"
impl LlmEndpoint {
    /// Builds a client for this endpoint.
    ///
    /// Rate limits and transient errors are retried with exponential backoff, which matters when
    /// indexing fires many prompts concurrently. After `retry_budget` the error is returned, so
    /// an unreachable or broken endpoint fails instead of hanging.
    pub fn client(&self, retry_budget: Duration) -> Result<Arc<dyn SimplePrompt>> {
        let openai = OpenAI::builder()
            .api_base(&self.base_url)
            .api_key(&self.api_key)
            .default_prompt_model(&self.model)
            .use_responses_api(matches!(self.api, OpenAiApi::Responses))
            .build()?;

        let backoff = BackoffConfiguration {
            max_elapsed_time_sec: retry_budget.as_secs(),
            ..BackoffConfiguration::default()
        };

        Ok(Arc::new(LanguageModelWithBackOff::new(openai, backoff)))
    }
}
```

Source: [`examples/ask-your-codebase/src/llm.rs#L79-L100`](https://github.com/bosun-ai/swiftide/blob/e377c4a2e76fe2559629ae0efdae3c6e0fb4d829/examples/ask-your-codebase/src/llm.rs#L79-L100)

## Bedrock agents

`AwsBedrock` talks to the Bedrock Converse API. It reads AWS credentials and the region from your usual Amazon Web Services (AWS) configuration, such as a command-line interface (CLI) profile or environment variables:

```rust
let bedrock = AwsBedrock::builder()
    .default_prompt_model("global.anthropic.claude-sonnet-4-6")
    .build()?;
```

Source: [`examples/aws_bedrock_agent.rs#L61-L63`](https://github.com/bosun-ai/swiftide/blob/e377c4a2e76fe2559629ae0efdae3c6e0fb4d829/examples/aws_bedrock_agent.rs#L61-L63)

The model ID here is a global inference profile. The agent itself looks like any other:

```rust
let mut agent = agents::Agent::builder()
    .llm(&bedrock)
    .tools(vec![current_utc_time(), format_timestamp()])
    .on_new_message(|_, msg| {
        let rendered = msg.to_string();
        Box::pin(async move {
            println!("{rendered}");
            Ok(())
        })
    })
    .limit(6)
    .build()?;
```

Source: [`examples/aws_bedrock_agent.rs#L67-L78`](https://github.com/bosun-ai/swiftide/blob/e377c4a2e76fe2559629ae0efdae3c6e0fb4d829/examples/aws_bedrock_agent.rs#L67-L78)

Setup and credentials are on [AWS Bedrock](/integrations/aws-bedrock/).

## Reasoning items

On `OpenAI`, reasoning models return their reasoning only through the Responses API. `AwsBedrock` returns signed Claude reasoning as `ReasoningItem` values without extra setup. Reasoning on `OpenAI` needs the Responses API and a reasoning effort; [Responses API and reasoning](/models/overview/#responses-api-and-reasoning) covers the client options.

```rust
// Reasoning models require the Responses API. Enabling reasoning effort also asks for a
// summary and encrypted reasoning content (enabled by default). If your OpenAI org is not
// verified for reasoning access, summaries may be absent. Disable with
// `reasoning_features(false)` if desired.
let openai = OpenAI::builder()
    .default_prompt_model("o3-mini")
    .default_options(Options::builder().reasoning_effort(ReasoningEffort::Low))
    .use_responses_api(true)
    .build()?;
```

Source: [`examples/responses_api_reasoning.rs#L34-L42`](https://github.com/bosun-ai/swiftide/blob/e377c4a2e76fe2559629ae0efdae3c6e0fb4d829/examples/responses_api_reasoning.rs#L34-L42)

Each `ReasoningItem` has a `summary` list and an optional `encrypted_content`. This helper joins the summaries:

```rust
fn reasoning_summary(reasoning: Option<&[ReasoningItem]>) -> Option<String> {
    let summary = reasoning
        .unwrap_or(&[])
        .iter()
        .flat_map(|item| item.summary.iter())
        .cloned()
        .collect::<Vec<_>>()
        .join("\n");

    if summary.is_empty() {
        None
    } else {
        Some(summary)
    }
}
```

Source: [`examples/responses_api_reasoning.rs#L10-L24`](https://github.com/bosun-ai/swiftide/blob/e377c4a2e76fe2559629ae0efdae3c6e0fb4d829/examples/responses_api_reasoning.rs#L10-L24)

The agent reads them in an `after_completion` hook:

```rust
let mut agent = Agent::builder()
    .llm(&openai)
    .on_new_message(|_, message| {
        if let ChatMessage::Assistant(content, _) = message
            && let Some(content) = content.as_deref()
        {
            println!("Assistant: {content}");
        }
        Box::pin(async move { Ok(()) })
    })
    .after_completion(|_, response| {
        if let Some(summary) = reasoning_summary(response.reasoning.as_deref()) {
            println!("Reasoning summary:\n{summary}");
        }

        let has_encrypted = response
            .reasoning
            .as_ref()
            .is_some_and(|items| items.iter().any(|item| item.encrypted_content.is_some()));
        println!("Encrypted reasoning content present: {has_encrypted}");
        Box::pin(async move { Ok(()) })
    })
    .build()?;
```

Source: [`examples/responses_api_reasoning.rs#L46-L68`](https://github.com/bosun-ai/swiftide/blob/e377c4a2e76fe2559629ae0efdae3c6e0fb4d829/examples/responses_api_reasoning.rs#L46-L68)

After the `after_completion` hooks run, the agent adds every item in `response.reasoning` to the message history as a `ChatMessage::Reasoning`. It adds them before the assistant message, through `Agent::add_message`, so `on_new_message` hooks see them too. A hook that empties `response.reasoning` keeps the items out of the history.

## Reasoning replay

What happens to those items on the next request depends on the client:

- The Responses API client replays them only when `reasoning_effort` is set on that client. It then sends back every item with a non-empty `encrypted_content` and sets `store` to false, so the reasoning lives in your message history and survives a saved and restored history (see [Context and history](/agents/context-and-history/)). Without `reasoning_effort`, it drops every `ChatMessage::Reasoning`.
- `AwsBedrock` replays an item only when it has non-empty `content` and a non-empty `encrypted_content`, which it sends as the signature. It drops anything else.
- The Chat Completions client and `Anthropic` leave `ChatMessage::Reasoning` out of their requests.

## Bedrock reasoning effort

`AwsBedrock` has its own `Options` and its own `ReasoningEffort` enum in `swiftide::integrations::aws_bedrock_v2`. Set `reasoning_effort` in those options and pass them to `default_options` on the builder. For the Opus 4.5 beta header and the other fields, see [AWS Bedrock settings](/models/overview/#aws-bedrock-settings).

## Tool schema policies

`ToolSpec::builder().build()` checks every schema against Swiftide's strict contract (see [Tools](/agents/tools/)) and rewrites what it can: a nullable type such as `["string", "null"]` becomes an `anyOf` with a null branch, a two-entry `oneOf` with a null branch becomes an `anyOf`, annotations next to a `$ref` are dropped, and every object gets `additionalProperties: false`.

Each provider then applies its own policy when it builds a request. Only `AwsBedrock` lets you change it.

- `OpenAI` and the other `GenericOpenAI` clients remove `$schema` and Rust integer formats such as `int32`, and list every property in `required`. They reject any remaining `oneOf` or array-valued `type`, and send the schema with `strict: true`.
- `Anthropic` and `AwsBedrock` send the strict schema unchanged. `AwsBedrock` marks tools strict unless you set `tool_strict` to `false` in its `Options`.

## Gotchas

- The OpenAI schema policy runs when the request is built, not when the tool is built. A schema it rejects fails the first completion with `AgentError::CompletionsFailed`, wrapping a permanent error that says `tool schema must be OpenAI compatible`.
- Gateways do not always speak the Responses API. Against the gateway used for the showcase runs, the Responses API fails because the gateway streams events with `"output": null`, which the client cannot parse. Use it with OpenAI itself.
- OpenAI rejects the `:` in Model Context Protocol (MCP) tool names; see [Tool names](/agents/mcp/#tool-names).
- Reasoning items are tied to the provider that made them. Bedrock items carry a Bedrock signature in `encrypted_content`, and an OpenAI Responses client with `reasoning_effort` set sends that to OpenAI as encrypted reasoning. OpenAI items usually have no `content`, so `AwsBedrock` drops them silently. Remove `ChatMessage::Reasoning` entries from the message history before you switch providers.
- `AwsBedrock::builder().build()` loads the AWS configuration with `block_in_place`, so it needs a multi-threaded Tokio runtime. A default `#[tokio::test]` or `flavor = "current_thread"` panics. Pass your own client with `.client(..)` otherwise.
- A client's `on_usage` callback counts every call on that client, including other agents that share it; see [Usage callbacks](/models/errors-backoff-and-usage/#usage-callbacks).

## Reference

- [`AgentBuilder::llm`](https://docs.rs/swiftide/0.34/swiftide/agents/struct.AgentBuilder.html#method.llm)
- [`AgentBuilder::after_completion`](https://docs.rs/swiftide/0.34/swiftide/agents/struct.AgentBuilder.html#method.after_completion)
- [`AgentBuilder::on_new_message`](https://docs.rs/swiftide/0.34/swiftide/agents/struct.AgentBuilder.html#method.on_new_message)
- [`Agent::add_message`](https://docs.rs/swiftide/0.34/swiftide/agents/struct.Agent.html#method.add_message)
- [`ChatCompletion`](https://docs.rs/swiftide/0.34/swiftide/traits/trait.ChatCompletion.html)
- [`GenericOpenAI`](https://docs.rs/swiftide/0.34/swiftide/integrations/openai/struct.GenericOpenAI.html)
- [`GenericOpenAIBuilder::use_responses_api`](https://docs.rs/swiftide/0.34/swiftide/integrations/openai/struct.GenericOpenAIBuilder.html#method.use_responses_api)
- [`Options` (OpenAI)](https://docs.rs/swiftide/0.34/swiftide/integrations/openai/struct.Options.html)
- [`AzureConfig`](https://docs.rs/swiftide/0.34/swiftide/integrations/openai/struct.AzureConfig.html)
- [`Anthropic`](https://docs.rs/swiftide/0.34/swiftide/integrations/anthropic/struct.Anthropic.html)
- [`Ollama`](https://docs.rs/swiftide/0.34/swiftide/integrations/ollama/struct.Ollama.html)
- [`AwsBedrock`](https://docs.rs/swiftide/0.34/swiftide/integrations/aws_bedrock_v2/struct.AwsBedrock.html)
- [`Options` (AWS Bedrock)](https://docs.rs/swiftide/0.34/swiftide/integrations/aws_bedrock_v2/struct.Options.html)
- [`ReasoningEffort` (AWS Bedrock)](https://docs.rs/swiftide/0.34/swiftide/integrations/aws_bedrock_v2/enum.ReasoningEffort.html)
- [`ChatCompletionResponse`](https://docs.rs/swiftide/0.34/swiftide/chat_completion/struct.ChatCompletionResponse.html)
- [`ChatMessage`](https://docs.rs/swiftide/0.34/swiftide/chat_completion/enum.ChatMessage.html)
- [`ReasoningItem`](https://docs.rs/swiftide/0.34/swiftide/chat_completion/struct.ReasoningItem.html)
- [`ToolSpec`](https://docs.rs/swiftide/0.34/swiftide/chat_completion/struct.ToolSpec.html)
- [`AgentError`](https://docs.rs/swiftide/0.34/swiftide/agents/errors/enum.AgentError.html)

## Next

[Contrib tools and hooks](/agents/contrib/)