Direct Model Calls
Some work is one prompt and one answer: classify a string, draft a summary, extract a field. That needs the provider edge — drivers, endpoints, credentials, retries, error classification — but none of the agent loop around it.
Model::complete is the whole API for that case:
use everruns::{Model, OpenAI};
let model = Model::new("gpt-5.6-terra", OpenAI::from_env()?);let answer = model.complete("Name the three primary colors.").await?;println!("{answer}");The model is the same value an agent takes, reached
through the same Provider. Nothing is
persisted: a direct completion owns no session, no history, and no workspace.
Reach for an agent as soon as the work needs tools, multiple turns, durability,
or events.
Offline by default
Section titled “Offline by default”Model::simulated needs no credentials and no network, so direct calls are
testable the same way agents are:
use everruns::Model;
let answer = Model::simulated("4").complete("What is 2 + 2?").await?;assert_eq!(answer, "4");See Testing and simulation for scripted multi-response simulators.
System messages, context, and controls
Section titled “System messages, context, and controls”Model::completion describes the call before sending it. Messages append in
call order; each control maps to one provider request field and stays unset
unless assigned, so the provider keeps its own defaults.
use everruns::{Model, ReasoningEffort};
let response = model .completion() .system("Answer with a single word.") .user("What is the capital of France?") .max_tokens(16) .reasoning_effort(ReasoningEffort::Low) .send() .await?;
println!("{}", response.text);println!("{:?} tokens", response.metadata.total_tokens);send returns the full LlmResponse — text, reasoning artifacts, tool calls,
and call metadata. text() returns only the answer text. Replay prior turns
with .assistant(...): the completion carries no history of its own, so
context is whatever the call passes.
When the model is a bare provider-visible id, attach the provider on the completion instead of the model:
use everruns::{Model, OpenAI};
let answer = Model::from("gpt-5.6-terra") .completion() .provider(OpenAI::from_env()?) .user("Summarize this in one line: ...") .text() .await?;Streaming
Section titled “Streaming”stream() returns the provider’s events as they arrive, ending with a Done
event carrying the call’s metadata:
use everruns::{LlmStreamEvent, Model};use futures::StreamExt;
let mut stream = model.completion().user("Write a haiku.").stream().await?;while let Some(event) = stream.next().await { if let LlmStreamEvent::TextDelta(delta) = event? { print!("{delta}"); }}Errors
Section titled “Errors”CompletionError separates configuration mistakes from provider failures:
MissingProvider— the model names an id but nothing says how to reach it.NoMessages— the completion was sent empty.Call(..)— the provider call failed, carrying theAgentLoopErrorand its fullLlmErrorclassification.
The first two are caught before any request leaves the process.
Going lower
Section titled “Going lower”Completion is a thin value-first layer over Provider, which is public.
Applications that already hold a Provider — or implement their own
ChatDriver — can call it directly with
everruns’s LlmMessage, LlmMessageRole, LlmCallConfig, and LlmResponse
re-exports:
use everruns::{LlmCallConfig, LlmMessage, LlmMessageRole, Provider};
let response = provider .chat_completion( vec![LlmMessage::text(LlmMessageRole::User, "What is 2 + 2?")], &LlmCallConfig::new("gpt-5.6-terra"), ) .await?;That surface is the driver boundary itself: every field of LlmCallConfig,
including tool definitions, is available, and nothing is defaulted for you.
The runnable version of this page is
direct_llm.rs,
which runs offline without an API key.