> ## Documentation Index
> Fetch the complete documentation index at: https://rydantic.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Build a typed agent

> Classify a support request with a validated output model and one bounded correction.

Route a support message to a department and return an urgency flag and confidence score. Start with a deterministic local provider so you can observe invalid output and its correction on every run.

15 minutesOffline providerValidated final output

## 1. Set up the project

```sh theme={"system"}
cargo new support-triage
cd support-triage
```

Add these dependencies to the generated manifest:

```toml Cargo.toml theme={"system"}
[dependencies]
rydantic-ai = { version = "=0.6.0", default-features = false }
serde_json = "1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
```

Import `Model` through `rydantic_ai::rydantic` to use the same core model version as the agent. The offline example needs no HTTP transport.

## 2. Define the result you will accept

`Triage` contains a `Department` enum, a boolean `urgent` flag and a confidence between `0.0` and `1.0`. Enum variants serialize to `billing` or `technical`; unknown result fields are rejected.

The agent's type is `Agent<(), Triage>`. The first parameter is application dependency data; `()` means this example has none. The second is the model the final output must satisfy.

## 3. Add the local provider

Copy the complete source into `src/main.rs`. It is also available at `crates/rydantic-ai/examples/typed_agent.rs`.

```rust crates/rydantic-ai/examples/typed_agent.rs theme={"system"}
//! Offline output validation and correction. No API credentials or network needed.
use rydantic_ai::{
    rydantic::Model, Agent, AgentLimits, BoxFuture, ModelProvider, ModelRequest, ModelResponse,
    Result, RunContext,
};
use serde_json::json;
use std::sync::{
    atomic::{AtomicUsize, Ordering},
    Arc,
};

#[derive(Debug, PartialEq, Model)]
enum Department {
    #[model(rename = "billing")]
    Billing,
    #[model(rename = "technical")]
    Technical,
}

#[derive(Debug, Model)]
#[model(extra = "forbid")]
struct Triage {
    department: Department,
    urgent: bool,
    #[model(ge = 0.0, le = 1.0)]
    confidence: f64,
}

#[derive(Default)]
struct DemoProvider {
    calls: AtomicUsize,
}

impl ModelProvider for DemoProvider {
    fn complete(
        &self,
        _request: ModelRequest,
        _context: RunContext,
    ) -> BoxFuture<'_, Result<ModelResponse>> {
        Box::pin(async move {
            // The first response deliberately violates the output model.
            let confidence = if self.calls.fetch_add(1, Ordering::SeqCst) == 0 {
                1.2
            } else {
                0.9
            };
            let text = json!({
                "department": "billing", "urgent": true, "confidence": confidence
            })
            .to_string();
            ModelResponse::from_value(json!({
                "status": "completed",
                "output": [{"type": "message", "content": [
                    {"type": "output_text", "text": text}
                ]}],
                "usage": {"input_tokens": 10, "output_tokens": 5}
            }))
        })
    }
}

#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
    let agent = Agent::<(), Triage>::new(Arc::new(DemoProvider::default()), "demo-triage")
        .instructions("Route support requests to billing or technical support.")
        .limits(AgentLimits {
            max_model_calls: 2,
            max_validation_retries: 1,
            ..Default::default()
        });
    let result = agent
        .run(
            "Payment failed for three days. Please help now.",
            Arc::new(()),
            RunContext::default(),
        )
        .await?;
    assert_eq!(result.output.department, Department::Billing);
    assert_eq!(result.output.confidence, 0.9);
    assert_eq!(result.model_calls, 2);
    assert_eq!(result.usage.total()?, 30);
    println!("{}", result.output.dump_json()?);
    println!(
        "model_calls={} total_tokens={}",
        result.model_calls,
        result.usage.total()?
    );
    Ok(())
}
```

`DemoProvider` returns fixed example responses and simulated token counts. It performs no inference. Its first response uses confidence `1.2`, which violates the model. The second uses `0.9`.

## 4. Observe validation correction

```sh theme={"system"}
cargo run
```

From a source checkout:

```sh theme={"system"}
cargo run --locked -p rydantic-ai --no-default-features --example typed_agent
```

Expected output:

```text theme={"system"}
{"confidence":0.9,"department":"billing","urgent":true}
model_calls=2 total_tokens=30
```

The first result fails model validation. The agent adds structured error feedback to the next request, then accepts the second result. `max_validation_retries: 1` allows one correction, and `max_model_calls: 2` caps the total calls. Usage includes both completed responses.

Change the provider to always return `1.2`. The run terminates with an output-validation error after the retry budget is used. Restore the valid second response to continue.

## 5. Connect a real API

Enable the default HTTP feature by changing the dependency to `rydantic-ai = "=0.6.0"`. Keep `Triage`, and replace the demo provider and model name with your configured provider:

```rust theme={"system"}
use rydantic_ai::{Api, HttpProvider};

let provider = Arc::new(HttpProvider::from_env(Api::ChatCompletions)?);
let model = std::env::var("MODEL")?;
let agent = Agent::<(), Triage>::new(provider, model)
    .instructions("Route support requests to billing or technical support.");
```

Set `BASEURL`, `MODEL` and optional `APIKEY` in your local environment. Remove the demo's fixed-output and fixed-usage assertions when using a real model. Model capability and credentials determine whether that run succeeds.

Choose `Api::Responses` or `Api::AnthropicMessages` for the other supported protocols. The [provider guide](/integrations/api-providers) explains URL prefixes, authentication and schema capabilities.

> **[Next: stream structured results](/tutorials/streaming-ai)**
>
> Add partial previews and validated list output.

> **[Tools, graphs and multimodal inputs](/examples/agents)**
>
> Run focused agent examples.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.