rig_core/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)]
28#[non_exhaustive]
29pub enum OutputMode {
30 /// Resolve at request time: [`OutputMode::Tool`] when the agent has an
31 /// `output_schema` **and** at least one function tool (and the tool choice
32 /// permits the output-tool call), otherwise [`OutputMode::Native`]. This is
33 /// the default and only changes behavior for the tool + schema case (which is
34 /// broken under `Native` on providers whose native constraint suppresses
35 /// tool calls).
36 #[default]
37 Auto,
38 /// Register the schema as a synthetic "output tool" the model calls to
39 /// finalize. No native structured-output constraint is sent, so the model can
40 /// freely call its other tools first. Best-effort: the model is instructed to
41 /// call the output tool but is not forced to, so validate the result.
42 /// (pydantic-ai `ToolOutput`.)
43 Tool,
44 /// Use the provider's native structured output (`format`/`response_format`).
45 /// Constrains every turn, so the output is guaranteed to match the schema,
46 /// but may suppress tool calls on some providers (e.g. Ollama). (pydantic-ai
47 /// `NativeOutput`.)
48 Native,
49 /// Inject the schema into the system prompt and return the model's final text
50 /// verbatim. The caller parses it — the text is *not* guaranteed to be clean
51 /// JSON and may include prose or markdown fences, so extract/validate before
52 /// deserializing. Useful for weak/local models that lack reliable tool calling
53 /// or native structured output. (pydantic-ai `PromptedOutput`.)
54 Prompted,
55}