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};
#[::(= "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(...)andsession.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 acommit_*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};
#[(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