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}
34
35impl OptionFields<'_> {
36    /// Which options are set, in [`OptionMap`] field order.
37    pub(super) fn set(&self) -> [bool; 8] {
38        let OptionFields {
39            reasoning,
40            cache,
41            service_tier,
42            verbosity,
43            parallel_tool_calls,
44            top_p,
45            seed,
46            stop,
47        } = self;
48        [
49            reasoning.is_some(),
50            cache.is_some(),
51            service_tier.is_some(),
52            verbosity.is_some(),
53            parallel_tool_calls.is_some(),
54            top_p.is_some(),
55            seed.is_some(),
56            !stop.is_empty(),
57        ]
58    }
59}
60
61/// What a wire does with one option.
62#[non_exhaustive]
63#[derive(Clone, Debug, PartialEq)]
64pub enum Mapping {
65    /// The option is unset. For a set option this is an encode error.
66    Nothing,
67    /// Deep-merge this JSON object into the body, above the wire's own
68    /// encoding of the request and below `additional_params`.
69    Send(Value),
70    /// Honour the option by sending nothing: the provider default already
71    /// does what was asked. Logged at `debug` with the reason.
72    Omit(&'static str),
73    /// Honour the option through markers the wire's base builder writes
74    /// inside the body's arrays. Valid for `cache` only.
75    Place,
76    /// The wire or model cannot honour it: an error under
77    /// [`OnUnsupported::Error`](super::OnUnsupported::Error), a warning
78    /// under [`OnUnsupported::Ignore`](super::OnUnsupported::Ignore).
79    Unsupported(String),
80}
81
82impl Mapping {
83    /// `Nothing` for an unset option, otherwise what `map` answers for it.
84    pub fn of<T>(value: Option<T>, map: impl FnOnce(T) -> Mapping) -> Mapping {
85        value.map_or(Mapping::Nothing, map)
86    }
87
88    /// `Nothing` for an empty stop list, otherwise what `map` answers.
89    pub fn of_stop(stop: &[String], map: impl FnOnce(&[String]) -> Mapping) -> Mapping {
90        if stop.is_empty() {
91            Mapping::Nothing
92        } else {
93            map(stop)
94        }
95    }
96
97    /// The refusal for `reason`. Answer it only for a set option, through
98    /// [`Mapping::of`] or [`Mapping::of_stop`]: an unset option answers
99    /// [`Mapping::Nothing`].
100    pub fn unsupported(reason: impl Into<String>) -> Mapping {
101        Mapping::Unsupported(reason.into())
102    }
103}
104
105/// One [`Mapping`] per option, by field name. Not `#[non_exhaustive]` and no
106/// `Default`, so a wire writes every field.
107#[derive(Clone, Debug, PartialEq)]
108pub struct OptionMap {
109    /// [`GenerationOptions::reasoning`](super::GenerationOptions::reasoning).
110    pub reasoning: Mapping,
111    /// [`GenerationOptions::cache`](super::GenerationOptions::cache).
112    pub cache: Mapping,
113    /// [`GenerationOptions::service_tier`](super::GenerationOptions::service_tier).
114    pub service_tier: Mapping,
115    /// [`GenerationOptions::verbosity`](super::GenerationOptions::verbosity).
116    pub verbosity: Mapping,
117    /// [`GenerationOptions::parallel_tool_calls`](super::GenerationOptions::parallel_tool_calls).
118    pub parallel_tool_calls: Mapping,
119    /// [`GenerationOptions::top_p`](super::GenerationOptions::top_p).
120    pub top_p: Mapping,
121    /// [`GenerationOptions::seed`](super::GenerationOptions::seed).
122    pub seed: Mapping,
123    /// [`GenerationOptions::stop`](super::GenerationOptions::stop).
124    pub stop: Mapping,
125}
126
127impl OptionMap {
128    /// Every option's name and answer, in field order.
129    pub(super) fn into_slots(self) -> [(&'static str, Mapping); 8] {
130        let OptionMap {
131            reasoning,
132            cache,
133            service_tier,
134            verbosity,
135            parallel_tool_calls,
136            top_p,
137            seed,
138            stop,
139        } = self;
140        [
141            ("reasoning", reasoning),
142            ("cache", cache),
143            ("service_tier", service_tier),
144            ("verbosity", verbosity),
145            ("parallel_tool_calls", parallel_tool_calls),
146            ("top_p", top_p),
147            ("seed", seed),
148            ("stop", stop),
149        ]
150    }
151}