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