Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rust quickstart

Use the crate when the application making LLM requests is already written in Rust. The translation engine runs in-process; there is no proxy to start.

Availability: Rust: direct engine API · CLI/HTTP/clients: not used in this path

1. Add the dependencies

cargo add llmshim tokio serde_json

tokio and serde_json are direct dependencies of your application. llmshim does not re-export them.

Set at least one provider key before running the program:

export ANTHROPIC_API_KEY=sk-ant-...

2. Send a completion

use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let router = llmshim::router::Router::from_env();

    // The public request contract is a serde_json::Value.
    let request = json!({
        "model": "openai/gpt-5.6-sol",
        "messages": [
            {"role": "user", "content": "What is Rust in one sentence?"}
        ],
        "max_tokens": 128
    });

    let response = llmshim::completion(&router, &request).await?;
    let text = response["choices"][0]["message"]["content"]
        .as_str()
        .unwrap_or("");

    println!("{text}");
    Ok(())
}

Run it with cargo run. Router::from_env() registers the providers whose environment variables are present. Change only the model address to route the same conversation elsewhere.

The result uses an OpenAI Chat Completions-style shape, regardless of which provider answered. The assistant text is at response["choices"][0]["message"]["content"].

3. Stream content

Streaming uses the StreamExt trait, so add futures as a direct dependency:

cargo add futures

This complete example prints text deltas as they arrive:

use futures::StreamExt;
use serde_json::json;
use std::io::{self, Write};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let router = llmshim::router::Router::from_env();
    let request = json!({
        "model": "anthropic/claude-sonnet-5",
        "messages": [
            {"role": "user", "content": "Write a haiku about Rust."}
        ],
        "max_tokens": 128
    });

    let mut stream = llmshim::stream(&router, &request).await?;

    while let Some(chunk) = stream.next().await {
        let chunk = chunk?;
        let parsed: serde_json::Value = serde_json::from_str(&chunk)?;

        if let Some(text) = parsed
            .pointer("/choices/0/delta/content")
            .and_then(|value| value.as_str())
        {
            print!("{text}");
            io::stdout().flush()?;
        }
    }

    println!();
    Ok(())
}

Each item is a JSON string in the normalized Chat Completions delta shape. A chunk can carry something other than text, such as reasoning, usage, or a tool call, so production code should inspect the fields it needs.

For config-file loading, aliases, and model inference, see Models and the Router. Runnable repository examples are available in examples/chat.rs and examples/stream.rs.