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