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/// window.
1107#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1108pub struct ContextUse {
1109 /// Tokens in use.
1110 pub tokens: u64,
1111 /// The model's window, when known.
1112 pub window: Option<u32>,
1113}
1114
1115impl ContextUse {
1116 /// The share of the window in use, in percent, when the window is known.
1117 pub fn percent(&self) -> Option<u64> {
1118 self.window
1119 .filter(|window| *window > 0)
1120 .map(|window| self.tokens.saturating_mul(100) / u64::from(window))
1121 }
1122}
1123
1124/// `45k/200k (22%)`, or `45k` when the window is not known.
1125impl std::fmt::Display for ContextUse {
1126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1127 match (self.window, self.percent()) {
1128 (Some(window), Some(percent)) => write!(
1129 f,
1130 "{}/{} ({percent}%)",
1131 tokens_label(self.tokens),
1132 tokens_label(u64::from(window))
1133 ),
1134 _ => f.write_str(&tokens_label(self.tokens)),
1135 }
1136 }
1137}
1138
1139/// A token count in at most four characters plus a unit: `999`, `1.2k`,
1140/// `45k`, `1.2M`.
1141pub fn tokens_label(count: u64) -> String {
1142 // Shown rounded, so the float conversion's precision does not matter.
1143 let scaled = |unit: f64| count as f64 / unit;
1144 match count {
1145 0..1_000 => count.to_string(),
1146 1_000..10_000 => format!("{:.1}k", scaled(1e3)),
1147 10_000..1_000_000 => format!("{:.0}k", scaled(1e3)),
1148 1_000_000..10_000_000 => format!("{:.1}M", scaled(1e6)),
1149 _ => format!("{:.0}M", scaled(1e6)),
1150 }
1151}
1152
1153/// A cost in USD: tenths of a cent below a dollar, cents above.
1154fn dollars_label(cost: f64) -> String {
1155 if cost < 1.0 {
1156 format!("${cost:.3}")
1157 } else {
1158 format!("${cost:.2}")
1159 }
1160}
1161
1162/// Model capabilities used by runtimes when preparing requests.
1163/// Defaults are conservative; construct through [`Self::new`] and setters.
1164#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
1165pub struct ProviderCapabilities {
1166 /// Whether native structured output can remain enabled with tool calls
1167 /// without suppressing them. Defaults to `false`.
1168 pub composes_native_output_with_tools: bool,
1169 /// Whether the model answers a forced tool choice (`Required` or
1170 /// `Specific`) with an error. Rig then does not force the output tool
1171 /// of its own structured-output flow (the extractor). Defaults to `false`.
1172 #[serde(default, skip_serializing_if = "crate::json_utils::is_false")]
1173 pub rejects_forced_tool_choice: bool,
1174}
1175
1176impl ProviderCapabilities {
1177 /// Create the conservative capability set used by default.
1178 pub const fn new() -> Self {
1179 Self {
1180 composes_native_output_with_tools: false,
1181 rejects_forced_tool_choice: false,
1182 }
1183 }
1184
1185 /// Declare whether native structured output composes with tool calls.
1186 pub const fn with_native_output_tool_composition(mut self, supported: bool) -> Self {
1187 self.composes_native_output_with_tools = supported;
1188 self
1189 }
1190
1191 /// Declare whether the model rejects a forced tool choice.
1192 pub const fn with_forced_tool_choice_rejected(mut self, rejected: bool) -> Self {
1193 self.rejects_forced_tool_choice = rejected;
1194 self
1195 }
1196}
1197
1198/// A completion request to any provider's model.
1199#[non_exhaustive]
1200#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1201pub struct CompletionRequest {
1202 /// Optional model override for this request.
1203 pub model: Option<String>,
1204 /// The chat history to be sent to the completion model provider.
1205 /// The very last message is the prompt.
1206 ///
1207 /// It must hold at least one message, and every user and assistant
1208 /// message must carry content. The field is public, so this is a rule
1209 /// rather than a type guarantee: [`Self::validate_message_content`]
1210 /// checks it at the request boundary.
1211 ///
1212 /// Each message is shared, with the conversation it came from and the
1213 /// records of the request, so copying a request copies pointers.
1214 pub chat_history: Vec<Arc<Message>>,
1215 /// The documents to be sent to the completion model provider
1216 pub documents: Vec<Document>,
1217 /// The tools to be sent to the completion model provider, each shared,
1218 /// so copying a request copies pointers.
1219 pub tools: Vec<Arc<ToolDefinition>>,
1220 /// The temperature to be sent to the completion model provider
1221 pub temperature: Option<f64>,
1222 /// The max tokens to be sent to the completion model provider
1223 pub max_tokens: Option<u64>,
1224 /// Which tools the model may or must call before it answers.
1225 pub tool_choice: Option<ToolChoice>,
1226 /// Additional provider-specific parameters to be sent to the completion model provider
1227 pub additional_params: Option<serde_json::Value>,
1228 /// Optional JSON Schema for structured output. When set, providers that support
1229 /// native structured outputs will constrain the model's response to match this schema.
1230 pub output_schema: Option<schemars::Schema>,
1231 /// Opt-in for sensitive request, response, and tool-content telemetry.
1232 /// Defaults to `false` and is excluded from serialization. Enabling it can
1233 /// expose prompts, context, tool results, and model output in span attributes
1234 /// and increase telemetry storage costs. Requires explicit caller consent.
1235 /// Agent drivers record normalized content; direct provider coverage varies,
1236 /// especially for streams consumed after the provider returns.
1237 #[serde(skip)]
1238 pub record_telemetry_content: bool,
1239 /// Whether a finish reason outside the normalized vocabulary
1240 /// ([`FinishReason::Other`]) ends the turn as a normal stop instead of a
1241 /// failure. Defaults to `false`. The response carries the choice, so its
1242 /// [`CompletionResponse::stop`], the runtimes and history replay agree.
1243 /// `Other` also holds genuine failures, such as a malformed tool call or a
1244 /// recitation block, so with this set the tool calls of such a reply run.
1245 /// [`FinishReason::ContentFilter`] still fails the turn.
1246 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
1247 pub accept_unknown_finish_reasons: bool,
1248 /// Send other models' reasoning in the history as reasoning where the
1249 /// target reads it ([`Accepts::foreign_reasoning`]), instead of as plain
1250 /// text. Defaults to `false`.
1251 ///
1252 /// [`Accepts::foreign_reasoning`]: crate::completion::Accepts::foreign_reasoning
1253 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
1254 pub foreign_reasoning: bool,
1255 /// Portable generation options. Precedence, lowest first: the mapped
1256 /// options, then [`Self::provider_options`], then `additional_params`.
1257 #[serde(default, skip_serializing_if = "GenerationOptions::is_default")]
1258 pub options: GenerationOptions,
1259 /// Typed per-provider options. The wire reads only the entry of its own
1260 /// provider, above the mapped options and below `additional_params`.
1261 #[serde(default, skip_serializing_if = "ProviderOptions::is_empty")]
1262 pub provider_options: ProviderOptions,
1263 /// Where a provider that caches prompts by marker (Anthropic's Messages
1264 /// family) may mark the end of a stable prefix that later requests
1265 /// repeat, beside its own markers, so content after it (per-turn
1266 /// memory, a reminder) does not cost the cached prefix. A provider that
1267 /// takes fewer markers keeps the points furthest into the request; one
1268 /// that caches prefixes without markers ignores them. Empty marks
1269 /// nothing.
1270 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1271 pub cache_points: Vec<CachePoint>,
1272}
1273
1274/// A point in a [`CompletionRequest`] that ends a stable prefix, for
1275/// [`CompletionRequest::cache_points`]. Prefixes run in the order a request
1276/// is sent: tools, then the system prompt, then the messages.
1277#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
1278#[serde(rename_all = "snake_case")]
1279pub enum CachePoint {
1280 /// After the tool definitions.
1281 Tools,
1282 /// After the system prompt.
1283 System,
1284 /// After the first `n` user and assistant messages. System messages
1285 /// are not counted, wherever they sit; `Messages(0)` is [`Self::System`].
1286 /// The point names the request's messages as written: adapting them
1287 /// for a wire, which drops some and joins adjacent user messages, moves
1288 /// the point with them, inside a joined message when it ends there.
1289 Messages(usize),
1290 /// After the first `items` content items of the user or assistant
1291 /// message at `message`, counted as [`Self::Messages`] counts them.
1292 Within {
1293 /// The message, from 0, system messages not counted.
1294 message: usize,
1295 /// How many of its content items the prefix holds.
1296 items: usize,
1297 },
1298}
1299
1300impl CompletionRequest {
1301 /// The system instructions of this request: the content of the leading
1302 /// [`Message::System`] in `chat_history`, which is where
1303 /// [`Self::preamble`] places it.
1304 pub fn system_instructions(&self) -> Option<&str> {
1305 match self.chat_history.first().map(Arc::as_ref) {
1306 Some(Message::System { content }) => Some(content.as_str()),
1307 _ => None,
1308 }
1309 }
1310
1311 /// Reject a request with no messages, a user or assistant message with no
1312 /// content, or a tool result with no content blocks. The error is
1313 /// [`ProviderError::Request`] and names the role and index of the first
1314 /// offending message.
1315 ///
1316 /// Every wire rejects an empty turn, so this turns a remote 400 into a
1317 /// local error. It checks the request direction only: a provider may
1318 /// return empty assistant content, which the reply keeps and the
1319 /// runtime judges. `System` content is a
1320 /// `String` and is not checked. A tool result holding one empty text
1321 /// block is not empty.
1322 ///
1323 /// [`Model::call`](crate::driver::Model::call),
1324 /// [`Model::stream`](crate::driver::Model::stream) and their `_observed`
1325 /// twins run it before encoding, so it covers
1326 /// [`DynModel`](crate::DynModel), every model a handler serves and the
1327 /// agent runtimes built on them. The OpenAI Responses websocket session
1328 /// sends without the driver and runs it on each send. Code that encodes
1329 /// a request some other way should call it first.
1330 pub fn validate_message_content(&self) -> Result<(), ProviderError> {
1331 if self.chat_history.is_empty() {
1332 return Err(ProviderError::request(
1333 "request has an empty chat history; providers require at least one message",
1334 ));
1335 }
1336
1337 let empty_message = |role: &str, index: usize| {
1338 ProviderError::request(format!(
1339 "{role} message at index {index} has no content; \
1340 providers reject empty content blocks"
1341 ))
1342 };
1343
1344 for (index, message) in self.chat_history.iter().enumerate() {
1345 match &**message {
1346 Message::System { .. } => {}
1347 Message::Assistant(AssistantMessage { content, .. }) => {
1348 if content.is_empty() {
1349 return Err(empty_message("assistant", index));
1350 }
1351 }
1352 Message::User { content } => {
1353 if content.is_empty() {
1354 return Err(empty_message("user", index));
1355 }
1356 }
1357 }
1358 }
1359
1360 Ok(())
1361 }
1362
1363 /// The output schema's `"title"`, else `"response_schema"`: a name for
1364 /// providers that need one beside the JSON Schema (such as OpenAI).
1365 pub fn output_schema_name(&self) -> Option<String> {
1366 self.output_schema.as_ref().map(|schema| {
1367 schema
1368 .as_object()
1369 .and_then(|o| o.get("title"))
1370 .and_then(|v| v.as_str())
1371 .unwrap_or("response_schema")
1372 .to_string()
1373 })
1374 }
1375
1376 /// Returns documents normalized into a message (if any).
1377 /// Most providers do not accept documents directly as input, so it needs to convert into a
1378 /// `Message` so that it can be incorporated into `chat_history`.
1379 pub fn normalized_documents(&self) -> Option<Message> {
1380 Self::normalized_documents_from(&self.documents)
1381 }
1382
1383 fn normalized_documents_from(documents: &[Document]) -> Option<Message> {
1384 if documents.is_empty() {
1385 return None;
1386 }
1387
1388 let content = documents
1389 .iter()
1390 .map(|doc| UserContent::document_text(doc.to_string(), Some(DocumentMediaType::TXT)))
1391 .collect();
1392
1393 Some(Message::User { content })
1394 }
1395}
1396
1397/// Place the documents message `message` at the first non-system position,
1398/// so document context lands after any leading system messages. A user
1399/// message already there takes the documents at its front instead, so the
1400/// history keeps alternating roles. Telemetry and the sent request must
1401/// agree on this placement.
1402pub(crate) fn insert_after_leading_system(chat_history: &mut Vec<Arc<Message>>, message: Message) {
1403 let insert_at = chat_history
1404 .iter()
1405 .position(|message| !matches!(&**message, Message::System { .. }))
1406 .unwrap_or(chat_history.len());
1407 let joins =
1408 |at: &&mut Arc<Message>| matches!(&***at, Message::User { content } if !content.is_empty());
1409 match (
1410 chat_history
1411 .get_mut(insert_at)
1412 .filter(joins)
1413 .map(Arc::make_mut),
1414 message,
1415 ) {
1416 (Some(Message::User { content }), Message::User { content: documents }) => {
1417 content.splice(0..0, documents);
1418 }
1419 (_, message) => chat_history.insert(insert_at, Arc::new(message)),
1420 }
1421}
1422
1423fn merge_provider_tools_into_additional_params(
1424 additional_params: Option<serde_json::Value>,
1425 provider_tools: Vec<ProviderToolDefinition>,
1426) -> Option<serde_json::Value> {
1427 if provider_tools.is_empty() {
1428 return additional_params;
1429 }
1430
1431 let mut provider_tools_json = provider_tools
1432 .into_iter()
1433 .map(|ProviderToolDefinition { kind, mut config }| {
1434 // Force the provider tool type from the strongly-typed field.
1435 config.insert("type".to_string(), serde_json::Value::String(kind));
1436 serde_json::Value::Object(config)
1437 })
1438 .collect::<Vec<_>>();
1439
1440 let mut params_map = match additional_params {
1441 Some(serde_json::Value::Object(map)) => map,
1442 Some(serde_json::Value::Bool(stream)) => {
1443 let mut map = serde_json::Map::new();
1444 map.insert("stream".to_string(), serde_json::Value::Bool(stream));
1445 map
1446 }
1447 _ => serde_json::Map::new(),
1448 };
1449
1450 let mut merged_tools = match params_map.shift_remove("tools") {
1451 Some(serde_json::Value::Array(existing)) => existing,
1452 _ => Vec::new(),
1453 };
1454 merged_tools.append(&mut provider_tools_json);
1455 params_map.insert("tools".to_string(), serde_json::Value::Array(merged_tools));
1456 Some(serde_json::Value::Object(params_map))
1457}
1458
1459impl CompletionRequest {
1460 /// A request whose conversation is the one user message `prompt`, with
1461 /// no preamble, documents or tools. The setters below add to it and
1462 /// check nothing; [`Self::validate_message_content`] checks the content
1463 /// when the request is sent.
1464 ///
1465 /// Each setter changes the request's public fields as it is called, so
1466 /// order matters where two setters touch the same field: a second
1467 /// [`Self::preamble`] adds a second system message, and
1468 /// [`Self::additional_params`] with a `tools` key (or `None`) replaces
1469 /// provider tools added before it. Set `additional_params` first.
1470 ///
1471 /// ```
1472 /// use rig_core::completion::CompletionRequest;
1473 ///
1474 /// let request = CompletionRequest::new("Who are you?")
1475 /// .preamble("You are a concise assistant.")
1476 /// .temperature(0.5);
1477 /// assert_eq!(request.chat_history.len(), 2);
1478 /// assert_eq!(request.temperature, Some(0.5));
1479 /// ```
1480 pub fn new(prompt: impl Into<Message>) -> Self {
1481 Self::conversation(vec![Arc::new(prompt.into())])
1482 }
1483
1484 /// A request for `chat_history` as given, with nothing else set.
1485 fn conversation(chat_history: Vec<Arc<Message>>) -> Self {
1486 Self {
1487 model: None,
1488 chat_history,
1489 documents: Vec::new(),
1490 tools: Vec::new(),
1491 temperature: None,
1492 max_tokens: None,
1493 tool_choice: None,
1494 additional_params: None,
1495 output_schema: None,
1496 record_telemetry_content: false,
1497 accept_unknown_finish_reasons: false,
1498 foreign_reasoning: false,
1499 options: GenerationOptions::default(),
1500 provider_options: ProviderOptions::default(),
1501 cache_points: Vec::new(),
1502 }
1503 }
1504
1505 /// Put `preamble` first in the conversation, as a [`Message::System`],
1506 /// ahead of any system message already there.
1507 pub fn preamble(mut self, preamble: impl Into<String>) -> Self {
1508 self.chat_history
1509 .insert(0, Arc::new(Message::system(preamble.into())));
1510 self
1511 }
1512
1513 /// Override the model for this request.
1514 pub fn model<S: Into<String>>(mut self, model: impl Into<Option<S>>) -> Self {
1515 self.model = model.into().map(Into::into);
1516 self.warn_if_shadowed("model", self.model.is_some());
1517 self
1518 }
1519
1520 /// Add `message` to the conversation, before the prompt (its last
1521 /// message).
1522 pub fn message(self, message: impl Into<Arc<Message>>) -> Self {
1523 self.messages([message])
1524 }
1525
1526 /// Add `messages` to the conversation in order, before the prompt (its
1527 /// last message).
1528 pub fn messages(mut self, messages: impl IntoIterator<Item = impl Into<Arc<Message>>>) -> Self {
1529 let prompt = self.chat_history.pop();
1530 self.chat_history
1531 .extend(messages.into_iter().map(Into::into));
1532 self.chat_history.extend(prompt);
1533 self
1534 }
1535
1536 /// Add a document.
1537 pub fn document(mut self, document: Document) -> Self {
1538 self.documents.push(document);
1539 self
1540 }
1541
1542 /// Add documents in order.
1543 pub fn documents(mut self, documents: impl IntoIterator<Item = Document>) -> Self {
1544 self.documents.extend(documents);
1545 self
1546 }
1547
1548 /// Add a tool.
1549 pub fn tool(self, tool: impl Into<Arc<ToolDefinition>>) -> Self {
1550 self.tools([tool])
1551 }
1552
1553 /// Add tools in order.
1554 pub fn tools(
1555 mut self,
1556 tools: impl IntoIterator<Item = impl Into<Arc<ToolDefinition>>>,
1557 ) -> Self {
1558 let first = self.tools.is_empty();
1559 self.tools.extend(tools.into_iter().map(Into::into));
1560 self.warn_if_shadowed("tools", first && !self.tools.is_empty());
1561 self
1562 }
1563
1564 /// Add a provider-hosted tool: appended to `additional_params.tools`,
1565 /// so a later [`Self::additional_params`] with a `tools` key replaces
1566 /// it.
1567 pub fn provider_tool(self, tool: ProviderToolDefinition) -> Self {
1568 self.provider_tools(vec![tool])
1569 }
1570
1571 /// Add provider-hosted tools in order: appended to
1572 /// `additional_params.tools`.
1573 pub fn provider_tools(mut self, tools: Vec<ProviderToolDefinition>) -> Self {
1574 self.additional_params =
1575 merge_provider_tools_into_additional_params(self.additional_params.take(), tools);
1576 self
1577 }
1578
1579 /// Merge provider-specific parameters into the request's, key by key;
1580 /// `None` clears them, provider tools included. Provider conversion
1581 /// determines precedence over typed fields, and a key that overrides a
1582 /// typed field this request sets is logged.
1583 pub fn additional_params(
1584 mut self,
1585 additional_params: impl Into<Option<serde_json::Value>>,
1586 ) -> Self {
1587 let additional_params = additional_params.into();
1588 for key in shadowed_typed_fields(
1589 additional_params.as_ref(),
1590 &[
1591 ("temperature", self.temperature.is_some()),
1592 ("max_tokens", self.max_tokens.is_some()),
1593 ("tool_choice", self.tool_choice.is_some()),
1594 ("model", self.model.is_some()),
1595 ("tools", !self.tools.is_empty()),
1596 ("response_format", self.output_schema.is_some()),
1597 ],
1598 ) {
1599 warn_shadowed(key);
1600 }
1601 self.additional_params =
1602 json_utils::merge_params(self.additional_params.take(), additional_params);
1603 self
1604 }
1605
1606 /// Set, or with `None` clear, the temperature.
1607 pub fn temperature(mut self, temperature: impl Into<Option<f64>>) -> Self {
1608 self.temperature = temperature.into();
1609 self.warn_if_shadowed("temperature", self.temperature.is_some());
1610 self
1611 }
1612
1613 /// Set, or with `None` clear, the output-token limit. Provider-specific
1614 /// defaults and requirements apply.
1615 pub fn max_tokens(mut self, max_tokens: impl Into<Option<u64>>) -> Self {
1616 self.max_tokens = max_tokens.into();
1617 self.warn_if_shadowed("max_tokens", self.max_tokens.is_some());
1618 self
1619 }
1620
1621 /// Set the tool-selection policy.
1622 pub fn tool_choice(mut self, tool_choice: ToolChoice) -> Self {
1623 self.tool_choice = Some(tool_choice);
1624 self.warn_if_shadowed("tool_choice", true);
1625 self
1626 }
1627
1628 /// Set, or with `None` clear, a native structured-output schema for
1629 /// providers that support one. The returned content is not
1630 /// deserialized.
1631 pub fn output_schema(mut self, schema: impl Into<Option<schemars::Schema>>) -> Self {
1632 self.output_schema = schema.into();
1633 self.warn_if_shadowed("response_format", self.output_schema.is_some());
1634 self
1635 }
1636
1637 /// Opt in to sensitive content telemetry, off by default. See
1638 /// [`Self::record_telemetry_content`] for what that exposes.
1639 pub fn record_content_telemetry(mut self, enabled: bool) -> Self {
1640 self.record_telemetry_content = enabled;
1641 self
1642 }
1643
1644 /// Accept, or with `false` refuse, finish reasons outside the normalized
1645 /// vocabulary as a normal stop. See
1646 /// [`Self::accept_unknown_finish_reasons`] for what that lets through.
1647 ///
1648 /// [`Self::accept_unknown_finish_reasons`]: field@Self::accept_unknown_finish_reasons
1649 pub fn accept_unknown_finish_reasons(mut self, accept: bool) -> Self {
1650 self.accept_unknown_finish_reasons = accept;
1651 self
1652 }
1653
1654 /// Replace the portable generation options with `options`, a reusable
1655 /// value. Calls apply in order: this replaces every field, so a
1656 /// shortcut such as [`Self::seed`] called before it is lost, and one
1657 /// called after it sets its one field on top.
1658 ///
1659 /// ```
1660 /// use rig_core::completion::{CompletionRequest, Effort, GenerationOptions};
1661 ///
1662 /// let shared = GenerationOptions::new().reasoning(Effort::High).seed(1);
1663 /// let request = CompletionRequest::new("hi").seed(7).options(shared.clone()).seed(2);
1664 /// assert_eq!(request.options, shared.seed(2));
1665 /// ```
1666 pub fn options(mut self, options: GenerationOptions) -> Self {
1667 self.options = options;
1668 self
1669 }
1670
1671 /// Set the reasoning level or budget in [`Self::options`](field@Self::options), as
1672 /// [`GenerationOptions::reasoning`](GenerationOptions::reasoning) does,
1673 /// keeping its other fields. See
1674 /// [`Self::options`](method@Self::options) for the order of calls.
1675 pub fn reasoning(mut self, reasoning: impl Into<Reasoning>) -> Self {
1676 self.options = std::mem::take(&mut self.options).reasoning(reasoning);
1677 self
1678 }
1679
1680 /// Set the cache retention in [`Self::options`](field@Self::options), as
1681 /// [`GenerationOptions::cache`](GenerationOptions::cache) does,
1682 /// keeping its other fields. See
1683 /// [`Self::options`](method@Self::options) for the order of calls.
1684 pub fn cache(mut self, cache: CacheRetention) -> Self {
1685 self.options = std::mem::take(&mut self.options).cache(cache);
1686 self
1687 }
1688
1689 /// Set the service tier in [`Self::options`](field@Self::options), as
1690 /// [`GenerationOptions::service_tier`](GenerationOptions::service_tier) does,
1691 /// keeping its other fields. See
1692 /// [`Self::options`](method@Self::options) for the order of calls.
1693 pub fn service_tier(mut self, tier: ServiceTier) -> Self {
1694 self.options = std::mem::take(&mut self.options).service_tier(tier);
1695 self
1696 }
1697
1698 /// Set the answer verbosity in [`Self::options`](field@Self::options), as
1699 /// [`GenerationOptions::verbosity`](GenerationOptions::verbosity) does,
1700 /// keeping its other fields. See
1701 /// [`Self::options`](method@Self::options) for the order of calls.
1702 pub fn verbosity(mut self, verbosity: Verbosity) -> Self {
1703 self.options = std::mem::take(&mut self.options).verbosity(verbosity);
1704 self
1705 }
1706
1707 /// Set whether the model may call several tools in one turn in
1708 /// [`Self::options`](field@Self::options), as
1709 /// [`GenerationOptions::parallel_tool_calls`] does, keeping its other
1710 /// fields. See
1711 /// [`Self::options`](method@Self::options) for the order of calls.
1712 pub fn parallel_tool_calls(mut self, parallel: bool) -> Self {
1713 self.options = std::mem::take(&mut self.options).parallel_tool_calls(parallel);
1714 self
1715 }
1716
1717 /// Set the nucleus sampling probability mass in [`Self::options`](field@Self::options), as
1718 /// [`GenerationOptions::top_p`](GenerationOptions::top_p) does,
1719 /// keeping its other fields. See
1720 /// [`Self::options`](method@Self::options) for the order of calls.
1721 pub fn top_p(mut self, top_p: f64) -> Self {
1722 self.options = std::mem::take(&mut self.options).top_p(top_p);
1723 self
1724 }
1725
1726 /// Set the sampling seed in [`Self::options`](field@Self::options), as
1727 /// [`GenerationOptions::seed`](GenerationOptions::seed) does,
1728 /// keeping its other fields. See
1729 /// [`Self::options`](method@Self::options) for the order of calls.
1730 pub fn seed(mut self, seed: u64) -> Self {
1731 self.options = std::mem::take(&mut self.options).seed(seed);
1732 self
1733 }
1734
1735 /// Set the stop sequences in [`Self::options`](field@Self::options), as
1736 /// [`GenerationOptions::stop`](GenerationOptions::stop) does,
1737 /// keeping its other fields. See
1738 /// [`Self::options`](method@Self::options) for the order of calls.
1739 pub fn stop<S: Into<String>>(mut self, stop: impl IntoIterator<Item = S>) -> Self {
1740 self.options = std::mem::take(&mut self.options).stop(stop);
1741 self
1742 }
1743
1744 /// Set what happens to an option the wire or model cannot honour in
1745 /// [`Self::options`](field@Self::options), as
1746 /// [`GenerationOptions::on_unsupported`] does, keeping its other fields. See
1747 /// [`Self::options`](method@Self::options) for the order of calls.
1748 pub fn on_unsupported(mut self, policy: OnUnsupported) -> Self {
1749 self.options = std::mem::take(&mut self.options).on_unsupported(policy);
1750 self
1751 }
1752
1753 /// Replace the typed per-provider options.
1754 ///
1755 /// Calls apply in order: this replaces every entry, so an entry set by
1756 /// an earlier [`Self::provider_option`] is lost, and a later
1757 /// [`Self::provider_option`] replaces its provider's entry on top.
1758 pub fn provider_options(mut self, options: ProviderOptions) -> Self {
1759 self.provider_options = options;
1760 self
1761 }
1762
1763 /// Store `options` as the entry of their provider
1764 /// ([`ExtensionOptions::Ext`]), replacing that provider's entry and
1765 /// keeping every other, as [`ProviderOptions::set`] does. Options that
1766 /// do not serialize fail the request's encode.
1767 ///
1768 /// The entry is always stored under `O::Ext`'s key, the built-in
1769 /// provider the options type belongs to. A third-party provider whose
1770 /// extension reuses a built-in options type (say `OpenAiOptions` for an
1771 /// OpenAI-compatible gateway) must store them with
1772 /// [`ProviderOptions::with::<P>`](ProviderOptions::with) instead, or its
1773 /// wire never reads them.
1774 ///
1775 /// ```
1776 /// use rig_core::completion::{CompletionRequest, ProviderOptions};
1777 /// use rig_core::providers::openrouter::extension::{
1778 /// OpenRouterExt, OpenRouterOptions, ProviderPreferences,
1779 /// };
1780 ///
1781 /// let request = CompletionRequest::new("hi").provider_option(
1782 /// OpenRouterOptions::new().provider(ProviderPreferences::new().allow_fallbacks(false)),
1783 /// );
1784 /// assert!(request.provider_options.contains::<OpenRouterExt>());
1785 /// ```
1786 pub fn provider_option<O: ExtensionOptions>(mut self, options: O) -> Self {
1787 self.provider_options = std::mem::take(&mut self.provider_options).set(options);
1788 self
1789 }
1790
1791 /// The input messages telemetry records: the conversation with the
1792 /// documents inserted after any leading system messages.
1793 pub fn messages_for_telemetry(&self) -> Vec<Arc<Message>> {
1794 let mut messages = self.chat_history.clone();
1795 if let Some(documents) = self.normalized_documents() {
1796 insert_after_leading_system(&mut messages, documents);
1797 }
1798 messages
1799 }
1800
1801 /// Log a typed field `key` that `additional_params` already overrides.
1802 fn warn_if_shadowed(&self, key: &'static str, set: bool) {
1803 if !shadowed_typed_fields(self.additional_params.as_ref(), &[(key, set)]).is_empty() {
1804 warn_shadowed(key);
1805 }
1806 }
1807}
1808
1809fn warn_shadowed(key: &str) {
1810 if matches!(key, "tools" | "response_format") {
1811 tracing::warn!(
1812 key,
1813 "additional_params also carries `{key}`; the provider decides how it combines with the typed field"
1814 );
1815 } else {
1816 tracing::warn!(
1817 key,
1818 "additional_params overrides the typed `{key}` field set on the same request"
1819 );
1820 }
1821}
1822
1823impl From<&str> for CompletionRequest {
1824 fn from(prompt: &str) -> Self {
1825 Self::new(prompt)
1826 }
1827}
1828
1829impl From<String> for CompletionRequest {
1830 fn from(prompt: String) -> Self {
1831 Self::new(prompt)
1832 }
1833}
1834
1835impl From<Message> for CompletionRequest {
1836 fn from(prompt: Message) -> Self {
1837 Self::new(prompt)
1838 }
1839}
1840
1841/// The conversation as given, ending with the prompt. An empty one fails
1842/// [`CompletionRequest::validate_message_content`].
1843impl From<Vec<Message>> for CompletionRequest {
1844 fn from(chat_history: Vec<Message>) -> Self {
1845 Self::conversation(chat_history.into_iter().map(Arc::new).collect())
1846 }
1847}
1848
1849/// The passthrough keys that will override a typed field the caller also set.
1850/// The override itself is the documented precedence (see
1851/// [`CompletionRequest::additional_params`]); naming the collisions
1852/// makes an accidental one visible instead of silent.
1853pub(crate) fn shadowed_typed_fields<'a>(
1854 additional_params: Option<&serde_json::Value>,
1855 typed: &[(&'a str, bool)],
1856) -> Vec<&'a str> {
1857 let Some(serde_json::Value::Object(params)) = additional_params else {
1858 return Vec::new();
1859 };
1860 typed
1861 .iter()
1862 .filter(|(key, set)| *set && params.contains_key(*key))
1863 .map(|(key, _)| *key)
1864 .collect()
1865}
1866
1867#[cfg(test)]
1868mod tests;
1869
1870#[cfg(test)]
1871mod response_identity_tests;
1872
1873#[cfg(test)]
1874mod plain_value_tests;