Skip to main content

rig_core/completion/options/
mapping.rs

1//! What a completion wire answers for each option: [`OptionFields`] is the
2//! request's options as a wire reads them, and [`OptionMap`] holds one
3//! [`Mapping`] per option. Neither has `..` or a default, so a new option
4//! fails to compile in every wire until it answers for it.
5
6use serde_json::Value;
7
8use super::{CacheRetention, Reasoning, ServiceTier, Verbosity};
9
10/// Every option but the policy, borrowed, as a wire's
11/// [`map_options`](crate::completion::ReplayTarget::map_options) reads it.
12/// Not `#[non_exhaustive]`, so wires in companion crates destructure it
13/// whole, which `#[non_exhaustive]` forbids for
14/// [`GenerationOptions`](super::GenerationOptions).
15#[derive(Clone, Copy, Debug, PartialEq)]
16pub struct OptionFields<'a> {
17    /// How much the model reasons.
18    pub reasoning: Option<&'a Reasoning>,
19    /// How long the prompt prefix stays cached.
20    pub cache: Option<&'a CacheRetention>,
21    /// The processing tier.
22    pub service_tier: Option<&'a ServiceTier>,
23    /// How long the answer should be.
24    pub verbosity: Option<&'a Verbosity>,
25    /// Whether several tool calls may come in one turn.
26    pub parallel_tool_calls: Option<bool>,
27    /// Nucleus sampling probability mass.
28    pub top_p: Option<f64>,
29    /// Sampling seed.
30    pub seed: Option<u64>,
31    /// Stop sequences; empty means unset.
32    pub stop: &'a [String],
33    /// The key the provider routes prompt-cache lookups by.
34    pub cache_key: Option<&'a str>,
35}
36
37impl OptionFields<'_> {
38    /// Which options are set, in [`OptionMap`] field order.
39    pub(crate) fn set(&self) -> [bool; 9] {
40        let OptionFields {
41            reasoning,
42            cache,
43            service_tier,
44            verbosity,
45            parallel_tool_calls,
46            top_p,
47            seed,
48            stop,
49            cache_key,
50        } = self;
51        [
52            reasoning.is_some(),
53            cache.is_some(),
54            service_tier.is_some(),
55            verbosity.is_some(),
56            parallel_tool_calls.is_some(),
57            top_p.is_some(),
58            seed.is_some(),
59            !stop.is_empty(),
60            cache_key.is_some(),
61        ]
62    }
63}
64
65/// What a wire does with one option.
66#[non_exhaustive]
67#[derive(Clone, Debug, PartialEq)]
68pub enum Mapping {
69    /// The option is unset. For a set option this is an encode error.
70    Nothing,
71    /// Deep-merge this JSON object into the body, above the wire's own
72    /// encoding of the request and below `additional_params`.
73    Send(Value),
74    /// Honour the option by sending nothing: the provider default already
75    /// does what was asked. Logged at `debug` with the reason.
76    Omit(&'static str),
77    /// Honour the option through markers the wire's base builder writes
78    /// inside the body's arrays. Valid for `cache` only.
79    Place,
80    /// The wire or model cannot honour it: an error under
81    /// [`OnUnsupported::Error`](super::OnUnsupported::Error), a warning
82    /// under [`OnUnsupported::Ignore`](super::OnUnsupported::Ignore).
83    Unsupported(String),
84}
85
86impl Mapping {
87    /// `Nothing` for an unset option, otherwise what `map` answers for it.
88    pub fn of<T>(value: Option<T>, map: impl FnOnce(T) -> Mapping) -> Mapping {
89        value.map_or(Mapping::Nothing, map)
90    }
91
92    /// `Nothing` for an empty stop list, otherwise what `map` answers.
93    pub fn of_stop(stop: &[String], map: impl FnOnce(&[String]) -> Mapping) -> Mapping {
94        if stop.is_empty() {
95            Mapping::Nothing
96        } else {
97            map(stop)
98        }
99    }
100
101    /// The refusal for `reason`. Answer it only for a set option, through
102    /// [`Mapping::of`] or [`Mapping::of_stop`]: an unset option answers
103    /// [`Mapping::Nothing`].
104    pub fn unsupported(reason: impl Into<String>) -> Mapping {
105        Mapping::Unsupported(reason.into())
106    }
107
108    /// The answer for [`cache_key`](super::GenerationOptions::cache_key) of
109    /// a wire whose provider routes its prompt cache without a key:
110    /// `Nothing` when unset, else honoured by sending nothing, as the key
111    /// is only a routing hint.
112    pub fn unrouted(cache_key: Option<&str>) -> Mapping {
113        Mapping::of(cache_key, |_| {
114            Mapping::Omit("the provider routes its prompt cache without a key")
115        })
116    }
117}
118
119/// One [`Mapping`] per option, by field name. Not `#[non_exhaustive]` and no
120/// `Default`, so a wire writes every field.
121#[derive(Clone, Debug, PartialEq)]
122pub struct OptionMap {
123    /// [`GenerationOptions::reasoning`](super::GenerationOptions::reasoning).
124    pub reasoning: Mapping,
125    /// [`GenerationOptions::cache`](super::GenerationOptions::cache).
126    pub cache: Mapping,
127    /// [`GenerationOptions::service_tier`](super::GenerationOptions::service_tier).
128    pub service_tier: Mapping,
129    /// [`GenerationOptions::verbosity`](super::GenerationOptions::verbosity).
130    pub verbosity: Mapping,
131    /// [`GenerationOptions::parallel_tool_calls`](super::GenerationOptions::parallel_tool_calls).
132    pub parallel_tool_calls: Mapping,
133    /// [`GenerationOptions::top_p`](super::GenerationOptions::top_p).
134    pub top_p: Mapping,
135    /// [`GenerationOptions::seed`](super::GenerationOptions::seed).
136    pub seed: Mapping,
137    /// [`GenerationOptions::stop`](super::GenerationOptions::stop).
138    pub stop: Mapping,
139    /// [`GenerationOptions::cache_key`](super::GenerationOptions::cache_key).
140    pub cache_key: Mapping,
141}
142
143impl OptionMap {
144    /// Every option's name and answer, in field order.
145    pub(super) fn into_slots(self) -> [(&'static str, Mapping); 9] {
146        let OptionMap {
147            reasoning,
148            cache,
149            service_tier,
150            verbosity,
151            parallel_tool_calls,
152            top_p,
153            seed,
154            stop,
155            cache_key,
156        } = self;
157        [
158            ("reasoning", reasoning),
159            ("cache", cache),
160            ("service_tier", service_tier),
161            ("verbosity", verbosity),
162            ("parallel_tool_calls", parallel_tool_calls),
163            ("top_p", top_p),
164            ("seed", seed),
165            ("stop", stop),
166            ("cache_key", cache_key),
167        ]
168    }
169}