1use std::{convert::Infallible, str::FromStr};
2
3use crate::OneOrMany;
4use serde::{Deserialize, Serialize};
5use thiserror::Error;
6
7use super::CompletionError;
8
9pub trait ConvertMessage: Sized + Send + Sync {
19 type Error: std::error::Error + Send;
20
21 fn convert_from_message(message: Message) -> Result<Vec<Self>, Self::Error>;
22}
23
24#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
35#[serde(tag = "role", rename_all = "lowercase")]
36pub enum Message {
37 User { content: OneOrMany<UserContent> },
39
40 Assistant {
42 id: Option<String>,
43 content: OneOrMany<AssistantContent>,
44 },
45}
46
47#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
51#[serde(tag = "type", rename_all = "lowercase")]
52pub enum UserContent {
53 Text(Text),
54 ToolResult(ToolResult),
55 Image(Image),
56 Audio(Audio),
57 Video(Video),
58 Document(Document),
59}
60
61#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
63#[serde(untagged)]
64pub enum AssistantContent {
65 Text(Text),
66 ToolCall(ToolCall),
67 Reasoning(Reasoning),
68}
69
70#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
71pub struct Reasoning {
72 pub id: Option<String>,
73 pub reasoning: Vec<String>,
74}
75
76impl Reasoning {
77 pub fn new(input: &str) -> Self {
79 Self {
80 id: None,
81 reasoning: vec![input.to_string()],
82 }
83 }
84
85 pub fn multi(input: Vec<String>) -> Self {
86 Self {
87 id: None,
88 reasoning: input,
89 }
90 }
91
92 pub fn optional_id(mut self, id: Option<String>) -> Self {
93 self.id = id;
94 self
95 }
96
97 pub fn with_id(mut self, id: String) -> Self {
98 self.id = Some(id);
99 self
100 }
101}
102
103#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
105pub struct ToolResult {
106 pub id: String,
107 #[serde(skip_serializing_if = "Option::is_none")]
108 pub call_id: Option<String>,
109 pub content: OneOrMany<ToolResultContent>,
110}
111
112#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
114#[serde(tag = "type", rename_all = "lowercase")]
115pub enum ToolResultContent {
116 Text(Text),
117 Image(Image),
118}
119
120#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
122pub struct ToolCall {
123 pub id: String,
124 pub call_id: Option<String>,
125 pub function: ToolFunction,
126}
127
128#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
130pub struct ToolFunction {
131 pub name: String,
132 pub arguments: serde_json::Value,
133}
134
135#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
141pub struct Text {
142 pub text: String,
143}
144
145impl Text {
146 pub fn text(&self) -> &str {
147 &self.text
148 }
149}
150
151impl std::fmt::Display for Text {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 let Self { text } = self;
154 write!(f, "{text}")
155 }
156}
157
158#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
160pub struct Image {
161 pub data: DocumentSourceKind,
162 #[serde(skip_serializing_if = "Option::is_none")]
163 pub media_type: Option<ImageMediaType>,
164 #[serde(skip_serializing_if = "Option::is_none")]
165 pub detail: Option<ImageDetail>,
166 #[serde(flatten, skip_serializing_if = "Option::is_none")]
167 pub additional_params: Option<serde_json::Value>,
168}
169
170#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)]
172#[serde(tag = "type", rename_all = "camelCase")]
173#[non_exhaustive]
174pub enum DocumentSourceKind {
175 Url(String),
177 Base64(String),
179 #[default]
180 Unknown,
182}
183
184impl DocumentSourceKind {
185 pub fn url(url: &str) -> Self {
186 Self::Url(url.to_string())
187 }
188
189 pub fn base64(base64_string: &str) -> Self {
190 Self::Base64(base64_string.to_string())
191 }
192
193 pub fn unknown() -> Self {
194 Self::Unknown
195 }
196}
197
198impl std::fmt::Display for DocumentSourceKind {
199 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200 match self {
201 Self::Url(string) => write!(f, "{string}"),
202 Self::Base64(string) => write!(f, "{string}"),
203 Self::Unknown => write!(f, "<unknown>"),
204 }
205 }
206}
207
208#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
210pub struct Audio {
211 pub data: String,
212 #[serde(skip_serializing_if = "Option::is_none")]
213 pub format: Option<ContentFormat>,
214 #[serde(skip_serializing_if = "Option::is_none")]
215 pub media_type: Option<AudioMediaType>,
216 #[serde(flatten, skip_serializing_if = "Option::is_none")]
217 pub additional_params: Option<serde_json::Value>,
218}
219
220#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
222pub struct Video {
223 pub data: String,
224 #[serde(skip_serializing_if = "Option::is_none")]
225 pub format: Option<ContentFormat>,
226 #[serde(skip_serializing_if = "Option::is_none")]
227 pub media_type: Option<VideoMediaType>,
228 #[serde(flatten, skip_serializing_if = "Option::is_none")]
229 pub additional_params: Option<serde_json::Value>,
230}
231
232#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
234pub struct Document {
235 pub data: String,
236 #[serde(skip_serializing_if = "Option::is_none")]
237 pub format: Option<ContentFormat>,
238 #[serde(skip_serializing_if = "Option::is_none")]
239 pub media_type: Option<DocumentMediaType>,
240 #[serde(flatten, skip_serializing_if = "Option::is_none")]
241 pub additional_params: Option<serde_json::Value>,
242}
243
244#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
246#[serde(rename_all = "lowercase")]
247pub enum ContentFormat {
248 #[default]
249 Base64,
250 String,
251}
252
253#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
255pub enum MediaType {
256 Image(ImageMediaType),
257 Audio(AudioMediaType),
258 Document(DocumentMediaType),
259 Video(VideoMediaType),
260}
261
262#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
265#[serde(rename_all = "lowercase")]
266pub enum ImageMediaType {
267 JPEG,
268 PNG,
269 GIF,
270 WEBP,
271 HEIC,
272 HEIF,
273 SVG,
274}
275
276#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
280#[serde(rename_all = "lowercase")]
281pub enum DocumentMediaType {
282 PDF,
283 TXT,
284 RTF,
285 HTML,
286 CSS,
287 MARKDOWN,
288 CSV,
289 XML,
290 Javascript,
291 Python,
292}
293
294#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
297#[serde(rename_all = "lowercase")]
298pub enum AudioMediaType {
299 WAV,
300 MP3,
301 AIFF,
302 AAC,
303 OGG,
304 FLAC,
305}
306
307#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
310#[serde(rename_all = "lowercase")]
311pub enum VideoMediaType {
312 AVI,
313 MP4,
314 MPEG,
315}
316
317#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
319#[serde(rename_all = "lowercase")]
320pub enum ImageDetail {
321 Low,
322 High,
323 #[default]
324 Auto,
325}
326
327impl Message {
332 pub(crate) fn rag_text(&self) -> Option<String> {
335 match self {
336 Message::User { content } => {
337 for item in content.iter() {
338 if let UserContent::Text(Text { text }) = item {
339 return Some(text.clone());
340 }
341 }
342 None
343 }
344 _ => None,
345 }
346 }
347
348 pub fn user(text: impl Into<String>) -> Self {
350 Message::User {
351 content: OneOrMany::one(UserContent::text(text)),
352 }
353 }
354
355 pub fn assistant(text: impl Into<String>) -> Self {
357 Message::Assistant {
358 id: None,
359 content: OneOrMany::one(AssistantContent::text(text)),
360 }
361 }
362
363 pub fn assistant_with_id(id: String, text: impl Into<String>) -> Self {
365 Message::Assistant {
366 id: Some(id),
367 content: OneOrMany::one(AssistantContent::text(text)),
368 }
369 }
370
371 pub fn tool_result(id: impl Into<String>, content: impl Into<String>) -> Self {
373 Message::User {
374 content: OneOrMany::one(UserContent::ToolResult(ToolResult {
375 id: id.into(),
376 call_id: None,
377 content: OneOrMany::one(ToolResultContent::text(content)),
378 })),
379 }
380 }
381
382 pub fn tool_result_with_call_id(
383 id: impl Into<String>,
384 call_id: Option<String>,
385 content: impl Into<String>,
386 ) -> Self {
387 Message::User {
388 content: OneOrMany::one(UserContent::ToolResult(ToolResult {
389 id: id.into(),
390 call_id,
391 content: OneOrMany::one(ToolResultContent::text(content)),
392 })),
393 }
394 }
395}
396
397impl UserContent {
398 pub fn text(text: impl Into<String>) -> Self {
400 UserContent::Text(text.into().into())
401 }
402
403 pub fn image_base64(
405 data: impl Into<String>,
406 media_type: Option<ImageMediaType>,
407 detail: Option<ImageDetail>,
408 ) -> Self {
409 UserContent::Image(Image {
410 data: DocumentSourceKind::Base64(data.into()),
411 media_type,
412 detail,
413 additional_params: None,
414 })
415 }
416
417 pub fn image_url(
419 url: impl Into<String>,
420 media_type: Option<ImageMediaType>,
421 detail: Option<ImageDetail>,
422 ) -> Self {
423 UserContent::Image(Image {
424 data: DocumentSourceKind::Url(url.into()),
425 media_type,
426 detail,
427 additional_params: None,
428 })
429 }
430
431 pub fn audio(
433 data: impl Into<String>,
434 format: Option<ContentFormat>,
435 media_type: Option<AudioMediaType>,
436 ) -> Self {
437 UserContent::Audio(Audio {
438 data: data.into(),
439 format,
440 media_type,
441 additional_params: None,
442 })
443 }
444
445 pub fn document(
447 data: impl Into<String>,
448 format: Option<ContentFormat>,
449 media_type: Option<DocumentMediaType>,
450 ) -> Self {
451 UserContent::Document(Document {
452 data: data.into(),
453 format,
454 media_type,
455 additional_params: None,
456 })
457 }
458
459 pub fn tool_result(id: impl Into<String>, content: OneOrMany<ToolResultContent>) -> Self {
461 UserContent::ToolResult(ToolResult {
462 id: id.into(),
463 call_id: None,
464 content,
465 })
466 }
467
468 pub fn tool_result_with_call_id(
470 id: impl Into<String>,
471 call_id: String,
472 content: OneOrMany<ToolResultContent>,
473 ) -> Self {
474 UserContent::ToolResult(ToolResult {
475 id: id.into(),
476 call_id: Some(call_id),
477 content,
478 })
479 }
480}
481
482impl AssistantContent {
483 pub fn text(text: impl Into<String>) -> Self {
485 AssistantContent::Text(text.into().into())
486 }
487
488 pub fn tool_call(
490 id: impl Into<String>,
491 name: impl Into<String>,
492 arguments: serde_json::Value,
493 ) -> Self {
494 AssistantContent::ToolCall(ToolCall {
495 id: id.into(),
496 call_id: None,
497 function: ToolFunction {
498 name: name.into(),
499 arguments,
500 },
501 })
502 }
503
504 pub fn tool_call_with_call_id(
505 id: impl Into<String>,
506 call_id: String,
507 name: impl Into<String>,
508 arguments: serde_json::Value,
509 ) -> Self {
510 AssistantContent::ToolCall(ToolCall {
511 id: id.into(),
512 call_id: Some(call_id),
513 function: ToolFunction {
514 name: name.into(),
515 arguments,
516 },
517 })
518 }
519}
520
521impl ToolResultContent {
522 pub fn text(text: impl Into<String>) -> Self {
524 ToolResultContent::Text(text.into().into())
525 }
526
527 pub fn image_base64(
529 data: impl Into<String>,
530 media_type: Option<ImageMediaType>,
531 detail: Option<ImageDetail>,
532 ) -> Self {
533 ToolResultContent::Image(Image {
534 data: DocumentSourceKind::Base64(data.into()),
535 media_type,
536 detail,
537 additional_params: None,
538 })
539 }
540
541 pub fn image_url(
543 url: impl Into<String>,
544 media_type: Option<ImageMediaType>,
545 detail: Option<ImageDetail>,
546 ) -> Self {
547 ToolResultContent::Image(Image {
548 data: DocumentSourceKind::Url(url.into()),
549 media_type,
550 detail,
551 additional_params: None,
552 })
553 }
554}
555
556pub trait MimeType {
558 fn from_mime_type(mime_type: &str) -> Option<Self>
559 where
560 Self: Sized;
561 fn to_mime_type(&self) -> &'static str;
562}
563
564impl MimeType for MediaType {
565 fn from_mime_type(mime_type: &str) -> Option<Self> {
566 ImageMediaType::from_mime_type(mime_type)
567 .map(MediaType::Image)
568 .or_else(|| {
569 DocumentMediaType::from_mime_type(mime_type)
570 .map(MediaType::Document)
571 .or_else(|| AudioMediaType::from_mime_type(mime_type).map(MediaType::Audio))
572 })
573 }
574
575 fn to_mime_type(&self) -> &'static str {
576 match self {
577 MediaType::Image(media_type) => media_type.to_mime_type(),
578 MediaType::Audio(media_type) => media_type.to_mime_type(),
579 MediaType::Document(media_type) => media_type.to_mime_type(),
580 MediaType::Video(media_type) => media_type.to_mime_type(),
581 }
582 }
583}
584
585impl MimeType for ImageMediaType {
586 fn from_mime_type(mime_type: &str) -> Option<Self> {
587 match mime_type {
588 "image/jpeg" => Some(ImageMediaType::JPEG),
589 "image/png" => Some(ImageMediaType::PNG),
590 "image/gif" => Some(ImageMediaType::GIF),
591 "image/webp" => Some(ImageMediaType::WEBP),
592 "image/heic" => Some(ImageMediaType::HEIC),
593 "image/heif" => Some(ImageMediaType::HEIF),
594 "image/svg+xml" => Some(ImageMediaType::SVG),
595 _ => None,
596 }
597 }
598
599 fn to_mime_type(&self) -> &'static str {
600 match self {
601 ImageMediaType::JPEG => "image/jpeg",
602 ImageMediaType::PNG => "image/png",
603 ImageMediaType::GIF => "image/gif",
604 ImageMediaType::WEBP => "image/webp",
605 ImageMediaType::HEIC => "image/heic",
606 ImageMediaType::HEIF => "image/heif",
607 ImageMediaType::SVG => "image/svg+xml",
608 }
609 }
610}
611
612impl MimeType for DocumentMediaType {
613 fn from_mime_type(mime_type: &str) -> Option<Self> {
614 match mime_type {
615 "application/pdf" => Some(DocumentMediaType::PDF),
616 "text/plain" => Some(DocumentMediaType::TXT),
617 "text/rtf" => Some(DocumentMediaType::RTF),
618 "text/html" => Some(DocumentMediaType::HTML),
619 "text/css" => Some(DocumentMediaType::CSS),
620 "text/md" | "text/markdown" => Some(DocumentMediaType::MARKDOWN),
621 "text/csv" => Some(DocumentMediaType::CSV),
622 "text/xml" => Some(DocumentMediaType::XML),
623 "application/x-javascript" | "text/x-javascript" => Some(DocumentMediaType::Javascript),
624 "application/x-python" | "text/x-python" => Some(DocumentMediaType::Python),
625 _ => None,
626 }
627 }
628
629 fn to_mime_type(&self) -> &'static str {
630 match self {
631 DocumentMediaType::PDF => "application/pdf",
632 DocumentMediaType::TXT => "text/plain",
633 DocumentMediaType::RTF => "text/rtf",
634 DocumentMediaType::HTML => "text/html",
635 DocumentMediaType::CSS => "text/css",
636 DocumentMediaType::MARKDOWN => "text/markdown",
637 DocumentMediaType::CSV => "text/csv",
638 DocumentMediaType::XML => "text/xml",
639 DocumentMediaType::Javascript => "application/x-javascript",
640 DocumentMediaType::Python => "application/x-python",
641 }
642 }
643}
644
645impl MimeType for AudioMediaType {
646 fn from_mime_type(mime_type: &str) -> Option<Self> {
647 match mime_type {
648 "audio/wav" => Some(AudioMediaType::WAV),
649 "audio/mp3" => Some(AudioMediaType::MP3),
650 "audio/aiff" => Some(AudioMediaType::AIFF),
651 "audio/aac" => Some(AudioMediaType::AAC),
652 "audio/ogg" => Some(AudioMediaType::OGG),
653 "audio/flac" => Some(AudioMediaType::FLAC),
654 _ => None,
655 }
656 }
657
658 fn to_mime_type(&self) -> &'static str {
659 match self {
660 AudioMediaType::WAV => "audio/wav",
661 AudioMediaType::MP3 => "audio/mp3",
662 AudioMediaType::AIFF => "audio/aiff",
663 AudioMediaType::AAC => "audio/aac",
664 AudioMediaType::OGG => "audio/ogg",
665 AudioMediaType::FLAC => "audio/flac",
666 }
667 }
668}
669
670impl MimeType for VideoMediaType {
671 fn from_mime_type(mime_type: &str) -> Option<Self>
672 where
673 Self: Sized,
674 {
675 match mime_type {
676 "video/avi" => Some(VideoMediaType::AVI),
677 "video/mp4" => Some(VideoMediaType::MP4),
678 "video/mpeg" => Some(VideoMediaType::MPEG),
679 &_ => None,
680 }
681 }
682
683 fn to_mime_type(&self) -> &'static str {
684 match self {
685 VideoMediaType::AVI => "video/avi",
686 VideoMediaType::MP4 => "video/mp4",
687 VideoMediaType::MPEG => "video/mpeg",
688 }
689 }
690}
691
692impl std::str::FromStr for ImageDetail {
693 type Err = ();
694
695 fn from_str(s: &str) -> Result<Self, Self::Err> {
696 match s.to_lowercase().as_str() {
697 "low" => Ok(ImageDetail::Low),
698 "high" => Ok(ImageDetail::High),
699 "auto" => Ok(ImageDetail::Auto),
700 _ => Err(()),
701 }
702 }
703}
704
705impl From<String> for Text {
710 fn from(text: String) -> Self {
711 Text { text }
712 }
713}
714
715impl From<&String> for Text {
716 fn from(text: &String) -> Self {
717 text.to_owned().into()
718 }
719}
720
721impl From<&str> for Text {
722 fn from(text: &str) -> Self {
723 text.to_owned().into()
724 }
725}
726
727impl FromStr for Text {
728 type Err = Infallible;
729
730 fn from_str(s: &str) -> Result<Self, Self::Err> {
731 Ok(s.into())
732 }
733}
734
735impl From<String> for Message {
736 fn from(text: String) -> Self {
737 Message::User {
738 content: OneOrMany::one(UserContent::Text(text.into())),
739 }
740 }
741}
742
743impl From<&str> for Message {
744 fn from(text: &str) -> Self {
745 Message::User {
746 content: OneOrMany::one(UserContent::Text(text.into())),
747 }
748 }
749}
750
751impl From<&String> for Message {
752 fn from(text: &String) -> Self {
753 Message::User {
754 content: OneOrMany::one(UserContent::Text(text.into())),
755 }
756 }
757}
758
759impl From<Text> for Message {
760 fn from(text: Text) -> Self {
761 Message::User {
762 content: OneOrMany::one(UserContent::Text(text)),
763 }
764 }
765}
766
767impl From<Image> for Message {
768 fn from(image: Image) -> Self {
769 Message::User {
770 content: OneOrMany::one(UserContent::Image(image)),
771 }
772 }
773}
774
775impl From<Audio> for Message {
776 fn from(audio: Audio) -> Self {
777 Message::User {
778 content: OneOrMany::one(UserContent::Audio(audio)),
779 }
780 }
781}
782
783impl From<Document> for Message {
784 fn from(document: Document) -> Self {
785 Message::User {
786 content: OneOrMany::one(UserContent::Document(document)),
787 }
788 }
789}
790
791impl From<String> for ToolResultContent {
792 fn from(text: String) -> Self {
793 ToolResultContent::text(text)
794 }
795}
796
797impl From<String> for AssistantContent {
798 fn from(text: String) -> Self {
799 AssistantContent::text(text)
800 }
801}
802
803impl From<String> for UserContent {
804 fn from(text: String) -> Self {
805 UserContent::text(text)
806 }
807}
808
809impl From<AssistantContent> for Message {
810 fn from(content: AssistantContent) -> Self {
811 Message::Assistant {
812 id: None,
813 content: OneOrMany::one(content),
814 }
815 }
816}
817
818impl From<UserContent> for Message {
819 fn from(content: UserContent) -> Self {
820 Message::User {
821 content: OneOrMany::one(content),
822 }
823 }
824}
825
826impl From<OneOrMany<AssistantContent>> for Message {
827 fn from(content: OneOrMany<AssistantContent>) -> Self {
828 Message::Assistant { id: None, content }
829 }
830}
831
832impl From<OneOrMany<UserContent>> for Message {
833 fn from(content: OneOrMany<UserContent>) -> Self {
834 Message::User { content }
835 }
836}
837
838impl From<ToolCall> for Message {
839 fn from(tool_call: ToolCall) -> Self {
840 Message::Assistant {
841 id: None,
842 content: OneOrMany::one(AssistantContent::ToolCall(tool_call)),
843 }
844 }
845}
846
847impl From<ToolResult> for Message {
848 fn from(tool_result: ToolResult) -> Self {
849 Message::User {
850 content: OneOrMany::one(UserContent::ToolResult(tool_result)),
851 }
852 }
853}
854
855impl From<ToolResultContent> for Message {
856 fn from(tool_result_content: ToolResultContent) -> Self {
857 Message::User {
858 content: OneOrMany::one(UserContent::ToolResult(ToolResult {
859 id: String::new(),
860 call_id: None,
861 content: OneOrMany::one(tool_result_content),
862 })),
863 }
864 }
865}
866
867#[derive(Debug, Error)]
873pub enum MessageError {
874 #[error("Message conversion error: {0}")]
875 ConversionError(String),
876}
877
878impl From<MessageError> for CompletionError {
879 fn from(error: MessageError) -> Self {
880 CompletionError::RequestError(error.into())
881 }
882}