rig/completion/
request.rs

1//! This module provides functionality for working with completion models.
2//! It provides traits, structs, and enums for generating completion requests,
3//! handling completion responses, and defining completion models.
4//!
5//! The main traits defined in this module are:
6//! - [Prompt]: Defines a high-level LLM one-shot prompt interface.
7//! - [Chat]: Defines a high-level LLM chat interface with chat history.
8//! - [Completion]: Defines a low-level LLM completion interface for generating completion requests.
9//! - [CompletionModel]: Defines a completion model that can be used to generate completion
10//!   responses from requests.
11//!
12//! The [Prompt] and [Chat] traits are high level traits that users are expected to use
13//! to interact with LLM models. Moreover, it is good practice to implement one of these
14//! traits for composite agents that use multiple LLM models to generate responses.
15//!
16//! The [Completion] trait defines a lower level interface that is useful when the user want
17//! to further customize the request before sending it to the completion model provider.
18//!
19//! The [CompletionModel] trait is meant to act as the interface between providers and
20//! the library. It defines the methods that need to be implemented by the user to define
21//! a custom base completion model (i.e.: a private or third party LLM provider).
22//!
23//! The module also provides various structs and enums for representing generic completion requests,
24//! responses, and errors.
25//!
26//! Example Usage:
27//! ```rust
28//! use rig::providers::openai::{Client, self};
29//! use rig::completion::*;
30//!
31//! // Initialize the OpenAI client and a completion model
32//! let openai = Client::new("your-openai-api-key");
33//!
34//! let gpt_4 = openai.completion_model(openai::GPT_4);
35//!
36//! // Create the completion request
37//! let request = gpt_4.completion_request("Who are you?")
38//!     .preamble("\
39//!         You are Marvin, an extremely smart but depressed robot who is \
40//!         nonetheless helpful towards humanity.\
41//!     ")
42//!     .temperature(0.5)
43//!     .build();
44//!
45//! // Send the completion request and get the completion response
46//! let response = gpt_4.completion(request)
47//!     .await
48//!     .expect("Failed to get completion response");
49//!
50//! // Handle the completion response
51//! match completion_response.choice {
52//!     ModelChoice::Message(message) => {
53//!         // Handle the completion response as a message
54//!         println!("Received message: {}", message);
55//!     }
56//!     ModelChoice::ToolCall(tool_name, tool_params) => {
57//!         // Handle the completion response as a tool call
58//!         println!("Received tool call: {} {:?}", tool_name, tool_params);
59//!     }
60//! }
61//! ```
62//!
63//! For more information on how to use the completion functionality, refer to the documentation of
64//! the individual traits, structs, and enums defined in this module.
65
66use super::message::{AssistantContent, DocumentMediaType};
67use crate::client::completion::CompletionModelHandle;
68use crate::streaming::StreamingCompletionResponse;
69use crate::{OneOrMany, streaming};
70use crate::{
71    json_utils,
72    message::{Message, UserContent},
73    tool::ToolSetError,
74};
75use futures::future::BoxFuture;
76use serde::de::DeserializeOwned;
77use serde::{Deserialize, Serialize};
78use std::collections::HashMap;
79use std::ops::{Add, AddAssign};
80use std::sync::Arc;
81use thiserror::Error;
82
83// Errors
84#[derive(Debug, Error)]
85pub enum CompletionError {
86    /// Http error (e.g.: connection error, timeout, etc.)
87    #[error("HttpError: {0}")]
88    HttpError(#[from] reqwest::Error),
89
90    /// Json error (e.g.: serialization, deserialization)
91    #[error("JsonError: {0}")]
92    JsonError(#[from] serde_json::Error),
93
94    /// Url error (e.g.: invalid URL)
95    #[error("UrlError: {0}")]
96    UrlError(#[from] url::ParseError),
97
98    /// Error building the completion request
99    #[error("RequestError: {0}")]
100    RequestError(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),
101
102    /// Error parsing the completion response
103    #[error("ResponseError: {0}")]
104    ResponseError(String),
105
106    /// Error returned by the completion model provider
107    #[error("ProviderError: {0}")]
108    ProviderError(String),
109}
110
111/// Prompt errors
112#[derive(Debug, Error)]
113pub enum PromptError {
114    /// Something went wrong with the completion
115    #[error("CompletionError: {0}")]
116    CompletionError(#[from] CompletionError),
117
118    /// There was an error while using a tool
119    #[error("ToolCallError: {0}")]
120    ToolError(#[from] ToolSetError),
121
122    /// The LLM tried to call too many tools during a multi-turn conversation.
123    /// To fix this, you may either need to lower the amount of tools your model has access to (and then create other agents to share the tool load)
124    /// or increase the amount of turns given in `.multi_turn()`.
125    #[error("MaxDepthError: (reached limit: {max_depth})")]
126    MaxDepthError {
127        max_depth: usize,
128        chat_history: Box<Vec<Message>>,
129        prompt: Message,
130    },
131}
132
133#[derive(Clone, Debug, Deserialize, Serialize)]
134pub struct Document {
135    pub id: String,
136    pub text: String,
137    #[serde(flatten)]
138    pub additional_props: HashMap<String, String>,
139}
140
141impl std::fmt::Display for Document {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        write!(
144            f,
145            concat!("<file id: {}>\n", "{}\n", "</file>\n"),
146            self.id,
147            if self.additional_props.is_empty() {
148                self.text.clone()
149            } else {
150                let mut sorted_props = self.additional_props.iter().collect::<Vec<_>>();
151                sorted_props.sort_by(|a, b| a.0.cmp(b.0));
152                let metadata = sorted_props
153                    .iter()
154                    .map(|(k, v)| format!("{k}: {v:?}"))
155                    .collect::<Vec<_>>()
156                    .join(" ");
157                format!("<metadata {} />\n{}", metadata, self.text)
158            }
159        )
160    }
161}
162
163#[derive(Clone, Debug, Deserialize, Serialize)]
164pub struct ToolDefinition {
165    pub name: String,
166    pub description: String,
167    pub parameters: serde_json::Value,
168}
169
170// ================================================================
171// Implementations
172// ================================================================
173/// Trait defining a high-level LLM simple prompt interface (i.e.: prompt in, response out).
174pub trait Prompt: Send + Sync {
175    /// Send a simple prompt to the underlying completion model.
176    ///
177    /// If the completion model's response is a message, then it is returned as a string.
178    ///
179    /// If the completion model's response is a tool call, then the tool is called and
180    /// the result is returned as a string.
181    ///
182    /// If the tool does not exist, or the tool call fails, then an error is returned.
183    fn prompt(
184        &self,
185        prompt: impl Into<Message> + Send,
186    ) -> impl std::future::IntoFuture<Output = Result<String, PromptError>, IntoFuture: Send>;
187}
188
189/// Trait defining a high-level LLM chat interface (i.e.: prompt and chat history in, response out).
190pub trait Chat: Send + Sync {
191    /// Send a prompt with optional chat history to the underlying completion model.
192    ///
193    /// If the completion model's response is a message, then it is returned as a string.
194    ///
195    /// If the completion model's response is a tool call, then the tool is called and the result
196    /// is returned as a string.
197    ///
198    /// If the tool does not exist, or the tool call fails, then an error is returned.
199    fn chat(
200        &self,
201        prompt: impl Into<Message> + Send,
202        chat_history: Vec<Message>,
203    ) -> impl std::future::IntoFuture<Output = Result<String, PromptError>, IntoFuture: Send>;
204}
205
206/// Trait defining a low-level LLM completion interface
207pub trait Completion<M: CompletionModel> {
208    /// Generates a completion request builder for the given `prompt` and `chat_history`.
209    /// This function is meant to be called by the user to further customize the
210    /// request at prompt time before sending it.
211    ///
212    /// ❗IMPORTANT: The type that implements this trait might have already
213    /// populated fields in the builder (the exact fields depend on the type).
214    /// For fields that have already been set by the model, calling the corresponding
215    /// method on the builder will overwrite the value set by the model.
216    ///
217    /// For example, the request builder returned by [`Agent::completion`](crate::agent::Agent::completion) will already
218    /// contain the `preamble` provided when creating the agent.
219    fn completion(
220        &self,
221        prompt: impl Into<Message> + Send,
222        chat_history: Vec<Message>,
223    ) -> impl std::future::Future<Output = Result<CompletionRequestBuilder<M>, CompletionError>> + Send;
224}
225
226/// General completion response struct that contains the high-level completion choice
227/// and the raw response. The completion choice contains one or more assistant content.
228#[derive(Debug)]
229pub struct CompletionResponse<T> {
230    /// The completion choice (represented by one or more assistant message content)
231    /// returned by the completion model provider
232    pub choice: OneOrMany<AssistantContent>,
233    /// Tokens used during prompting and responding
234    pub usage: Usage,
235    /// The raw response returned by the completion model provider
236    pub raw_response: T,
237}
238
239/// A trait for grabbing the token usage of a completion response.
240///
241/// Primarily designed for streamed completion responses in streamed multi-turn, as otherwise it would be impossible to do.
242pub trait GetTokenUsage {
243    fn token_usage(&self) -> Option<crate::completion::Usage>;
244}
245
246impl GetTokenUsage for () {
247    fn token_usage(&self) -> Option<crate::completion::Usage> {
248        None
249    }
250}
251
252impl<T> GetTokenUsage for Option<T>
253where
254    T: GetTokenUsage,
255{
256    fn token_usage(&self) -> Option<crate::completion::Usage> {
257        if let Some(usage) = self {
258            usage.token_usage()
259        } else {
260            None
261        }
262    }
263}
264
265/// Struct representing the token usage for a completion request.
266/// If tokens used are `0`, then the provider failed to supply token usage metrics.
267#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
268pub struct Usage {
269    /// The number of input ("prompt") tokens used in a given request.
270    pub input_tokens: u64,
271    /// The number of output ("completion") tokens used in a given request.
272    pub output_tokens: u64,
273    /// We store this separately as some providers may only report one number
274    pub total_tokens: u64,
275}
276
277impl Usage {
278    /// Creates a new instance of `Usage`.
279    pub fn new() -> Self {
280        Self {
281            input_tokens: 0,
282            output_tokens: 0,
283            total_tokens: 0,
284        }
285    }
286}
287
288impl Default for Usage {
289    fn default() -> Self {
290        Self::new()
291    }
292}
293
294impl Add for Usage {
295    type Output = Self;
296
297    fn add(self, other: Self) -> Self::Output {
298        Self {
299            input_tokens: self.input_tokens + other.input_tokens,
300            output_tokens: self.output_tokens + other.output_tokens,
301            total_tokens: self.total_tokens + other.total_tokens,
302        }
303    }
304}
305
306impl AddAssign for Usage {
307    fn add_assign(&mut self, other: Self) {
308        self.input_tokens += other.input_tokens;
309        self.output_tokens += other.output_tokens;
310        self.total_tokens += other.total_tokens;
311    }
312}
313
314/// Trait defining a completion model that can be used to generate completion responses.
315/// This trait is meant to be implemented by the user to define a custom completion model,
316/// either from a third party provider (e.g.: OpenAI) or a local model.
317pub trait CompletionModel: Clone + Send + Sync {
318    /// The raw response type returned by the underlying completion model.
319    type Response: Send + Sync + Serialize + DeserializeOwned;
320    /// The raw response type returned by the underlying completion model when streaming.
321    type StreamingResponse: Clone
322        + Unpin
323        + Send
324        + Sync
325        + Serialize
326        + DeserializeOwned
327        + GetTokenUsage;
328
329    /// Generates a completion response for the given completion request.
330    fn completion(
331        &self,
332        request: CompletionRequest,
333    ) -> impl std::future::Future<
334        Output = Result<CompletionResponse<Self::Response>, CompletionError>,
335    > + Send;
336
337    fn stream(
338        &self,
339        request: CompletionRequest,
340    ) -> impl std::future::Future<
341        Output = Result<StreamingCompletionResponse<Self::StreamingResponse>, CompletionError>,
342    > + Send;
343
344    /// Generates a completion request builder for the given `prompt`.
345    fn completion_request(&self, prompt: impl Into<Message>) -> CompletionRequestBuilder<Self> {
346        CompletionRequestBuilder::new(self.clone(), prompt)
347    }
348}
349pub trait CompletionModelDyn: Send + Sync {
350    fn completion(
351        &self,
352        request: CompletionRequest,
353    ) -> BoxFuture<'_, Result<CompletionResponse<()>, CompletionError>>;
354
355    fn stream(
356        &self,
357        request: CompletionRequest,
358    ) -> BoxFuture<'_, Result<StreamingCompletionResponse<()>, CompletionError>>;
359
360    fn completion_request(
361        &self,
362        prompt: Message,
363    ) -> CompletionRequestBuilder<CompletionModelHandle<'_>>;
364}
365
366impl<T, R> CompletionModelDyn for T
367where
368    T: CompletionModel<StreamingResponse = R>,
369    R: Clone + Unpin + GetTokenUsage + 'static,
370{
371    fn completion(
372        &self,
373        request: CompletionRequest,
374    ) -> BoxFuture<'_, Result<CompletionResponse<()>, CompletionError>> {
375        Box::pin(async move {
376            self.completion(request)
377                .await
378                .map(|resp| CompletionResponse {
379                    choice: resp.choice,
380                    usage: resp.usage,
381                    raw_response: (),
382                })
383        })
384    }
385
386    fn stream(
387        &self,
388        request: CompletionRequest,
389    ) -> BoxFuture<'_, Result<StreamingCompletionResponse<()>, CompletionError>> {
390        Box::pin(async move {
391            let resp = self.stream(request).await?;
392            let inner = resp.inner;
393
394            let stream = Box::pin(streaming::StreamingResultDyn {
395                inner: Box::pin(inner),
396            });
397
398            Ok(StreamingCompletionResponse::stream(stream))
399        })
400    }
401
402    /// Generates a completion request builder for the given `prompt`.
403    fn completion_request(
404        &self,
405        prompt: Message,
406    ) -> CompletionRequestBuilder<CompletionModelHandle<'_>> {
407        CompletionRequestBuilder::new(
408            CompletionModelHandle {
409                inner: Arc::new(self.clone()),
410            },
411            prompt,
412        )
413    }
414}
415
416/// Struct representing a general completion request that can be sent to a completion model provider.
417#[derive(Debug, Clone)]
418pub struct CompletionRequest {
419    /// The preamble to be sent to the completion model provider
420    pub preamble: Option<String>,
421    /// The chat history to be sent to the completion model provider.
422    /// The very last message will always be the prompt (hence why there is *always* one)
423    pub chat_history: OneOrMany<Message>,
424    /// The documents to be sent to the completion model provider
425    pub documents: Vec<Document>,
426    /// The tools to be sent to the completion model provider
427    pub tools: Vec<ToolDefinition>,
428    /// The temperature to be sent to the completion model provider
429    pub temperature: Option<f64>,
430    /// The max tokens to be sent to the completion model provider
431    pub max_tokens: Option<u64>,
432    /// Additional provider-specific parameters to be sent to the completion model provider
433    pub additional_params: Option<serde_json::Value>,
434}
435
436impl CompletionRequest {
437    /// Returns documents normalized into a message (if any).
438    /// Most providers do not accept documents directly as input, so it needs to convert into a
439    ///  `Message` so that it can be incorporated into `chat_history` as a
440    pub fn normalized_documents(&self) -> Option<Message> {
441        if self.documents.is_empty() {
442            return None;
443        }
444
445        // Most providers will convert documents into a text unless it can handle document messages.
446        // We use `UserContent::document` for those who handle it directly!
447        let messages = self
448            .documents
449            .iter()
450            .map(|doc| {
451                UserContent::document(
452                    doc.to_string(),
453                    // In the future, we can customize `Document` to pass these extra types through.
454                    // Most providers ditch these but they might want to use them.
455                    Some(DocumentMediaType::TXT),
456                )
457            })
458            .collect::<Vec<_>>();
459
460        Some(Message::User {
461            content: OneOrMany::many(messages).expect("There will be atleast one document"),
462        })
463    }
464}
465
466/// Builder struct for constructing a completion request.
467///
468/// Example usage:
469/// ```rust
470/// use rig::{
471///     providers::openai::{Client, self},
472///     completion::CompletionRequestBuilder,
473/// };
474///
475/// let openai = Client::new("your-openai-api-key");
476/// let model = openai.completion_model(openai::GPT_4O).build();
477///
478/// // Create the completion request and execute it separately
479/// let request = CompletionRequestBuilder::new(model, "Who are you?".to_string())
480///     .preamble("You are Marvin from the Hitchhiker's Guide to the Galaxy.".to_string())
481///     .temperature(0.5)
482///     .build();
483///
484/// let response = model.completion(request)
485///     .await
486///     .expect("Failed to get completion response");
487/// ```
488///
489/// Alternatively, you can execute the completion request directly from the builder:
490/// ```rust
491/// use rig::{
492///     providers::openai::{Client, self},
493///     completion::CompletionRequestBuilder,
494/// };
495///
496/// let openai = Client::new("your-openai-api-key");
497/// let model = openai.completion_model(openai::GPT_4O).build();
498///
499/// // Create the completion request and execute it directly
500/// let response = CompletionRequestBuilder::new(model, "Who are you?".to_string())
501///     .preamble("You are Marvin from the Hitchhiker's Guide to the Galaxy.".to_string())
502///     .temperature(0.5)
503///     .send()
504///     .await
505///     .expect("Failed to get completion response");
506/// ```
507///
508/// Note: It is usually unnecessary to create a completion request builder directly.
509/// Instead, use the [CompletionModel::completion_request] method.
510pub struct CompletionRequestBuilder<M: CompletionModel> {
511    model: M,
512    prompt: Message,
513    preamble: Option<String>,
514    chat_history: Vec<Message>,
515    documents: Vec<Document>,
516    tools: Vec<ToolDefinition>,
517    temperature: Option<f64>,
518    max_tokens: Option<u64>,
519    additional_params: Option<serde_json::Value>,
520}
521
522impl<M: CompletionModel> CompletionRequestBuilder<M> {
523    pub fn new(model: M, prompt: impl Into<Message>) -> Self {
524        Self {
525            model,
526            prompt: prompt.into(),
527            preamble: None,
528            chat_history: Vec::new(),
529            documents: Vec::new(),
530            tools: Vec::new(),
531            temperature: None,
532            max_tokens: None,
533            additional_params: None,
534        }
535    }
536
537    /// Sets the preamble for the completion request.
538    pub fn preamble(mut self, preamble: String) -> Self {
539        self.preamble = Some(preamble);
540        self
541    }
542
543    pub fn without_preamble(mut self) -> Self {
544        self.preamble = None;
545        self
546    }
547
548    /// Adds a message to the chat history for the completion request.
549    pub fn message(mut self, message: Message) -> Self {
550        self.chat_history.push(message);
551        self
552    }
553
554    /// Adds a list of messages to the chat history for the completion request.
555    pub fn messages(self, messages: Vec<Message>) -> Self {
556        messages
557            .into_iter()
558            .fold(self, |builder, msg| builder.message(msg))
559    }
560
561    /// Adds a document to the completion request.
562    pub fn document(mut self, document: Document) -> Self {
563        self.documents.push(document);
564        self
565    }
566
567    /// Adds a list of documents to the completion request.
568    pub fn documents(self, documents: Vec<Document>) -> Self {
569        documents
570            .into_iter()
571            .fold(self, |builder, doc| builder.document(doc))
572    }
573
574    /// Adds a tool to the completion request.
575    pub fn tool(mut self, tool: ToolDefinition) -> Self {
576        self.tools.push(tool);
577        self
578    }
579
580    /// Adds a list of tools to the completion request.
581    pub fn tools(self, tools: Vec<ToolDefinition>) -> Self {
582        tools
583            .into_iter()
584            .fold(self, |builder, tool| builder.tool(tool))
585    }
586
587    /// Adds additional parameters to the completion request.
588    /// This can be used to set additional provider-specific parameters. For example,
589    /// Cohere's completion models accept a `connectors` parameter that can be used to
590    /// specify the data connectors used by Cohere when executing the completion
591    /// (see `examples/cohere_connectors.rs`).
592    pub fn additional_params(mut self, additional_params: serde_json::Value) -> Self {
593        match self.additional_params {
594            Some(params) => {
595                self.additional_params = Some(json_utils::merge(params, additional_params));
596            }
597            None => {
598                self.additional_params = Some(additional_params);
599            }
600        }
601        self
602    }
603
604    /// Sets the additional parameters for the completion request.
605    /// This can be used to set additional provider-specific parameters. For example,
606    /// Cohere's completion models accept a `connectors` parameter that can be used to
607    /// specify the data connectors used by Cohere when executing the completion
608    /// (see `examples/cohere_connectors.rs`).
609    pub fn additional_params_opt(mut self, additional_params: Option<serde_json::Value>) -> Self {
610        self.additional_params = additional_params;
611        self
612    }
613
614    /// Sets the temperature for the completion request.
615    pub fn temperature(mut self, temperature: f64) -> Self {
616        self.temperature = Some(temperature);
617        self
618    }
619
620    /// Sets the temperature for the completion request.
621    pub fn temperature_opt(mut self, temperature: Option<f64>) -> Self {
622        self.temperature = temperature;
623        self
624    }
625
626    /// Sets the max tokens for the completion request.
627    /// Note: This is required if using Anthropic
628    pub fn max_tokens(mut self, max_tokens: u64) -> Self {
629        self.max_tokens = Some(max_tokens);
630        self
631    }
632
633    /// Sets the max tokens for the completion request.
634    /// Note: This is required if using Anthropic
635    pub fn max_tokens_opt(mut self, max_tokens: Option<u64>) -> Self {
636        self.max_tokens = max_tokens;
637        self
638    }
639
640    /// Builds the completion request.
641    pub fn build(self) -> CompletionRequest {
642        let chat_history = OneOrMany::many([self.chat_history, vec![self.prompt]].concat())
643            .expect("There will always be atleast the prompt");
644
645        CompletionRequest {
646            preamble: self.preamble,
647            chat_history,
648            documents: self.documents,
649            tools: self.tools,
650            temperature: self.temperature,
651            max_tokens: self.max_tokens,
652            additional_params: self.additional_params,
653        }
654    }
655
656    /// Sends the completion request to the completion model provider and returns the completion response.
657    pub async fn send(self) -> Result<CompletionResponse<M::Response>, CompletionError> {
658        let model = self.model.clone();
659        model.completion(self.build()).await
660    }
661
662    /// Stream the completion request
663    pub async fn stream<'a>(
664        self,
665    ) -> Result<StreamingCompletionResponse<M::StreamingResponse>, CompletionError>
666    where
667        <M as CompletionModel>::StreamingResponse: 'a,
668        Self: 'a,
669    {
670        let model = self.model.clone();
671        model.stream(self.build()).await
672    }
673}
674
675#[cfg(test)]
676mod tests {
677
678    use super::*;
679
680    #[test]
681    fn test_document_display_without_metadata() {
682        let doc = Document {
683            id: "123".to_string(),
684            text: "This is a test document.".to_string(),
685            additional_props: HashMap::new(),
686        };
687
688        let expected = "<file id: 123>\nThis is a test document.\n</file>\n";
689        assert_eq!(format!("{doc}"), expected);
690    }
691
692    #[test]
693    fn test_document_display_with_metadata() {
694        let mut additional_props = HashMap::new();
695        additional_props.insert("author".to_string(), "John Doe".to_string());
696        additional_props.insert("length".to_string(), "42".to_string());
697
698        let doc = Document {
699            id: "123".to_string(),
700            text: "This is a test document.".to_string(),
701            additional_props,
702        };
703
704        let expected = concat!(
705            "<file id: 123>\n",
706            "<metadata author: \"John Doe\" length: \"42\" />\n",
707            "This is a test document.\n",
708            "</file>\n"
709        );
710        assert_eq!(format!("{doc}"), expected);
711    }
712
713    #[test]
714    fn test_normalize_documents_with_documents() {
715        let doc1 = Document {
716            id: "doc1".to_string(),
717            text: "Document 1 text.".to_string(),
718            additional_props: HashMap::new(),
719        };
720
721        let doc2 = Document {
722            id: "doc2".to_string(),
723            text: "Document 2 text.".to_string(),
724            additional_props: HashMap::new(),
725        };
726
727        let request = CompletionRequest {
728            preamble: None,
729            chat_history: OneOrMany::one("What is the capital of France?".into()),
730            documents: vec![doc1, doc2],
731            tools: Vec::new(),
732            temperature: None,
733            max_tokens: None,
734            additional_params: None,
735        };
736
737        let expected = Message::User {
738            content: OneOrMany::many(vec![
739                UserContent::document(
740                    "<file id: doc1>\nDocument 1 text.\n</file>\n".to_string(),
741                    Some(DocumentMediaType::TXT),
742                ),
743                UserContent::document(
744                    "<file id: doc2>\nDocument 2 text.\n</file>\n".to_string(),
745                    Some(DocumentMediaType::TXT),
746                ),
747            ])
748            .expect("There will be at least one document"),
749        };
750
751        assert_eq!(request.normalized_documents(), Some(expected));
752    }
753
754    #[test]
755    fn test_normalize_documents_without_documents() {
756        let request = CompletionRequest {
757            preamble: None,
758            chat_history: OneOrMany::one("What is the capital of France?".into()),
759            documents: Vec::new(),
760            tools: Vec::new(),
761            temperature: None,
762            max_tokens: None,
763            additional_params: None,
764        };
765
766        assert_eq!(request.normalized_documents(), None);
767    }
768}