This guide gets you from zero to a running text turn in about five minutes.

On This Page

Installation

Add lutum to your Cargo.toml. Choose the feature flag for the provider you want to use:

[dependencies]
# OpenAI Responses API (also works with Ollama and OpenAI-compatible endpoints)
lutum = { version = "0.1.0", features = ["openai"] }
tokio = { version = "1", features = ["macros", "rt"] }
[dependencies]
# Anthropic Claude Messages API
lutum = { version = "0.1.0", features = ["claude"] }
tokio = { version = "1", features = ["macros", "rt"] }

For structured output or tool schemas, also add:

schemars = { version = "1", features = ["derive"] }
serde = { version = "1", features = ["derive"] }

First Text Turn

Build a Lutum context, open a Session, push messages, run a turn, and commit the result:

use std::sync::Arc;
use lutum::{Lutum, ModelName, OpenAiAdapter, Session, SharedPoolBudgetManager, SharedPoolBudgetOptions};

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let adapter = OpenAiAdapter::new(std::env::var("OPENAI_API_KEY")?)
        .with_default_model(ModelName::new("gpt-4o-mini")?);

    let llm = Lutum::new(
        Arc::new(adapter),
        SharedPoolBudgetManager::new(SharedPoolBudgetOptions::default()),
    );

    let mut session = Session::new();
    session.push_system("You are concise.");
    session.push_user("Explain what a borrow checker does in one sentence.");

    let result = session
        .text_turn(&llm)
        .collect()
        .await?;

    println!("{}", result.assistant_text());
    session.commit_text(result);

    Ok(())
}

Key points:

  • Lutum::new(adapter, budget) creates the execution context.
  • Session::new() opens a transcript.
  • session.push_user(...) and session.push_system(...) stage messages.
  • .collect().await? runs the turn and reduces the event stream into a result.
  • session.commit_text(result) stores the turn in the transcript. Nothing is stored until you call a commit_* method.

First Structured Turn

Replace text_turn() with structured_turn::<O>() where O derives JsonSchema and Deserialize:

use lutum::{Session, StructuredTurnOutcome};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
struct Sentiment {
    label: String,   // "positive", "negative", or "neutral"
    score: f32,      // 0.0 – 1.0
}

// reuse the session from above, or build a new one
session.push_user("Classify the sentiment of: 'I love this crate!'");

let result = session
    .structured_turn::<Sentiment>(&llm)
    .collect()
    .await?;

match result.semantic.clone() {
    StructuredTurnOutcome::Structured(s) => {
        println!("{} ({:.2})", s.label, s.score);
        session.commit_structured(result);
    }
    StructuredTurnOutcome::Refusal(reason) => eprintln!("refused: {reason}"),
}

Without Session

If you don't need transcript management, drive turns directly from Lutum:

use lutum::{Lutum, ModelInput};

let input = ModelInput::builder()
    .system("You are concise.")
    .user("What is 7 × 6?")
    .build();

let result = llm
    .text_turn(input)
    .collect()
    .await?;

println!("{}", result.assistant_text());

Next Steps

  • Execution Model — understand the three-layer model and transcript storage
  • Tools — add tool calling to your turns
  • Session — explicit transcript management, multi-turn loops, and branching
  • Adapters — configure Claude, OpenAI, or OpenRouter