Skip to main content

rig_core/completion/
request.rs

1//! Completion requests, normalized responses, and provider model contracts.
2//!
3//! ```
4//! use rig_core::completion::CompletionRequest;
5//!
6//! let request = CompletionRequest::new("Who are you?")
7//!     .preamble("You are a concise assistant.")
8//!     .temperature(0.5);
9//! assert_eq!(request.temperature, Some(0.5));
10//! ```
11
12use super::message::{
13    AssistantContent, AssistantMessage, DocumentMediaType, Origin, StopReason, ToolCall,
14};
15use super::options::{
16    CacheRetention, GenerationOptions, OnUnsupported, Reasoning, ServiceTier, Verbosity,
17};
18use super::provider_options::{ExtensionOptions, ProviderExtension, ProviderOptions, ReplyExtras};
19use crate::error::ProviderError;
20use crate::message::ToolChoice;
21use crate::{
22    json_utils,
23    message::{Message, ToolName, UserContent},
24};
25
26use serde::{Deserialize, Serialize};
27use std::collections::HashMap;
28use std::ops::{Add, AddAssign};
29
30#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
31pub struct Document {
32    /// Stable document identifier included in the serialized context block.
33    pub id: String,
34    /// Text content passed to the model as retrieval or static context.
35    pub text: String,
36    /// Additional string metadata rendered before the document text.
37    #[serde(flatten)]
38    pub additional_props: HashMap<String, String>,
39}
40
41impl std::fmt::Display for Document {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        write!(
44            f,
45            concat!("<file id: {}>\n", "{}\n", "</file>\n"),
46            self.id,
47            if self.additional_props.is_empty() {
48                self.text.clone()
49            } else {
50                let mut sorted_props = self.additional_props.iter().collect::<Vec<_>>();
51                sorted_props.sort_by(|a, b| a.0.cmp(b.0));
52                let metadata = sorted_props
53                    .iter()
54                    .map(|(k, v)| format!("{k}: {v:?}"))
55                    .collect::<Vec<_>>()
56                    .join(" ");
57                format!("<metadata {} />\n{}", metadata, self.text)
58            }
59        )
60    }
61}
62
63#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
64pub struct ToolDefinition {
65    /// Tool name exposed to the model. It must match the registered tool name.
66    pub name: ToolName,
67    /// Human-readable description sent to the model.
68    pub description: String,
69    /// JSON Schema describing tool arguments.
70    pub parameters: serde_json::Value,
71}
72
73impl ToolDefinition {
74    /// A tool the model may call by `name`, with arguments matching the
75    /// JSON Schema `parameters`.
76    pub fn new(
77        name: ToolName,
78        description: impl Into<String>,
79        parameters: serde_json::Value,
80    ) -> Self {
81        Self {
82            name,
83            description: description.into(),
84            parameters,
85        }
86    }
87}
88
89/// Provider-native tool definition.
90///
91/// Stored under `additional_params.tools` and forwarded by providers that support
92/// provider-managed tools.
93#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
94pub struct ProviderToolDefinition {
95    /// Tool type/kind name as expected by the target provider (for example `web_search`).
96    #[serde(rename = "type")]
97    pub kind: String,
98    /// Additional provider-specific configuration for this hosted tool.
99    #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
100    pub config: serde_json::Map<String, serde_json::Value>,
101}
102
103impl ProviderToolDefinition {
104    /// Creates a provider-hosted tool definition by type.
105    pub fn new(kind: impl Into<String>) -> Self {
106        Self {
107            kind: kind.into(),
108            config: serde_json::Map::new(),
109        }
110    }
111
112    /// Adds a provider-specific configuration key/value.
113    pub fn with_config(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
114        self.config.insert(key.into(), value);
115        self
116    }
117}
118
119/// Normalized generation ending. Unmapped provider values remain in [`Self::Other`].
120/// Failure statuses may accompany parseable output; callers must decide whether
121/// such output is usable rather than treating every response as successful.
122#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
123#[serde(rename_all = "snake_case")]
124#[non_exhaustive]
125pub enum FinishReason {
126    /// Natural end of the response.
127    Stop,
128    /// The response hit the output-token limit.
129    Length,
130    /// The model stopped to call one or more tools.
131    ToolCalls,
132    /// The provider filtered the content.
133    ContentFilter,
134    /// A provider-specific reason outside the normalized vocabulary, carried
135    /// verbatim in the provider's own wire spelling.
136    Other(String),
137}
138
139impl FinishReason {
140    /// Changes [`Self::Stop`] to [`Self::ToolCalls`] when output contains a tool
141    /// call. All other reasons remain unchanged. Response builders and streaming
142    /// aggregation apply this reconciliation.
143    pub fn reconcile_with_output(self, has_tool_call: bool) -> Self {
144        if has_tool_call && matches!(self, Self::Stop) {
145            Self::ToolCalls
146        } else {
147            self
148        }
149    }
150
151    /// Returns whether the reason is [`Self::Length`] or [`Self::ContentFilter`].
152    /// These reasons permit answerless turns without treating absent content as
153    /// a malformed response. Unknown reasons are not classified as truncation.
154    pub fn truncated_output(&self) -> bool {
155        matches!(self, Self::Length | Self::ContentFilter)
156    }
157
158    /// Formats an answerless-turn diagnostic with budget or filtering advice
159    /// for known truncation reasons, and a generic explanation otherwise.
160    pub fn no_answer_message(&self) -> String {
161        let remedy = match self {
162            Self::Length => {
163                "the turn ran out of output budget before producing one — \
164                 raise max_tokens for this request"
165            }
166            Self::ContentFilter => {
167                "the provider filtered the response — the content, not the \
168                 budget, is what it objected to"
169            }
170            _ => "the turn ended before producing one",
171        };
172        format!(
173            "the model produced no answer and stopped with \
174             finish_reason={self:?}; {remedy}"
175        )
176    }
177}
178
179/// Assistant content and normalized completion metadata. The choice may be
180/// empty, including for truncated or filtered turns. Provider-specific data is
181/// available through [`Self::raw`] without retaining a concrete model type.
182///
183/// A response goes straight back into the conversation as the assistant
184/// turn: `history.extend(response.message())`.
185#[non_exhaustive]
186#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
187#[serde(from = "CompletionResponseRepr")]
188pub struct CompletionResponse {
189    /// Assistant content returned by the provider, one block per provider
190    /// output item, in provider order. Possibly empty.
191    pub choice: Vec<AssistantContent>,
192    /// Tokens used during prompting and responding
193    pub usage: Usage,
194    /// The wire, provider and requested model that produced the response,
195    /// with the model and response id the provider reported.
196    pub origin: Origin,
197    /// The provider's report that the turn failed, such as a refusal's
198    /// explanation. The turn's message then ends in [`StopReason::Error`]
199    /// and is never replayed.
200    #[serde(default, skip_serializing_if = "Option::is_none")]
201    pub error: Option<String>,
202    /// Why the reply stopped before the provider ended it, when the caller
203    /// stopped reading: the turn's message then ends in
204    /// [`StopReason::Aborted`] and is never replayed.
205    #[serde(default, skip_serializing_if = "Option::is_none")]
206    pub aborted: Option<String>,
207    /// Request identifier from HTTP headers or SDK metadata, not the body's
208    /// response ID. `None` when the provider reports none.
209    #[serde(default, skip_serializing_if = "Option::is_none")]
210    pub provider_request_id: Option<String>,
211    /// Reported finish reason, reconciled by the setters with tool-call output.
212    /// Read through [`Self::finish_reason`].
213    #[serde(default)]
214    finish_reason: Option<FinishReason>,
215    /// Whether [`FinishReason::Other`] ends the turn as a normal stop. Read
216    /// through [`Self::accepts_unknown_finish_reasons`].
217    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
218    accepts_unknown_finish_reasons: bool,
219    /// Provider response document for typed inspection through deserialization.
220    /// Parsed wire types may omit unmodeled fields. This data does not override
221    /// normalized fields; callers constructing responses must supply it.
222    pub raw: serde_json::Value,
223}
224
225/// Distinct response and transport identifiers for one model call.
226/// Unreported identifiers remain `None`.
227#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
228pub struct ResponseIdentity {
229    /// Response-wide ID.
230    #[serde(default, skip_serializing_if = "Option::is_none")]
231    pub response_id: Option<String>,
232    /// Transport request ID from HTTP headers or SDK metadata.
233    #[serde(default, skip_serializing_if = "Option::is_none")]
234    pub provider_request_id: Option<String>,
235}
236
237impl CompletionResponse {
238    /// Create a response from its required parts; optional metadata starts
239    /// unset. `raw` is the provider's own document for this response,
240    /// serialized; see [`Self::raw`].
241    pub fn new(
242        choice: Vec<AssistantContent>,
243        usage: Usage,
244        origin: Origin,
245        raw: serde_json::Value,
246    ) -> Self {
247        Self {
248            choice,
249            usage,
250            origin,
251            error: None,
252            aborted: None,
253            provider_request_id: None,
254            finish_reason: None,
255            accepts_unknown_finish_reasons: false,
256            raw,
257        }
258    }
259
260    /// The provider descriptor name (`"openai"`).
261    pub fn provider(&self) -> &str {
262        &self.origin.provider
263    }
264
265    /// `P`'s typed view of [`Self::raw`]. `None` unless `P` is the provider
266    /// that produced the reply.
267    ///
268    /// # Errors
269    ///
270    /// The inner result fails when `raw` does not hold `P`'s extras.
271    pub fn extras<P: ProviderExtension>(&self) -> Option<Result<P::Extras, serde_json::Error>> {
272        (self.origin.provider == P::PROVIDER)
273            .then(|| P::Extras::from_reply(&self.origin.api, &self.raw))
274    }
275
276    /// `P`'s typed view of [`Self::raw`], or the empty view (every field
277    /// `None`) when it cannot be read: when another provider produced the
278    /// reply, or when `raw` does not hold `P`'s extras. A decode failure is
279    /// logged at `debug`; use [`Self::extras`] to tell the cases apart.
280    pub fn extras_lossy<P: ProviderExtension>(&self) -> P::Extras
281    where
282        P::Extras: Default,
283    {
284        match self.extras::<P>() {
285            Some(Ok(extras)) => extras,
286            Some(Err(error)) => {
287                tracing::debug!(
288                    provider = P::PROVIDER,
289                    api = %self.origin.api,
290                    %error,
291                    "reply extras did not decode; reading them as empty"
292                );
293                P::Extras::default()
294            }
295            None => P::Extras::default(),
296        }
297    }
298
299    /// The model the provider reported, when it reported one.
300    pub fn model(&self) -> Option<&str> {
301        self.origin.response_model.as_deref()
302    }
303
304    /// The provider's response id, when it sent one.
305    pub fn response_id(&self) -> Option<&str> {
306        self.origin.response_id.as_deref()
307    }
308
309    /// Why the model stopped generating, when the provider reported it.
310    pub fn finish_reason(&self) -> Option<FinishReason> {
311        self.finish_reason.clone()
312    }
313
314    /// Whether a finish reason outside the normalized vocabulary ends the
315    /// turn as a normal stop, as the request asked
316    /// ([`CompletionRequest::accept_unknown_finish_reasons`]).
317    ///
318    /// [`CompletionRequest::accept_unknown_finish_reasons`]: field@CompletionRequest::accept_unknown_finish_reasons
319    pub fn accepts_unknown_finish_reasons(&self) -> bool {
320        self.accepts_unknown_finish_reasons
321    }
322
323    /// Accept, or refuse, [`FinishReason::Other`] as a normal stop in
324    /// [`Self::stop`]. Filtered content and a reported failure still fail.
325    pub fn accept_unknown_finish_reasons(mut self, accept: bool) -> Self {
326        self.accepts_unknown_finish_reasons = accept;
327        self
328    }
329
330    /// How the turn ended, for history. It fails closed: only a natural
331    /// stop, the token limit and a stop to call tools are successes. A
332    /// reported failure, filtered content and any finish reason outside
333    /// that set are [`StopReason::Error`], unless the response accepts
334    /// unknown reasons ([`Self::accepts_unknown_finish_reasons`]), which then
335    /// stop like [`FinishReason::Stop`]. A reply the caller stopped reading
336    /// is [`StopReason::Aborted`].
337    pub fn stop(&self) -> StopReason {
338        if let Some(error) = &self.error {
339            return StopReason::Error(error.clone());
340        }
341        if let Some(reason) = &self.aborted {
342            return StopReason::Aborted(reason.clone());
343        }
344        match &self.finish_reason {
345            Some(FinishReason::Length) => StopReason::Length,
346            Some(FinishReason::ToolCalls) => StopReason::ToolUse,
347            Some(FinishReason::ContentFilter) => {
348                StopReason::Error("Provider finish_reason: content_filter".to_owned())
349            }
350            Some(FinishReason::Other(reason)) if !self.accepts_unknown_finish_reasons => {
351                StopReason::Error(format!("Provider finish_reason: {reason}"))
352            }
353            Some(FinishReason::Stop | FinishReason::Other(_)) | None
354                if self.tool_calls().next().is_some() =>
355            {
356                StopReason::ToolUse
357            }
358            Some(FinishReason::Stop | FinishReason::Other(_)) | None => StopReason::Stop,
359        }
360    }
361
362    /// This response's identity metadata as one [`ResponseIdentity`] carrier.
363    pub fn identity(&self) -> ResponseIdentity {
364        ResponseIdentity {
365            response_id: self.origin.response_id.clone(),
366            provider_request_id: self.provider_request_id.clone(),
367        }
368    }
369
370    /// Attach the normalized finish reason, reconciled against the choice via
371    /// [`FinishReason::reconcile_with_output`].
372    pub fn with_finish_reason(self, finish_reason: FinishReason) -> Self {
373        self.with_optional_finish_reason(Some(finish_reason))
374    }
375
376    /// The text parts of [`Self::choice`], concatenated in order.
377    pub fn text(&self) -> String {
378        self.choice
379            .iter()
380            .filter_map(|part| match part {
381                AssistantContent::Text(text) => Some(text.text.as_str()),
382                _ => None,
383            })
384            .collect()
385    }
386
387    /// The reasoning text of [`Self::choice`], concatenated in order.
388    /// Redacted reasoning has no text.
389    pub fn reasoning(&self) -> String {
390        self.choice
391            .iter()
392            .filter_map(|part| match part {
393                AssistantContent::Reasoning(reasoning) => Some(reasoning.text.as_str()),
394                _ => None,
395            })
396            .collect()
397    }
398
399    /// The assistant turn to append to the conversation: [`Self::choice`] in
400    /// order with its origin and stop, or `None` for an
401    /// empty choice.
402    pub fn message(&self) -> Option<Message> {
403        if self.choice.is_empty() {
404            return None;
405        }
406        Some(Message::Assistant(AssistantMessage {
407            content: self.choice.clone(),
408            ..self.head()
409        }))
410    }
411
412    /// The turn this response began, holding `content`, for a runtime that
413    /// cut the reply short and answers its calls itself (an agent rolling a
414    /// turn back over an invalid call). It keeps the origin, and stops to
415    /// call tools, since the runtime answers them. A block keeps its provider
416    /// item only when this response holds the same block with it. A response
417    /// the provider did not end, or one the consumer has not wholly taken,
418    /// holds no provider item, so its turn replays canonically.
419    pub fn continued(&self, content: Vec<AssistantContent>) -> AssistantMessage {
420        let content = content
421            .into_iter()
422            .map(|block| {
423                if self.choice.contains(&block) {
424                    block
425                } else {
426                    crate::operation::completion::canonical(block)
427                }
428            })
429            .collect();
430        AssistantMessage::rolled_back(Some(self.origin.clone()), content)
431    }
432
433    /// The turn's origin and stop with no content, for a
434    /// runtime that carries the content separately.
435    pub fn head(&self) -> AssistantMessage {
436        AssistantMessage {
437            content: Vec::new(),
438            origin: Some(self.origin.clone()),
439            stop: Some(self.stop()),
440        }
441    }
442
443    /// The tool calls in [`Self::choice`], in order.
444    pub fn tool_calls(&self) -> impl Iterator<Item = &ToolCall> {
445        self.choice.iter().filter_map(|part| match part {
446            AssistantContent::ToolCall(call) => Some(call),
447            _ => None,
448        })
449    }
450
451    /// Sets or clears the finish reason, reconciling a present reason with the choice.
452    pub fn with_optional_finish_reason(mut self, finish_reason: Option<FinishReason>) -> Self {
453        let has_tool_call = self
454            .choice
455            .iter()
456            .any(|content| matches!(content, AssistantContent::ToolCall(_)));
457        self.finish_reason =
458            finish_reason.map(|reason| reason.reconcile_with_output(has_tool_call));
459        self
460    }
461}
462
463/// Deserialization shape routed through builders for finish-reason reconciliation
464/// and empty-identifier normalization.
465#[derive(Deserialize)]
466struct CompletionResponseRepr {
467    choice: Vec<AssistantContent>,
468    usage: Usage,
469    origin: Origin,
470    #[serde(default)]
471    error: Option<String>,
472    #[serde(default)]
473    aborted: Option<String>,
474    #[serde(default)]
475    provider_request_id: Option<String>,
476    #[serde(default)]
477    finish_reason: Option<FinishReason>,
478    #[serde(default)]
479    accepts_unknown_finish_reasons: bool,
480    raw: serde_json::Value,
481}
482
483impl From<CompletionResponseRepr> for CompletionResponse {
484    fn from(repr: CompletionResponseRepr) -> Self {
485        let CompletionResponseRepr {
486            choice,
487            usage,
488            mut origin,
489            error,
490            aborted,
491            provider_request_id,
492            finish_reason,
493            accepts_unknown_finish_reasons,
494            raw,
495        } = repr;
496        use crate::provider_response::reported;
497        origin.response_id = reported(origin.response_id);
498        origin.response_model = reported(origin.response_model);
499        let mut response =
500            Self::new(choice, usage, origin, raw).with_optional_finish_reason(finish_reason);
501        response.error = error;
502        response.aborted = aborted;
503        response.provider_request_id = reported(provider_request_id);
504        response.accepts_unknown_finish_reasons = accepts_unknown_finish_reasons;
505        response
506    }
507}
508
509/// The token usage a provider reported for one completion.
510///
511/// Every provider mapping keeps one contract, so the counters read the same
512/// way on every provider:
513///
514/// - `cached_input_tokens + cache_creation_input_tokens <= input_tokens`:
515///   input counts every prompt token, cache reads and writes included.
516/// - `reasoning_tokens <= output_tokens`: output counts every generated
517///   token, reasoning included.
518/// - `total_tokens == input_tokens + output_tokens`, absent unless both are
519///   reported.
520///
521/// A counter the provider did not send is `None`; a reported zero is
522/// `Some(0)`. Serialized as the same keys, absent when `None`.
523///
524/// ```
525/// use rig_core::completion::Usage;
526///
527/// let usage = Usage::new().input_tokens(12).output_tokens(3).total_tokens(15);
528/// assert_eq!(usage.input_tokens, Some(12));
529/// assert!(usage.cost.is_none());
530/// ```
531#[non_exhaustive]
532#[derive(Debug, Default, PartialEq, Clone, Copy, Serialize, Deserialize)]
533pub struct Usage {
534    /// Every input token of the request: uncached, read from a cache,
535    /// written to a cache, and any prompt a provider's hosted tools added.
536    #[serde(default, skip_serializing_if = "Option::is_none")]
537    pub input_tokens: Option<u64>,
538    /// Every output token, reasoning included.
539    #[serde(default, skip_serializing_if = "Option::is_none")]
540    pub output_tokens: Option<u64>,
541    /// `input_tokens + output_tokens`; absent unless both are reported.
542    #[serde(default, skip_serializing_if = "Option::is_none")]
543    pub total_tokens: Option<u64>,
544    /// The part of `input_tokens` read from a provider-managed cache.
545    #[serde(default, skip_serializing_if = "Option::is_none")]
546    pub cached_input_tokens: Option<u64>,
547    /// The part of `input_tokens` written to a provider-managed cache.
548    #[serde(default, skip_serializing_if = "Option::is_none")]
549    pub cache_creation_input_tokens: Option<u64>,
550    /// The part of `input_tokens` a provider's hosted tools added to the prompt.
551    #[serde(default, skip_serializing_if = "Option::is_none")]
552    pub tool_use_prompt_tokens: Option<u64>,
553    /// The part of `output_tokens` spent on internal reasoning ("thinking",
554    /// "thoughts").
555    #[serde(default, skip_serializing_if = "Option::is_none")]
556    pub reasoning_tokens: Option<u64>,
557    /// What the turn cost in USD, when known. Never derived from the token
558    /// counters here, and none of them is derived from it. A cost the
559    /// provider reports is its figure. One priced from the built-in catalog
560    /// ([`Pricing::cost`](crate::catalog::Pricing::cost)) is the
561    /// standard-tier list price of the counted tokens: it leaves out the
562    /// service tier, long-context price tiers and hosted-tool fees (web
563    /// search, code execution), so it can be lower than the bill.
564    #[serde(default, skip_serializing_if = "Option::is_none")]
565    pub cost: Option<Cost>,
566}
567
568impl Usage {
569    /// No counter and no cost reported.
570    pub const fn new() -> Self {
571        Self {
572            input_tokens: None,
573            output_tokens: None,
574            total_tokens: None,
575            cached_input_tokens: None,
576            cache_creation_input_tokens: None,
577            tool_use_prompt_tokens: None,
578            reasoning_tokens: None,
579            cost: None,
580        }
581    }
582
583    /// Whether the provider reported any counter or a cost.
584    pub fn is_reported(&self) -> bool {
585        *self != Self::default()
586    }
587
588    /// Set, or with `None` clear, [`Self::input_tokens`](field@Self::input_tokens).
589    pub fn input_tokens(mut self, tokens: impl Into<Option<u64>>) -> Self {
590        self.input_tokens = tokens.into();
591        self
592    }
593
594    /// Set, or with `None` clear, [`Self::output_tokens`](field@Self::output_tokens).
595    pub fn output_tokens(mut self, tokens: impl Into<Option<u64>>) -> Self {
596        self.output_tokens = tokens.into();
597        self
598    }
599
600    /// Set, or with `None` clear, [`Self::total_tokens`](field@Self::total_tokens).
601    pub fn total_tokens(mut self, tokens: impl Into<Option<u64>>) -> Self {
602        self.total_tokens = tokens.into();
603        self
604    }
605
606    /// Set, or with `None` clear,
607    /// [`Self::cached_input_tokens`](field@Self::cached_input_tokens).
608    pub fn cached_input_tokens(mut self, tokens: impl Into<Option<u64>>) -> Self {
609        self.cached_input_tokens = tokens.into();
610        self
611    }
612
613    /// Set, or with `None` clear,
614    /// [`Self::cache_creation_input_tokens`](field@Self::cache_creation_input_tokens).
615    pub fn cache_creation_input_tokens(mut self, tokens: impl Into<Option<u64>>) -> Self {
616        self.cache_creation_input_tokens = tokens.into();
617        self
618    }
619
620    /// Set, or with `None` clear,
621    /// [`Self::tool_use_prompt_tokens`](field@Self::tool_use_prompt_tokens).
622    pub fn tool_use_prompt_tokens(mut self, tokens: impl Into<Option<u64>>) -> Self {
623        self.tool_use_prompt_tokens = tokens.into();
624        self
625    }
626
627    /// Set, or with `None` clear, [`Self::reasoning_tokens`](field@Self::reasoning_tokens).
628    pub fn reasoning_tokens(mut self, tokens: impl Into<Option<u64>>) -> Self {
629        self.reasoning_tokens = tokens.into();
630        self
631    }
632
633    /// Set, or with `None` clear, [`Self::cost`](field@Self::cost).
634    pub fn cost(mut self, cost: impl Into<Option<Cost>>) -> Self {
635        self.cost = cost.into();
636        self
637    }
638}
639
640/// What one or more turns cost, in USD. `total` is what was charged.
641///
642/// A part is `None` when its source does not give it: a provider that
643/// reports only a total leaves every part `None`, and one that reports
644/// input and output leaves the cache parts `None`. A cost computed from
645/// the catalog ([`Pricing::cost`](crate::catalog::Pricing::cost)) has every
646/// part `Some`, and it prices a cache rate the catalog does not list at the
647/// input rate, so a `Some` cache part may be that estimate rather than a
648/// listed price. A part is never filled with a placeholder `0.0`. Summing
649/// costs keeps a part only when every side has it. A `None` part is left
650/// out when serialized, and an absent one reads back as `None`.
651#[non_exhaustive]
652#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
653pub struct Cost {
654    /// Uncached input tokens.
655    #[serde(default, skip_serializing_if = "Option::is_none")]
656    pub input: Option<f64>,
657    /// Output tokens, reasoning included.
658    #[serde(default, skip_serializing_if = "Option::is_none")]
659    pub output: Option<f64>,
660    /// Input tokens read from a cache.
661    #[serde(default, skip_serializing_if = "Option::is_none")]
662    pub cache_read: Option<f64>,
663    /// Input tokens written to a cache.
664    #[serde(default, skip_serializing_if = "Option::is_none")]
665    pub cache_write: Option<f64>,
666    /// The whole charge.
667    pub total: f64,
668}
669
670impl Cost {
671    /// A cost split into its parts, each known; `total` is their sum.
672    pub fn from_parts(input: f64, output: f64, cache_read: f64, cache_write: f64) -> Self {
673        Self {
674            input: Some(input),
675            output: Some(output),
676            cache_read: Some(cache_read),
677            cache_write: Some(cache_write),
678            total: input + output + cache_read + cache_write,
679        }
680    }
681
682    /// A cost known only as its total; every part is `None`.
683    pub fn from_total(total: f64) -> Self {
684        Self {
685            input: None,
686            output: None,
687            cache_read: None,
688            cache_write: None,
689            total,
690        }
691    }
692
693    /// Set, or with `None` clear, [`Self::input`](field@Self::input).
694    /// `total` is left as it is.
695    pub fn input(mut self, input: impl Into<Option<f64>>) -> Self {
696        self.input = input.into();
697        self
698    }
699
700    /// Set, or with `None` clear, [`Self::output`](field@Self::output).
701    /// `total` is left as it is.
702    pub fn output(mut self, output: impl Into<Option<f64>>) -> Self {
703        self.output = output.into();
704        self
705    }
706
707    /// Set, or with `None` clear, [`Self::cache_read`](field@Self::cache_read).
708    /// `total` is left as it is.
709    pub fn cache_read(mut self, cache_read: impl Into<Option<f64>>) -> Self {
710        self.cache_read = cache_read.into();
711        self
712    }
713
714    /// Set, or with `None` clear, [`Self::cache_write`](field@Self::cache_write).
715    /// `total` is left as it is.
716    pub fn cache_write(mut self, cache_write: impl Into<Option<f64>>) -> Self {
717        self.cache_write = cache_write.into();
718        self
719    }
720}
721
722/// Sum one part of two costs: unknown when either side's is unknown, since
723/// a known part plus an unknown one is not known.
724fn add_part(lhs: Option<f64>, rhs: Option<f64>) -> Option<f64> {
725    Some(lhs? + rhs?)
726}
727
728/// `total` always sums. Each part sums only when both sides know it, and is
729/// `None` otherwise, so a summed part never understates what it covers.
730impl Add for Cost {
731    type Output = Self;
732
733    fn add(self, other: Self) -> Self::Output {
734        Self {
735            input: add_part(self.input, other.input),
736            output: add_part(self.output, other.output),
737            cache_read: add_part(self.cache_read, other.cache_read),
738            cache_write: add_part(self.cache_write, other.cache_write),
739            total: self.total + other.total,
740        }
741    }
742}
743
744/// Sum two counters where an unreported side does not turn a reported one
745/// into "unreported".
746fn add_counter(lhs: Option<u64>, rhs: Option<u64>) -> Option<u64> {
747    match (lhs, rhs) {
748        (None, None) => None,
749        (lhs, rhs) => Some(lhs.unwrap_or(0) + rhs.unwrap_or(0)),
750    }
751}
752
753impl Add for Usage {
754    type Output = Self;
755
756    fn add(mut self, other: Self) -> Self::Output {
757        self += other;
758        self
759    }
760}
761
762/// Token counters add where an unreported side adds nothing. Cost sums only
763/// when both sides have one: a turn whose cost is unknown makes the sum
764/// unknown, rather than too low. A side that reports nothing at all
765/// ([`Usage::is_reported`] is `false`) is the identity, so a fold from
766/// [`Usage::default`] keeps its first turn's cost.
767impl AddAssign for Usage {
768    fn add_assign(&mut self, other: Self) {
769        if !other.is_reported() {
770            return;
771        }
772        if !self.is_reported() {
773            *self = other;
774            return;
775        }
776        self.cost = match (self.cost, other.cost) {
777            (Some(lhs), Some(rhs)) => Some(lhs + rhs),
778            _ => None,
779        };
780        self.input_tokens = add_counter(self.input_tokens, other.input_tokens);
781        self.output_tokens = add_counter(self.output_tokens, other.output_tokens);
782        self.total_tokens = add_counter(self.total_tokens, other.total_tokens);
783        self.cached_input_tokens = add_counter(self.cached_input_tokens, other.cached_input_tokens);
784        self.cache_creation_input_tokens = add_counter(
785            self.cache_creation_input_tokens,
786            other.cache_creation_input_tokens,
787        );
788        self.tool_use_prompt_tokens =
789            add_counter(self.tool_use_prompt_tokens, other.tool_use_prompt_tokens);
790        self.reasoning_tokens = add_counter(self.reasoning_tokens, other.reasoning_tokens);
791    }
792}
793
794/// Model capabilities used by runtimes when preparing requests.
795/// Defaults are conservative; construct through [`Self::new`] and setters.
796#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
797pub struct ProviderCapabilities {
798    /// Whether native structured output can remain enabled with tool calls
799    /// without suppressing them. Defaults to `false`.
800    pub composes_native_output_with_tools: bool,
801    /// Whether the model answers a forced tool choice (`Required` or
802    /// `Specific`) with an error. Rig then does not force the output tool
803    /// of its own structured-output flow (the extractor). Defaults to `false`.
804    #[serde(default, skip_serializing_if = "crate::json_utils::is_false")]
805    pub rejects_forced_tool_choice: bool,
806}
807
808impl ProviderCapabilities {
809    /// Create the conservative capability set used by default.
810    pub const fn new() -> Self {
811        Self {
812            composes_native_output_with_tools: false,
813            rejects_forced_tool_choice: false,
814        }
815    }
816
817    /// Declare whether native structured output composes with tool calls.
818    pub const fn with_native_output_tool_composition(mut self, supported: bool) -> Self {
819        self.composes_native_output_with_tools = supported;
820        self
821    }
822
823    /// Declare whether the model rejects a forced tool choice.
824    pub const fn with_forced_tool_choice_rejected(mut self, rejected: bool) -> Self {
825        self.rejects_forced_tool_choice = rejected;
826        self
827    }
828}
829
830/// Struct representing a general completion request that can be sent to a completion model provider.
831#[non_exhaustive]
832#[derive(Debug, Clone, Serialize, Deserialize)]
833pub struct CompletionRequest {
834    /// Optional model override for this request.
835    pub model: Option<String>,
836    /// The chat history to be sent to the completion model provider.
837    /// The very last message is the prompt.
838    ///
839    /// It must hold at least one message, and every user and assistant
840    /// message must carry content. The field is public, so this is a rule
841    /// rather than a type guarantee: [`Self::validate_message_content`]
842    /// checks it at the request boundary.
843    pub chat_history: Vec<Message>,
844    /// The documents to be sent to the completion model provider
845    pub documents: Vec<Document>,
846    /// The tools to be sent to the completion model provider
847    pub tools: Vec<ToolDefinition>,
848    /// The temperature to be sent to the completion model provider
849    pub temperature: Option<f64>,
850    /// The max tokens to be sent to the completion model provider
851    pub max_tokens: Option<u64>,
852    /// Whether tools are required to be used by the model provider or not before providing a response.
853    pub tool_choice: Option<ToolChoice>,
854    /// Additional provider-specific parameters to be sent to the completion model provider
855    pub additional_params: Option<serde_json::Value>,
856    /// Optional JSON Schema for structured output. When set, providers that support
857    /// native structured outputs will constrain the model's response to match this schema.
858    pub output_schema: Option<schemars::Schema>,
859    /// Opt-in for sensitive request, response, and tool-content telemetry.
860    /// Defaults to `false` and is excluded from serialization. Enabling it can
861    /// expose prompts, context, tool results, and model output in span attributes
862    /// and increase telemetry storage costs. Requires explicit caller consent.
863    /// Agent drivers record normalized content; direct provider coverage varies,
864    /// especially for streams consumed after the provider returns.
865    #[serde(skip)]
866    pub record_telemetry_content: bool,
867    /// Whether a finish reason outside the normalized vocabulary
868    /// ([`FinishReason::Other`]) ends the turn as a normal stop instead of a
869    /// failure. Defaults to `false`. The response carries the choice, so its
870    /// [`CompletionResponse::stop`], the runtimes and history replay agree.
871    /// `Other` also holds genuine failures, such as a malformed tool call or a
872    /// recitation block, so with this set the tool calls of such a reply run.
873    /// [`FinishReason::ContentFilter`] still fails the turn.
874    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
875    pub accept_unknown_finish_reasons: bool,
876    /// Portable generation options. Precedence, lowest first: the mapped
877    /// options, then [`Self::provider_options`], then `additional_params`.
878    #[serde(default, skip_serializing_if = "GenerationOptions::is_default")]
879    pub options: GenerationOptions,
880    /// Typed per-provider options. The wire reads only the entry of its own
881    /// provider, above the mapped options and below `additional_params`.
882    #[serde(default, skip_serializing_if = "ProviderOptions::is_empty")]
883    pub provider_options: ProviderOptions,
884}
885
886impl CompletionRequest {
887    /// The system instructions of this request: the content of the leading
888    /// [`Message::System`] in `chat_history`, which is where
889    /// [`Self::preamble`] places it.
890    pub fn system_instructions(&self) -> Option<&str> {
891        match self.chat_history.first() {
892            Some(Message::System { content }) => Some(content.as_str()),
893            _ => None,
894        }
895    }
896
897    /// Reject a request with no messages, a user or assistant message with no
898    /// content, or a tool result with no content blocks. The error is
899    /// [`ProviderError::Request`] and names the role and index of the first
900    /// offending message.
901    ///
902    /// Every wire rejects an empty turn, so this turns a remote 400 into a
903    /// local error. It checks the request direction only: a provider may
904    /// return empty assistant content, which the reply keeps and the
905    /// runtime judges. `System` content is a
906    /// `String` and is not checked. A tool result holding one empty text
907    /// block is not empty.
908    ///
909    /// [`Model::call`](crate::driver::Model::call),
910    /// [`Model::stream`](crate::driver::Model::stream) and their `_observed`
911    /// twins run it before encoding, so it covers
912    /// [`DynModel`](crate::DynModel), every model the bus serves and the
913    /// agent runtimes built on them. The OpenAI Responses websocket session
914    /// sends without the driver and runs it on each send. Code that encodes
915    /// a request some other way should call it first.
916    pub fn validate_message_content(&self) -> Result<(), ProviderError> {
917        if self.chat_history.is_empty() {
918            return Err(ProviderError::request(
919                "request has an empty chat history; providers require at least one message",
920            ));
921        }
922
923        let empty_message = |role: &str, index: usize| {
924            ProviderError::request(format!(
925                "{role} message at index {index} has no content; \
926                 providers reject empty content blocks"
927            ))
928        };
929
930        for (index, message) in self.chat_history.iter().enumerate() {
931            match message {
932                Message::System { .. } => {}
933                Message::Assistant(AssistantMessage { content, .. }) => {
934                    if content.is_empty() {
935                        return Err(empty_message("assistant", index));
936                    }
937                }
938                Message::User { content } => {
939                    if content.is_empty() {
940                        return Err(empty_message("user", index));
941                    }
942                }
943            }
944        }
945
946        Ok(())
947    }
948
949    /// Extracts a name from the output schema's `"title"` field, falling back to `"response_schema"`.
950    /// Useful for providers that require a name alongside the JSON Schema (e.g., OpenAI).
951    pub fn output_schema_name(&self) -> Option<String> {
952        self.output_schema.as_ref().map(|schema| {
953            schema
954                .as_object()
955                .and_then(|o| o.get("title"))
956                .and_then(|v| v.as_str())
957                .unwrap_or("response_schema")
958                .to_string()
959        })
960    }
961
962    /// Returns documents normalized into a message (if any).
963    /// Most providers do not accept documents directly as input, so it needs to convert into a
964    /// `Message` so that it can be incorporated into `chat_history`.
965    pub fn normalized_documents(&self) -> Option<Message> {
966        Self::normalized_documents_from(&self.documents)
967    }
968
969    fn normalized_documents_from(documents: &[Document]) -> Option<Message> {
970        if documents.is_empty() {
971            return None;
972        }
973
974        let content = documents
975            .iter()
976            .map(|doc| UserContent::document_text(doc.to_string(), Some(DocumentMediaType::TXT)))
977            .collect();
978
979        Some(Message::User { content })
980    }
981
982    pub(crate) fn chat_history_with_documents(&self) -> Vec<Message> {
983        let mut chat_history = self.chat_history.clone();
984        if let Some(documents) = self.normalized_documents() {
985            insert_after_leading_system(&mut chat_history, documents);
986        }
987        chat_history
988    }
989}
990
991/// Place the documents message `message` at the first non-system position,
992/// so document context lands after any leading system messages. A user
993/// message already there takes the documents at its front instead, so the
994/// history keeps alternating roles. Telemetry and the sent request must
995/// agree on this placement.
996fn insert_after_leading_system(chat_history: &mut Vec<Message>, message: Message) {
997    let insert_at = chat_history
998        .iter()
999        .position(|message| !matches!(message, Message::System { .. }))
1000        .unwrap_or(chat_history.len());
1001    match (chat_history.get_mut(insert_at), message) {
1002        (Some(Message::User { content }), Message::User { content: documents })
1003            if !content.is_empty() =>
1004        {
1005            content.splice(0..0, documents);
1006        }
1007        (_, message) => chat_history.insert(insert_at, message),
1008    }
1009}
1010
1011fn merge_provider_tools_into_additional_params(
1012    additional_params: Option<serde_json::Value>,
1013    provider_tools: Vec<ProviderToolDefinition>,
1014) -> Option<serde_json::Value> {
1015    if provider_tools.is_empty() {
1016        return additional_params;
1017    }
1018
1019    let mut provider_tools_json = provider_tools
1020        .into_iter()
1021        .map(|ProviderToolDefinition { kind, mut config }| {
1022            // Force the provider tool type from the strongly-typed field.
1023            config.insert("type".to_string(), serde_json::Value::String(kind));
1024            serde_json::Value::Object(config)
1025        })
1026        .collect::<Vec<_>>();
1027
1028    let mut params_map = match additional_params {
1029        Some(serde_json::Value::Object(map)) => map,
1030        Some(serde_json::Value::Bool(stream)) => {
1031            let mut map = serde_json::Map::new();
1032            map.insert("stream".to_string(), serde_json::Value::Bool(stream));
1033            map
1034        }
1035        _ => serde_json::Map::new(),
1036    };
1037
1038    let mut merged_tools = match params_map.shift_remove("tools") {
1039        Some(serde_json::Value::Array(existing)) => existing,
1040        _ => Vec::new(),
1041    };
1042    merged_tools.append(&mut provider_tools_json);
1043    params_map.insert("tools".to_string(), serde_json::Value::Array(merged_tools));
1044    Some(serde_json::Value::Object(params_map))
1045}
1046
1047impl CompletionRequest {
1048    /// A request whose conversation is the one user message `prompt`, with
1049    /// no preamble, documents or tools. The setters below add to it and
1050    /// check nothing; [`Self::validate_message_content`] checks the content
1051    /// when the request is sent.
1052    ///
1053    /// Each setter changes the request's public fields as it is called, so
1054    /// order matters where two setters touch the same field: a second
1055    /// [`Self::preamble`] adds a second system message, and
1056    /// [`Self::additional_params`] with a `tools` key (or `None`) replaces
1057    /// provider tools added before it. Set `additional_params` first.
1058    ///
1059    /// ```
1060    /// use rig_core::completion::CompletionRequest;
1061    ///
1062    /// let request = CompletionRequest::new("Who are you?")
1063    ///     .preamble("You are a concise assistant.")
1064    ///     .temperature(0.5);
1065    /// assert_eq!(request.chat_history.len(), 2);
1066    /// assert_eq!(request.temperature, Some(0.5));
1067    /// ```
1068    pub fn new(prompt: impl Into<Message>) -> Self {
1069        Self::conversation(vec![prompt.into()])
1070    }
1071
1072    /// A request for `chat_history` as given, with nothing else set.
1073    fn conversation(chat_history: Vec<Message>) -> Self {
1074        Self {
1075            model: None,
1076            chat_history,
1077            documents: Vec::new(),
1078            tools: Vec::new(),
1079            temperature: None,
1080            max_tokens: None,
1081            tool_choice: None,
1082            additional_params: None,
1083            output_schema: None,
1084            record_telemetry_content: false,
1085            accept_unknown_finish_reasons: false,
1086            options: GenerationOptions::default(),
1087            provider_options: ProviderOptions::default(),
1088        }
1089    }
1090
1091    /// Put `preamble` first in the conversation, as a [`Message::System`],
1092    /// ahead of any system message already there.
1093    pub fn preamble(mut self, preamble: impl Into<String>) -> Self {
1094        self.chat_history
1095            .insert(0, Message::system(preamble.into()));
1096        self
1097    }
1098
1099    /// Override the model for this request.
1100    pub fn model<S: Into<String>>(mut self, model: impl Into<Option<S>>) -> Self {
1101        self.model = model.into().map(Into::into);
1102        self.warn_if_shadowed("model", self.model.is_some());
1103        self
1104    }
1105
1106    /// Add `message` to the conversation, before the prompt (its last
1107    /// message).
1108    pub fn message(self, message: Message) -> Self {
1109        self.messages([message])
1110    }
1111
1112    /// Add `messages` to the conversation in order, before the prompt (its
1113    /// last message).
1114    pub fn messages(mut self, messages: impl IntoIterator<Item = Message>) -> Self {
1115        let prompt = self.chat_history.pop();
1116        self.chat_history.extend(messages);
1117        self.chat_history.extend(prompt);
1118        self
1119    }
1120
1121    /// Add a document.
1122    pub fn document(mut self, document: Document) -> Self {
1123        self.documents.push(document);
1124        self
1125    }
1126
1127    /// Add documents in order.
1128    pub fn documents(mut self, documents: impl IntoIterator<Item = Document>) -> Self {
1129        self.documents.extend(documents);
1130        self
1131    }
1132
1133    /// Add a tool.
1134    pub fn tool(self, tool: ToolDefinition) -> Self {
1135        self.tools(vec![tool])
1136    }
1137
1138    /// Add tools in order.
1139    pub fn tools(mut self, tools: Vec<ToolDefinition>) -> Self {
1140        let first = self.tools.is_empty();
1141        self.tools.extend(tools);
1142        self.warn_if_shadowed("tools", first && !self.tools.is_empty());
1143        self
1144    }
1145
1146    /// Add a provider-hosted tool: appended to `additional_params.tools`,
1147    /// so a later [`Self::additional_params`] with a `tools` key replaces
1148    /// it.
1149    pub fn provider_tool(self, tool: ProviderToolDefinition) -> Self {
1150        self.provider_tools(vec![tool])
1151    }
1152
1153    /// Add provider-hosted tools in order: appended to
1154    /// `additional_params.tools`.
1155    pub fn provider_tools(mut self, tools: Vec<ProviderToolDefinition>) -> Self {
1156        self.additional_params =
1157            merge_provider_tools_into_additional_params(self.additional_params.take(), tools);
1158        self
1159    }
1160
1161    /// Merge provider-specific parameters into the request's, key by key;
1162    /// `None` clears them, provider tools included. Provider conversion determines precedence over typed fields,
1163    /// and a key that overrides a typed field this request sets is logged.
1164    pub fn additional_params(
1165        mut self,
1166        additional_params: impl Into<Option<serde_json::Value>>,
1167    ) -> Self {
1168        let additional_params = additional_params.into();
1169        for key in shadowed_typed_fields(
1170            additional_params.as_ref(),
1171            &[
1172                ("temperature", self.temperature.is_some()),
1173                ("max_tokens", self.max_tokens.is_some()),
1174                ("tool_choice", self.tool_choice.is_some()),
1175                ("model", self.model.is_some()),
1176                ("tools", !self.tools.is_empty()),
1177                ("response_format", self.output_schema.is_some()),
1178            ],
1179        ) {
1180            warn_shadowed(key);
1181        }
1182        self.additional_params =
1183            json_utils::merge_params(self.additional_params.take(), additional_params);
1184        self
1185    }
1186
1187    /// Set, or with `None` clear, the temperature.
1188    pub fn temperature(mut self, temperature: impl Into<Option<f64>>) -> Self {
1189        self.temperature = temperature.into();
1190        self.warn_if_shadowed("temperature", self.temperature.is_some());
1191        self
1192    }
1193
1194    /// Set, or with `None` clear, the output-token limit. Provider-specific
1195    /// defaults and requirements apply.
1196    pub fn max_tokens(mut self, max_tokens: impl Into<Option<u64>>) -> Self {
1197        self.max_tokens = max_tokens.into();
1198        self.warn_if_shadowed("max_tokens", self.max_tokens.is_some());
1199        self
1200    }
1201
1202    /// Set the tool-selection policy.
1203    pub fn tool_choice(mut self, tool_choice: ToolChoice) -> Self {
1204        self.tool_choice = Some(tool_choice);
1205        self.warn_if_shadowed("tool_choice", true);
1206        self
1207    }
1208
1209    /// Set, or with `None` clear, a native structured-output schema for
1210    /// providers that support one. The returned content is not
1211    /// deserialized.
1212    pub fn output_schema(mut self, schema: impl Into<Option<schemars::Schema>>) -> Self {
1213        self.output_schema = schema.into();
1214        self.warn_if_shadowed("response_format", self.output_schema.is_some());
1215        self
1216    }
1217
1218    /// Opt in to sensitive content telemetry, off by default. See
1219    /// [`Self::record_telemetry_content`] for what that exposes.
1220    pub fn record_content_telemetry(mut self, enabled: bool) -> Self {
1221        self.record_telemetry_content = enabled;
1222        self
1223    }
1224
1225    /// Accept, or with `false` refuse, finish reasons outside the normalized
1226    /// vocabulary as a normal stop. See
1227    /// [`Self::accept_unknown_finish_reasons`] for what that lets through.
1228    ///
1229    /// [`Self::accept_unknown_finish_reasons`]: field@Self::accept_unknown_finish_reasons
1230    pub fn accept_unknown_finish_reasons(mut self, accept: bool) -> Self {
1231        self.accept_unknown_finish_reasons = accept;
1232        self
1233    }
1234
1235    /// Replace the portable generation options with `options`, a reusable
1236    /// value. Calls apply in order: this replaces every field, so a
1237    /// shortcut such as [`Self::seed`] called before it is lost, and one
1238    /// called after it sets its one field on top.
1239    ///
1240    /// ```
1241    /// use rig_core::completion::{CompletionRequest, Effort, GenerationOptions};
1242    ///
1243    /// let shared = GenerationOptions::new().reasoning(Effort::High).seed(1);
1244    /// let request = CompletionRequest::new("hi").seed(7).options(shared.clone()).seed(2);
1245    /// assert_eq!(request.options, shared.seed(2));
1246    /// ```
1247    pub fn options(mut self, options: GenerationOptions) -> Self {
1248        self.options = options;
1249        self
1250    }
1251
1252    /// Set the reasoning level or budget in [`Self::options`](field@Self::options), as
1253    /// [`GenerationOptions::reasoning`](GenerationOptions::reasoning) does,
1254    /// keeping its other fields. See
1255    /// [`Self::options`](method@Self::options) for the order of calls.
1256    pub fn reasoning(mut self, reasoning: impl Into<Reasoning>) -> Self {
1257        self.options = std::mem::take(&mut self.options).reasoning(reasoning);
1258        self
1259    }
1260
1261    /// Set the cache retention in [`Self::options`](field@Self::options), as
1262    /// [`GenerationOptions::cache`](GenerationOptions::cache) does,
1263    /// keeping its other fields. See
1264    /// [`Self::options`](method@Self::options) for the order of calls.
1265    pub fn cache(mut self, cache: CacheRetention) -> Self {
1266        self.options = std::mem::take(&mut self.options).cache(cache);
1267        self
1268    }
1269
1270    /// Set the service tier in [`Self::options`](field@Self::options), as
1271    /// [`GenerationOptions::service_tier`](GenerationOptions::service_tier) does,
1272    /// keeping its other fields. See
1273    /// [`Self::options`](method@Self::options) for the order of calls.
1274    pub fn service_tier(mut self, tier: ServiceTier) -> Self {
1275        self.options = std::mem::take(&mut self.options).service_tier(tier);
1276        self
1277    }
1278
1279    /// Set the answer verbosity in [`Self::options`](field@Self::options), as
1280    /// [`GenerationOptions::verbosity`](GenerationOptions::verbosity) does,
1281    /// keeping its other fields. See
1282    /// [`Self::options`](method@Self::options) for the order of calls.
1283    pub fn verbosity(mut self, verbosity: Verbosity) -> Self {
1284        self.options = std::mem::take(&mut self.options).verbosity(verbosity);
1285        self
1286    }
1287
1288    /// Set whether the model may call several tools in one turn in [`Self::options`](field@Self::options), as
1289    /// [`GenerationOptions::parallel_tool_calls`](GenerationOptions::parallel_tool_calls) does,
1290    /// keeping its other fields. See
1291    /// [`Self::options`](method@Self::options) for the order of calls.
1292    pub fn parallel_tool_calls(mut self, parallel: bool) -> Self {
1293        self.options = std::mem::take(&mut self.options).parallel_tool_calls(parallel);
1294        self
1295    }
1296
1297    /// Set the nucleus sampling probability mass in [`Self::options`](field@Self::options), as
1298    /// [`GenerationOptions::top_p`](GenerationOptions::top_p) does,
1299    /// keeping its other fields. See
1300    /// [`Self::options`](method@Self::options) for the order of calls.
1301    pub fn top_p(mut self, top_p: f64) -> Self {
1302        self.options = std::mem::take(&mut self.options).top_p(top_p);
1303        self
1304    }
1305
1306    /// Set the sampling seed in [`Self::options`](field@Self::options), as
1307    /// [`GenerationOptions::seed`](GenerationOptions::seed) does,
1308    /// keeping its other fields. See
1309    /// [`Self::options`](method@Self::options) for the order of calls.
1310    pub fn seed(mut self, seed: u64) -> Self {
1311        self.options = std::mem::take(&mut self.options).seed(seed);
1312        self
1313    }
1314
1315    /// Set the stop sequences in [`Self::options`](field@Self::options), as
1316    /// [`GenerationOptions::stop`](GenerationOptions::stop) does,
1317    /// keeping its other fields. See
1318    /// [`Self::options`](method@Self::options) for the order of calls.
1319    pub fn stop<S: Into<String>>(mut self, stop: impl IntoIterator<Item = S>) -> Self {
1320        self.options = std::mem::take(&mut self.options).stop(stop);
1321        self
1322    }
1323
1324    /// Set what happens to an option the wire or model cannot honour in [`Self::options`](field@Self::options), as
1325    /// [`GenerationOptions::on_unsupported`](GenerationOptions::on_unsupported) does,
1326    /// keeping its other fields. See
1327    /// [`Self::options`](method@Self::options) for the order of calls.
1328    pub fn on_unsupported(mut self, policy: OnUnsupported) -> Self {
1329        self.options = std::mem::take(&mut self.options).on_unsupported(policy);
1330        self
1331    }
1332
1333    /// Replace the typed per-provider options.
1334    ///
1335    /// Calls apply in order: this replaces every entry, so an entry set by
1336    /// an earlier [`Self::provider_option`] is lost, and a later
1337    /// [`Self::provider_option`] replaces its provider's entry on top.
1338    pub fn provider_options(mut self, options: ProviderOptions) -> Self {
1339        self.provider_options = options;
1340        self
1341    }
1342
1343    /// Store `options` as the entry of their provider
1344    /// ([`ExtensionOptions::Ext`]), replacing that provider's entry and
1345    /// keeping every other, as [`ProviderOptions::set`] does. Options that
1346    /// do not serialize fail the request's encode.
1347    ///
1348    /// The entry is always stored under `O::Ext`'s key, the built-in
1349    /// provider the options type belongs to. A third-party provider whose
1350    /// extension reuses a built-in options type (say `OpenAiOptions` for an
1351    /// OpenAI-compatible gateway) must store them with
1352    /// [`ProviderOptions::with::<P>`](ProviderOptions::with) instead, or its
1353    /// wire never reads them.
1354    ///
1355    /// ```
1356    /// use rig_core::completion::{CompletionRequest, ProviderOptions};
1357    /// use rig_core::providers::openrouter::extension::{
1358    ///     OpenRouterExt, OpenRouterOptions, ProviderPreferences,
1359    /// };
1360    ///
1361    /// let request = CompletionRequest::new("hi").provider_option(
1362    ///     OpenRouterOptions::new().provider(ProviderPreferences::new().allow_fallbacks(false)),
1363    /// );
1364    /// assert!(request.provider_options.contains::<OpenRouterExt>());
1365    /// ```
1366    pub fn provider_option<O: ExtensionOptions>(mut self, options: O) -> Self {
1367        self.provider_options = std::mem::take(&mut self.provider_options).set(options);
1368        self
1369    }
1370
1371    /// The input messages telemetry records: the conversation with the
1372    /// documents inserted after any leading system messages.
1373    pub fn messages_for_telemetry(&self) -> Vec<Message> {
1374        self.chat_history_with_documents()
1375    }
1376
1377    /// Log a typed field `key` that `additional_params` already overrides.
1378    fn warn_if_shadowed(&self, key: &'static str, set: bool) {
1379        if !shadowed_typed_fields(self.additional_params.as_ref(), &[(key, set)]).is_empty() {
1380            warn_shadowed(key);
1381        }
1382    }
1383}
1384
1385fn warn_shadowed(key: &str) {
1386    if matches!(key, "tools" | "response_format") {
1387        tracing::warn!(
1388            key,
1389            "additional_params also carries `{key}`; the provider decides how it combines with the typed field"
1390        );
1391    } else {
1392        tracing::warn!(
1393            key,
1394            "additional_params overrides the typed `{key}` field set on the same request"
1395        );
1396    }
1397}
1398
1399impl From<&str> for CompletionRequest {
1400    fn from(prompt: &str) -> Self {
1401        Self::new(prompt)
1402    }
1403}
1404
1405impl From<String> for CompletionRequest {
1406    fn from(prompt: String) -> Self {
1407        Self::new(prompt)
1408    }
1409}
1410
1411impl From<Message> for CompletionRequest {
1412    fn from(prompt: Message) -> Self {
1413        Self::new(prompt)
1414    }
1415}
1416
1417/// The conversation as given, ending with the prompt. An empty one fails
1418/// [`CompletionRequest::validate_message_content`].
1419impl From<Vec<Message>> for CompletionRequest {
1420    fn from(chat_history: Vec<Message>) -> Self {
1421        Self::conversation(chat_history)
1422    }
1423}
1424
1425/// The passthrough keys that will override a typed field the caller also set.
1426/// The override itself is the documented precedence (see
1427/// [`CompletionRequest::additional_params`]); naming the collisions
1428/// makes an accidental one visible instead of silent.
1429pub(crate) fn shadowed_typed_fields<'a>(
1430    additional_params: Option<&serde_json::Value>,
1431    typed: &[(&'a str, bool)],
1432) -> Vec<&'a str> {
1433    let Some(serde_json::Value::Object(params)) = additional_params else {
1434        return Vec::new();
1435    };
1436    typed
1437        .iter()
1438        .filter(|(key, set)| *set && params.contains_key(*key))
1439        .map(|(key, _)| *key)
1440        .collect()
1441}
1442
1443#[cfg(test)]
1444mod tests;
1445
1446#[cfg(test)]
1447mod response_identity_tests;
1448
1449#[cfg(test)]
1450mod plain_value_tests;