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}