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}