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};
9use crate::completion::CompletionRequest;
10
11/// Every option but the policy, borrowed, as a wire's
12/// [`map_options`](crate::completion::ReplayTarget::map_options) reads it.
13/// Not `#[non_exhaustive]`, so wires in companion crates destructure it
14/// whole, which `#[non_exhaustive]` forbids for
15/// [`GenerationOptions`](super::GenerationOptions).
16#[derive(Clone, Copy, Debug, PartialEq)]
17pub struct OptionFields<'a> {
18    /// How much the model reasons.
19    pub reasoning: Option<&'a Reasoning>,
20    /// How long the prompt prefix stays cached.
21    pub cache: Option<&'a CacheRetention>,
22    /// The processing tier.
23    pub service_tier: Option<&'a ServiceTier>,
24    /// How long the answer should be.
25    pub verbosity: Option<&'a Verbosity>,
26    /// Whether several tool calls may come in one turn.
27    pub parallel_tool_calls: Option<bool>,
28    /// Nucleus sampling probability mass.
29    pub top_p: Option<f64>,
30    /// Sampling seed.
31    pub seed: Option<u64>,
32    /// Stop sequences; empty means unset.
33    pub stop: &'a [String],
34    /// The key the provider routes prompt-cache lookups by.
35    pub cache_key: Option<&'a str>,
36}
37
38impl OptionFields<'_> {
39    /// Whether `option` is set. The request's `temperature` is not one of
40    /// these fields, so it reads as unset.
41    pub(crate) fn is_set(&self, option: OptionName) -> bool {
42        match option {
43            OptionName::Reasoning => self.reasoning.is_some(),
44            OptionName::Cache => self.cache.is_some(),
45            OptionName::ServiceTier => self.service_tier.is_some(),
46            OptionName::Verbosity => self.verbosity.is_some(),
47            OptionName::ParallelToolCalls => self.parallel_tool_calls.is_some(),
48            OptionName::TopP => self.top_p.is_some(),
49            OptionName::Seed => self.seed.is_some(),
50            OptionName::Stop => !self.stop.is_empty(),
51            OptionName::CacheKey => self.cache_key.is_some(),
52            OptionName::Temperature => false,
53        }
54    }
55}
56
57/// A generation option, or the request's `temperature`, which the catalog
58/// also refuses.
59#[derive(Clone, Copy, Debug, PartialEq, Eq)]
60pub(crate) enum OptionName {
61    Reasoning,
62    Cache,
63    ServiceTier,
64    Verbosity,
65    ParallelToolCalls,
66    TopP,
67    Seed,
68    Stop,
69    CacheKey,
70    Temperature,
71}
72
73impl OptionName {
74    /// The field name errors and logs give the option.
75    pub(crate) const fn as_str(self) -> &'static str {
76        match self {
77            Self::Reasoning => "reasoning",
78            Self::Cache => "cache",
79            Self::ServiceTier => "service_tier",
80            Self::Verbosity => "verbosity",
81            Self::ParallelToolCalls => "parallel_tool_calls",
82            Self::TopP => "top_p",
83            Self::Seed => "seed",
84            Self::Stop => "stop",
85            Self::CacheKey => "cache_key",
86            Self::Temperature => "temperature",
87        }
88    }
89
90    /// Unset the option on `request`.
91    pub(crate) fn clear(self, request: &mut CompletionRequest) {
92        let options = &mut request.options;
93        match self {
94            Self::Reasoning => options.reasoning = None,
95            Self::Cache => options.cache = None,
96            Self::ServiceTier => options.service_tier = None,
97            Self::Verbosity => options.verbosity = None,
98            Self::ParallelToolCalls => options.parallel_tool_calls = None,
99            Self::TopP => options.top_p = None,
100            Self::Seed => options.seed = None,
101            Self::Stop => options.stop.clear(),
102            Self::CacheKey => options.cache_key = None,
103            Self::Temperature => request.temperature = None,
104        }
105    }
106}
107
108/// What a wire does with one option.
109#[non_exhaustive]
110#[derive(Clone, Debug, PartialEq)]
111pub enum Mapping {
112    /// The option is unset. For a set option this is an encode error.
113    Nothing,
114    /// Deep-merge this JSON object into the body, above the wire's own
115    /// encoding of the request and below `additional_params`.
116    Send(Value),
117    /// Honour the option by sending nothing: the provider default already
118    /// does what was asked. Logged at `debug` with the reason.
119    Omit(&'static str),
120    /// Honour the option through markers the wire's base builder writes
121    /// inside the body's arrays. Valid for `cache` only.
122    Place,
123    /// The wire or model cannot honour it: an error under
124    /// [`OnUnsupported::Error`](super::OnUnsupported::Error), a warning
125    /// under [`OnUnsupported::Ignore`](super::OnUnsupported::Ignore).
126    Unsupported(String),
127}
128
129impl Mapping {
130    /// `Nothing` for an unset option, otherwise what `map` answers for it.
131    pub fn of<T>(value: Option<T>, map: impl FnOnce(T) -> Mapping) -> Mapping {
132        value.map_or(Mapping::Nothing, map)
133    }
134
135    /// `Nothing` for an empty stop list, otherwise what `map` answers.
136    pub fn of_stop(stop: &[String], map: impl FnOnce(&[String]) -> Mapping) -> Mapping {
137        if stop.is_empty() {
138            Mapping::Nothing
139        } else {
140            map(stop)
141        }
142    }
143
144    /// The refusal for `reason`. Answer it only for a set option, through
145    /// [`Mapping::of`] or [`Mapping::of_stop`]: an unset option answers
146    /// [`Mapping::Nothing`].
147    pub fn unsupported(reason: impl Into<String>) -> Mapping {
148        Mapping::Unsupported(reason.into())
149    }
150
151    /// The answer for [`cache_key`](super::GenerationOptions::cache_key) of
152    /// a wire whose provider routes its prompt cache without a key:
153    /// `Nothing` when unset, else honoured by sending nothing, as the key
154    /// is only a routing hint.
155    pub fn unrouted(cache_key: Option<&str>) -> Mapping {
156        Mapping::of(cache_key, |_| {
157            Mapping::Omit("the provider routes its prompt cache without a key")
158        })
159    }
160}
161
162/// One [`Mapping`] per option, by field name. Not `#[non_exhaustive]` and no
163/// `Default`, so a wire writes every field.
164#[derive(Clone, Debug, PartialEq)]
165pub struct OptionMap {
166    /// [`GenerationOptions::reasoning`](super::GenerationOptions::reasoning).
167    pub reasoning: Mapping,
168    /// [`GenerationOptions::cache`](super::GenerationOptions::cache).
169    pub cache: Mapping,
170    /// [`GenerationOptions::service_tier`](super::GenerationOptions::service_tier).
171    pub service_tier: Mapping,
172    /// [`GenerationOptions::verbosity`](super::GenerationOptions::verbosity).
173    pub verbosity: Mapping,
174    /// [`GenerationOptions::parallel_tool_calls`](super::GenerationOptions::parallel_tool_calls).
175    pub parallel_tool_calls: Mapping,
176    /// [`GenerationOptions::top_p`](super::GenerationOptions::top_p).
177    pub top_p: Mapping,
178    /// [`GenerationOptions::seed`](super::GenerationOptions::seed).
179    pub seed: Mapping,
180    /// [`GenerationOptions::stop`](super::GenerationOptions::stop).
181    pub stop: Mapping,
182    /// [`GenerationOptions::cache_key`](super::GenerationOptions::cache_key).
183    pub cache_key: Mapping,
184}
185
186impl OptionMap {
187    /// Every option's name and answer, in field order.
188    pub(super) fn into_slots(self) -> [(OptionName, Mapping); 9] {
189        let OptionMap {
190            reasoning,
191            cache,
192            service_tier,
193            verbosity,
194            parallel_tool_calls,
195            top_p,
196            seed,
197            stop,
198            cache_key,
199        } = self;
200        [
201            (OptionName::Reasoning, reasoning),
202            (OptionName::Cache, cache),
203            (OptionName::ServiceTier, service_tier),
204            (OptionName::Verbosity, verbosity),
205            (OptionName::ParallelToolCalls, parallel_tool_calls),
206            (OptionName::TopP, top_p),
207            (OptionName::Seed, seed),
208            (OptionName::Stop, stop),
209            (OptionName::CacheKey, cache_key),
210        ]
211    }
212}