Skip to content
Get Started

Error handling

Every fallible Rig call returns a typed error, so you can react to why something failed instead of parsing strings. The two you’ll meet most often are PromptError (from agent.prompt(...)) and CompletionError (from a raw completion request); embeddings return EmbeddingError and vector stores return VectorStoreError.

agent.prompt(...) returns Result<String, PromptError>. PromptError distinguishes a failed model call from a tool failure from hitting the multi-turn limit:

match agent.prompt("What is 2 + 2?").await {
Ok(reply) => println!("{reply}"),
Err(PromptError::MaxTurnsError { max_turns, .. }) => {
eprintln!("the model kept calling tools and hit the {max_turns}-turn limit");
}
Err(PromptError::ToolError(e)) => {
eprintln!("a tool returned an error: {e}");
}
Err(PromptError::CompletionError(e)) => {
eprintln!("the underlying model call failed: {e}");
}
Err(e) => eprintln!("other prompt error: {e}"),
}

CompletionError describes failures at the provider boundary — network, (de)serialization, or a provider-side rejection. Matching on it lets you separate transient failures (worth retrying) from fatal ones (worth surfacing):

match result {
Ok(text) => println!("{text}"),
// Transient — a retry may succeed.
Err(CompletionError::HttpError(e)) => eprintln!("network/timeout, retryable: {e}"),
// Fatal — the request or response was malformed.
Err(CompletionError::JsonError(e)) => eprintln!("(de)serialization failed: {e}"),
Err(CompletionError::ResponseError(msg)) => eprintln!("provider rejected it: {msg}"),
Err(e) => eprintln!("{e}"),
}

Rig doesn’t retry for you — that keeps the core predictable — so wrap calls in your own backoff loop. This is the idiomatic pattern: retry a bounded number of times with exponential backoff, and give up on the last attempt.

use std::time::Duration;
/// Prompt an agent, retrying a few times with exponential backoff.
async fn prompt_with_retry(
agent: &Agent<openai::responses_api::ResponsesCompletionModel>,
input: &str,
max_attempts: u32,
) -> Result<String, PromptError> {
let mut attempt = 0;
loop {
attempt += 1;
match agent.prompt(input).await {
Ok(reply) => return Ok(reply),
Err(e) if attempt < max_attempts => {
let backoff = Duration::from_millis(200 * 2u64.pow(attempt - 1));
eprintln!("attempt {attempt} failed ({e}); retrying in {backoff:?}");
tokio::time::sleep(backoff).await;
}
Err(e) => return Err(e),
}
}
}
attempt 1 failed (HttpError: request timed out); retrying in 200ms
attempt 2 failed (HttpError: request timed out); retrying in 400ms
# ...succeeds on attempt 3