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: String,
162 #[serde(skip_serializing_if = "Option::is_none")]
163 pub format: Option<ContentFormat>,
164 #[serde(skip_serializing_if = "Option::is_none")]
165 pub media_type: Option<ImageMediaType>,
166 #[serde(skip_serializing_if = "Option::is_none")]
167 pub detail: Option<ImageDetail>,
168 #[serde(flatten, skip_serializing_if = "Option::is_none")]
169 pub additional_params: Option<serde_json::Value>,
170}
171
172#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
174pub struct Audio {
175 pub data: String,
176 #[serde(skip_serializing_if = "Option::is_none")]
177 pub format: Option<ContentFormat>,
178 #[serde(skip_serializing_if = "Option::is_none")]
179 pub media_type: Option<AudioMediaType>,
180 #[serde(flatten, skip_serializing_if = "Option::is_none")]
181 pub additional_params: Option<serde_json::Value>,
182}
183
184#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
186pub struct Video {
187 pub data: String,
188 #[serde(skip_serializing_if = "Option::is_none")]
189 pub format: Option<ContentFormat>,
190 #[serde(skip_serializing_if = "Option::is_none")]
191 pub media_type: Option<VideoMediaType>,
192 #[serde(flatten, skip_serializing_if = "Option::is_none")]
193 pub additional_params: Option<serde_json::Value>,
194}
195
196#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
198pub struct Document {
199 pub data: String,
200 #[serde(skip_serializing_if = "Option::is_none")]
201 pub format: Option<ContentFormat>,
202 #[serde(skip_serializing_if = "Option::is_none")]
203 pub media_type: Option<DocumentMediaType>,
204 #[serde(flatten, skip_serializing_if = "Option::is_none")]
205 pub additional_params: Option<serde_json::Value>,
206}
207
208#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
210#[serde(rename_all = "lowercase")]
211pub enum ContentFormat {
212 #[default]
213 Base64,
214 String,
215}
216
217#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
219pub enum MediaType {
220 Image(ImageMediaType),
221 Audio(AudioMediaType),
222 Document(DocumentMediaType),
223 Video(VideoMediaType),
224}
225
226#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
229#[serde(rename_all = "lowercase")]
230pub enum ImageMediaType {
231 JPEG,
232 PNG,
233 GIF,
234 WEBP,
235 HEIC,
236 HEIF,
237 SVG,
238}
239
240#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
244#[serde(rename_all = "lowercase")]
245pub enum DocumentMediaType {
246 PDF,
247 TXT,
248 RTF,
249 HTML,
250 CSS,
251 MARKDOWN,
252 CSV,
253 XML,
254 Javascript,
255 Python,
256}
257
258#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
261#[serde(rename_all = "lowercase")]
262pub enum AudioMediaType {
263 WAV,
264 MP3,
265 AIFF,
266 AAC,
267 OGG,
268 FLAC,
269}
270
271#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
274#[serde(rename_all = "lowercase")]
275pub enum VideoMediaType {
276 AVI,
277 MP4,
278 MPEG,
279}
280
281#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
283#[serde(rename_all = "lowercase")]
284pub enum ImageDetail {
285 Low,
286 High,
287 #[default]
288 Auto,
289}
290
291impl Message {
296 pub(crate) fn rag_text(&self) -> Option<String> {
299 match self {
300 Message::User { content } => {
301 for item in content.iter() {
302 if let UserContent::Text(Text { text }) = item {
303 return Some(text.clone());
304 }
305 }
306 None
307 }
308 _ => None,
309 }
310 }
311
312 pub fn user(text: impl Into<String>) -> Self {
314 Message::User {
315 content: OneOrMany::one(UserContent::text(text)),
316 }
317 }
318
319 pub fn assistant(text: impl Into<String>) -> Self {
321 Message::Assistant {
322 id: None,
323 content: OneOrMany::one(AssistantContent::text(text)),
324 }
325 }
326
327 pub fn assistant_with_id(id: String, text: impl Into<String>) -> Self {
329 Message::Assistant {
330 id: Some(id),
331 content: OneOrMany::one(AssistantContent::text(text)),
332 }
333 }
334
335 pub fn tool_result(id: impl Into<String>, content: impl Into<String>) -> Self {
337 Message::User {
338 content: OneOrMany::one(UserContent::ToolResult(ToolResult {
339 id: id.into(),
340 call_id: None,
341 content: OneOrMany::one(ToolResultContent::text(content)),
342 })),
343 }
344 }
345
346 pub fn tool_result_with_call_id(
347 id: impl Into<String>,
348 call_id: Option<String>,
349 content: impl Into<String>,
350 ) -> Self {
351 Message::User {
352 content: OneOrMany::one(UserContent::ToolResult(ToolResult {
353 id: id.into(),
354 call_id,
355 content: OneOrMany::one(ToolResultContent::text(content)),
356 })),
357 }
358 }
359}
360
361impl UserContent {
362 pub fn text(text: impl Into<String>) -> Self {
364 UserContent::Text(text.into().into())
365 }
366
367 pub fn image(
369 data: impl Into<String>,
370 format: Option<ContentFormat>,
371 media_type: Option<ImageMediaType>,
372 detail: Option<ImageDetail>,
373 ) -> Self {
374 UserContent::Image(Image {
375 data: data.into(),
376 format,
377 media_type,
378 detail,
379 additional_params: None,
380 })
381 }
382
383 pub fn audio(
385 data: impl Into<String>,
386 format: Option<ContentFormat>,
387 media_type: Option<AudioMediaType>,
388 ) -> Self {
389 UserContent::Audio(Audio {
390 data: data.into(),
391 format,
392 media_type,
393 additional_params: None,
394 })
395 }
396
397 pub fn document(
399 data: impl Into<String>,
400 format: Option<ContentFormat>,
401 media_type: Option<DocumentMediaType>,
402 ) -> Self {
403 UserContent::Document(Document {
404 data: data.into(),
405 format,
406 media_type,
407 additional_params: None,
408 })
409 }
410
411 pub fn tool_result(id: impl Into<String>, content: OneOrMany<ToolResultContent>) -> Self {
413 UserContent::ToolResult(ToolResult {
414 id: id.into(),
415 call_id: None,
416 content,
417 })
418 }
419
420 pub fn tool_result_with_call_id(
422 id: impl Into<String>,
423 call_id: String,
424 content: OneOrMany<ToolResultContent>,
425 ) -> Self {
426 UserContent::ToolResult(ToolResult {
427 id: id.into(),
428 call_id: Some(call_id),
429 content,
430 })
431 }
432}
433
434impl AssistantContent {
435 pub fn text(text: impl Into<String>) -> Self {
437 AssistantContent::Text(text.into().into())
438 }
439
440 pub fn tool_call(
442 id: impl Into<String>,
443 name: impl Into<String>,
444 arguments: serde_json::Value,
445 ) -> Self {
446 AssistantContent::ToolCall(ToolCall {
447 id: id.into(),
448 call_id: None,
449 function: ToolFunction {
450 name: name.into(),
451 arguments,
452 },
453 })
454 }
455
456 pub fn tool_call_with_call_id(
457 id: impl Into<String>,
458 call_id: String,
459 name: impl Into<String>,
460 arguments: serde_json::Value,
461 ) -> Self {
462 AssistantContent::ToolCall(ToolCall {
463 id: id.into(),
464 call_id: Some(call_id),
465 function: ToolFunction {
466 name: name.into(),
467 arguments,
468 },
469 })
470 }
471}
472
473impl ToolResultContent {
474 pub fn text(text: impl Into<String>) -> Self {
476 ToolResultContent::Text(text.into().into())
477 }
478
479 pub fn image(
481 data: impl Into<String>,
482 format: Option<ContentFormat>,
483 media_type: Option<ImageMediaType>,
484 detail: Option<ImageDetail>,
485 ) -> Self {
486 ToolResultContent::Image(Image {
487 data: data.into(),
488 format,
489 media_type,
490 detail,
491 additional_params: None,
492 })
493 }
494}
495
496pub trait MimeType {
498 fn from_mime_type(mime_type: &str) -> Option<Self>
499 where
500 Self: Sized;
501 fn to_mime_type(&self) -> &'static str;
502}
503
504impl MimeType for MediaType {
505 fn from_mime_type(mime_type: &str) -> Option<Self> {
506 ImageMediaType::from_mime_type(mime_type)
507 .map(MediaType::Image)
508 .or_else(|| {
509 DocumentMediaType::from_mime_type(mime_type)
510 .map(MediaType::Document)
511 .or_else(|| AudioMediaType::from_mime_type(mime_type).map(MediaType::Audio))
512 })
513 }
514
515 fn to_mime_type(&self) -> &'static str {
516 match self {
517 MediaType::Image(media_type) => media_type.to_mime_type(),
518 MediaType::Audio(media_type) => media_type.to_mime_type(),
519 MediaType::Document(media_type) => media_type.to_mime_type(),
520 MediaType::Video(media_type) => media_type.to_mime_type(),
521 }
522 }
523}
524
525impl MimeType for ImageMediaType {
526 fn from_mime_type(mime_type: &str) -> Option<Self> {
527 match mime_type {
528 "image/jpeg" => Some(ImageMediaType::JPEG),
529 "image/png" => Some(ImageMediaType::PNG),
530 "image/gif" => Some(ImageMediaType::GIF),
531 "image/webp" => Some(ImageMediaType::WEBP),
532 "image/heic" => Some(ImageMediaType::HEIC),
533 "image/heif" => Some(ImageMediaType::HEIF),
534 "image/svg+xml" => Some(ImageMediaType::SVG),
535 _ => None,
536 }
537 }
538
539 fn to_mime_type(&self) -> &'static str {
540 match self {
541 ImageMediaType::JPEG => "image/jpeg",
542 ImageMediaType::PNG => "image/png",
543 ImageMediaType::GIF => "image/gif",
544 ImageMediaType::WEBP => "image/webp",
545 ImageMediaType::HEIC => "image/heic",
546 ImageMediaType::HEIF => "image/heif",
547 ImageMediaType::SVG => "image/svg+xml",
548 }
549 }
550}
551
552impl MimeType for DocumentMediaType {
553 fn from_mime_type(mime_type: &str) -> Option<Self> {
554 match mime_type {
555 "application/pdf" => Some(DocumentMediaType::PDF),
556 "text/plain" => Some(DocumentMediaType::TXT),
557 "text/rtf" => Some(DocumentMediaType::RTF),
558 "text/html" => Some(DocumentMediaType::HTML),
559 "text/css" => Some(DocumentMediaType::CSS),
560 "text/md" | "text/markdown" => Some(DocumentMediaType::MARKDOWN),
561 "text/csv" => Some(DocumentMediaType::CSV),
562 "text/xml" => Some(DocumentMediaType::XML),
563 "application/x-javascript" | "text/x-javascript" => Some(DocumentMediaType::Javascript),
564 "application/x-python" | "text/x-python" => Some(DocumentMediaType::Python),
565 _ => None,
566 }
567 }
568
569 fn to_mime_type(&self) -> &'static str {
570 match self {
571 DocumentMediaType::PDF => "application/pdf",
572 DocumentMediaType::TXT => "text/plain",
573 DocumentMediaType::RTF => "text/rtf",
574 DocumentMediaType::HTML => "text/html",
575 DocumentMediaType::CSS => "text/css",
576 DocumentMediaType::MARKDOWN => "text/markdown",
577 DocumentMediaType::CSV => "text/csv",
578 DocumentMediaType::XML => "text/xml",
579 DocumentMediaType::Javascript => "application/x-javascript",
580 DocumentMediaType::Python => "application/x-python",
581 }
582 }
583}
584
585impl MimeType for AudioMediaType {
586 fn from_mime_type(mime_type: &str) -> Option<Self> {
587 match mime_type {
588 "audio/wav" => Some(AudioMediaType::WAV),
589 "audio/mp3" => Some(AudioMediaType::MP3),
590 "audio/aiff" => Some(AudioMediaType::AIFF),
591 "audio/aac" => Some(AudioMediaType::AAC),
592 "audio/ogg" => Some(AudioMediaType::OGG),
593 "audio/flac" => Some(AudioMediaType::FLAC),
594 _ => None,
595 }
596 }
597
598 fn to_mime_type(&self) -> &'static str {
599 match self {
600 AudioMediaType::WAV => "audio/wav",
601 AudioMediaType::MP3 => "audio/mp3",
602 AudioMediaType::AIFF => "audio/aiff",
603 AudioMediaType::AAC => "audio/aac",
604 AudioMediaType::OGG => "audio/ogg",
605 AudioMediaType::FLAC => "audio/flac",
606 }
607 }
608}
609
610impl MimeType for VideoMediaType {
611 fn from_mime_type(mime_type: &str) -> Option<Self>
612 where
613 Self: Sized,
614 {
615 match mime_type {
616 "video/avi" => Some(VideoMediaType::AVI),
617 "video/mp4" => Some(VideoMediaType::MP4),
618 "video/mpeg" => Some(VideoMediaType::MPEG),
619 &_ => None,
620 }
621 }
622
623 fn to_mime_type(&self) -> &'static str {
624 match self {
625 VideoMediaType::AVI => "video/avi",
626 VideoMediaType::MP4 => "video/mp4",
627 VideoMediaType::MPEG => "video/mpeg",
628 }
629 }
630}
631
632impl std::str::FromStr for ImageDetail {
633 type Err = ();
634
635 fn from_str(s: &str) -> Result<Self, Self::Err> {
636 match s.to_lowercase().as_str() {
637 "low" => Ok(ImageDetail::Low),
638 "high" => Ok(ImageDetail::High),
639 "auto" => Ok(ImageDetail::Auto),
640 _ => Err(()),
641 }
642 }
643}
644
645impl From<String> for Text {
650 fn from(text: String) -> Self {
651 Text { text }
652 }
653}
654
655impl From<&String> for Text {
656 fn from(text: &String) -> Self {
657 text.to_owned().into()
658 }
659}
660
661impl From<&str> for Text {
662 fn from(text: &str) -> Self {
663 text.to_owned().into()
664 }
665}
666
667impl FromStr for Text {
668 type Err = Infallible;
669
670 fn from_str(s: &str) -> Result<Self, Self::Err> {
671 Ok(s.into())
672 }
673}
674
675impl From<String> for Message {
676 fn from(text: String) -> Self {
677 Message::User {
678 content: OneOrMany::one(UserContent::Text(text.into())),
679 }
680 }
681}
682
683impl From<&str> for Message {
684 fn from(text: &str) -> Self {
685 Message::User {
686 content: OneOrMany::one(UserContent::Text(text.into())),
687 }
688 }
689}
690
691impl From<&String> for Message {
692 fn from(text: &String) -> Self {
693 Message::User {
694 content: OneOrMany::one(UserContent::Text(text.into())),
695 }
696 }
697}
698
699impl From<Text> for Message {
700 fn from(text: Text) -> Self {
701 Message::User {
702 content: OneOrMany::one(UserContent::Text(text)),
703 }
704 }
705}
706
707impl From<Image> for Message {
708 fn from(image: Image) -> Self {
709 Message::User {
710 content: OneOrMany::one(UserContent::Image(image)),
711 }
712 }
713}
714
715impl From<Audio> for Message {
716 fn from(audio: Audio) -> Self {
717 Message::User {
718 content: OneOrMany::one(UserContent::Audio(audio)),
719 }
720 }
721}
722
723impl From<Document> for Message {
724 fn from(document: Document) -> Self {
725 Message::User {
726 content: OneOrMany::one(UserContent::Document(document)),
727 }
728 }
729}
730
731impl From<String> for ToolResultContent {
732 fn from(text: String) -> Self {
733 ToolResultContent::text(text)
734 }
735}
736
737impl From<String> for AssistantContent {
738 fn from(text: String) -> Self {
739 AssistantContent::text(text)
740 }
741}
742
743impl From<String> for UserContent {
744 fn from(text: String) -> Self {
745 UserContent::text(text)
746 }
747}
748
749impl From<AssistantContent> for Message {
750 fn from(content: AssistantContent) -> Self {
751 Message::Assistant {
752 id: None,
753 content: OneOrMany::one(content),
754 }
755 }
756}
757
758impl From<UserContent> for Message {
759 fn from(content: UserContent) -> Self {
760 Message::User {
761 content: OneOrMany::one(content),
762 }
763 }
764}
765
766impl From<OneOrMany<AssistantContent>> for Message {
767 fn from(content: OneOrMany<AssistantContent>) -> Self {
768 Message::Assistant { id: None, content }
769 }
770}
771
772impl From<OneOrMany<UserContent>> for Message {
773 fn from(content: OneOrMany<UserContent>) -> Self {
774 Message::User { content }
775 }
776}
777
778impl From<ToolCall> for Message {
779 fn from(tool_call: ToolCall) -> Self {
780 Message::Assistant {
781 id: None,
782 content: OneOrMany::one(AssistantContent::ToolCall(tool_call)),
783 }
784 }
785}
786
787impl From<ToolResult> for Message {
788 fn from(tool_result: ToolResult) -> Self {
789 Message::User {
790 content: OneOrMany::one(UserContent::ToolResult(tool_result)),
791 }
792 }
793}
794
795impl From<ToolResultContent> for Message {
796 fn from(tool_result_content: ToolResultContent) -> Self {
797 Message::User {
798 content: OneOrMany::one(UserContent::ToolResult(ToolResult {
799 id: String::new(),
800 call_id: None,
801 content: OneOrMany::one(tool_result_content),
802 })),
803 }
804 }
805}
806
807#[derive(Debug, Error)]
813pub enum MessageError {
814 #[error("Message conversion error: {0}")]
815 ConversionError(String),
816}
817
818impl From<MessageError> for CompletionError {
819 fn from(error: MessageError) -> Self {
820 CompletionError::RequestError(error.into())
821 }
822}