Skip to content
Everruns Cloud is open in early access. Run agents without operating the platform.

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.

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.

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?;

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}");
}
}

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 the AgentLoopError and its full LlmError classification.

The first two are caught before any request leaves the process.

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.