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, CallId, 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};
29use std::sync::Arc;
30
31/// A document sent with a request as context, rendered before the chat history.
32#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
33pub struct Document {
34 /// Stable document identifier included in the serialized context block.
35 pub id: String,
36 /// Text content passed to the model as retrieval or static context.
37 pub text: String,
38 /// Additional string metadata rendered before the document text.
39 #[serde(flatten)]
40 pub additional_props: HashMap<String, String>,
41}
42
43impl std::fmt::Display for Document {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 write!(
46 f,
47 concat!("<file id: {}>\n", "{}\n", "</file>\n"),
48 self.id,
49 if self.additional_props.is_empty() {
50 self.text.clone()
51 } else {
52 let mut sorted_props = self.additional_props.iter().collect::<Vec<_>>();
53 sorted_props.sort_by(|a, b| a.0.cmp(b.0));
54 let metadata = sorted_props
55 .iter()
56 .map(|(k, v)| format!("{k}: {v:?}"))
57 .collect::<Vec<_>>()
58 .join(" ");
59 format!("<metadata {} />\n{}", metadata, self.text)
60 }
61 )
62 }
63}
64
65/// A tool the model may call: its name, description and argument schema,
66/// sent to the model, and what the tool does to the world
67/// ([`ToolAnnotations`]), which stays with the host.
68#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
69pub struct ToolDefinition {
70 /// Tool name exposed to the model. It must match the registered tool name.
71 pub name: ToolName,
72 /// Human-readable description sent to the model.
73 pub description: String,
74 /// JSON Schema describing tool arguments.
75 pub parameters: serde_json::Value,
76 /// What calling the tool does to the world, for the host's policies
77 /// (approval, plan mode, running calls side by side). Never sent to
78 /// the model; written only when something is stated.
79 #[serde(default, skip_serializing_if = "ToolAnnotations::is_empty")]
80 pub annotations: ToolAnnotations,
81}
82
83impl ToolDefinition {
84 /// A tool the model may call by `name`, with arguments matching the
85 /// JSON Schema `parameters`, and no annotations.
86 pub fn new(
87 name: ToolName,
88 description: impl Into<String>,
89 parameters: serde_json::Value,
90 ) -> Self {
91 Self {
92 name,
93 description: description.into(),
94 parameters,
95 annotations: ToolAnnotations::default(),
96 }
97 }
98
99 /// The same definition with `annotations`.
100 pub fn with_annotations(mut self, annotations: ToolAnnotations) -> Self {
101 self.annotations = annotations;
102 self
103 }
104}
105
106/// What calling a tool does to the world, as the Model Context Protocol's
107/// tool annotations state it, and spelled as they are on its wire
108/// (`readOnlyHint`, …), so an MCP tool's annotations read straight into
109/// one. Each is a hint a tool's author gives: a host's policies read them
110/// (approval, plan mode, which calls run side by side), never trusting a
111/// tool it does not trust for them. A hint left unset takes the protocol's
112/// default, which the accessors apply: not read-only, destructive, not
113/// idempotent, open world.
114///
115/// ```
116/// use rig_core::completion::ToolAnnotations;
117///
118/// let read = ToolAnnotations::default().with_read_only(true).with_open_world(false);
119/// assert!(read.read_only() && !read.destructive() && !read.open_world());
120/// let unknown = ToolAnnotations::default();
121/// assert!(!unknown.read_only() && unknown.destructive() && unknown.open_world());
122/// let mcp: ToolAnnotations = serde_json::from_value(serde_json::json!({"readOnlyHint": true}))?;
123/// assert!(mcp.read_only());
124/// # Ok::<(), serde_json::Error>(())
125/// ```
126#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Deserialize, Serialize)]
127#[serde(rename_all = "camelCase")]
128pub struct ToolAnnotations {
129 /// A human-readable title for the tool.
130 #[serde(default, skip_serializing_if = "Option::is_none")]
131 pub title: Option<String>,
132 /// Whether the tool changes nothing in its environment.
133 #[serde(default, skip_serializing_if = "Option::is_none")]
134 pub read_only_hint: Option<bool>,
135 /// Whether a tool that changes things may also destroy or overwrite
136 /// what was there (rather than only add). Meaningless for a read-only
137 /// tool.
138 #[serde(default, skip_serializing_if = "Option::is_none")]
139 pub destructive_hint: Option<bool>,
140 /// Whether calling the tool again with the same arguments has no
141 /// further effect. Meaningless for a read-only tool.
142 #[serde(default, skip_serializing_if = "Option::is_none")]
143 pub idempotent_hint: Option<bool>,
144 /// Whether the tool reaches beyond a closed set of things, such as the
145 /// web or a shell; `false` for one that works on a known store, such
146 /// as the project's files.
147 #[serde(default, skip_serializing_if = "Option::is_none")]
148 pub open_world_hint: Option<bool>,
149}
150
151impl ToolAnnotations {
152 /// Whether nothing is stated.
153 pub fn is_empty(&self) -> bool {
154 *self == Self::default()
155 }
156
157 /// Whether the tool changes nothing: the hint, else `false`.
158 pub fn read_only(&self) -> bool {
159 self.read_only_hint.unwrap_or(false)
160 }
161
162 /// Whether the tool may destroy or overwrite something: never for a
163 /// read-only tool, else the hint, else `true`.
164 pub fn destructive(&self) -> bool {
165 !self.read_only() && self.destructive_hint.unwrap_or(true)
166 }
167
168 /// Whether repeating a call has no further effect: always for a
169 /// read-only tool, else the hint, else `false`.
170 pub fn idempotent(&self) -> bool {
171 self.read_only() || self.idempotent_hint.unwrap_or(false)
172 }
173
174 /// Whether the tool reaches an open world: the hint, else `true`.
175 pub fn open_world(&self) -> bool {
176 self.open_world_hint.unwrap_or(true)
177 }
178
179 /// The same annotations with a title.
180 pub fn with_title(mut self, title: impl Into<String>) -> Self {
181 self.title = Some(title.into());
182 self
183 }
184
185 /// The same annotations stating whether the tool is read-only.
186 pub fn with_read_only(mut self, read_only: bool) -> Self {
187 self.read_only_hint = Some(read_only);
188 self
189 }
190
191 /// The same annotations stating whether the tool is destructive.
192 pub fn with_destructive(mut self, destructive: bool) -> Self {
193 self.destructive_hint = Some(destructive);
194 self
195 }
196
197 /// The same annotations stating whether the tool is idempotent.
198 pub fn with_idempotent(mut self, idempotent: bool) -> Self {
199 self.idempotent_hint = Some(idempotent);
200 self
201 }
202
203 /// The same annotations stating whether the tool reaches an open world.
204 pub fn with_open_world(mut self, open_world: bool) -> Self {
205 self.open_world_hint = Some(open_world);
206 self
207 }
208}
209
210/// Provider-native tool definition.
211///
212/// Stored under `additional_params.tools` and forwarded by providers that support
213/// provider-managed tools.
214#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
215pub struct ProviderToolDefinition {
216 /// Tool type/kind name as expected by the target provider (for example `web_search`).
217 #[serde(rename = "type")]
218 pub kind: String,
219 /// Additional provider-specific configuration for this hosted tool.
220 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
221 pub config: serde_json::Map<String, serde_json::Value>,
222}
223
224impl ProviderToolDefinition {
225 /// Creates a provider-hosted tool definition by type.
226 pub fn new(kind: impl Into<String>) -> Self {
227 Self {
228 kind: kind.into(),
229 config: serde_json::Map::new(),
230 }
231 }
232
233 /// Adds a provider-specific configuration key/value.
234 pub fn with_config(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
235 self.config.insert(key.into(), value);
236 self
237 }
238}
239
240/// Normalized generation ending. Unmapped provider values remain in [`Self::Other`].
241/// Failure statuses may accompany parseable output; callers must decide whether
242/// such output is usable rather than treating every response as successful.
243#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
244#[serde(rename_all = "snake_case")]
245#[non_exhaustive]
246pub enum FinishReason {
247 /// Natural end of the response.
248 Stop,
249 /// The response hit the output-token limit.
250 Length,
251 /// The model stopped to call one or more tools.
252 ToolCalls,
253 /// The provider filtered the content.
254 ContentFilter,
255 /// A provider-specific reason outside the normalized vocabulary, carried
256 /// verbatim in the provider's own wire spelling.
257 Other(String),
258}
259
260impl FinishReason {
261 /// Changes [`Self::Stop`] to [`Self::ToolCalls`] when output contains a tool
262 /// call. All other reasons remain unchanged. Response builders and streaming
263 /// aggregation apply this reconciliation.
264 pub fn reconcile_with_output(self, has_tool_call: bool) -> Self {
265 if has_tool_call && matches!(self, Self::Stop) {
266 Self::ToolCalls
267 } else {
268 self
269 }
270 }
271
272 /// Returns whether the reason is [`Self::Length`] or [`Self::ContentFilter`].
273 /// These reasons permit answerless turns without treating absent content as
274 /// a malformed response. Unknown reasons are not classified as truncation.
275 pub fn truncated_output(&self) -> bool {
276 matches!(self, Self::Length | Self::ContentFilter)
277 }
278
279 /// Formats an answerless-turn diagnostic with budget or filtering advice
280 /// for known truncation reasons, and a generic explanation otherwise.
281 pub fn no_answer_message(&self) -> String {
282 let remedy = match self {
283 Self::Length => {
284 "the turn ran out of output budget before producing one — \
285 raise max_tokens for this request"
286 }
287 Self::ContentFilter => {
288 "the provider filtered the response — the content, not the \
289 budget, is what it objected to"
290 }
291 _ => "the turn ended before producing one",
292 };
293 format!(
294 "the model produced no answer and stopped with \
295 finish_reason={self:?}; {remedy}"
296 )
297 }
298}
299
300/// Assistant content and normalized completion metadata. The choice may be
301/// empty, including for truncated or filtered turns. Provider-specific data is
302/// available through [`Self::raw`] without retaining a concrete model type.
303///
304/// A response goes straight back into the conversation as the assistant
305/// turn: `history.extend(response.message())`.
306#[non_exhaustive]
307#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
308#[serde(from = "CompletionResponseRepr")]
309pub struct CompletionResponse {
310 /// Assistant content returned by the provider, one block per provider
311 /// output item, in provider order. Possibly empty.
312 pub choice: Vec<AssistantContent>,
313 /// Tokens used during prompting and responding
314 pub usage: Usage,
315 /// The wire, provider and requested model that produced the response,
316 /// with the model and response id the provider reported.
317 pub origin: Origin,
318 /// The provider's report that the turn failed, such as a refusal's
319 /// explanation. The turn's message then ends in [`StopReason::Error`]
320 /// and is never replayed.
321 #[serde(default, skip_serializing_if = "Option::is_none")]
322 pub error: Option<String>,
323 /// Why the reply stopped before the provider ended it, when the caller
324 /// stopped reading: the turn's message then ends in
325 /// [`StopReason::Aborted`] and is never replayed.
326 #[serde(default, skip_serializing_if = "Option::is_none")]
327 pub aborted: Option<String>,
328 /// Request identifier from HTTP headers or SDK metadata, not the body's
329 /// response ID. `None` when the provider reports none.
330 #[serde(default, skip_serializing_if = "Option::is_none")]
331 pub provider_request_id: Option<String>,
332 /// Reported finish reason, reconciled by the setters with tool-call output.
333 /// Read through [`Self::finish_reason`].
334 #[serde(default)]
335 finish_reason: Option<FinishReason>,
336 /// Whether [`FinishReason::Other`] ends the turn as a normal stop. Read
337 /// through [`Self::accepts_unknown_finish_reasons`].
338 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
339 accepts_unknown_finish_reasons: bool,
340 /// The calls the provider ended the reply in the middle of. Read
341 /// through [`Self::unfinished_calls`].
342 #[serde(default, skip_serializing_if = "Vec::is_empty")]
343 unfinished_calls: Vec<CallId>,
344 /// Provider response document for typed inspection through deserialization.
345 /// Parsed wire types may omit unmodeled fields. This data does not override
346 /// normalized fields; callers constructing responses must supply it.
347 pub raw: serde_json::Value,
348}
349
350/// Distinct response and transport identifiers for one model call.
351/// Unreported identifiers remain `None`.
352#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
353pub struct ResponseIdentity {
354 /// Response-wide ID.
355 #[serde(default, skip_serializing_if = "Option::is_none")]
356 pub response_id: Option<String>,
357 /// Transport request ID from HTTP headers or SDK metadata.
358 #[serde(default, skip_serializing_if = "Option::is_none")]
359 pub provider_request_id: Option<String>,
360}
361
362impl CompletionResponse {
363 /// Create a response from its required parts; optional metadata starts
364 /// unset. `raw` is the provider's own document for this response,
365 /// serialized; see [`Self::raw`].
366 pub fn new(
367 choice: Vec<AssistantContent>,
368 usage: Usage,
369 origin: Origin,
370 raw: serde_json::Value,
371 ) -> Self {
372 Self {
373 choice,
374 usage,
375 origin,
376 error: None,
377 aborted: None,
378 provider_request_id: None,
379 finish_reason: None,
380 accepts_unknown_finish_reasons: false,
381 unfinished_calls: Vec::new(),
382 raw,
383 }
384 }
385
386 /// The provider descriptor name (`"openai"`).
387 pub fn provider(&self) -> &str {
388 &self.origin.provider
389 }
390
391 /// `P`'s typed view of [`Self::raw`]. `None` unless `P` is the provider
392 /// that produced the reply.
393 ///
394 /// # Errors
395 ///
396 /// The inner result fails when `raw` does not hold `P`'s extras.
397 pub fn extras<P: ProviderExtension>(&self) -> Option<Result<P::Extras, serde_json::Error>> {
398 (self.origin.provider == P::PROVIDER)
399 .then(|| P::Extras::from_reply(&self.origin.api, &self.raw))
400 }
401
402 /// `P`'s typed view of [`Self::raw`], or the empty view (every field
403 /// `None`) when it cannot be read: when another provider produced the
404 /// reply, or when `raw` does not hold `P`'s extras. A decode failure is
405 /// logged at `debug`; use [`Self::extras`] to tell the cases apart.
406 pub fn extras_lossy<P: ProviderExtension>(&self) -> P::Extras
407 where
408 P::Extras: Default,
409 {
410 match self.extras::<P>() {
411 Some(Ok(extras)) => extras,
412 Some(Err(error)) => {
413 tracing::debug!(
414 provider = P::PROVIDER,
415 api = %self.origin.api,
416 %error,
417 "reply extras did not decode; reading them as empty"
418 );
419 P::Extras::default()
420 }
421 None => P::Extras::default(),
422 }
423 }
424
425 /// The model the provider reported, when it reported one.
426 pub fn model(&self) -> Option<&str> {
427 self.origin.response_model.as_deref()
428 }
429
430 /// The provider's response id, when it sent one.
431 pub fn response_id(&self) -> Option<&str> {
432 self.origin.response_id.as_deref()
433 }
434
435 /// Why the model stopped generating, when the provider reported it.
436 pub fn finish_reason(&self) -> Option<FinishReason> {
437 self.finish_reason.clone()
438 }
439
440 /// Whether a finish reason outside the normalized vocabulary ends the
441 /// turn as a normal stop, as the request asked
442 /// ([`CompletionRequest::accept_unknown_finish_reasons`]).
443 ///
444 /// [`CompletionRequest::accept_unknown_finish_reasons`]: field@CompletionRequest::accept_unknown_finish_reasons
445 pub fn accepts_unknown_finish_reasons(&self) -> bool {
446 self.accepts_unknown_finish_reasons
447 }
448
449 /// The tool calls the provider ended the reply in the middle of (their
450 /// arguments as far as they came), in the order they opened, such as a
451 /// call cut by [`FinishReason::Length`]. Empty when every call in
452 /// [`Self::choice`] was finished.
453 pub fn unfinished_calls(&self) -> impl Iterator<Item = &ToolCall> {
454 self.tool_calls()
455 .filter(|call| self.unfinished_calls.contains(&call.id))
456 }
457
458 /// The same response, `calls` (ids of calls in [`Self::choice`]) being
459 /// the ones the provider ended the reply in the middle of.
460 pub fn with_unfinished_calls(mut self, calls: impl IntoIterator<Item = CallId>) -> Self {
461 self.unfinished_calls = calls.into_iter().collect();
462 self
463 }
464
465 /// Accept, or refuse, [`FinishReason::Other`] as a normal stop in
466 /// [`Self::stop`]. Filtered content and a reported failure still fail.
467 pub fn accept_unknown_finish_reasons(mut self, accept: bool) -> Self {
468 self.accepts_unknown_finish_reasons = accept;
469 self
470 }
471
472 /// How the turn ended, for history. It fails closed: only a natural
473 /// stop, the token limit and a stop to call tools are successes. A
474 /// reported failure, filtered content and any finish reason outside
475 /// that set are [`StopReason::Error`], unless the response accepts
476 /// unknown reasons ([`Self::accepts_unknown_finish_reasons`]), which then
477 /// stop like [`FinishReason::Stop`]. A reply the caller stopped reading
478 /// is [`StopReason::Aborted`].
479 pub fn stop(&self) -> StopReason {
480 if let Some(error) = &self.error {
481 return StopReason::Error(error.clone());
482 }
483 if let Some(reason) = &self.aborted {
484 return StopReason::Aborted(reason.clone());
485 }
486 match &self.finish_reason {
487 Some(FinishReason::Length) => StopReason::Length,
488 Some(FinishReason::ToolCalls) => StopReason::ToolUse,
489 Some(FinishReason::ContentFilter) => {
490 StopReason::Error("Provider finish_reason: content_filter".to_owned())
491 }
492 Some(FinishReason::Other(reason)) if !self.accepts_unknown_finish_reasons => {
493 StopReason::Error(format!("Provider finish_reason: {reason}"))
494 }
495 Some(FinishReason::Stop | FinishReason::Other(_)) | None
496 if self.tool_calls().next().is_some() =>
497 {
498 StopReason::ToolUse
499 }
500 Some(FinishReason::Stop | FinishReason::Other(_)) | None => StopReason::Stop,
501 }
502 }
503
504 /// This response's identity metadata as one [`ResponseIdentity`] carrier.
505 pub fn identity(&self) -> ResponseIdentity {
506 ResponseIdentity {
507 response_id: self.origin.response_id.clone(),
508 provider_request_id: self.provider_request_id.clone(),
509 }
510 }
511
512 /// Attach the normalized finish reason, reconciled against the choice via
513 /// [`FinishReason::reconcile_with_output`].
514 pub fn with_finish_reason(self, finish_reason: FinishReason) -> Self {
515 self.with_optional_finish_reason(Some(finish_reason))
516 }
517
518 /// The text parts of [`Self::choice`], concatenated in order.
519 pub fn text(&self) -> String {
520 self.choice
521 .iter()
522 .filter_map(|part| match part {
523 AssistantContent::Text(text) => Some(text.text.as_str()),
524 _ => None,
525 })
526 .collect()
527 }
528
529 /// The reasoning text of [`Self::choice`], concatenated in order.
530 /// Redacted reasoning has no text.
531 pub fn reasoning(&self) -> String {
532 self.choice
533 .iter()
534 .filter_map(|part| match part {
535 AssistantContent::Reasoning(reasoning) => Some(reasoning.text.as_str()),
536 _ => None,
537 })
538 .collect()
539 }
540
541 /// The assistant turn to append to the conversation: [`Self::choice`] in
542 /// order with its origin and stop, or `None` for an
543 /// empty choice.
544 pub fn message(&self) -> Option<Message> {
545 if self.choice.is_empty() {
546 return None;
547 }
548 Some(Message::Assistant(AssistantMessage {
549 content: self.choice.clone(),
550 ..self.head()
551 }))
552 }
553
554 /// The turn this response began, holding `content`, for a runtime that
555 /// cut the reply short and answers its calls itself (an agent rolling a
556 /// turn back over an invalid call). It keeps the origin, and stops to
557 /// call tools, since the runtime answers them. A block keeps its provider
558 /// item only when this response holds the same block with it. A response
559 /// the provider did not end, or one the consumer has not wholly taken,
560 /// holds no provider item, so its turn replays canonically.
561 pub fn continued(&self, content: Vec<AssistantContent>) -> AssistantMessage {
562 let content = content
563 .into_iter()
564 .map(|block| {
565 if self.choice.contains(&block) {
566 block
567 } else {
568 crate::operation::completion::canonical(block)
569 }
570 })
571 .collect();
572 AssistantMessage::rolled_back(Some(self.origin.clone()), content)
573 }
574
575 /// The turn's origin and stop with no content, for a
576 /// runtime that carries the content separately.
577 pub fn head(&self) -> AssistantMessage {
578 AssistantMessage {
579 content: Vec::new(),
580 origin: Some(self.origin.clone()),
581 stop: Some(self.stop()),
582 }
583 }
584
585 /// The tool calls in [`Self::choice`], in order.
586 pub fn tool_calls(&self) -> impl Iterator<Item = &ToolCall> {
587 self.choice.iter().filter_map(|part| match part {
588 AssistantContent::ToolCall(call) => Some(call),
589 _ => None,
590 })
591 }
592
593 /// Sets or clears the finish reason, reconciling a present reason with the choice.
594 pub fn with_optional_finish_reason(mut self, finish_reason: Option<FinishReason>) -> Self {
595 let has_tool_call = self
596 .choice
597 .iter()
598 .any(|content| matches!(content, AssistantContent::ToolCall(_)));
599 self.finish_reason =
600 finish_reason.map(|reason| reason.reconcile_with_output(has_tool_call));
601 self
602 }
603}
604
605/// Deserialization shape routed through builders for finish-reason reconciliation
606/// and empty-identifier normalization.
607#[derive(Deserialize)]
608struct CompletionResponseRepr {
609 choice: Vec<AssistantContent>,
610 usage: Usage,
611 origin: Origin,
612 #[serde(default)]
613 error: Option<String>,
614 #[serde(default)]
615 aborted: Option<String>,
616 #[serde(default)]
617 provider_request_id: Option<String>,
618 #[serde(default)]
619 finish_reason: Option<FinishReason>,
620 #[serde(default)]
621 accepts_unknown_finish_reasons: bool,
622 #[serde(default)]
623 unfinished_calls: Vec<CallId>,
624 raw: serde_json::Value,
625}
626
627impl From<CompletionResponseRepr> for CompletionResponse {
628 fn from(repr: CompletionResponseRepr) -> Self {
629 let CompletionResponseRepr {
630 choice,
631 usage,
632 mut origin,
633 error,
634 aborted,
635 provider_request_id,
636 finish_reason,
637 accepts_unknown_finish_reasons,
638 unfinished_calls,
639 raw,
640 } = repr;
641 use crate::provider_response::reported;
642 origin.response_id = reported(origin.response_id);
643 origin.response_model = reported(origin.response_model);
644 let mut response =
645 Self::new(choice, usage, origin, raw).with_optional_finish_reason(finish_reason);
646 response.error = error;
647 response.aborted = aborted;
648 response.provider_request_id = reported(provider_request_id);
649 response.accepts_unknown_finish_reasons = accepts_unknown_finish_reasons;
650 response.unfinished_calls = unfinished_calls;
651 response
652 }
653}
654
655/// The token usage a provider reported for one completion.
656///
657/// Every provider mapping keeps one contract, so the counters read the same
658/// way on every provider:
659///
660/// - `cached_input_tokens + cache_creation_input_tokens <= input_tokens`:
661/// input counts every prompt token, cache reads and writes included.
662/// - `reasoning_tokens <= output_tokens`: output counts every generated
663/// token, reasoning included.
664/// - `total_tokens == input_tokens + output_tokens`, absent unless both are
665/// reported.
666///
667/// A counter the provider did not send is `None`; a reported zero is
668/// `Some(0)`. Serialized as the same keys, absent when `None`.
669///
670/// ```
671/// use rig_core::completion::Usage;
672///
673/// let usage = Usage::new().input_tokens(12).output_tokens(3).total_tokens(15);
674/// assert_eq!(usage.input_tokens, Some(12));
675/// assert!(usage.cost.is_none());
676/// ```
677#[non_exhaustive]
678#[derive(Debug, Default, PartialEq, Clone, Copy, Serialize, Deserialize)]
679pub struct Usage {
680 /// Every input token of the request: uncached, read from a cache,
681 /// written to a cache, and any prompt a provider's hosted tools added.
682 #[serde(default, skip_serializing_if = "Option::is_none")]
683 pub input_tokens: Option<u64>,
684 /// Every output token, reasoning included.
685 #[serde(default, skip_serializing_if = "Option::is_none")]
686 pub output_tokens: Option<u64>,
687 /// `input_tokens + output_tokens`; absent unless both are reported.
688 #[serde(default, skip_serializing_if = "Option::is_none")]
689 pub total_tokens: Option<u64>,
690 /// The part of `input_tokens` read from a provider-managed cache.
691 #[serde(default, skip_serializing_if = "Option::is_none")]
692 pub cached_input_tokens: Option<u64>,
693 /// The part of `input_tokens` written to a provider-managed cache.
694 #[serde(default, skip_serializing_if = "Option::is_none")]
695 pub cache_creation_input_tokens: Option<u64>,
696 /// The part of `input_tokens` a provider's hosted tools added to the prompt.
697 #[serde(default, skip_serializing_if = "Option::is_none")]
698 pub tool_use_prompt_tokens: Option<u64>,
699 /// The part of `output_tokens` spent on internal reasoning ("thinking",
700 /// "thoughts").
701 #[serde(default, skip_serializing_if = "Option::is_none")]
702 pub reasoning_tokens: Option<u64>,
703 /// What the turn cost in USD, when known. Never derived from the token
704 /// counters here, and none of them is derived from it. A cost the
705 /// provider reports is its figure. One priced from the model's catalog
706 /// spec ([`Pricing::cost`](crate::catalog::Pricing::cost), from the
707 /// facts the wire was connected with) is the
708 /// standard-tier list price of the counted tokens: it leaves out the
709 /// service tier, long-context price tiers and hosted-tool fees (web
710 /// search, code execution), so it can be lower than the bill.
711 #[serde(default, skip_serializing_if = "Option::is_none")]
712 pub cost: Option<Cost>,
713}
714
715impl Usage {
716 /// No counter and no cost reported.
717 pub const fn new() -> Self {
718 Self {
719 input_tokens: None,
720 output_tokens: None,
721 total_tokens: None,
722 cached_input_tokens: None,
723 cache_creation_input_tokens: None,
724 tool_use_prompt_tokens: None,
725 reasoning_tokens: None,
726 cost: None,
727 }
728 }
729
730 /// Whether the provider reported any counter or a cost.
731 pub fn is_reported(&self) -> bool {
732 *self != Self::default()
733 }
734
735 /// The tokens the call read and wrote, what a conversation's next
736 /// request sends again: [`Self::total_tokens`](field@Self::total_tokens),
737 /// else the input and output tokens. `None` without input tokens.
738 pub fn context_tokens(&self) -> Option<u64> {
739 self.total_tokens.or_else(|| {
740 Some(
741 self.input_tokens?
742 .saturating_add(self.output_tokens.unwrap_or(0)),
743 )
744 })
745 }
746
747 /// Set, or with `None` clear, [`Self::input_tokens`](field@Self::input_tokens).
748 pub fn input_tokens(mut self, tokens: impl Into<Option<u64>>) -> Self {
749 self.input_tokens = tokens.into();
750 self
751 }
752
753 /// Set, or with `None` clear, [`Self::output_tokens`](field@Self::output_tokens).
754 pub fn output_tokens(mut self, tokens: impl Into<Option<u64>>) -> Self {
755 self.output_tokens = tokens.into();
756 self
757 }
758
759 /// Set, or with `None` clear, [`Self::total_tokens`](field@Self::total_tokens).
760 pub fn total_tokens(mut self, tokens: impl Into<Option<u64>>) -> Self {
761 self.total_tokens = tokens.into();
762 self
763 }
764
765 /// Set, or with `None` clear,
766 /// [`Self::cached_input_tokens`](field@Self::cached_input_tokens).
767 pub fn cached_input_tokens(mut self, tokens: impl Into<Option<u64>>) -> Self {
768 self.cached_input_tokens = tokens.into();
769 self
770 }
771
772 /// Set, or with `None` clear,
773 /// [`Self::cache_creation_input_tokens`](field@Self::cache_creation_input_tokens).
774 pub fn cache_creation_input_tokens(mut self, tokens: impl Into<Option<u64>>) -> Self {
775 self.cache_creation_input_tokens = tokens.into();
776 self
777 }
778
779 /// Set, or with `None` clear,
780 /// [`Self::tool_use_prompt_tokens`](field@Self::tool_use_prompt_tokens).
781 pub fn tool_use_prompt_tokens(mut self, tokens: impl Into<Option<u64>>) -> Self {
782 self.tool_use_prompt_tokens = tokens.into();
783 self
784 }
785
786 /// Set, or with `None` clear, [`Self::reasoning_tokens`](field@Self::reasoning_tokens).
787 pub fn reasoning_tokens(mut self, tokens: impl Into<Option<u64>>) -> Self {
788 self.reasoning_tokens = tokens.into();
789 self
790 }
791
792 /// Set, or with `None` clear, [`Self::cost`](field@Self::cost).
793 pub fn cost(mut self, cost: impl Into<Option<Cost>>) -> Self {
794 self.cost = cost.into();
795 self
796 }
797}
798
799/// What one or more turns cost, in USD. `total` is what was charged, or
800/// when [`Self::is_complete`] is `false`, a lower bound of it.
801///
802/// A part is `None` when its source does not give it: a provider that
803/// reports only a total leaves every part `None`, and one that reports
804/// input and output leaves the cache parts `None`. A cost computed from
805/// the catalog ([`Pricing::cost`](crate::catalog::Pricing::cost)) leaves a
806/// cache part `None` when the catalog lists no rate for it and the usage
807/// has such tokens; its `total` then sums the known parts only, and it is
808/// not complete. A part is never filled with a placeholder `0.0`. Summing
809/// costs keeps a part only when every side has it, and is complete only
810/// when every side is. A `None` part is left out when serialized, and an
811/// absent one reads back as `None`.
812#[non_exhaustive]
813#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
814pub struct Cost {
815 /// Uncached input tokens.
816 #[serde(default, skip_serializing_if = "Option::is_none")]
817 pub input: Option<f64>,
818 /// Output tokens, reasoning included.
819 #[serde(default, skip_serializing_if = "Option::is_none")]
820 pub output: Option<f64>,
821 /// Input tokens read from a cache.
822 #[serde(default, skip_serializing_if = "Option::is_none")]
823 pub cache_read: Option<f64>,
824 /// Input tokens written to a cache.
825 #[serde(default, skip_serializing_if = "Option::is_none")]
826 pub cache_write: Option<f64>,
827 /// The whole charge, or a lower bound of it when the cost is not
828 /// complete.
829 pub total: f64,
830 /// Whether a part with tokens had no price, so `total` leaves it out.
831 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
832 incomplete: bool,
833}
834
835impl Cost {
836 /// Whether [`Self::total`](field@Self::total) is the whole charge.
837 /// `false` when a part the usage has tokens for had no known price, so
838 /// `total` is a lower bound.
839 pub fn is_complete(&self) -> bool {
840 !self.incomplete
841 }
842
843 /// A cost from the catalog's prices, where `None` marks a part whose
844 /// tokens had no price: `total` sums the known parts, and the cost is
845 /// complete only when no part is `None`.
846 pub(crate) fn priced(
847 input: f64,
848 output: f64,
849 cache_read: Option<f64>,
850 cache_write: Option<f64>,
851 ) -> Self {
852 Self {
853 input: Some(input),
854 output: Some(output),
855 cache_read,
856 cache_write,
857 total: input + output + cache_read.unwrap_or(0.0) + cache_write.unwrap_or(0.0),
858 incomplete: cache_read.is_none() || cache_write.is_none(),
859 }
860 }
861
862 /// A cost split into its parts, each known; `total` is their sum.
863 pub fn from_parts(input: f64, output: f64, cache_read: f64, cache_write: f64) -> Self {
864 Self {
865 input: Some(input),
866 output: Some(output),
867 cache_read: Some(cache_read),
868 cache_write: Some(cache_write),
869 total: input + output + cache_read + cache_write,
870 incomplete: false,
871 }
872 }
873
874 /// A cost known only as its total; every part is `None`.
875 pub fn from_total(total: f64) -> Self {
876 Self {
877 input: None,
878 output: None,
879 cache_read: None,
880 cache_write: None,
881 total,
882 incomplete: false,
883 }
884 }
885
886 /// Set, or with `None` clear, [`Self::input`](field@Self::input).
887 /// `total` is left as it is.
888 pub fn input(mut self, input: impl Into<Option<f64>>) -> Self {
889 self.input = input.into();
890 self
891 }
892
893 /// Set, or with `None` clear, [`Self::output`](field@Self::output).
894 /// `total` is left as it is.
895 pub fn output(mut self, output: impl Into<Option<f64>>) -> Self {
896 self.output = output.into();
897 self
898 }
899
900 /// Set, or with `None` clear, [`Self::cache_read`](field@Self::cache_read).
901 /// `total` is left as it is.
902 pub fn cache_read(mut self, cache_read: impl Into<Option<f64>>) -> Self {
903 self.cache_read = cache_read.into();
904 self
905 }
906
907 /// Set, or with `None` clear, [`Self::cache_write`](field@Self::cache_write).
908 /// `total` is left as it is.
909 pub fn cache_write(mut self, cache_write: impl Into<Option<f64>>) -> Self {
910 self.cache_write = cache_write.into();
911 self
912 }
913}
914
915/// Sum one part of two costs: unknown when either side's is unknown, since
916/// a known part plus an unknown one is not known.
917fn add_part(lhs: Option<f64>, rhs: Option<f64>) -> Option<f64> {
918 Some(lhs? + rhs?)
919}
920
921/// `total` always sums. Each part sums only when both sides know it, and is
922/// `None` otherwise, so a summed part never understates what it covers. The
923/// sum is complete only when both sides are.
924impl Add for Cost {
925 type Output = Self;
926
927 fn add(self, other: Self) -> Self::Output {
928 Self {
929 input: add_part(self.input, other.input),
930 output: add_part(self.output, other.output),
931 cache_read: add_part(self.cache_read, other.cache_read),
932 cache_write: add_part(self.cache_write, other.cache_write),
933 total: self.total + other.total,
934 incomplete: self.incomplete || other.incomplete,
935 }
936 }
937}
938
939/// Sum two counters where an unreported side does not turn a reported one
940/// into "unreported".
941fn add_counter(lhs: Option<u64>, rhs: Option<u64>) -> Option<u64> {
942 match (lhs, rhs) {
943 (None, None) => None,
944 (lhs, rhs) => Some(lhs.unwrap_or(0) + rhs.unwrap_or(0)),
945 }
946}
947
948impl Add for Usage {
949 type Output = Self;
950
951 fn add(mut self, other: Self) -> Self::Output {
952 self += other;
953 self
954 }
955}
956
957/// Token counters add where an unreported side adds nothing. Cost sums only
958/// when both sides have one: a turn whose cost is unknown makes the sum
959/// unknown, rather than too low. A side that reports nothing at all
960/// ([`Usage::is_reported`] is `false`) is the identity, so a fold from
961/// [`Usage::default`] keeps its first turn's cost.
962impl AddAssign for Usage {
963 fn add_assign(&mut self, other: Self) {
964 if !other.is_reported() {
965 return;
966 }
967 if !self.is_reported() {
968 *self = other;
969 return;
970 }
971 self.cost = match (self.cost, other.cost) {
972 (Some(lhs), Some(rhs)) => Some(lhs + rhs),
973 _ => None,
974 };
975 self.input_tokens = add_counter(self.input_tokens, other.input_tokens);
976 self.output_tokens = add_counter(self.output_tokens, other.output_tokens);
977 self.total_tokens = add_counter(self.total_tokens, other.total_tokens);
978 self.cached_input_tokens = add_counter(self.cached_input_tokens, other.cached_input_tokens);
979 self.cache_creation_input_tokens = add_counter(
980 self.cache_creation_input_tokens,
981 other.cache_creation_input_tokens,
982 );
983 self.tool_use_prompt_tokens =
984 add_counter(self.tool_use_prompt_tokens, other.tool_use_prompt_tokens);
985 self.reasoning_tokens = add_counter(self.reasoning_tokens, other.reasoning_tokens);
986 }
987}
988
989/// Many calls' [`Usage`] summed for a running account: the token counters,
990/// the known cost, and how many calls the sum holds and how many of them
991/// were not fully priced.
992///
993/// ```
994/// use rig_core::completion::{Usage, UsageTotals};
995///
996/// let mut totals = UsageTotals::default();
997/// totals.record(&Usage::new().input_tokens(100).output_tokens(20).total_tokens(120));
998/// assert_eq!((totals.calls, totals.unpriced, totals.uncached_input()), (1, 1, 100));
999/// ```
1000#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
1001pub struct UsageTotals {
1002 /// The token counters summed, with no cost: a call that did not
1003 /// report a counter adds nothing to it.
1004 pub tokens: Usage,
1005 /// The known costs summed, in USD.
1006 pub cost: f64,
1007 /// The calls whose cost is not known, or known only in part, so
1008 /// [`Self::cost`](field@Self::cost) is a lower bound when any.
1009 pub unpriced: u64,
1010 /// The calls summed.
1011 pub calls: u64,
1012}
1013
1014impl UsageTotals {
1015 /// Adds a finished call's `usage`.
1016 pub fn record(&mut self, usage: &Usage) {
1017 self.calls += 1;
1018 match usage.cost {
1019 Some(cost) => {
1020 self.cost += cost.total;
1021 if !cost.is_complete() {
1022 self.unpriced += 1;
1023 }
1024 }
1025 None => self.unpriced += 1,
1026 }
1027 self.tokens += usage.cost(None);
1028 }
1029
1030 /// Adds what `other` summed.
1031 pub fn add(&mut self, other: &UsageTotals) {
1032 self.tokens += other.tokens;
1033 self.cost += other.cost;
1034 self.unpriced += other.unpriced;
1035 self.calls += other.calls;
1036 }
1037
1038 /// Input tokens neither read from nor written to a cache.
1039 pub fn uncached_input(&self) -> u64 {
1040 self.tokens
1041 .input_tokens
1042 .unwrap_or(0)
1043 .saturating_sub(self.tokens.cached_input_tokens.unwrap_or(0))
1044 .saturating_sub(self.tokens.cache_creation_input_tokens.unwrap_or(0))
1045 }
1046
1047 /// The tokens read and written, cached input included.
1048 pub fn total_tokens(&self) -> u64 {
1049 self.tokens
1050 .input_tokens
1051 .unwrap_or(0)
1052 .saturating_add(self.tokens.output_tokens.unwrap_or(0))
1053 }
1054
1055 /// The cost as `$0.123`, ending in `+` when some calls were not priced,
1056 /// or `None` when no call was.
1057 pub fn cost_label(&self) -> Option<String> {
1058 if self.unpriced >= self.calls {
1059 return None;
1060 }
1061 let more = if self.unpriced > 0 { "+" } else { "" };
1062 Some(format!("{}{more}", dollars_label(self.cost)))
1063 }
1064
1065 /// The [cost](Self::cost_label), or as `1.2M tokens` when no call was
1066 /// priced, such as a local or subscription model's.
1067 pub fn cost_or_tokens(&self) -> String {
1068 self.cost_label()
1069 .unwrap_or_else(|| format!("{} tokens", tokens_label(self.total_tokens())))
1070 }
1071}
1072
1073/// One line for a person or a log: the calls, the tokens by kind and the
1074/// cost.
1075impl std::fmt::Display for UsageTotals {
1076 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1077 match self.calls {
1078 1 => f.write_str("1 model call")?,
1079 calls => write!(f, "{calls} model calls")?,
1080 }
1081 write!(f, ": {} in", tokens_label(self.uncached_input()))?;
1082 if let Some(read) = self.tokens.cached_input_tokens.filter(|read| *read > 0) {
1083 write!(f, ", {} cache read", tokens_label(read))?;
1084 }
1085 if let Some(written) = self
1086 .tokens
1087 .cache_creation_input_tokens
1088 .filter(|written| *written > 0)
1089 {
1090 write!(f, ", {} cache written", tokens_label(written))?;
1091 }
1092 write!(
1093 f,
1094 ", {} out",
1095 tokens_label(self.tokens.output_tokens.unwrap_or(0))
1096 )?;
1097 if let Some(thought) = self.tokens.reasoning_tokens.filter(|thought| *thought > 0) {
1098 write!(f, " ({} reasoning)", tokens_label(thought))?;
1099 }
1100 let cost = self.cost_label();
1101 write!(f, "; {}", cost.as_deref().unwrap_or("cost unknown"))
1102 }
1103}
1104
1105/// The context a conversation's next request sends, against the model's
1106/// input limit.
1107#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1108pub struct ContextUse {
1109 /// Tokens in use.
1110 pub tokens: u64,
1111 /// The model's input limit, when known.
1112 pub limit: Option<u32>,
1113}
1114
1115impl ContextUse {
1116 /// The share of the input limit in use, in percent, when the limit is
1117 /// known.
1118 pub fn percent(&self) -> Option<u64> {
1119 self.limit
1120 .filter(|limit| *limit > 0)
1121 .map(|limit| self.tokens.saturating_mul(100) / u64::from(limit))
1122 }
1123}
1124
1125/// `45k/200k (22%)`, or `45k` when the input limit is not known.
1126impl std::fmt::Display for ContextUse {
1127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1128 match (self.limit, self.percent()) {
1129 (Some(limit), Some(percent)) => write!(
1130 f,
1131 "{}/{} ({percent}%)",
1132 tokens_label(self.tokens),
1133 tokens_label(u64::from(limit))
1134 ),
1135 _ => f.write_str(&tokens_label(self.tokens)),
1136 }
1137 }
1138}
1139
1140/// A token count in at most four characters plus a unit: `999`, `1.2k`,
1141/// `45k`, `1.2M`.
1142pub fn tokens_label(count: u64) -> String {
1143 // Shown rounded, so the float conversion's precision does not matter.
1144 let scaled = |unit: f64| count as f64 / unit;
1145 match count {
1146 0..1_000 => count.to_string(),
1147 1_000..10_000 => format!("{:.1}k", scaled(1e3)),
1148 10_000..1_000_000 => format!("{:.0}k", scaled(1e3)),
1149 1_000_000..10_000_000 => format!("{:.1}M", scaled(1e6)),
1150 _ => format!("{:.0}M", scaled(1e6)),
1151 }
1152}
1153
1154/// A cost in USD: tenths of a cent below a dollar, cents above.
1155fn dollars_label(cost: f64) -> String {
1156 if cost < 1.0 {
1157 format!("${cost:.3}")
1158 } else {
1159 format!("${cost:.2}")
1160 }
1161}
1162
1163/// Model capabilities used by runtimes when preparing requests.
1164/// Defaults are conservative; construct through [`Self::new`] and setters.
1165#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
1166pub struct ProviderCapabilities {
1167 /// Whether native structured output can remain enabled with tool calls
1168 /// without suppressing them. Defaults to `false`.
1169 pub composes_native_output_with_tools: bool,
1170 /// Whether the model answers a forced tool choice (`Required` or
1171 /// `Specific`) with an error. Rig then does not force the output tool
1172 /// of its own structured-output flow (the extractor). Defaults to `false`.
1173 #[serde(default, skip_serializing_if = "crate::json_utils::is_false")]
1174 pub rejects_forced_tool_choice: bool,
1175}
1176
1177impl ProviderCapabilities {
1178 /// Create the conservative capability set used by default.
1179 pub const fn new() -> Self {
1180 Self {
1181 composes_native_output_with_tools: false,
1182 rejects_forced_tool_choice: false,
1183 }
1184 }
1185
1186 /// Declare whether native structured output composes with tool calls.
1187 pub const fn with_native_output_tool_composition(mut self, supported: bool) -> Self {
1188 self.composes_native_output_with_tools = supported;
1189 self
1190 }
1191
1192 /// Declare whether the model rejects a forced tool choice.
1193 pub const fn with_forced_tool_choice_rejected(mut self, rejected: bool) -> Self {
1194 self.rejects_forced_tool_choice = rejected;
1195 self
1196 }
1197}
1198
1199/// A completion request to any provider's model.
1200#[non_exhaustive]
1201#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1202pub struct CompletionRequest {
1203 /// Optional model override for this request.
1204 pub model: Option<String>,
1205 /// The chat history to be sent to the completion model provider.
1206 /// The very last message is the prompt.
1207 ///
1208 /// It must hold at least one message, and every user and assistant
1209 /// message must carry content. The field is public, so this is a rule
1210 /// rather than a type guarantee: [`Self::validate_message_content`]
1211 /// checks it at the request boundary.
1212 ///
1213 /// Each message is shared, with the conversation it came from and the
1214 /// records of the request, so copying a request copies pointers.
1215 pub chat_history: Vec<Arc<Message>>,
1216 /// The documents to be sent to the completion model provider
1217 pub documents: Vec<Document>,
1218 /// The tools to be sent to the completion model provider, each shared,
1219 /// so copying a request copies pointers.
1220 pub tools: Vec<Arc<ToolDefinition>>,
1221 /// The temperature to be sent to the completion model provider
1222 pub temperature: Option<f64>,
1223 /// The max tokens to be sent to the completion model provider
1224 pub max_tokens: Option<u64>,
1225 /// Which tools the model may or must call before it answers.
1226 pub tool_choice: Option<ToolChoice>,
1227 /// Additional provider-specific parameters to be sent to the completion model provider
1228 pub additional_params: Option<serde_json::Value>,
1229 /// Optional JSON Schema for structured output. When set, providers that support
1230 /// native structured outputs will constrain the model's response to match this schema.
1231 pub output_schema: Option<schemars::Schema>,
1232 /// Opt-in for sensitive request, response, and tool-content telemetry.
1233 /// Defaults to `false` and is excluded from serialization. Enabling it can
1234 /// expose prompts, context, tool results, and model output in span attributes
1235 /// and increase telemetry storage costs. Requires explicit caller consent.
1236 /// Agent drivers record normalized content; direct provider coverage varies,
1237 /// especially for streams consumed after the provider returns.
1238 #[serde(skip)]
1239 pub record_telemetry_content: bool,
1240 /// Whether a finish reason outside the normalized vocabulary
1241 /// ([`FinishReason::Other`]) ends the turn as a normal stop instead of a
1242 /// failure. Defaults to `false`. The response carries the choice, so its
1243 /// [`CompletionResponse::stop`], the runtimes and history replay agree.
1244 /// `Other` also holds genuine failures, such as a malformed tool call or a
1245 /// recitation block, so with this set the tool calls of such a reply run.
1246 /// [`FinishReason::ContentFilter`] still fails the turn.
1247 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
1248 pub accept_unknown_finish_reasons: bool,
1249 /// Send other models' reasoning in the history as reasoning where the
1250 /// target reads it ([`Accepts::foreign_reasoning`]), instead of as plain
1251 /// text. Defaults to `false`.
1252 ///
1253 /// [`Accepts::foreign_reasoning`]: crate::completion::Accepts::foreign_reasoning
1254 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
1255 pub foreign_reasoning: bool,
1256 /// Portable generation options. Precedence, lowest first: the mapped
1257 /// options, then [`Self::provider_options`], then `additional_params`.
1258 #[serde(default, skip_serializing_if = "GenerationOptions::is_default")]
1259 pub options: GenerationOptions,
1260 /// Typed per-provider options. The wire reads only the entry of its own
1261 /// provider, above the mapped options and below `additional_params`.
1262 #[serde(default, skip_serializing_if = "ProviderOptions::is_empty")]
1263 pub provider_options: ProviderOptions,
1264 /// Where a provider that caches prompts by marker (Anthropic's Messages
1265 /// family) may mark the end of a stable prefix that later requests
1266 /// repeat, beside its own markers, so content after it (per-turn
1267 /// memory, a reminder) does not cost the cached prefix. A provider that
1268 /// takes fewer markers keeps the points furthest into the request; one
1269 /// that caches prefixes without markers ignores them. Empty marks
1270 /// nothing.
1271 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1272 pub cache_points: Vec<CachePoint>,
1273}
1274
1275/// A point in a [`CompletionRequest`] that ends a stable prefix, for
1276/// [`CompletionRequest::cache_points`]. Prefixes run in the order a request
1277/// is sent: tools, then the system prompt, then the messages.
1278#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
1279#[serde(rename_all = "snake_case")]
1280pub enum CachePoint {
1281 /// After the tool definitions.
1282 Tools,
1283 /// After the system prompt.
1284 System,
1285 /// After the first `n` user and assistant messages. System messages
1286 /// are not counted, wherever they sit; `Messages(0)` is [`Self::System`].
1287 /// The point names the request's messages as written: adapting them
1288 /// for a wire, which drops some and joins adjacent user messages, moves
1289 /// the point with them, inside a joined message when it ends there.
1290 Messages(usize),
1291 /// After the first `items` content items of the user or assistant
1292 /// message at `message`, counted as [`Self::Messages`] counts them.
1293 Within {
1294 /// The message, from 0, system messages not counted.
1295 message: usize,
1296 /// How many of its content items the prefix holds.
1297 items: usize,
1298 },
1299}
1300
1301impl CompletionRequest {
1302 /// The system instructions of this request: the content of the leading
1303 /// [`Message::System`] in `chat_history`, which is where
1304 /// [`Self::preamble`] places it.
1305 pub fn system_instructions(&self) -> Option<&str> {
1306 match self.chat_history.first().map(Arc::as_ref) {
1307 Some(Message::System { content }) => Some(content.as_str()),
1308 _ => None,
1309 }
1310 }
1311
1312 /// Reject a request with no messages, a user or assistant message with no
1313 /// content, or a tool result with no content blocks. The error is
1314 /// [`ProviderError::Request`] and names the role and index of the first
1315 /// offending message.
1316 ///
1317 /// Every wire rejects an empty turn, so this turns a remote 400 into a
1318 /// local error. It checks the request direction only: a provider may
1319 /// return empty assistant content, which the reply keeps and the
1320 /// runtime judges. `System` content is a
1321 /// `String` and is not checked. A tool result holding one empty text
1322 /// block is not empty.
1323 ///
1324 /// [`Model::call`](crate::driver::Model::call),
1325 /// [`Model::stream`](crate::driver::Model::stream) and their `_observed`
1326 /// twins run it before encoding, so it covers
1327 /// [`DynModel`](crate::DynModel), every model a handler serves and the
1328 /// agent runtimes built on them. The OpenAI Responses websocket session
1329 /// sends without the driver and runs it on each send. Code that encodes
1330 /// a request some other way should call it first.
1331 pub fn validate_message_content(&self) -> Result<(), ProviderError> {
1332 if self.chat_history.is_empty() {
1333 return Err(ProviderError::request(
1334 "request has an empty chat history; providers require at least one message",
1335 ));
1336 }
1337
1338 let empty_message = |role: &str, index: usize| {
1339 ProviderError::request(format!(
1340 "{role} message at index {index} has no content; \
1341 providers reject empty content blocks"
1342 ))
1343 };
1344
1345 for (index, message) in self.chat_history.iter().enumerate() {
1346 match &**message {
1347 Message::System { .. } => {}
1348 Message::Assistant(AssistantMessage { content, .. }) => {
1349 if content.is_empty() {
1350 return Err(empty_message("assistant", index));
1351 }
1352 }
1353 Message::User { content } => {
1354 if content.is_empty() {
1355 return Err(empty_message("user", index));
1356 }
1357 }
1358 }
1359 }
1360
1361 Ok(())
1362 }
1363
1364 /// The output schema's `"title"`, else `"response_schema"`: a name for
1365 /// providers that need one beside the JSON Schema (such as OpenAI).
1366 pub fn output_schema_name(&self) -> Option<String> {
1367 self.output_schema.as_ref().map(|schema| {
1368 schema
1369 .as_object()
1370 .and_then(|o| o.get("title"))
1371 .and_then(|v| v.as_str())
1372 .unwrap_or("response_schema")
1373 .to_string()
1374 })
1375 }
1376
1377 /// Returns documents normalized into a message (if any).
1378 /// Most providers do not accept documents directly as input, so it needs to convert into a
1379 /// `Message` so that it can be incorporated into `chat_history`.
1380 pub fn normalized_documents(&self) -> Option<Message> {
1381 Self::normalized_documents_from(&self.documents)
1382 }
1383
1384 fn normalized_documents_from(documents: &[Document]) -> Option<Message> {
1385 if documents.is_empty() {
1386 return None;
1387 }
1388
1389 let content = documents
1390 .iter()
1391 .map(|doc| UserContent::document_text(doc.to_string(), Some(DocumentMediaType::TXT)))
1392 .collect();
1393
1394 Some(Message::User { content })
1395 }
1396}
1397
1398/// Place the documents message `message` at the first non-system position,
1399/// so document context lands after any leading system messages. A user
1400/// message already there takes the documents at its front instead, so the
1401/// history keeps alternating roles. Telemetry and the sent request must
1402/// agree on this placement.
1403pub(crate) fn insert_after_leading_system(chat_history: &mut Vec<Arc<Message>>, message: Message) {
1404 let insert_at = chat_history
1405 .iter()
1406 .position(|message| !matches!(&**message, Message::System { .. }))
1407 .unwrap_or(chat_history.len());
1408 let joins =
1409 |at: &&mut Arc<Message>| matches!(&***at, Message::User { content } if !content.is_empty());
1410 match (
1411 chat_history
1412 .get_mut(insert_at)
1413 .filter(joins)
1414 .map(Arc::make_mut),
1415 message,
1416 ) {
1417 (Some(Message::User { content }), Message::User { content: documents }) => {
1418 content.splice(0..0, documents);
1419 }
1420 (_, message) => chat_history.insert(insert_at, Arc::new(message)),
1421 }
1422}
1423
1424fn merge_provider_tools_into_additional_params(
1425 additional_params: Option<serde_json::Value>,
1426 provider_tools: Vec<ProviderToolDefinition>,
1427) -> Option<serde_json::Value> {
1428 if provider_tools.is_empty() {
1429 return additional_params;
1430 }
1431
1432 let mut provider_tools_json = provider_tools
1433 .into_iter()
1434 .map(|ProviderToolDefinition { kind, mut config }| {
1435 // Force the provider tool type from the strongly-typed field.
1436 config.insert("type".to_string(), serde_json::Value::String(kind));
1437 serde_json::Value::Object(config)
1438 })
1439 .collect::<Vec<_>>();
1440
1441 let mut params_map = match additional_params {
1442 Some(serde_json::Value::Object(map)) => map,
1443 Some(serde_json::Value::Bool(stream)) => {
1444 let mut map = serde_json::Map::new();
1445 map.insert("stream".to_string(), serde_json::Value::Bool(stream));
1446 map
1447 }
1448 _ => serde_json::Map::new(),
1449 };
1450
1451 let mut merged_tools = match params_map.shift_remove("tools") {
1452 Some(serde_json::Value::Array(existing)) => existing,
1453 _ => Vec::new(),
1454 };
1455 merged_tools.append(&mut provider_tools_json);
1456 params_map.insert("tools".to_string(), serde_json::Value::Array(merged_tools));
1457 Some(serde_json::Value::Object(params_map))
1458}
1459
1460impl CompletionRequest {
1461 /// A request whose conversation is the one user message `prompt`, with
1462 /// no preamble, documents or tools. The setters below add to it and
1463 /// check nothing; [`Self::validate_message_content`] checks the content
1464 /// when the request is sent.
1465 ///
1466 /// Each setter changes the request's public fields as it is called, so
1467 /// order matters where two setters touch the same field: a second
1468 /// [`Self::preamble`] adds a second system message, and
1469 /// [`Self::additional_params`] with a `tools` key (or `None`) replaces
1470 /// provider tools added before it. Set `additional_params` first.
1471 ///
1472 /// ```
1473 /// use rig_core::completion::CompletionRequest;
1474 ///
1475 /// let request = CompletionRequest::new("Who are you?")
1476 /// .preamble("You are a concise assistant.")
1477 /// .temperature(0.5);
1478 /// assert_eq!(request.chat_history.len(), 2);
1479 /// assert_eq!(request.temperature, Some(0.5));
1480 /// ```
1481 pub fn new(prompt: impl Into<Message>) -> Self {
1482 Self::conversation(vec![Arc::new(prompt.into())])
1483 }
1484
1485 /// A request for `chat_history` as given, with nothing else set.
1486 fn conversation(chat_history: Vec<Arc<Message>>) -> Self {
1487 Self {
1488 model: None,
1489 chat_history,
1490 documents: Vec::new(),
1491 tools: Vec::new(),
1492 temperature: None,
1493 max_tokens: None,
1494 tool_choice: None,
1495 additional_params: None,
1496 output_schema: None,
1497 record_telemetry_content: false,
1498 accept_unknown_finish_reasons: false,
1499 foreign_reasoning: false,
1500 options: GenerationOptions::default(),
1501 provider_options: ProviderOptions::default(),
1502 cache_points: Vec::new(),
1503 }
1504 }
1505
1506 /// Put `preamble` first in the conversation, as a [`Message::System`],
1507 /// ahead of any system message already there.
1508 pub fn preamble(mut self, preamble: impl Into<String>) -> Self {
1509 self.chat_history
1510 .insert(0, Arc::new(Message::system(preamble.into())));
1511 self
1512 }
1513
1514 /// Override the model for this request.
1515 pub fn model<S: Into<String>>(mut self, model: impl Into<Option<S>>) -> Self {
1516 self.model = model.into().map(Into::into);
1517 self.warn_if_shadowed("model", self.model.is_some());
1518 self
1519 }
1520
1521 /// Add `message` to the conversation, before the prompt (its last
1522 /// message).
1523 pub fn message(self, message: impl Into<Arc<Message>>) -> Self {
1524 self.messages([message])
1525 }
1526
1527 /// Add `messages` to the conversation in order, before the prompt (its
1528 /// last message).
1529 pub fn messages(mut self, messages: impl IntoIterator<Item = impl Into<Arc<Message>>>) -> Self {
1530 let prompt = self.chat_history.pop();
1531 self.chat_history
1532 .extend(messages.into_iter().map(Into::into));
1533 self.chat_history.extend(prompt);
1534 self
1535 }
1536
1537 /// Add a document.
1538 pub fn document(mut self, document: Document) -> Self {
1539 self.documents.push(document);
1540 self
1541 }
1542
1543 /// Add documents in order.
1544 pub fn documents(mut self, documents: impl IntoIterator<Item = Document>) -> Self {
1545 self.documents.extend(documents);
1546 self
1547 }
1548
1549 /// Add a tool.
1550 pub fn tool(self, tool: impl Into<Arc<ToolDefinition>>) -> Self {
1551 self.tools([tool])
1552 }
1553
1554 /// Add tools in order.
1555 pub fn tools(
1556 mut self,
1557 tools: impl IntoIterator<Item = impl Into<Arc<ToolDefinition>>>,
1558 ) -> Self {
1559 let first = self.tools.is_empty();
1560 self.tools.extend(tools.into_iter().map(Into::into));
1561 self.warn_if_shadowed("tools", first && !self.tools.is_empty());
1562 self
1563 }
1564
1565 /// Add a provider-hosted tool: appended to `additional_params.tools`,
1566 /// so a later [`Self::additional_params`] with a `tools` key replaces
1567 /// it.
1568 pub fn provider_tool(self, tool: ProviderToolDefinition) -> Self {
1569 self.provider_tools(vec![tool])
1570 }
1571
1572 /// Add provider-hosted tools in order: appended to
1573 /// `additional_params.tools`.
1574 pub fn provider_tools(mut self, tools: Vec<ProviderToolDefinition>) -> Self {
1575 self.additional_params =
1576 merge_provider_tools_into_additional_params(self.additional_params.take(), tools);
1577 self
1578 }
1579
1580 /// Merge provider-specific parameters into the request's, key by key;
1581 /// `None` clears them, provider tools included. Provider conversion
1582 /// determines precedence over typed fields, and a key that overrides a
1583 /// typed field this request sets is logged.
1584 pub fn additional_params(
1585 mut self,
1586 additional_params: impl Into<Option<serde_json::Value>>,
1587 ) -> Self {
1588 let additional_params = additional_params.into();
1589 for key in shadowed_typed_fields(
1590 additional_params.as_ref(),
1591 &[
1592 ("temperature", self.temperature.is_some()),
1593 ("max_tokens", self.max_tokens.is_some()),
1594 ("tool_choice", self.tool_choice.is_some()),
1595 ("model", self.model.is_some()),
1596 ("tools", !self.tools.is_empty()),
1597 ("response_format", self.output_schema.is_some()),
1598 ],
1599 ) {
1600 warn_shadowed(key);
1601 }
1602 self.additional_params =
1603 json_utils::merge_params(self.additional_params.take(), additional_params);
1604 self
1605 }
1606
1607 /// Set, or with `None` clear, the temperature.
1608 pub fn temperature(mut self, temperature: impl Into<Option<f64>>) -> Self {
1609 self.temperature = temperature.into();
1610 self.warn_if_shadowed("temperature", self.temperature.is_some());
1611 self
1612 }
1613
1614 /// Set, or with `None` clear, the output-token limit. Provider-specific
1615 /// defaults and requirements apply.
1616 pub fn max_tokens(mut self, max_tokens: impl Into<Option<u64>>) -> Self {
1617 self.max_tokens = max_tokens.into();
1618 self.warn_if_shadowed("max_tokens", self.max_tokens.is_some());
1619 self
1620 }
1621
1622 /// Set the tool-selection policy.
1623 pub fn tool_choice(mut self, tool_choice: ToolChoice) -> Self {
1624 self.tool_choice = Some(tool_choice);
1625 self.warn_if_shadowed("tool_choice", true);
1626 self
1627 }
1628
1629 /// Set, or with `None` clear, a native structured-output schema for
1630 /// providers that support one. The returned content is not
1631 /// deserialized.
1632 pub fn output_schema(mut self, schema: impl Into<Option<schemars::Schema>>) -> Self {
1633 self.output_schema = schema.into();
1634 self.warn_if_shadowed("response_format", self.output_schema.is_some());
1635 self
1636 }
1637
1638 /// Opt in to sensitive content telemetry, off by default. See
1639 /// [`Self::record_telemetry_content`] for what that exposes.
1640 pub fn record_content_telemetry(mut self, enabled: bool) -> Self {
1641 self.record_telemetry_content = enabled;
1642 self
1643 }
1644
1645 /// Accept, or with `false` refuse, finish reasons outside the normalized
1646 /// vocabulary as a normal stop. See
1647 /// [`Self::accept_unknown_finish_reasons`] for what that lets through.
1648 ///
1649 /// [`Self::accept_unknown_finish_reasons`]: field@Self::accept_unknown_finish_reasons
1650 pub fn accept_unknown_finish_reasons(mut self, accept: bool) -> Self {
1651 self.accept_unknown_finish_reasons = accept;
1652 self
1653 }
1654
1655 /// Replace the portable generation options with `options`, a reusable
1656 /// value. Calls apply in order: this replaces every field, so a
1657 /// shortcut such as [`Self::seed`] called before it is lost, and one
1658 /// called after it sets its one field on top.
1659 ///
1660 /// ```
1661 /// use rig_core::completion::{CompletionRequest, Effort, GenerationOptions};
1662 ///
1663 /// let shared = GenerationOptions::new().reasoning(Effort::High).seed(1);
1664 /// let request = CompletionRequest::new("hi").seed(7).options(shared.clone()).seed(2);
1665 /// assert_eq!(request.options, shared.seed(2));
1666 /// ```
1667 pub fn options(mut self, options: GenerationOptions) -> Self {
1668 self.options = options;
1669 self
1670 }
1671
1672 /// Set the reasoning level or budget in [`Self::options`](field@Self::options), as
1673 /// [`GenerationOptions::reasoning`](GenerationOptions::reasoning) does,
1674 /// keeping its other fields. See
1675 /// [`Self::options`](method@Self::options) for the order of calls.
1676 pub fn reasoning(mut self, reasoning: impl Into<Reasoning>) -> Self {
1677 self.options = std::mem::take(&mut self.options).reasoning(reasoning);
1678 self
1679 }
1680
1681 /// Set the cache retention in [`Self::options`](field@Self::options), as
1682 /// [`GenerationOptions::cache`](GenerationOptions::cache) does,
1683 /// keeping its other fields. See
1684 /// [`Self::options`](method@Self::options) for the order of calls.
1685 pub fn cache(mut self, cache: CacheRetention) -> Self {
1686 self.options = std::mem::take(&mut self.options).cache(cache);
1687 self
1688 }
1689
1690 /// Set the service tier in [`Self::options`](field@Self::options), as
1691 /// [`GenerationOptions::service_tier`](GenerationOptions::service_tier) does,
1692 /// keeping its other fields. See
1693 /// [`Self::options`](method@Self::options) for the order of calls.
1694 pub fn service_tier(mut self, tier: ServiceTier) -> Self {
1695 self.options = std::mem::take(&mut self.options).service_tier(tier);
1696 self
1697 }
1698
1699 /// Set the answer verbosity in [`Self::options`](field@Self::options), as
1700 /// [`GenerationOptions::verbosity`](GenerationOptions::verbosity) does,
1701 /// keeping its other fields. See
1702 /// [`Self::options`](method@Self::options) for the order of calls.
1703 pub fn verbosity(mut self, verbosity: Verbosity) -> Self {
1704 self.options = std::mem::take(&mut self.options).verbosity(verbosity);
1705 self
1706 }
1707
1708 /// Set whether the model may call several tools in one turn in
1709 /// [`Self::options`](field@Self::options), as
1710 /// [`GenerationOptions::parallel_tool_calls`] does, keeping its other
1711 /// fields. See
1712 /// [`Self::options`](method@Self::options) for the order of calls.
1713 pub fn parallel_tool_calls(mut self, parallel: bool) -> Self {
1714 self.options = std::mem::take(&mut self.options).parallel_tool_calls(parallel);
1715 self
1716 }
1717
1718 /// Set the nucleus sampling probability mass in [`Self::options`](field@Self::options), as
1719 /// [`GenerationOptions::top_p`](GenerationOptions::top_p) does,
1720 /// keeping its other fields. See
1721 /// [`Self::options`](method@Self::options) for the order of calls.
1722 pub fn top_p(mut self, top_p: f64) -> Self {
1723 self.options = std::mem::take(&mut self.options).top_p(top_p);
1724 self
1725 }
1726
1727 /// Set the sampling seed in [`Self::options`](field@Self::options), as
1728 /// [`GenerationOptions::seed`](GenerationOptions::seed) does,
1729 /// keeping its other fields. See
1730 /// [`Self::options`](method@Self::options) for the order of calls.
1731 pub fn seed(mut self, seed: u64) -> Self {
1732 self.options = std::mem::take(&mut self.options).seed(seed);
1733 self
1734 }
1735
1736 /// Set the stop sequences in [`Self::options`](field@Self::options), as
1737 /// [`GenerationOptions::stop`](GenerationOptions::stop) does,
1738 /// keeping its other fields. See
1739 /// [`Self::options`](method@Self::options) for the order of calls.
1740 pub fn stop<S: Into<String>>(mut self, stop: impl IntoIterator<Item = S>) -> Self {
1741 self.options = std::mem::take(&mut self.options).stop(stop);
1742 self
1743 }
1744
1745 /// Set what happens to an option the wire or model cannot honour in
1746 /// [`Self::options`](field@Self::options), as
1747 /// [`GenerationOptions::on_unsupported`] does, keeping its other fields. See
1748 /// [`Self::options`](method@Self::options) for the order of calls.
1749 pub fn on_unsupported(mut self, policy: OnUnsupported) -> Self {
1750 self.options = std::mem::take(&mut self.options).on_unsupported(policy);
1751 self
1752 }
1753
1754 /// Replace the typed per-provider options.
1755 ///
1756 /// Calls apply in order: this replaces every entry, so an entry set by
1757 /// an earlier [`Self::provider_option`] is lost, and a later
1758 /// [`Self::provider_option`] replaces its provider's entry on top.
1759 pub fn provider_options(mut self, options: ProviderOptions) -> Self {
1760 self.provider_options = options;
1761 self
1762 }
1763
1764 /// Store `options` as the entry of their provider
1765 /// ([`ExtensionOptions::Ext`]), replacing that provider's entry and
1766 /// keeping every other, as [`ProviderOptions::set`] does. Options that
1767 /// do not serialize fail the request's encode.
1768 ///
1769 /// The entry is always stored under `O::Ext`'s key, the built-in
1770 /// provider the options type belongs to. A third-party provider whose
1771 /// extension reuses a built-in options type (say `OpenAiOptions` for an
1772 /// OpenAI-compatible gateway) must store them with
1773 /// [`ProviderOptions::with::<P>`](ProviderOptions::with) instead, or its
1774 /// wire never reads them.
1775 ///
1776 /// ```
1777 /// use rig_core::completion::{CompletionRequest, ProviderOptions};
1778 /// use rig_core::providers::openrouter::extension::{
1779 /// OpenRouterExt, OpenRouterOptions, ProviderPreferences,
1780 /// };
1781 ///
1782 /// let request = CompletionRequest::new("hi").provider_option(
1783 /// OpenRouterOptions::new().provider(ProviderPreferences::new().allow_fallbacks(false)),
1784 /// );
1785 /// assert!(request.provider_options.contains::<OpenRouterExt>());
1786 /// ```
1787 pub fn provider_option<O: ExtensionOptions>(mut self, options: O) -> Self {
1788 self.provider_options = std::mem::take(&mut self.provider_options).set(options);
1789 self
1790 }
1791
1792 /// The input messages telemetry records: the conversation with the
1793 /// documents inserted after any leading system messages.
1794 pub fn messages_for_telemetry(&self) -> Vec<Arc<Message>> {
1795 let mut messages = self.chat_history.clone();
1796 if let Some(documents) = self.normalized_documents() {
1797 insert_after_leading_system(&mut messages, documents);
1798 }
1799 messages
1800 }
1801
1802 /// Log a typed field `key` that `additional_params` already overrides.
1803 fn warn_if_shadowed(&self, key: &'static str, set: bool) {
1804 if !shadowed_typed_fields(self.additional_params.as_ref(), &[(key, set)]).is_empty() {
1805 warn_shadowed(key);
1806 }
1807 }
1808}
1809
1810fn warn_shadowed(key: &str) {
1811 if matches!(key, "tools" | "response_format") {
1812 tracing::warn!(
1813 key,
1814 "additional_params also carries `{key}`; the provider decides how it combines with the typed field"
1815 );
1816 } else {
1817 tracing::warn!(
1818 key,
1819 "additional_params overrides the typed `{key}` field set on the same request"
1820 );
1821 }
1822}
1823
1824impl From<&str> for CompletionRequest {
1825 fn from(prompt: &str) -> Self {
1826 Self::new(prompt)
1827 }
1828}
1829
1830impl From<String> for CompletionRequest {
1831 fn from(prompt: String) -> Self {
1832 Self::new(prompt)
1833 }
1834}
1835
1836impl From<Message> for CompletionRequest {
1837 fn from(prompt: Message) -> Self {
1838 Self::new(prompt)
1839 }
1840}
1841
1842/// The conversation as given, ending with the prompt. An empty one fails
1843/// [`CompletionRequest::validate_message_content`].
1844impl From<Vec<Message>> for CompletionRequest {
1845 fn from(chat_history: Vec<Message>) -> Self {
1846 Self::conversation(chat_history.into_iter().map(Arc::new).collect())
1847 }
1848}
1849
1850/// The passthrough keys that will override a typed field the caller also set.
1851/// The override itself is the documented precedence (see
1852/// [`CompletionRequest::additional_params`]); naming the collisions
1853/// makes an accidental one visible instead of silent.
1854pub(crate) fn shadowed_typed_fields<'a>(
1855 additional_params: Option<&serde_json::Value>,
1856 typed: &[(&'a str, bool)],
1857) -> Vec<&'a str> {
1858 let Some(serde_json::Value::Object(params)) = additional_params else {
1859 return Vec::new();
1860 };
1861 typed
1862 .iter()
1863 .filter(|(key, set)| *set && params.contains_key(*key))
1864 .map(|(key, _)| *key)
1865 .collect()
1866}
1867
1868#[cfg(test)]
1869mod tests;
1870
1871#[cfg(test)]
1872mod response_identity_tests;
1873
1874#[cfg(test)]
1875mod plain_value_tests;