instructors
Type-safe structured output extraction from LLMs. The Rust instructor.
Define a Rust struct → instructors generates the JSON Schema → LLM returns valid JSON → you get a typed value. With automatic validation and retry.
Quick Start
use *;
let client = openai;
let result: = client
.extract
.model
.await?;
println!; // "John Doe"
println!; // Some("john@example.com")
println!;
Installation
[]
= "1"
Providers
| Provider | Constructor | Mechanism |
|---|---|---|
| OpenAI | Client::openai(key) |
response_format strict JSON Schema |
| Anthropic | Client::anthropic(key) |
tool_use with forced tool choice |
| OpenAI-compatible | Client::openai_compatible(key, url) |
Same as OpenAI (DeepSeek, Together, etc.) |
| Anthropic-compatible | Client::anthropic_compatible(key, url) |
Same as Anthropic |
| Google Gemini | Client::gemini(key) |
response_schema structured JSON |
| Gemini-compatible | Client::gemini_compatible(key, url) |
Same as Gemini |
// OpenAI
let client = openai;
// Anthropic
let client = anthropic;
// DeepSeek, Together, or any OpenAI-compatible API
let client = openai_compatible;
// Anthropic-compatible proxy
let client = anthropic_compatible;
// Google Gemini
let client = gemini;
// Gemini-compatible proxy
let client = gemini_compatible;
Validation
Validate extracted data with automatic retry — invalid results are fed back to the LLM with error details.
Closure-based
let user: User = client.extract
.validate
.await?.value;
Trait-based
use *;
let email: Email = client.extract.validated.await?.value;
List Extraction
Extract multiple items from text with extract_many:
let entities: = client
.extract_many
.await?.value;
Batch Processing
Process multiple prompts concurrently with configurable concurrency:
let prompts = vec!;
let results = client
.
.concurrency
.validate
.run
.await;
// each result is independent — partial failures don't affect others
for result in results
Multi-turn Conversations
Pass message history for context-aware extraction:
use Message;
let result = client.
.messages
.await?;
Streaming
Stream partial JSON tokens as they arrive:
let result = client.
.on_stream
.await?;
All three providers (OpenAI, Anthropic, Gemini) support streaming. The final result is assembled from all chunks and deserialized as usual.
Image Input
Extract structured data from images using vision-capable models:
use ImageInput;
// from URL
let result = client.
.image
.model
.await?;
// from base64
let result = client.
.image
.await?;
// multiple images
let result = client.
.images
.await?;
Provider Fallback
Chain multiple providers for automatic failover:
let client = openai
.with_fallback
.with_fallback;
// tries OpenAI first → Anthropic on failure → DeepSeek as last resort
let result = client..await?;
Each fallback is tried in order after the primary provider exhausts its retries.
Retry & Timeout
Enable exponential backoff on HTTP 429/503 errors and set an overall request timeout:
use Duration;
use BackoffConfig;
let client = openai
.with_retry_backoff // 500ms base, 30s cap, 3 retries
.with_timeout; // overall timeout
// per-request override
let result = client.
.retry_backoff
.timeout
.await?;
Without backoff configured, HTTP 429/503 errors fail immediately (default behavior unchanged).
Lifecycle Hooks
Observe requests and responses:
let result = client.
.on_request
.on_response
.await?;
Classification
Enums work naturally for classification tasks:
let sentiment: Sentiment = client
.extract
.await?.value;
Nested Types
Complex nested structures with vectors, options, and enums:
let paper: Paper = client.extract.model.await?.value;
Configuration
let result: MyStruct = client
.extract
.model // override model
.system // custom system prompt
.temperature // deterministic output
.max_tokens // limit output tokens
.max_retries // retry on parse/validation failure
.context // append to prompt
.retry_backoff // HTTP 429/503 backoff
.timeout // overall timeout
.await?
.value;
Client Defaults
Set defaults once, override per-request:
let client = openai
.with_model
.with_temperature
.with_max_retries
.with_system;
// all extractions use the defaults above
let a: TypeA = client.extract.await?.value;
let b: TypeB = client.extract.await?.value;
// override for a specific request
let c: TypeC = client.extract.model.await?.value;
Cost Tracking
Built-in token counting and cost estimation via tiktoken:
let result = client..await?;
println!;
println!;
println!;
println!;
Disable with default-features = false:
[]
= { = "1", = false }
JSON Repair
When an LLM returns malformed JSON — trailing commas, single quotes, unquoted keys, markdown code fences, etc. — instructors automatically attempts to repair the output before deserialization. If repair succeeds, the fixed JSON is parsed directly without burning a retry. This saves both tokens and latency, especially with smaller or open-source models that are more likely to produce slightly broken output.
Repair is transparent: you don't need to configure anything. It runs on every response before serde_json parsing, and falls back to the normal retry path if the output can't be fixed.
How It Works
#[derive(JsonSchema)]generates a JSON Schema from your Rust type (via schemars)- Schema is cached per type (thread-local, zero lock contention)
- The schema is transformed for the target provider:
- OpenAI: wrapped in
response_formatwith strict mode (additionalProperties: false, all fields required) - Anthropic: wrapped as a
toolwithinput_schema, forced viatool_choice - Gemini: passed as
response_schemawithresponse_mime_type: "application/json"
- OpenAI: wrapped in
- LLM is constrained to produce valid JSON matching the schema
- Response JSON is automatically repaired if malformed (trailing commas, single quotes, unquoted keys, markdown fences)
- Response is deserialized with
serde_json::from_str::<T>() - If
Validatetrait or.validate()closure is present, validation runs - On parse/validation failure, error feedback is sent back and the request is retried