rig_agent/agent/run/output_mode.rs
1//! How an agent enforces its `output_schema` (see issue #1928).
2//!
3//! Mirrors pydantic-ai's output modes. When an agent has both tools and an
4//! `output_schema`, applying the provider's native structured-output constraint
5//! (`format`/`response_format`) on every turn suppresses tool calls — the model
6//! is forced to emit schema JSON instead of using its tools. `OutputMode` lets
7//! the structured output be produced as a *tool call* the model makes after
8//! using its tools, which composes correctly.
9
10use serde::{Deserialize, Serialize};
11
12/// Controls how an agent's `output_schema` is enforced.
13///
14/// # Strictness
15///
16/// [`Native`](OutputMode::Native) is the only mode whose output is *constrained*
17/// by the provider — the response is guaranteed to match the schema (on
18/// providers that support it). [`Tool`](OutputMode::Tool) and
19/// [`Prompted`](OutputMode::Prompted) are **best-effort**: the schema is offered
20/// to the model (as a tool or in the prompt) but the model is asked, not forced,
21/// to honor it, so the agent re-prompts a bounded number of times and otherwise
22/// validate the returned JSON before relying on it. The default
23/// [`Auto`](OutputMode::Auto) is provider-aware: for a tool + schema agent it
24/// routes to `Tool` only on providers whose native constraint would suppress
25/// tool calls, and keeps guaranteed `Native` structured output on providers that
26/// compose the two (e.g. OpenAI, Anthropic).
27#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
28pub enum OutputMode {
29 /// Resolve at request time: [`OutputMode::Tool`] when the agent has an
30 /// `output_schema` **and** at least one function tool (and the tool choice
31 /// permits the output-tool call), otherwise [`OutputMode::Native`]. This is
32 /// the default and only changes behavior for the tool + schema case (which is
33 /// broken under `Native` on providers whose native constraint suppresses
34 /// tool calls).
35 #[default]
36 Auto,
37 /// Register the schema as a synthetic "output tool" the model calls to
38 /// finalize. No native structured-output constraint is sent, so the model can
39 /// freely call its other tools first. Best-effort: the model is instructed to
40 /// call the output tool but is not forced to, so validate the result.
41 /// (pydantic-ai `ToolOutput`.)
42 Tool,
43 /// Use the provider's native structured output (`format`/`response_format`).
44 /// Constrains every turn, so the output is guaranteed to match the schema,
45 /// but may suppress tool calls on some providers (e.g. Ollama). (pydantic-ai
46 /// `NativeOutput`.)
47 Native,
48 /// Inject the schema into the system prompt and return the model's final text
49 /// verbatim. The caller parses it — the text is *not* guaranteed to be clean
50 /// JSON and may include prose or markdown fences, so extract/validate before
51 /// deserializing. Useful for weak/local models that lack reliable tool calling
52 /// or native structured output. (pydantic-ai `PromptedOutput`.)
53 Prompted,
54}