1use std::{collections::HashMap, fmt::Display, sync::Arc};
2
3use super::*;
4use either::Either;
5use image::DynamicImage;
6use indexmap::IndexMap;
7use serde_json::{json, Value};
8
9pub trait RequestLike {
11 fn messages_ref(&self) -> &[IndexMap<String, MessageContent>];
12 fn images_ref(&self) -> &[DynamicImage];
13 fn take_messages(&mut self) -> RequestMessage;
14 fn take_logits_processors(&mut self) -> Option<Vec<Arc<dyn CustomLogitsProcessor>>>;
15 fn take_adapters(&mut self) -> Option<Vec<String>>;
16 fn return_logprobs(&self) -> bool;
17 fn enable_search(&self) -> Option<bool>;
18 fn take_constraint(&mut self) -> Constraint;
19 fn take_tools(&mut self) -> Option<(Vec<Tool>, ToolChoice)>;
20 fn take_sampling_params(&mut self) -> SamplingParams;
21 fn take_web_search_options(&mut self) -> Option<WebSearchOptions>;
22 fn truncate_sequence(&self) -> bool {
23 false
24 }
25}
26
27#[derive(Debug, Clone, PartialEq)]
28pub struct TextMessages {
34 messages: Vec<IndexMap<String, MessageContent>>,
35 enable_thinking: Option<bool>,
36}
37
38impl From<TextMessages> for Vec<IndexMap<String, MessageContent>> {
39 fn from(value: TextMessages) -> Self {
40 value.messages
41 }
42}
43
44#[derive(Debug, Clone, PartialEq)]
45pub enum TextMessageRole {
47 User,
48 Assistant,
49 System,
50 Tool,
51 Custom(String),
52}
53
54impl Display for TextMessageRole {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 match self {
57 Self::User => write!(f, "user"),
58 Self::Assistant => write!(f, "assistant"),
59 Self::System => write!(f, "system"),
60 Self::Tool => write!(f, "tool"),
61 Self::Custom(c) => write!(f, "{c}"),
62 }
63 }
64}
65
66impl Default for TextMessages {
67 fn default() -> Self {
68 Self::new()
69 }
70}
71
72impl TextMessages {
73 pub fn new() -> Self {
74 Self {
75 messages: Vec::new(),
76 enable_thinking: None,
77 }
78 }
79
80 pub fn add_message(mut self, role: TextMessageRole, text: impl ToString) -> Self {
81 self.messages.push(IndexMap::from([
82 ("role".to_string(), Either::Left(role.to_string())),
83 ("content".to_string(), Either::Left(text.to_string())),
84 ]));
85 self
86 }
87
88 pub fn clear(mut self) -> Self {
89 self.messages.clear();
90 self
91 }
92
93 pub fn enable_thinking(mut self, enable_thinking: bool) -> Self {
94 self.enable_thinking = Some(enable_thinking);
95 self
96 }
97}
98
99impl RequestLike for TextMessages {
100 fn messages_ref(&self) -> &[IndexMap<String, MessageContent>] {
101 &self.messages
102 }
103 fn images_ref(&self) -> &[DynamicImage] {
104 &[]
105 }
106 fn take_messages(&mut self) -> RequestMessage {
107 let mut other = Vec::new();
108 std::mem::swap(&mut other, &mut self.messages);
109 RequestMessage::Chat {
110 messages: other,
111 enable_thinking: self.enable_thinking,
112 }
113 }
114 fn enable_search(&self) -> Option<bool> {
115 None
116 }
117 fn take_logits_processors(&mut self) -> Option<Vec<Arc<dyn CustomLogitsProcessor>>> {
118 None
119 }
120 fn take_adapters(&mut self) -> Option<Vec<String>> {
121 None
122 }
123 fn return_logprobs(&self) -> bool {
124 false
125 }
126 fn take_constraint(&mut self) -> Constraint {
127 Constraint::None
128 }
129 fn take_tools(&mut self) -> Option<(Vec<Tool>, ToolChoice)> {
130 None
131 }
132 fn take_sampling_params(&mut self) -> SamplingParams {
133 SamplingParams::deterministic()
134 }
135 fn take_web_search_options(&mut self) -> Option<WebSearchOptions> {
136 None
137 }
138}
139
140#[derive(Debug, Clone, PartialEq)]
141pub struct VisionMessages {
147 messages: Vec<IndexMap<String, MessageContent>>,
148 images: Vec<DynamicImage>,
149 audios: Vec<AudioInput>,
150 enable_thinking: Option<bool>,
151}
152
153impl Default for VisionMessages {
154 fn default() -> Self {
155 Self::new()
156 }
157}
158
159impl VisionMessages {
160 pub fn new() -> Self {
161 Self {
162 images: Vec::new(),
163 messages: Vec::new(),
164 audios: Vec::new(),
165 enable_thinking: None,
166 }
167 }
168
169 pub fn add_message(mut self, role: TextMessageRole, text: impl ToString) -> Self {
170 self.messages.push(IndexMap::from([
171 ("role".to_string(), Either::Left(role.to_string())),
172 ("content".to_string(), Either::Left(text.to_string())),
173 ]));
174 self
175 }
176
177 pub fn add_image_message(
178 self,
179 role: TextMessageRole,
180 text: impl ToString,
181 images: Vec<DynamicImage>,
182 model: &Model,
183 ) -> anyhow::Result<Self> {
184 self.add_multimodal_message(role, text, images, vec![], model)
185 }
186
187 pub fn add_audio_message(
188 self,
189 role: TextMessageRole,
190 text: impl ToString,
191 audios: Vec<AudioInput>,
192 model: &Model,
193 ) -> anyhow::Result<Self> {
194 self.add_multimodal_message(role, text, vec![], audios, model)
195 }
196
197 pub fn add_multimodal_message(
198 mut self,
199 role: TextMessageRole,
200 text: impl ToString,
201 images: Vec<DynamicImage>,
202 audios: Vec<AudioInput>,
203 model: &Model,
204 ) -> anyhow::Result<Self> {
205 let config = model.config().unwrap();
206 let prefixer = match &config.category {
207 ModelCategory::Vision { prefixer } => prefixer,
208 _ => {
209 anyhow::bail!("`add_image_message` expects a vision model.")
210 }
211 };
212
213 let n_added_images = images.len();
215 let prefixed = prefixer.prefix_image(
216 (self.images.len()..self.images.len() + n_added_images).collect(),
217 &text.to_string(),
218 );
219 self.images.extend(images);
220
221 let n_added_audios = audios.len();
223 let prefixed = prefixer.prefix_audio(
224 (self.audios.len()..self.audios.len() + n_added_audios).collect(),
225 &prefixed,
226 );
227 self.audios.extend(audios);
228
229 if n_added_images > 0 {
230 self.messages.push(IndexMap::from([
231 ("role".to_string(), Either::Left(role.to_string())),
232 (
233 "content".to_string(),
234 Either::Right(vec![
235 IndexMap::from([("type".to_string(), Value::String("image".to_string()))]),
236 IndexMap::from([
237 ("type".to_string(), Value::String("text".to_string())),
238 ("text".to_string(), Value::String(prefixed)),
239 ]),
240 ]),
241 ),
242 ]));
243 } else {
244 self.messages.push(IndexMap::from([
245 ("role".to_string(), Either::Left(role.to_string())),
246 ("content".to_string(), Either::Left(prefixed)),
247 ]));
248 }
249 Ok(self)
250 }
251
252 pub fn clear(mut self) -> Self {
253 self.messages.clear();
254 self.images.clear();
255 self.audios.clear();
256
257 self
258 }
259
260 pub fn enable_thinking(mut self, enable_thinking: bool) -> Self {
261 self.enable_thinking = Some(enable_thinking);
262 self
263 }
264}
265
266impl RequestLike for VisionMessages {
267 fn messages_ref(&self) -> &[IndexMap<String, MessageContent>] {
268 &self.messages
269 }
270 fn images_ref(&self) -> &[DynamicImage] {
271 &self.images
272 }
273 fn take_messages(&mut self) -> RequestMessage {
274 let mut other_messages = Vec::new();
275 std::mem::swap(&mut other_messages, &mut self.messages);
276 let mut other_images = Vec::new();
277 std::mem::swap(&mut other_images, &mut self.images);
278 let mut other_audios = Vec::new();
279 std::mem::swap(&mut other_audios, &mut self.audios);
280 RequestMessage::VisionChat {
281 images: other_images,
282 messages: other_messages,
283 audios: other_audios,
284 enable_thinking: self.enable_thinking,
285 }
286 }
287 fn enable_search(&self) -> Option<bool> {
288 None
289 }
290 fn take_logits_processors(&mut self) -> Option<Vec<Arc<dyn CustomLogitsProcessor>>> {
291 None
292 }
293 fn take_adapters(&mut self) -> Option<Vec<String>> {
294 None
295 }
296 fn return_logprobs(&self) -> bool {
297 false
298 }
299 fn take_constraint(&mut self) -> Constraint {
300 Constraint::None
301 }
302 fn take_tools(&mut self) -> Option<(Vec<Tool>, ToolChoice)> {
303 None
304 }
305 fn take_sampling_params(&mut self) -> SamplingParams {
306 SamplingParams::deterministic()
307 }
308 fn take_web_search_options(&mut self) -> Option<WebSearchOptions> {
309 None
310 }
311}
312
313#[derive(Clone)]
314pub struct RequestBuilder {
324 messages: Vec<IndexMap<String, MessageContent>>,
325 images: Vec<DynamicImage>,
326 audios: Vec<AudioInput>,
327 logits_processors: Vec<Arc<dyn CustomLogitsProcessor>>,
328 adapters: Vec<String>,
329 return_logprobs: bool,
330 constraint: Constraint,
331 tools: Vec<Tool>,
332 tool_choice: ToolChoice,
333 sampling_params: SamplingParams,
334 web_search_options: Option<WebSearchOptions>,
335 enable_thinking: Option<bool>,
336 truncate_sequence: bool,
337}
338
339impl Default for RequestBuilder {
340 fn default() -> Self {
341 Self::new()
342 }
343}
344
345impl From<TextMessages> for RequestBuilder {
346 fn from(value: TextMessages) -> Self {
347 Self {
348 messages: value.messages,
349 images: Vec::new(),
350 audios: Vec::new(),
351 logits_processors: Vec::new(),
352 adapters: Vec::new(),
353 return_logprobs: false,
354 constraint: Constraint::None,
355 tools: Vec::new(),
356 tool_choice: ToolChoice::Auto,
357 sampling_params: SamplingParams::deterministic(),
358 web_search_options: None,
359 enable_thinking: None,
360 truncate_sequence: false,
361 }
362 }
363}
364
365impl From<VisionMessages> for RequestBuilder {
366 fn from(value: VisionMessages) -> Self {
367 Self {
368 messages: value.messages,
369 images: value.images,
370 audios: value.audios,
371 logits_processors: Vec::new(),
372 adapters: Vec::new(),
373 return_logprobs: false,
374 constraint: Constraint::None,
375 tools: Vec::new(),
376 tool_choice: ToolChoice::Auto,
377 sampling_params: SamplingParams::deterministic(),
378 web_search_options: None,
379 enable_thinking: None,
380 truncate_sequence: false,
381 }
382 }
383}
384
385impl RequestBuilder {
386 pub fn new() -> Self {
387 Self {
388 messages: Vec::new(),
389 images: Vec::new(),
390 audios: Vec::new(),
391 logits_processors: Vec::new(),
392 adapters: Vec::new(),
393 return_logprobs: false,
394 constraint: Constraint::None,
395 tools: Vec::new(),
396 tool_choice: ToolChoice::Auto,
397 sampling_params: SamplingParams::deterministic(),
398 web_search_options: None,
399 enable_thinking: None,
400 truncate_sequence: false,
401 }
402 }
403
404 pub fn with_web_search_options(mut self, web_search_options: WebSearchOptions) -> Self {
405 self.web_search_options = Some(web_search_options);
406 self
407 }
408
409 pub fn add_message(mut self, role: TextMessageRole, text: impl ToString) -> Self {
414 self.messages.push(IndexMap::from([
415 ("role".to_string(), Either::Left(role.to_string())),
416 ("content".to_string(), Either::Left(text.to_string())),
417 ]));
418 self
419 }
420
421 pub fn add_tool_message(mut self, tool_content: impl ToString, tool_id: impl ToString) -> Self {
423 self.messages.push(IndexMap::from([
424 (
425 "role".to_string(),
426 Either::Left(TextMessageRole::Tool.to_string()),
427 ),
428 (
429 "content".to_string(),
430 Either::Left(tool_content.to_string()),
431 ),
432 (
433 "tool_call_id".to_string(),
434 Either::Left(tool_id.to_string()),
435 ),
436 ]));
437 self
438 }
439
440 pub fn add_message_with_tool_call(
441 mut self,
442 role: TextMessageRole,
443 text: impl ToString,
444 tool_calls: Vec<ToolCallResponse>,
445 ) -> Self {
446 let tool_messages = tool_calls
447 .iter()
448 .map(|t| {
449 IndexMap::from([
450 ("id".to_string(), Value::String(t.id.clone())),
451 ("type".to_string(), Value::String(t.tp.to_string())),
452 (
453 "function".to_string(),
454 json!({
455 "name": t.function.name,
456 "arguments": t.function.arguments,
457 }),
458 ),
459 ])
460 })
461 .collect();
462 self.messages.push(IndexMap::from([
463 ("role".to_string(), Either::Left(role.to_string())),
464 ("content".to_string(), Either::Left(text.to_string())),
465 ("function".to_string(), Either::Right(tool_messages)),
466 ]));
467 self
468 }
469
470 pub fn add_image_message(
471 self,
472 role: TextMessageRole,
473 text: impl ToString,
474 images: Vec<DynamicImage>,
475 model: &Model,
476 ) -> anyhow::Result<Self> {
477 self.add_multimodal_message(role, text, images, vec![], model)
478 }
479
480 pub fn add_audio_message(
481 self,
482 role: TextMessageRole,
483 text: impl ToString,
484 audios: Vec<AudioInput>,
485 model: &Model,
486 ) -> anyhow::Result<Self> {
487 self.add_multimodal_message(role, text, vec![], audios, model)
488 }
489
490 pub fn add_multimodal_message(
491 mut self,
492 role: TextMessageRole,
493 text: impl ToString,
494 images: Vec<DynamicImage>,
495 audios: Vec<AudioInput>,
496 model: &Model,
497 ) -> anyhow::Result<Self> {
498 let config = model.config().unwrap();
499 let prefixer = match &config.category {
500 ModelCategory::Vision { prefixer } => prefixer,
501 _ => {
502 anyhow::bail!("`add_image_message` expects a vision model.")
503 }
504 };
505
506 let n_added_images = images.len();
508 let prefixed = prefixer.prefix_image(
509 (self.images.len()..self.images.len() + n_added_images).collect(),
510 &text.to_string(),
511 );
512 self.images.extend(images);
513
514 let n_added_audios = audios.len();
516 let prefixed = prefixer.prefix_audio(
517 (self.audios.len()..self.audios.len() + n_added_audios).collect(),
518 &prefixed,
519 );
520 self.audios.extend(audios);
521
522 if n_added_images > 0 {
523 self.messages.push(IndexMap::from([
524 ("role".to_string(), Either::Left(role.to_string())),
525 (
526 "content".to_string(),
527 Either::Right(vec![
528 IndexMap::from([("type".to_string(), Value::String("image".to_string()))]),
529 IndexMap::from([
530 ("type".to_string(), Value::String("text".to_string())),
531 ("text".to_string(), Value::String(prefixed)),
532 ]),
533 ]),
534 ),
535 ]));
536 } else {
537 self.messages.push(IndexMap::from([
538 ("role".to_string(), Either::Left(role.to_string())),
539 ("content".to_string(), Either::Left(prefixed)),
540 ]));
541 }
542 Ok(self)
543 }
544
545 pub fn add_logits_processor(mut self, processor: Arc<dyn CustomLogitsProcessor>) -> Self {
546 self.logits_processors.push(processor);
547 self
548 }
549
550 pub fn set_adapters(mut self, adapters: Vec<String>) -> Self {
551 self.adapters = adapters;
552 self
553 }
554
555 pub fn set_tools(mut self, tools: Vec<Tool>) -> Self {
557 self.tools = tools;
558 self
559 }
560
561 pub fn set_tool_choice(mut self, tool_choice: ToolChoice) -> Self {
562 self.tool_choice = tool_choice;
563 self
564 }
565
566 pub fn return_logprobs(mut self, return_logprobs: bool) -> Self {
567 self.return_logprobs = return_logprobs;
568 self
569 }
570
571 pub fn set_constraint(mut self, constraint: Constraint) -> Self {
572 self.constraint = constraint;
573 self
574 }
575
576 pub fn set_sampling(mut self, params: SamplingParams) -> Self {
578 self.sampling_params = params;
579 self
580 }
581
582 pub fn set_deterministic_sampler(mut self) -> Self {
588 self.sampling_params = SamplingParams::deterministic();
589 self
590 }
591
592 pub fn set_sampler_temperature(mut self, temperature: f64) -> Self {
593 self.sampling_params.temperature = Some(temperature);
594 self
595 }
596
597 pub fn set_sampler_topk(mut self, topk: usize) -> Self {
598 self.sampling_params.top_k = Some(topk);
599 self
600 }
601
602 pub fn set_sampler_topp(mut self, topp: f64) -> Self {
603 self.sampling_params.top_p = Some(topp);
604 self
605 }
606
607 pub fn set_sampler_minp(mut self, minp: f64) -> Self {
608 self.sampling_params.min_p = Some(minp);
609 self
610 }
611
612 pub fn set_sampler_topn_logprobs(mut self, top_n_logprobs: usize) -> Self {
613 self.sampling_params.top_n_logprobs = top_n_logprobs;
614 self
615 }
616
617 pub fn set_sampler_frequency_penalty(mut self, frequency_penalty: f32) -> Self {
618 self.sampling_params.frequency_penalty = Some(frequency_penalty);
619 self
620 }
621
622 pub fn set_sampler_presence_penalty(mut self, presence_penalty: f32) -> Self {
623 self.sampling_params.presence_penalty = Some(presence_penalty);
624 self
625 }
626
627 pub fn set_sampler_stop_toks(mut self, stop_toks: StopTokens) -> Self {
628 self.sampling_params.stop_toks = Some(stop_toks);
629 self
630 }
631
632 pub fn set_sampler_max_len(mut self, max_len: usize) -> Self {
633 self.sampling_params.max_len = Some(max_len);
634 self
635 }
636
637 pub fn set_sampler_logits_bias(mut self, logits_bias: HashMap<u32, f32>) -> Self {
638 self.sampling_params.logits_bias = Some(logits_bias);
639 self
640 }
641
642 pub fn set_sampler_n_choices(mut self, n_choices: usize) -> Self {
643 self.sampling_params.n_choices = n_choices;
644 self
645 }
646
647 pub fn set_sampler_dry_params(mut self, dry_params: DrySamplingParams) -> Self {
648 self.sampling_params.dry_params = Some(dry_params);
649 self
650 }
651
652 pub fn enable_thinking(mut self, enable_thinking: bool) -> Self {
653 self.enable_thinking = Some(enable_thinking);
654 self
655 }
656
657 pub fn with_truncate_sequence(mut self, truncate_sequence: bool) -> Self {
659 self.truncate_sequence = truncate_sequence;
660 self
661 }
662}
663
664impl RequestLike for RequestBuilder {
665 fn messages_ref(&self) -> &[IndexMap<String, MessageContent>] {
666 &self.messages
667 }
668
669 fn images_ref(&self) -> &[DynamicImage] {
670 &self.images
671 }
672
673 fn take_messages(&mut self) -> RequestMessage {
674 if self.images.is_empty() && self.audios.is_empty() {
675 let mut other = Vec::new();
676 std::mem::swap(&mut other, &mut self.messages);
677 RequestMessage::Chat {
678 messages: other,
679 enable_thinking: self.enable_thinking,
680 }
681 } else {
682 let mut other_messages = Vec::new();
683 std::mem::swap(&mut other_messages, &mut self.messages);
684 let mut other_images = Vec::new();
685 std::mem::swap(&mut other_images, &mut self.images);
686 let mut other_audios = Vec::new();
687 std::mem::swap(&mut other_audios, &mut self.audios);
688 RequestMessage::VisionChat {
689 images: other_images,
690 messages: other_messages,
691 audios: other_audios,
692 enable_thinking: self.enable_thinking,
693 }
694 }
695 }
696
697 fn enable_search(&self) -> Option<bool> {
698 self.web_search_options.as_ref().map(|_| true)
699 }
700
701 fn take_logits_processors(&mut self) -> Option<Vec<Arc<dyn CustomLogitsProcessor>>> {
702 if self.logits_processors.is_empty() {
703 None
704 } else {
705 let mut other = Vec::new();
706 std::mem::swap(&mut other, &mut self.logits_processors);
707 Some(other)
708 }
709 }
710
711 fn take_adapters(&mut self) -> Option<Vec<String>> {
712 if self.adapters.is_empty() {
713 None
714 } else {
715 let mut other = Vec::new();
716 std::mem::swap(&mut other, &mut self.adapters);
717 Some(other)
718 }
719 }
720
721 fn return_logprobs(&self) -> bool {
722 self.return_logprobs
723 }
724
725 fn take_constraint(&mut self) -> Constraint {
726 let mut other = Constraint::None;
727 std::mem::swap(&mut other, &mut self.constraint);
728 other
729 }
730
731 fn take_tools(&mut self) -> Option<(Vec<Tool>, ToolChoice)> {
732 if self.tools.is_empty() {
733 None
734 } else {
735 let mut other_ts = Vec::new();
736 std::mem::swap(&mut other_ts, &mut self.tools);
737 let mut other_tc = ToolChoice::Auto;
738 std::mem::swap(&mut other_tc, &mut self.tool_choice);
739 Some((other_ts, other_tc))
740 }
741 }
742
743 fn take_sampling_params(&mut self) -> SamplingParams {
744 let mut other = SamplingParams::deterministic();
745 std::mem::swap(&mut other, &mut self.sampling_params);
746 other
747 }
748
749 fn take_web_search_options(&mut self) -> Option<WebSearchOptions> {
750 let mut other = None;
751 std::mem::swap(&mut other, &mut self.web_search_options);
752 other
753 }
754
755 fn truncate_sequence(&self) -> bool {
756 self.truncate_sequence
757 }
758}
759
760#[derive(Clone, Debug)]
761pub enum EmbeddingRequestInput {
763 Prompt(String),
765 Tokens(Vec<u32>),
767}
768
769impl EmbeddingRequestInput {
770 pub fn into_request_message(self) -> RequestMessage {
771 match self {
772 Self::Prompt(prompt) => RequestMessage::Embedding { prompt },
773 Self::Tokens(prompt) => RequestMessage::EmbeddingTokens { prompt },
774 }
775 }
776}
777
778#[derive(Clone, Debug)]
779pub struct EmbeddingRequest {
781 pub inputs: Vec<EmbeddingRequestInput>,
782 pub truncate_sequence: bool,
783}
784
785impl EmbeddingRequest {
786 pub fn builder() -> EmbeddingRequestBuilder {
788 EmbeddingRequestBuilder::new()
789 }
790}
791
792#[derive(Clone, Debug, Default)]
794pub struct EmbeddingRequestBuilder {
795 inputs: Vec<EmbeddingRequestInput>,
796 truncate_sequence: bool,
797}
798
799impl EmbeddingRequestBuilder {
800 pub fn new() -> Self {
802 Self::default()
803 }
804
805 pub fn add_prompt(mut self, prompt: impl Into<String>) -> Self {
807 self.inputs
808 .push(EmbeddingRequestInput::Prompt(prompt.into()));
809 self
810 }
811
812 pub fn add_prompts<I, S>(mut self, prompts: I) -> Self
814 where
815 I: IntoIterator<Item = S>,
816 S: Into<String>,
817 {
818 self.inputs.extend(
819 prompts
820 .into_iter()
821 .map(|prompt| EmbeddingRequestInput::Prompt(prompt.into())),
822 );
823 self
824 }
825
826 pub fn add_tokens(mut self, tokens: impl Into<Vec<u32>>) -> Self {
828 self.inputs
829 .push(EmbeddingRequestInput::Tokens(tokens.into()));
830 self
831 }
832
833 pub fn add_tokens_batch<I>(mut self, batches: I) -> Self
835 where
836 I: IntoIterator<Item = Vec<u32>>,
837 {
838 self.inputs
839 .extend(batches.into_iter().map(EmbeddingRequestInput::Tokens));
840 self
841 }
842
843 pub fn with_truncate_sequence(mut self, truncate: bool) -> Self {
845 self.truncate_sequence = truncate;
846 self
847 }
848
849 pub fn build(self) -> anyhow::Result<EmbeddingRequest> {
850 if self.inputs.is_empty() {
851 anyhow::bail!("Embedding request must contain at least one input.");
852 }
853
854 Ok(EmbeddingRequest {
855 inputs: self.inputs,
856 truncate_sequence: self.truncate_sequence,
857 })
858 }
859}