1use std::pin::Pin;
9
10use derive_builder::Builder;
11use futures::Stream;
12use serde::{Deserialize, Serialize};
13use url::Url;
14use uuid::Uuid;
15
16use crate::error::OpenAIError;
17
18pub use async_openai::types::chat::{
25 ChatChoiceLogprobs,
26 ChatCompletionAudio,
27 ChatCompletionAudioFormat,
28 ChatCompletionAudioVoice,
29 ChatCompletionFunctionCall,
30 ChatCompletionFunctions,
31 ChatCompletionFunctionsArgs,
32 ChatCompletionRequestAssistantMessageAudio,
33 ChatCompletionRequestAssistantMessageContent,
34 ChatCompletionRequestAssistantMessageContentPart,
35 ChatCompletionRequestDeveloperMessage,
36 ChatCompletionRequestDeveloperMessageArgs,
37 ChatCompletionRequestDeveloperMessageContent,
38 ChatCompletionRequestFunctionMessage,
39 ChatCompletionRequestFunctionMessageArgs,
40 ChatCompletionRequestMessageContentPartAudio,
41 ChatCompletionRequestMessageContentPartRefusal,
42 ChatCompletionRequestMessageContentPartText,
43 ChatCompletionRequestSystemMessage,
44 ChatCompletionRequestSystemMessageArgs,
46 ChatCompletionRequestSystemMessageContent,
47 ChatCompletionRequestSystemMessageContentPart,
48 ChatCompletionResponseMessageAudio,
49 ChatCompletionTokenLogprob,
50 Choice,
51 CompletionFinishReason,
52 CompletionTokensDetails,
53 CompletionUsage,
54 FunctionObject,
55 FunctionObjectArgs,
56 ImageDetail,
57 InputAudio,
58 InputAudioFormat,
59 Logprobs,
60 PredictionContent,
61 PredictionContentContent,
62 Prompt,
63 PromptTokensDetails,
64 ResponseFormat,
65 ResponseFormatJsonSchema,
66 Role,
67 ServiceTier,
68 TopLogprobs,
69 WebSearchContextSize,
70 WebSearchLocation,
71 WebSearchOptions,
72 WebSearchUserLocation,
73 WebSearchUserLocationType,
74};
75
76#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
84#[serde(untagged)]
85pub enum Stop {
86 String(String),
87 StringArray(Vec<String>),
88 TokenIdArray(Vec<u32>),
89}
90
91impl Stop {
92 pub fn strings(&self) -> Option<Vec<String>> {
93 match self {
94 Stop::String(s) => Some(vec![s.clone()]),
95 Stop::StringArray(arr) => Some(arr.clone()),
96 Stop::TokenIdArray(_) => None,
97 }
98 }
99
100 pub fn token_ids(&self) -> Option<Vec<u32>> {
101 match self {
102 Stop::TokenIdArray(arr) => Some(arr.clone()),
103 Stop::String(_) | Stop::StringArray(_) => None,
104 }
105 }
106}
107
108impl From<String> for Stop {
109 fn from(value: String) -> Self {
110 Stop::String(value)
111 }
112}
113
114impl From<&str> for Stop {
115 fn from(value: &str) -> Self {
116 Stop::String(value.to_string())
117 }
118}
119
120impl From<Vec<String>> for Stop {
121 fn from(value: Vec<String>) -> Self {
122 Stop::StringArray(value)
123 }
124}
125
126impl From<Vec<u32>> for Stop {
127 fn from(value: Vec<u32>) -> Self {
128 Stop::TokenIdArray(value)
129 }
130}
131
132impl From<async_openai::types::chat::StopConfiguration> for Stop {
133 fn from(value: async_openai::types::chat::StopConfiguration) -> Self {
134 match value {
135 async_openai::types::chat::StopConfiguration::String(value) => Stop::String(value),
136 async_openai::types::chat::StopConfiguration::StringArray(value) => {
137 Stop::StringArray(value)
138 }
139 }
140 }
141}
142
143pub use async_openai::types::chat::FinishReason;
145
146pub use async_openai::types::chat::FunctionType;
149
150#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
156#[serde(rename_all = "lowercase")]
157pub enum ReasoningEffort {
158 None,
159 Minimal,
160 Low,
161 Medium,
162 High,
163 Xhigh,
164 Max,
165}
166
167impl From<async_openai::types::chat::ReasoningEffort> for ReasoningEffort {
168 fn from(value: async_openai::types::chat::ReasoningEffort) -> Self {
169 match value {
170 async_openai::types::chat::ReasoningEffort::None => ReasoningEffort::None,
171 async_openai::types::chat::ReasoningEffort::Minimal => ReasoningEffort::Minimal,
172 async_openai::types::chat::ReasoningEffort::Low => ReasoningEffort::Low,
173 async_openai::types::chat::ReasoningEffort::Medium => ReasoningEffort::Medium,
174 async_openai::types::chat::ReasoningEffort::High => ReasoningEffort::High,
175 async_openai::types::chat::ReasoningEffort::Xhigh => ReasoningEffort::Xhigh,
176 }
177 }
178}
179
180fn deserialize_arguments<'de, D>(deserializer: D) -> Result<String, D::Error>
189where
190 D: serde::Deserializer<'de>,
191{
192 use serde::de::Error;
193 let value = serde_json::Value::deserialize(deserializer)?;
194 match value {
195 serde_json::Value::String(s) => Ok(s),
196 v @ serde_json::Value::Object(_) => {
197 Ok(serde_json::to_string(&v).unwrap())
199 }
200 other => Err(D::Error::custom(format!(
201 "expected string or object for `arguments`, got {other}"
202 ))),
203 }
204}
205
206fn deserialize_arguments_opt<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
207where
208 D: serde::Deserializer<'de>,
209{
210 use serde::de::Error;
211 let value = Option::<serde_json::Value>::deserialize(deserializer)?;
212 match value {
213 None => Ok(None),
214 Some(serde_json::Value::String(s)) => Ok(Some(s)),
215 Some(v @ serde_json::Value::Object(_)) => serde_json::to_string(&v)
216 .map(Some)
217 .map_err(|e| D::Error::custom(e.to_string())),
218 Some(other) => Err(D::Error::custom(format!(
219 "expected string or object for `arguments`, got {other}"
220 ))),
221 }
222}
223
224fn deserialize_optional_media<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
230where
231 D: serde::Deserializer<'de>,
232 T: serde::de::DeserializeOwned,
233{
234 use serde::de::Error;
235 match Option::<serde_json::Value>::deserialize(deserializer)? {
236 None => Ok(None),
237 Some(value) if value.get("url").and_then(serde_json::Value::as_str) == Some("") => Ok(None),
238 Some(value) => serde_json::from_value(value)
239 .map(Some)
240 .map_err(D::Error::custom),
241 }
242}
243
244#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)]
257pub struct FunctionCall {
258 pub name: String,
259 #[serde(deserialize_with = "deserialize_arguments")]
260 pub arguments: String,
261}
262
263#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)]
265pub struct FunctionCallStream {
266 pub name: Option<String>,
267 #[serde(default, deserialize_with = "deserialize_arguments_opt")]
268 pub arguments: Option<String>,
269}
270
271#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)]
277pub struct ChatCompletionMessageToolCallChunk {
278 pub index: u32,
279 pub id: Option<String>,
280 pub r#type: Option<FunctionType>,
281 pub function: Option<FunctionCallStream>,
282}
283
284#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
297#[builder(name = "ChatCompletionRequestMessageContentPartImageArgs")]
298#[builder(pattern = "mutable")]
299#[builder(setter(into, strip_option))]
300#[builder(derive(Debug))]
301#[builder(build_fn(error = "OpenAIError"))]
302pub struct ChatCompletionRequestMessageContentPartImage {
303 #[builder(default)]
304 #[serde(default, deserialize_with = "deserialize_optional_media")]
305 pub image_url: Option<ImageUrl>,
306 #[builder(default)]
307 #[serde(skip_serializing_if = "Option::is_none")]
308 pub uuid: Option<String>,
310}
311
312#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
317#[builder(name = "ImageUrlArgs")]
318#[builder(pattern = "mutable")]
319#[builder(setter(into, strip_option))]
320#[builder(derive(Debug))]
321#[builder(build_fn(error = "OpenAIError"))]
322pub struct ImageUrl {
323 pub url: Url,
324 pub detail: Option<ImageDetail>,
325 #[deprecated(note = "use the content-part `uuid` field for vLLM cache identities")]
326 #[serde(skip_serializing_if = "Option::is_none")]
327 pub uuid: Option<Uuid>,
328}
329
330#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
336#[serde(tag = "type")]
337#[serde(rename_all = "snake_case")]
338pub enum ChatCompletionRequestToolMessageContentPart {
339 Text(ChatCompletionRequestMessageContentPartText),
340 ImageUrl(ChatCompletionRequestMessageContentPartImage),
341 VideoUrl(ChatCompletionRequestMessageContentPartVideo),
342 AudioUrl(ChatCompletionRequestMessageContentPartAudioUrl),
343}
344
345#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
347#[serde(untagged)]
348pub enum ChatCompletionRequestToolMessageContent {
349 Text(String),
350 Array(Vec<ChatCompletionRequestToolMessageContentPart>),
351}
352
353impl Default for ChatCompletionRequestToolMessageContent {
354 fn default() -> Self {
355 Self::Text(String::new())
356 }
357}
358
359impl From<&str> for ChatCompletionRequestToolMessageContent {
360 fn from(value: &str) -> Self {
361 Self::Text(value.into())
362 }
363}
364
365impl From<String> for ChatCompletionRequestToolMessageContent {
366 fn from(value: String) -> Self {
367 Self::Text(value)
368 }
369}
370
371#[derive(Debug, Serialize, Deserialize, Default, Clone, Builder, PartialEq)]
373#[builder(name = "ChatCompletionRequestToolMessageArgs")]
374#[builder(pattern = "mutable")]
375#[builder(setter(into, strip_option), default)]
376#[builder(derive(Debug))]
377#[builder(build_fn(error = "OpenAIError"))]
378pub struct ChatCompletionRequestToolMessage {
379 pub content: ChatCompletionRequestToolMessageContent,
380 pub tool_call_id: String,
381}
382
383#[derive(Clone, Serialize, Default, Debug, Deserialize, PartialEq)]
384#[serde(rename_all = "lowercase")]
385pub enum ChatCompletionToolType {
386 #[default]
387 Function,
388}
389
390#[derive(Clone, Serialize, Default, Debug, Deserialize, PartialEq)]
391pub struct FunctionName {
392 pub name: String,
393}
394
395#[derive(Clone, Serialize, Default, Debug, Deserialize, PartialEq)]
396pub struct ChatCompletionNamedToolChoice {
397 pub r#type: ChatCompletionToolType,
398 pub function: FunctionName,
399}
400
401fn default_function_type() -> FunctionType {
402 FunctionType::Function
403}
404
405#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
411pub struct ChatCompletionMessageToolCall {
412 pub id: String,
413 #[serde(default = "default_function_type")]
414 pub r#type: FunctionType,
415 pub function: FunctionCall,
416}
417
418#[derive(Clone, Serialize, Default, Debug, Deserialize, PartialEq)]
420#[serde(rename_all = "lowercase")]
421pub enum ChatCompletionToolChoiceOption {
422 #[default]
423 None,
424 Auto,
425 Required,
426 #[serde(untagged)]
427 Named(ChatCompletionNamedToolChoice),
428}
429
430#[derive(Clone, Serialize, Default, Debug, Builder, Deserialize, PartialEq)]
431#[builder(name = "ChatCompletionToolArgs")]
432#[builder(pattern = "mutable")]
433#[builder(setter(into, strip_option), default)]
434#[builder(derive(Debug))]
435#[builder(build_fn(error = "OpenAIError"))]
436pub struct ChatCompletionTool {
437 #[builder(default = "ChatCompletionToolType::Function")]
438 pub r#type: ChatCompletionToolType,
439 pub function: FunctionObject,
440}
441
442#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
453#[serde(untagged)]
454pub enum StopReason {
455 String(String),
456 Int(i64),
457 IntArray(Vec<i64>),
458}
459
460#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
470#[serde(untagged)]
471pub enum ReasoningContent {
472 Text(String),
474 Segments(Vec<String>),
477}
478
479impl ReasoningContent {
480 pub fn to_flat_string(&self) -> String {
482 match self {
483 ReasoningContent::Text(s) => s.clone(),
484 ReasoningContent::Segments(segs) => segs
485 .iter()
486 .filter(|s| !s.is_empty())
487 .cloned()
488 .collect::<Vec<_>>()
489 .join("\n"),
490 }
491 }
492
493 pub fn segments(&self) -> Option<&[String]> {
495 match self {
496 ReasoningContent::Segments(segs) => Some(segs),
497 ReasoningContent::Text(_) => None,
498 }
499 }
500}
501
502#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
506pub struct ChatCompletionResponseContentPartText {
507 pub text: String,
508}
509
510#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
512pub struct ChatCompletionResponseContentPartImageUrl {
513 pub image_url: ImageUrlResponse,
514}
515
516#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
518pub struct ChatCompletionResponseContentPartVideoUrl {
519 pub video_url: VideoUrlResponse,
520}
521
522#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
524pub struct ChatCompletionResponseContentPartAudioUrl {
525 pub audio_url: AudioUrlResponse,
526}
527
528#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
529pub struct ImageUrlResponse {
530 pub url: String,
531 #[serde(skip_serializing_if = "Option::is_none")]
532 pub detail: Option<String>,
533}
534
535#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
536pub struct VideoUrlResponse {
537 pub url: String,
538}
539
540#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
541pub struct AudioUrlResponse {
542 pub url: String,
543}
544
545#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
547#[serde(tag = "type", rename_all = "snake_case")]
548pub enum ChatCompletionResponseContentPart {
549 Text(ChatCompletionResponseContentPartText),
550 ImageUrl(ChatCompletionResponseContentPartImageUrl),
551 VideoUrl(ChatCompletionResponseContentPartVideoUrl),
552 AudioUrl(ChatCompletionResponseContentPartAudioUrl),
553}
554
555#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
561#[serde(untagged)]
562pub enum ChatCompletionMessageContent {
563 Text(String),
565 Parts(Vec<ChatCompletionResponseContentPart>),
567}
568
569#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
572#[builder(name = "VideoUrlArgs")]
573#[builder(pattern = "mutable")]
574#[builder(setter(into, strip_option))]
575#[builder(derive(Debug))]
576#[builder(build_fn(error = "OpenAIError"))]
577pub struct VideoUrl {
578 pub url: Url,
579 pub detail: Option<ImageDetail>,
580 #[deprecated(note = "use the content-part `uuid` field for vLLM cache identities")]
581 #[serde(skip_serializing_if = "Option::is_none")]
582 pub uuid: Option<Uuid>,
583}
584
585#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
586#[builder(name = "ChatCompletionRequestMessageContentPartVideoArgs")]
587#[builder(pattern = "mutable")]
588#[builder(setter(into, strip_option))]
589#[builder(derive(Debug))]
590#[builder(build_fn(error = "OpenAIError"))]
591pub struct ChatCompletionRequestMessageContentPartVideo {
592 #[builder(default)]
593 #[serde(default, deserialize_with = "deserialize_optional_media")]
594 pub video_url: Option<VideoUrl>,
595 #[builder(default)]
596 #[serde(skip_serializing_if = "Option::is_none")]
597 pub uuid: Option<String>,
599}
600
601#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
602#[builder(name = "AudioUrlArgs")]
603#[builder(pattern = "mutable")]
604#[builder(setter(into, strip_option))]
605#[builder(derive(Debug))]
606#[builder(build_fn(error = "OpenAIError"))]
607pub struct AudioUrl {
608 pub url: Url,
609 #[deprecated(note = "use the content-part `uuid` field for vLLM cache identities")]
610 #[serde(skip_serializing_if = "Option::is_none")]
611 pub uuid: Option<Uuid>,
612}
613
614#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
615#[builder(name = "ChatCompletionRequestMessageContentPartAudioUrlArgs")]
616#[builder(pattern = "mutable")]
617#[builder(setter(into, strip_option))]
618#[builder(derive(Debug))]
619#[builder(build_fn(error = "OpenAIError"))]
620pub struct ChatCompletionRequestMessageContentPartAudioUrl {
621 #[builder(default)]
622 #[serde(default, deserialize_with = "deserialize_optional_media")]
623 pub audio_url: Option<AudioUrl>,
624 #[builder(default)]
625 #[serde(skip_serializing_if = "Option::is_none")]
626 pub uuid: Option<String>,
628}
629
630#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
634#[serde(untagged)]
635pub enum ChatCompletionRequestUserMessageContent {
636 Text(String),
637 Array(Vec<ChatCompletionRequestUserMessageContentPart>),
638}
639
640#[derive(Debug, Serialize, Deserialize, Default, Clone, Builder, PartialEq)]
641#[builder(name = "ChatCompletionRequestUserMessageArgs")]
642#[builder(pattern = "mutable")]
643#[builder(setter(into, strip_option), default)]
644#[builder(derive(Debug))]
645#[builder(build_fn(error = "OpenAIError"))]
646pub struct ChatCompletionRequestUserMessage {
647 pub content: ChatCompletionRequestUserMessageContent,
648 #[serde(skip_serializing_if = "Option::is_none")]
649 pub name: Option<String>,
650}
651
652impl Default for ChatCompletionRequestUserMessageContent {
653 fn default() -> Self {
654 Self::Text(String::new())
655 }
656}
657
658impl From<&str> for ChatCompletionRequestUserMessageContent {
659 fn from(value: &str) -> Self {
660 Self::Text(value.into())
661 }
662}
663
664impl From<String> for ChatCompletionRequestUserMessageContent {
665 fn from(value: String) -> Self {
666 Self::Text(value)
667 }
668}
669
670impl From<Vec<ChatCompletionRequestUserMessageContentPart>>
671 for ChatCompletionRequestUserMessageContent
672{
673 fn from(value: Vec<ChatCompletionRequestUserMessageContentPart>) -> Self {
674 Self::Array(value)
675 }
676}
677
678#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
684#[serde(tag = "type")]
685#[serde(rename_all = "snake_case")]
686pub enum ChatCompletionRequestUserMessageContentPart {
687 Text(ChatCompletionRequestMessageContentPartText),
688 ImageUrl(ChatCompletionRequestMessageContentPartImage),
689 VideoUrl(ChatCompletionRequestMessageContentPartVideo),
690 AudioUrl(ChatCompletionRequestMessageContentPartAudioUrl),
691 InputAudio(ChatCompletionRequestMessageContentPartAudio),
692}
693
694#[derive(Debug, Serialize, Deserialize, Default, Clone, Builder, PartialEq)]
700#[builder(name = "ChatCompletionRequestAssistantMessageArgs")]
701#[builder(pattern = "mutable")]
702#[builder(setter(into, strip_option), default)]
703#[builder(derive(Debug))]
704#[builder(build_fn(error = "OpenAIError"))]
705pub struct ChatCompletionRequestAssistantMessage {
706 #[serde(skip_serializing_if = "Option::is_none")]
707 pub content: Option<ChatCompletionRequestAssistantMessageContent>,
708 #[serde(skip_serializing_if = "Option::is_none")]
710 pub reasoning_content: Option<ReasoningContent>,
711 #[serde(skip_serializing_if = "Option::is_none")]
712 pub refusal: Option<String>,
713 #[serde(skip_serializing_if = "Option::is_none")]
714 pub name: Option<String>,
715 #[serde(skip_serializing_if = "Option::is_none")]
716 pub audio: Option<ChatCompletionRequestAssistantMessageAudio>,
717 #[serde(skip_serializing_if = "Option::is_none")]
718 pub tool_calls: Option<Vec<ChatCompletionMessageToolCall>>,
719 #[deprecated]
720 #[serde(skip_serializing_if = "Option::is_none")]
721 pub function_call: Option<FunctionCall>,
722}
723
724#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
730#[serde(tag = "role")]
731#[serde(rename_all = "lowercase")]
732pub enum ChatCompletionRequestMessage {
733 Developer(ChatCompletionRequestDeveloperMessage),
734 System(ChatCompletionRequestSystemMessage),
735 User(ChatCompletionRequestUserMessage),
736 Assistant(ChatCompletionRequestAssistantMessage),
737 Tool(ChatCompletionRequestToolMessage),
738 Function(ChatCompletionRequestFunctionMessage),
739}
740
741pub type ServiceTierResponse = ServiceTier;
743
744#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
750pub struct ChatCompletionResponseMessage {
751 pub content: Option<ChatCompletionMessageContent>,
755 #[serde(skip_serializing_if = "Option::is_none")]
756 pub refusal: Option<String>,
757 #[serde(skip_serializing_if = "Option::is_none")]
758 pub tool_calls: Option<Vec<ChatCompletionMessageToolCall>>,
759 pub role: Role,
760 #[serde(skip_serializing_if = "Option::is_none")]
761 #[deprecated]
762 pub function_call: Option<FunctionCall>,
763 #[serde(skip_serializing_if = "Option::is_none")]
764 pub audio: Option<ChatCompletionResponseMessageAudio>,
765 pub reasoning_content: Option<String>,
767}
768
769#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
774pub struct ChatCompletionStreamOptions {
775 pub include_usage: bool,
776 #[serde(default)]
779 pub continuous_usage_stats: bool,
780}
781
782#[derive(Clone, Serialize, Default, Debug, Builder, Deserialize, PartialEq)]
789#[builder(name = "CreateChatCompletionRequestArgs")]
790#[builder(pattern = "mutable")]
791#[builder(setter(into, strip_option), default)]
792#[builder(derive(Debug))]
793#[builder(build_fn(error = "OpenAIError"))]
794pub struct CreateChatCompletionRequest {
795 pub messages: Vec<ChatCompletionRequestMessage>,
796 pub model: String,
797 #[serde(skip_serializing_if = "Option::is_none")]
799 pub mm_processor_kwargs: Option<serde_json::Value>,
800 #[serde(skip_serializing_if = "Option::is_none")]
801 pub store: Option<bool>,
802 #[serde(skip_serializing_if = "Option::is_none")]
803 pub reasoning_effort: Option<ReasoningEffort>,
804 #[serde(skip_serializing_if = "Option::is_none")]
805 pub metadata: Option<serde_json::Value>,
806 #[serde(skip_serializing_if = "Option::is_none")]
807 pub frequency_penalty: Option<f32>,
808 #[serde(skip_serializing_if = "Option::is_none")]
809 pub logit_bias: Option<std::collections::HashMap<String, serde_json::Value>>,
810 #[serde(skip_serializing_if = "Option::is_none")]
811 pub logprobs: Option<bool>,
812 #[serde(skip_serializing_if = "Option::is_none")]
813 pub top_logprobs: Option<u8>,
814 #[deprecated]
815 #[serde(skip_serializing_if = "Option::is_none")]
816 pub max_tokens: Option<u32>,
817 #[serde(skip_serializing_if = "Option::is_none")]
818 pub max_completion_tokens: Option<u32>,
819 #[serde(skip_serializing_if = "Option::is_none")]
820 pub n: Option<u8>,
821 #[serde(skip_serializing_if = "Option::is_none")]
822 pub modalities: Option<Vec<async_openai::types::chat::ResponseModalities>>,
823 #[serde(skip_serializing_if = "Option::is_none")]
824 pub prediction: Option<PredictionContent>,
825 #[serde(skip_serializing_if = "Option::is_none")]
826 pub audio: Option<ChatCompletionAudio>,
827 #[serde(skip_serializing_if = "Option::is_none")]
828 pub presence_penalty: Option<f32>,
829 #[serde(skip_serializing_if = "Option::is_none")]
830 pub response_format: Option<ResponseFormat>,
831 #[serde(skip_serializing_if = "Option::is_none")]
832 pub seed: Option<i64>,
833 #[serde(skip_serializing_if = "Option::is_none")]
834 pub service_tier: Option<ServiceTier>,
835 #[serde(skip_serializing_if = "Option::is_none")]
836 pub stop: Option<Stop>,
837 #[serde(default, skip_serializing_if = "Option::is_none")]
838 pub stream: Option<bool>,
839 #[serde(skip_serializing_if = "Option::is_none")]
840 pub stream_options: Option<ChatCompletionStreamOptions>,
841 #[serde(skip_serializing_if = "Option::is_none")]
842 pub temperature: Option<f32>,
843 #[serde(skip_serializing_if = "Option::is_none")]
844 pub top_p: Option<f32>,
845 #[serde(skip_serializing_if = "Option::is_none")]
846 pub tools: Option<Vec<ChatCompletionTool>>,
847 #[serde(skip_serializing_if = "Option::is_none")]
848 pub tool_choice: Option<ChatCompletionToolChoiceOption>,
849 #[serde(skip_serializing_if = "Option::is_none")]
850 pub parallel_tool_calls: Option<bool>,
851 #[serde(skip_serializing_if = "Option::is_none")]
852 pub user: Option<String>,
853 #[deprecated]
854 #[serde(skip_serializing_if = "Option::is_none")]
855 pub function_call: Option<ChatCompletionFunctionCall>,
856 #[deprecated]
857 #[serde(skip_serializing_if = "Option::is_none")]
858 pub functions: Option<Vec<ChatCompletionFunctions>>,
859 #[serde(skip_serializing_if = "Option::is_none")]
860 pub web_search_options: Option<WebSearchOptions>,
861}
862
863#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
867pub struct ChatChoice {
868 pub index: u32,
869 pub message: ChatCompletionResponseMessage,
870 pub finish_reason: Option<FinishReason>,
871 pub logprobs: Option<ChatChoiceLogprobs>,
872}
873
874#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
876pub struct CreateChatCompletionResponse {
877 pub id: String,
878 pub choices: Vec<ChatChoice>,
879 pub created: u32,
880 pub model: String,
881 pub service_tier: Option<ServiceTierResponse>,
882 pub system_fingerprint: Option<String>,
883 pub object: String,
884 pub usage: Option<CompletionUsage>,
885}
886
887pub type ChatCompletionResponseStream =
888 Pin<Box<dyn Stream<Item = Result<CreateChatCompletionStreamResponse, OpenAIError>> + Send>>;
889
890#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
896pub struct ChatCompletionStreamResponseDelta {
897 #[serde(skip_serializing_if = "Option::is_none")]
898 pub content: Option<ChatCompletionMessageContent>,
899 #[serde(skip_serializing_if = "Option::is_none")]
900 pub function_call: Option<ChatCompletionStreamResponseDeltaFunctionCall>,
901 #[serde(skip_serializing_if = "Option::is_none")]
902 pub tool_calls: Option<Vec<ChatCompletionMessageToolCallChunk>>,
903 #[serde(skip_serializing_if = "Option::is_none")]
904 pub role: Option<Role>,
905 #[serde(skip_serializing_if = "Option::is_none")]
906 pub refusal: Option<String>,
907 #[serde(skip_serializing_if = "Option::is_none")]
909 pub reasoning_content: Option<String>,
910}
911
912#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
913pub struct ChatCompletionStreamResponseDeltaFunctionCall {
914 pub name: Option<String>,
915 #[serde(default, deserialize_with = "deserialize_arguments_opt")]
916 pub arguments: Option<String>,
917}
918
919#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
921pub struct ChatChoiceStream {
922 pub index: u32,
923 pub delta: ChatCompletionStreamResponseDelta,
924 pub finish_reason: Option<FinishReason>,
925 pub logprobs: Option<ChatChoiceLogprobs>,
926}
927
928#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
930pub struct CreateChatCompletionStreamResponse {
931 pub id: String,
932 pub choices: Vec<ChatChoiceStream>,
933 pub created: u32,
934 pub model: String,
935 pub service_tier: Option<ServiceTierResponse>,
936 pub system_fingerprint: Option<String>,
937 pub object: String,
938 pub usage: Option<CompletionUsage>,
939}
940
941#[cfg(test)]
942mod tests {
943 use super::*;
944
945 #[test]
946 fn stop_accepts_token_id_array() {
947 let stop: Stop = serde_json::from_value(serde_json::json!([32, 34])).unwrap();
948
949 assert_eq!(stop, Stop::TokenIdArray(vec![32, 34]));
950 }
951
952 #[test]
953 fn stop_accepts_string_and_string_array() {
954 let stop: Stop = serde_json::from_value(serde_json::json!(" The")).unwrap();
955
956 assert_eq!(stop, Stop::String(" The".to_string()));
957
958 let stop: Stop = serde_json::from_value(serde_json::json!(["A", "B"])).unwrap();
959
960 assert_eq!(
961 stop,
962 Stop::StringArray(vec!["A".to_string(), "B".to_string()])
963 );
964 }
965
966 #[test]
967 fn stop_token_id_display_string_remains_string_stop() {
968 let stop: Stop = serde_json::from_value(serde_json::json!("token_id:576")).unwrap();
969
970 assert_eq!(stop, Stop::String("token_id:576".to_string()));
971
972 let stop: Stop = serde_json::from_value(serde_json::json!(["token_id:576"])).unwrap();
973
974 assert_eq!(stop, Stop::StringArray(vec!["token_id:576".to_string()]));
975 }
976
977 #[test]
978 fn stop_rejects_single_token_id() {
979 let result = serde_json::from_value::<Stop>(serde_json::json!(576));
980
981 assert!(result.is_err());
982 }
983
984 #[test]
985 fn stop_converts_from_upstream_stop_configuration() {
986 let upstream =
987 async_openai::types::chat::StopConfiguration::StringArray(vec!["END".to_string()]);
988
989 assert_eq!(
990 Stop::from(upstream),
991 Stop::StringArray(vec!["END".to_string()])
992 );
993 }
994
995 #[test]
996 fn request_builder_accepts_upstream_reasoning_effort() {
997 let request = CreateChatCompletionRequestArgs::default()
998 .reasoning_effort(async_openai::types::chat::ReasoningEffort::High)
999 .build()
1000 .unwrap();
1001
1002 assert_eq!(request.reasoning_effort, Some(ReasoningEffort::High));
1003 }
1004
1005 #[test]
1006 fn tool_call_defaults_type_on_deserialize() {
1007 let tool_call: ChatCompletionMessageToolCall = serde_json::from_value(serde_json::json!({
1008 "id": "call_123",
1009 "function": {
1010 "name": "get_weather",
1011 "arguments": "{\"location\":\"SF\"}"
1012 }
1013 }))
1014 .unwrap();
1015
1016 assert_eq!(tool_call.r#type, FunctionType::Function);
1017 }
1018
1019 #[test]
1020 fn tool_call_serializes_type_for_wire_compat() {
1021 let tool_call = ChatCompletionMessageToolCall {
1022 id: "call_123".into(),
1023 r#type: FunctionType::Function,
1024 function: FunctionCall {
1025 name: "get_weather".into(),
1026 arguments: "{\"location\":\"SF\"}".into(),
1027 },
1028 };
1029
1030 let json = serde_json::to_value(tool_call).unwrap();
1031 assert_eq!(json["type"], "function");
1032 }
1033
1034 #[test]
1037 fn function_call_accepts_string_arguments() {
1038 let fc: FunctionCall = serde_json::from_value(serde_json::json!({
1039 "name": "get_weather",
1040 "arguments": "{\"location\":\"SF\"}"
1041 }))
1042 .unwrap();
1043 assert_eq!(fc.arguments, "{\"location\":\"SF\"}");
1044 }
1045
1046 #[test]
1047 fn function_call_accepts_dict_arguments() {
1048 let fc: FunctionCall = serde_json::from_value(serde_json::json!({
1049 "name": "get_weather",
1050 "arguments": {"location": "SF"}
1051 }))
1052 .unwrap();
1053 assert_eq!(fc.arguments, "{\"location\":\"SF\"}");
1054 }
1055
1056 #[test]
1057 fn function_call_rejects_integer_arguments() {
1058 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
1059 "name": "f",
1060 "arguments": 42
1061 }));
1062 assert!(result.is_err());
1063 }
1064
1065 #[test]
1066 fn function_call_rejects_boolean_arguments() {
1067 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
1068 "name": "f",
1069 "arguments": true
1070 }));
1071 assert!(result.is_err());
1072 }
1073
1074 #[test]
1075 fn function_call_rejects_null_arguments() {
1076 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
1077 "name": "f",
1078 "arguments": null
1079 }));
1080 assert!(result.is_err());
1081 }
1082
1083 #[test]
1084 fn function_call_rejects_array_arguments() {
1085 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
1086 "name": "f",
1087 "arguments": [1, 2, 3]
1088 }));
1089 assert!(result.is_err());
1090 }
1091
1092 #[test]
1093 fn function_call_stream_null_arguments_produces_none() {
1094 let fcs: FunctionCallStream = serde_json::from_value(serde_json::json!({
1095 "name": "f",
1096 "arguments": null
1097 }))
1098 .unwrap();
1099 assert_eq!(fcs.arguments, None);
1100 }
1101
1102 #[test]
1103 fn function_call_stream_rejects_integer_arguments() {
1104 let result = serde_json::from_value::<FunctionCallStream>(serde_json::json!({
1105 "name": "f",
1106 "arguments": 42
1107 }));
1108 assert!(result.is_err());
1109 }
1110
1111 #[test]
1112 fn function_call_stream_rejects_boolean_arguments() {
1113 let result = serde_json::from_value::<FunctionCallStream>(serde_json::json!({
1114 "name": "f",
1115 "arguments": true
1116 }));
1117 assert!(result.is_err());
1118 }
1119
1120 #[test]
1121 fn function_call_stream_accepts_dict_arguments() {
1122 let fcs: FunctionCallStream = serde_json::from_value(serde_json::json!({
1123 "name": "get_weather",
1124 "arguments": {"location": "SF"}
1125 }))
1126 .unwrap();
1127 assert_eq!(fcs.arguments.as_deref(), Some("{\"location\":\"SF\"}"));
1128 }
1129
1130 #[test]
1131 fn function_call_stream_accepts_null_arguments() {
1132 let fcs: FunctionCallStream = serde_json::from_value(serde_json::json!({
1133 "name": "get_weather"
1134 }))
1135 .unwrap();
1136 assert_eq!(fcs.arguments, None);
1137 }
1138
1139 #[test]
1140 fn tool_call_with_dict_arguments_roundtrip() {
1141 let tc: ChatCompletionMessageToolCall = serde_json::from_value(serde_json::json!({
1142 "id": "call_abc",
1143 "type": "function",
1144 "function": {
1145 "name": "search",
1146 "arguments": {"query": "hello", "limit": 10}
1147 }
1148 }))
1149 .unwrap();
1150 let parsed: serde_json::Value = serde_json::from_str(&tc.function.arguments).unwrap();
1152 assert_eq!(parsed, serde_json::json!({"query": "hello", "limit": 10}));
1153 let json = serde_json::to_value(&tc).unwrap();
1155 assert!(json["function"]["arguments"].is_string());
1156 }
1157
1158 #[test]
1159 fn stream_delta_function_call_accepts_dict_arguments() {
1160 let delta: ChatCompletionStreamResponseDeltaFunctionCall =
1161 serde_json::from_value(serde_json::json!({
1162 "name": "get_weather",
1163 "arguments": {"location": "SF"}
1164 }))
1165 .unwrap();
1166 assert_eq!(delta.arguments.as_deref(), Some("{\"location\":\"SF\"}"));
1167 }
1168
1169 fn parse_content_part(json: serde_json::Value) -> ChatCompletionRequestUserMessageContentPart {
1170 serde_json::from_value(json).expect("content part deserialization failed")
1171 }
1172
1173 #[test]
1174 fn image_url_url_and_top_level_uuid() {
1175 let part = parse_content_part(serde_json::json!({
1176 "type": "image_url",
1177 "image_url": {"url": "https://x.example/y.png"},
1178 "uuid": "image-123"
1179 }));
1180
1181 match part {
1182 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1183 assert_eq!(part.uuid.as_deref(), Some("image-123"));
1184 assert_eq!(
1185 part.image_url.as_ref().map(|image| image.url.as_str()),
1186 Some("https://x.example/y.png")
1187 );
1188 }
1189 _ => panic!("expected image_url part"),
1190 }
1191 }
1192
1193 #[test]
1194 fn image_url_null_and_top_level_uuid() {
1195 let part = parse_content_part(serde_json::json!({
1196 "type": "image_url",
1197 "image_url": null,
1198 "uuid": "sku-1234-a"
1199 }));
1200
1201 match part {
1202 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1203 assert!(part.image_url.is_none());
1204 assert_eq!(part.uuid.as_deref(), Some("sku-1234-a"));
1205 }
1206 _ => panic!("expected image_url part"),
1207 }
1208 }
1209
1210 #[test]
1211 fn empty_media_urls_deserialize_as_uuid_only() {
1212 for (part_type, media_field, uuid) in [
1213 ("image_url", "image_url", "image-cache-key"),
1214 ("video_url", "video_url", "video-cache-key"),
1215 ("audio_url", "audio_url", "audio-cache-key"),
1216 ] {
1217 let part = parse_content_part(serde_json::json!({
1218 "type": part_type,
1219 (media_field): {"url": ""},
1220 "uuid": uuid
1221 }));
1222 let json = serde_json::to_value(part).unwrap();
1223
1224 assert!(json[media_field].is_null());
1225 assert_eq!(json["uuid"], uuid);
1226 }
1227 }
1228
1229 #[test]
1230 fn image_url_null_without_uuid_deserializes_for_use_site_validation() {
1231 let part = parse_content_part(serde_json::json!({
1232 "type": "image_url",
1233 "image_url": null
1234 }));
1235
1236 match part {
1237 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1238 assert!(part.image_url.is_none());
1239 assert!(part.uuid.is_none());
1240 }
1241 _ => panic!("expected image_url part"),
1242 }
1243 }
1244
1245 #[test]
1246 fn image_url_serialize_uuid_only_uses_null_image_url() {
1247 let part = ChatCompletionRequestMessageContentPartImage {
1248 image_url: None,
1249 uuid: Some("image-123".to_string()),
1250 };
1251 let json = serde_json::to_value(part).unwrap();
1252
1253 assert!(json["image_url"].is_null());
1254 assert_eq!(json["uuid"], "image-123");
1255 }
1256
1257 #[test]
1258 fn cached_media_builders_allow_omitting_urls() {
1259 let image = ChatCompletionRequestMessageContentPartImageArgs::default()
1260 .uuid("image-123")
1261 .build()
1262 .unwrap();
1263 let video = ChatCompletionRequestMessageContentPartVideoArgs::default()
1264 .uuid("video-123")
1265 .build()
1266 .unwrap();
1267 let audio = ChatCompletionRequestMessageContentPartAudioUrlArgs::default()
1268 .uuid("audio-123")
1269 .build()
1270 .unwrap();
1271
1272 let image_json = serde_json::to_value(image).unwrap();
1273 let video_json = serde_json::to_value(video).unwrap();
1274 let audio_json = serde_json::to_value(audio).unwrap();
1275 assert!(image_json["image_url"].is_null());
1276 assert!(video_json["video_url"].is_null());
1277 assert!(audio_json["audio_url"].is_null());
1278 }
1279
1280 #[test]
1281 fn image_url_uuid_accepts_opaque_string() {
1282 let part = parse_content_part(serde_json::json!({
1283 "type": "image_url",
1284 "image_url": {"url": "https://x.example/y.png"},
1285 "uuid": "img-ac3921de680bb217"
1286 }));
1287
1288 match part {
1289 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1290 assert_eq!(part.uuid.as_deref(), Some("img-ac3921de680bb217"));
1291 }
1292 _ => panic!("expected image_url part"),
1293 }
1294 }
1295
1296 #[test]
1297 fn url_conversions_preserve_required_urls() {
1298 let image: ImageUrl = "https://x.example/image.png".into();
1299 let video: VideoUrl = "https://x.example/video.mp4".into();
1300 let audio: AudioUrl = "https://x.example/audio.wav".into();
1301
1302 assert_eq!(image.url.as_str(), "https://x.example/image.png");
1303 assert_eq!(video.url.as_str(), "https://x.example/video.mp4");
1304 assert_eq!(audio.url.as_str(), "https://x.example/audio.wav");
1305 }
1306
1307 #[test]
1308 fn invalid_media_urls_remain_rejected() {
1309 for (part_type, media_field) in [
1310 ("image_url", "image_url"),
1311 ("video_url", "video_url"),
1312 ("audio_url", "audio_url"),
1313 ] {
1314 let result = serde_json::from_value::<ChatCompletionRequestUserMessageContentPart>(
1315 serde_json::json!({
1316 "type": part_type,
1317 (media_field): {"url": "not a url"},
1318 "uuid": "cache-key"
1319 }),
1320 );
1321
1322 assert!(result.is_err(), "{part_type} accepted an invalid URL");
1323 }
1324 }
1325
1326 #[test]
1327 fn legacy_nested_media_uuids_remain_accepted() {
1328 let legacy_uuid = "92b888ad-e64a-478f-b688-5091e16544e3";
1329
1330 for (part_type, media_field, url) in [
1331 ("image_url", "image_url", "https://x.example/image.png"),
1332 ("video_url", "video_url", "https://x.example/video.mp4"),
1333 ("audio_url", "audio_url", "https://x.example/audio.wav"),
1334 ] {
1335 let part = parse_content_part(serde_json::json!({
1336 "type": part_type,
1337 (media_field): {"url": url, "uuid": legacy_uuid}
1338 }));
1339 let json = serde_json::to_value(part).unwrap();
1340
1341 assert_eq!(json[media_field]["url"], url);
1342 assert_eq!(json[media_field]["uuid"], legacy_uuid);
1343 assert!(json.get("uuid").is_none());
1344 }
1345 }
1346
1347 #[test]
1348 fn video_url_null_and_top_level_uuid() {
1349 let part = parse_content_part(serde_json::json!({
1350 "type": "video_url",
1351 "video_url": null,
1352 "uuid": "video-cache-key"
1353 }));
1354
1355 match part {
1356 ChatCompletionRequestUserMessageContentPart::VideoUrl(part) => {
1357 assert!(part.video_url.is_none());
1358 assert_eq!(part.uuid.as_deref(), Some("video-cache-key"));
1359 }
1360 _ => panic!("expected video_url part"),
1361 }
1362 }
1363
1364 #[test]
1365 fn audio_url_null_and_top_level_uuid() {
1366 let part = parse_content_part(serde_json::json!({
1367 "type": "audio_url",
1368 "audio_url": null,
1369 "uuid": "audio-cache-key"
1370 }));
1371
1372 match part {
1373 ChatCompletionRequestUserMessageContentPart::AudioUrl(part) => {
1374 assert!(part.audio_url.is_none());
1375 assert_eq!(part.uuid.as_deref(), Some("audio-cache-key"));
1376 }
1377 _ => panic!("expected audio_url part"),
1378 }
1379 }
1380
1381 #[test]
1382 fn message_content_array_preserves_uuid_alignment() {
1383 let payload = serde_json::json!({
1384 "role": "user",
1385 "content": [
1386 {"type": "text", "text": "describe these"},
1387 {
1388 "type": "image_url",
1389 "image_url": {"url": "https://x.example/img1.png"},
1390 "uuid": "image-1"
1391 },
1392 {"type": "image_url", "image_url": null, "uuid": "image-1"}
1393 ]
1394 });
1395 let message: ChatCompletionRequestUserMessage = serde_json::from_value(payload).unwrap();
1396 let ChatCompletionRequestUserMessageContent::Array(parts) = message.content else {
1397 panic!("expected content array");
1398 };
1399
1400 assert_eq!(parts.len(), 3);
1401 match &parts[1] {
1402 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1403 assert!(
1404 part.image_url
1405 .as_ref()
1406 .map(|image| image.url.as_str())
1407 .is_some()
1408 );
1409 assert_eq!(part.uuid.as_deref(), Some("image-1"));
1410 }
1411 _ => panic!("parts[1] should be image_url"),
1412 }
1413 match &parts[2] {
1414 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1415 assert!(part.image_url.is_none());
1416 assert_eq!(part.uuid.as_deref(), Some("image-1"));
1417 }
1418 _ => panic!("parts[2] should be image_url"),
1419 }
1420 }
1421
1422 #[test]
1423 fn tool_message_accepts_media_content() {
1424 let message: ChatCompletionRequestMessage = serde_json::from_value(serde_json::json!({
1425 "role": "tool",
1426 "tool_call_id": "call_media",
1427 "content": [
1428 {"type": "text", "text": "Screenshot captured"},
1429 {
1430 "type": "image_url",
1431 "image_url": {
1432 "url": "data:image/png;base64,aGVsbG8="
1433 }
1434 },
1435 {
1436 "type": "video_url",
1437 "video_url": {
1438 "url": "https://example.com/clip.mp4"
1439 }
1440 },
1441 {
1442 "type": "audio_url",
1443 "audio_url": {
1444 "url": "https://example.com/audio.wav"
1445 }
1446 }
1447 ]
1448 }))
1449 .unwrap();
1450
1451 let ChatCompletionRequestMessage::Tool(tool) = message else {
1452 panic!("expected tool message");
1453 };
1454 let ChatCompletionRequestToolMessageContent::Array(parts) = tool.content else {
1455 panic!("expected array content");
1456 };
1457 assert!(matches!(
1458 parts[1],
1459 ChatCompletionRequestToolMessageContentPart::ImageUrl(_)
1460 ));
1461 assert!(matches!(
1462 parts[2],
1463 ChatCompletionRequestToolMessageContentPart::VideoUrl(_)
1464 ));
1465 assert!(matches!(
1466 parts[3],
1467 ChatCompletionRequestToolMessageContentPart::AudioUrl(_)
1468 ));
1469 }
1470}