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(default, alias = "reasoning", skip_serializing_if = "Option::is_none")]
714 pub reasoning_content: Option<ReasoningContent>,
715 #[serde(skip_serializing_if = "Option::is_none")]
716 pub refusal: Option<String>,
717 #[serde(skip_serializing_if = "Option::is_none")]
718 pub name: Option<String>,
719 #[serde(skip_serializing_if = "Option::is_none")]
720 pub audio: Option<ChatCompletionRequestAssistantMessageAudio>,
721 #[serde(skip_serializing_if = "Option::is_none")]
722 pub tool_calls: Option<Vec<ChatCompletionMessageToolCall>>,
723 #[deprecated]
724 #[serde(skip_serializing_if = "Option::is_none")]
725 pub function_call: Option<FunctionCall>,
726}
727
728#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
734#[serde(tag = "role")]
735#[serde(rename_all = "lowercase")]
736pub enum ChatCompletionRequestMessage {
737 Developer(ChatCompletionRequestDeveloperMessage),
738 System(ChatCompletionRequestSystemMessage),
739 User(ChatCompletionRequestUserMessage),
740 Assistant(ChatCompletionRequestAssistantMessage),
741 Tool(ChatCompletionRequestToolMessage),
742 Function(ChatCompletionRequestFunctionMessage),
743}
744
745pub type ServiceTierResponse = ServiceTier;
747
748#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
754pub struct ChatCompletionResponseMessage {
755 pub content: Option<ChatCompletionMessageContent>,
759 #[serde(skip_serializing_if = "Option::is_none")]
760 pub refusal: Option<String>,
761 #[serde(skip_serializing_if = "Option::is_none")]
762 pub tool_calls: Option<Vec<ChatCompletionMessageToolCall>>,
763 pub role: Role,
764 #[serde(skip_serializing_if = "Option::is_none")]
765 #[deprecated]
766 pub function_call: Option<FunctionCall>,
767 #[serde(skip_serializing_if = "Option::is_none")]
768 pub audio: Option<ChatCompletionResponseMessageAudio>,
769 #[serde(default, alias = "reasoning")]
775 pub reasoning_content: Option<String>,
776}
777
778#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
783pub struct ChatCompletionStreamOptions {
784 pub include_usage: bool,
785 #[serde(default)]
788 pub continuous_usage_stats: bool,
789}
790
791#[derive(Clone, Serialize, Default, Debug, Builder, Deserialize, PartialEq)]
798#[builder(name = "CreateChatCompletionRequestArgs")]
799#[builder(pattern = "mutable")]
800#[builder(setter(into, strip_option), default)]
801#[builder(derive(Debug))]
802#[builder(build_fn(error = "OpenAIError"))]
803pub struct CreateChatCompletionRequest {
804 pub messages: Vec<ChatCompletionRequestMessage>,
805 pub model: String,
806 #[serde(skip_serializing_if = "Option::is_none")]
808 pub mm_processor_kwargs: Option<serde_json::Value>,
809 #[serde(skip_serializing_if = "Option::is_none")]
810 pub store: Option<bool>,
811 #[serde(skip_serializing_if = "Option::is_none")]
812 pub reasoning_effort: Option<ReasoningEffort>,
813 #[serde(skip_serializing_if = "Option::is_none")]
814 pub metadata: Option<serde_json::Value>,
815 #[serde(skip_serializing_if = "Option::is_none")]
816 pub frequency_penalty: Option<f32>,
817 #[serde(skip_serializing_if = "Option::is_none")]
818 pub logit_bias: Option<std::collections::HashMap<String, serde_json::Value>>,
819 #[serde(skip_serializing_if = "Option::is_none")]
820 pub logprobs: Option<bool>,
821 #[serde(skip_serializing_if = "Option::is_none")]
822 pub top_logprobs: Option<u8>,
823 #[deprecated]
824 #[serde(skip_serializing_if = "Option::is_none")]
825 pub max_tokens: Option<u32>,
826 #[serde(skip_serializing_if = "Option::is_none")]
827 pub max_completion_tokens: Option<u32>,
828 #[serde(skip_serializing_if = "Option::is_none")]
829 pub n: Option<u8>,
830 #[serde(skip_serializing_if = "Option::is_none")]
831 pub modalities: Option<Vec<async_openai::types::chat::ResponseModalities>>,
832 #[serde(skip_serializing_if = "Option::is_none")]
833 pub prediction: Option<PredictionContent>,
834 #[serde(skip_serializing_if = "Option::is_none")]
835 pub audio: Option<ChatCompletionAudio>,
836 #[serde(skip_serializing_if = "Option::is_none")]
837 pub presence_penalty: Option<f32>,
838 #[serde(skip_serializing_if = "Option::is_none")]
839 pub response_format: Option<ResponseFormat>,
840 #[serde(skip_serializing_if = "Option::is_none")]
841 pub seed: Option<i64>,
842 #[serde(skip_serializing_if = "Option::is_none")]
843 pub service_tier: Option<ServiceTier>,
844 #[serde(skip_serializing_if = "Option::is_none")]
845 pub stop: Option<Stop>,
846 #[serde(default, skip_serializing_if = "Option::is_none")]
847 pub stream: Option<bool>,
848 #[serde(skip_serializing_if = "Option::is_none")]
849 pub stream_options: Option<ChatCompletionStreamOptions>,
850 #[serde(skip_serializing_if = "Option::is_none")]
851 pub temperature: Option<f32>,
852 #[serde(skip_serializing_if = "Option::is_none")]
853 pub top_p: Option<f32>,
854 #[serde(skip_serializing_if = "Option::is_none")]
855 pub tools: Option<Vec<ChatCompletionTool>>,
856 #[serde(skip_serializing_if = "Option::is_none")]
857 pub tool_choice: Option<ChatCompletionToolChoiceOption>,
858 #[serde(skip_serializing_if = "Option::is_none")]
859 pub parallel_tool_calls: Option<bool>,
860 #[serde(skip_serializing_if = "Option::is_none")]
861 pub user: Option<String>,
862 #[deprecated]
863 #[serde(skip_serializing_if = "Option::is_none")]
864 pub function_call: Option<ChatCompletionFunctionCall>,
865 #[deprecated]
866 #[serde(skip_serializing_if = "Option::is_none")]
867 pub functions: Option<Vec<ChatCompletionFunctions>>,
868 #[serde(skip_serializing_if = "Option::is_none")]
869 pub web_search_options: Option<WebSearchOptions>,
870}
871
872#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
876pub struct ChatChoice {
877 pub index: u32,
878 pub message: ChatCompletionResponseMessage,
879 pub finish_reason: Option<FinishReason>,
880 pub logprobs: Option<ChatChoiceLogprobs>,
881}
882
883#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
885pub struct CreateChatCompletionResponse {
886 pub id: String,
887 pub choices: Vec<ChatChoice>,
888 pub created: u32,
889 pub model: String,
890 pub service_tier: Option<ServiceTierResponse>,
891 pub system_fingerprint: Option<String>,
892 pub object: String,
893 pub usage: Option<CompletionUsage>,
894}
895
896pub type ChatCompletionResponseStream =
897 Pin<Box<dyn Stream<Item = Result<CreateChatCompletionStreamResponse, OpenAIError>> + Send>>;
898
899#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
905pub struct ChatCompletionStreamResponseDelta {
906 #[serde(skip_serializing_if = "Option::is_none")]
907 pub content: Option<ChatCompletionMessageContent>,
908 #[serde(skip_serializing_if = "Option::is_none")]
909 pub function_call: Option<ChatCompletionStreamResponseDeltaFunctionCall>,
910 #[serde(skip_serializing_if = "Option::is_none")]
911 pub tool_calls: Option<Vec<ChatCompletionMessageToolCallChunk>>,
912 #[serde(skip_serializing_if = "Option::is_none")]
913 pub role: Option<Role>,
914 #[serde(skip_serializing_if = "Option::is_none")]
915 pub refusal: Option<String>,
916 #[serde(default, alias = "reasoning", skip_serializing_if = "Option::is_none")]
922 pub reasoning_content: Option<String>,
923}
924
925#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
926pub struct ChatCompletionStreamResponseDeltaFunctionCall {
927 pub name: Option<String>,
928 #[serde(default, deserialize_with = "deserialize_arguments_opt")]
929 pub arguments: Option<String>,
930}
931
932#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
934pub struct ChatChoiceStream {
935 pub index: u32,
936 pub delta: ChatCompletionStreamResponseDelta,
937 pub finish_reason: Option<FinishReason>,
938 pub logprobs: Option<ChatChoiceLogprobs>,
939}
940
941#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
943pub struct CreateChatCompletionStreamResponse {
944 pub id: String,
945 pub choices: Vec<ChatChoiceStream>,
946 pub created: u32,
947 pub model: String,
948 pub service_tier: Option<ServiceTierResponse>,
949 pub system_fingerprint: Option<String>,
950 pub object: String,
951 pub usage: Option<CompletionUsage>,
952}
953
954#[cfg(test)]
955mod tests {
956 use super::*;
957
958 #[test]
959 fn stop_accepts_token_id_array() {
960 let stop: Stop = serde_json::from_value(serde_json::json!([32, 34])).unwrap();
961
962 assert_eq!(stop, Stop::TokenIdArray(vec![32, 34]));
963 }
964
965 #[test]
966 fn stop_accepts_string_and_string_array() {
967 let stop: Stop = serde_json::from_value(serde_json::json!(" The")).unwrap();
968
969 assert_eq!(stop, Stop::String(" The".to_string()));
970
971 let stop: Stop = serde_json::from_value(serde_json::json!(["A", "B"])).unwrap();
972
973 assert_eq!(
974 stop,
975 Stop::StringArray(vec!["A".to_string(), "B".to_string()])
976 );
977 }
978
979 #[test]
980 fn stop_token_id_display_string_remains_string_stop() {
981 let stop: Stop = serde_json::from_value(serde_json::json!("token_id:576")).unwrap();
982
983 assert_eq!(stop, Stop::String("token_id:576".to_string()));
984
985 let stop: Stop = serde_json::from_value(serde_json::json!(["token_id:576"])).unwrap();
986
987 assert_eq!(stop, Stop::StringArray(vec!["token_id:576".to_string()]));
988 }
989
990 #[test]
991 fn stop_rejects_single_token_id() {
992 let result = serde_json::from_value::<Stop>(serde_json::json!(576));
993
994 assert!(result.is_err());
995 }
996
997 #[test]
998 fn stop_converts_from_upstream_stop_configuration() {
999 let upstream =
1000 async_openai::types::chat::StopConfiguration::StringArray(vec!["END".to_string()]);
1001
1002 assert_eq!(
1003 Stop::from(upstream),
1004 Stop::StringArray(vec!["END".to_string()])
1005 );
1006 }
1007
1008 #[test]
1009 fn request_builder_accepts_upstream_reasoning_effort() {
1010 let request = CreateChatCompletionRequestArgs::default()
1011 .reasoning_effort(async_openai::types::chat::ReasoningEffort::High)
1012 .build()
1013 .unwrap();
1014
1015 assert_eq!(request.reasoning_effort, Some(ReasoningEffort::High));
1016 }
1017
1018 #[test]
1019 fn tool_call_defaults_type_on_deserialize() {
1020 let tool_call: ChatCompletionMessageToolCall = serde_json::from_value(serde_json::json!({
1021 "id": "call_123",
1022 "function": {
1023 "name": "get_weather",
1024 "arguments": "{\"location\":\"SF\"}"
1025 }
1026 }))
1027 .unwrap();
1028
1029 assert_eq!(tool_call.r#type, FunctionType::Function);
1030 }
1031
1032 #[test]
1033 fn tool_call_serializes_type_for_wire_compat() {
1034 let tool_call = ChatCompletionMessageToolCall {
1035 id: "call_123".into(),
1036 r#type: FunctionType::Function,
1037 function: FunctionCall {
1038 name: "get_weather".into(),
1039 arguments: "{\"location\":\"SF\"}".into(),
1040 },
1041 };
1042
1043 let json = serde_json::to_value(tool_call).unwrap();
1044 assert_eq!(json["type"], "function");
1045 }
1046
1047 #[test]
1050 fn function_call_accepts_string_arguments() {
1051 let fc: FunctionCall = serde_json::from_value(serde_json::json!({
1052 "name": "get_weather",
1053 "arguments": "{\"location\":\"SF\"}"
1054 }))
1055 .unwrap();
1056 assert_eq!(fc.arguments, "{\"location\":\"SF\"}");
1057 }
1058
1059 #[test]
1060 fn function_call_accepts_dict_arguments() {
1061 let fc: FunctionCall = serde_json::from_value(serde_json::json!({
1062 "name": "get_weather",
1063 "arguments": {"location": "SF"}
1064 }))
1065 .unwrap();
1066 assert_eq!(fc.arguments, "{\"location\":\"SF\"}");
1067 }
1068
1069 #[test]
1070 fn function_call_rejects_integer_arguments() {
1071 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
1072 "name": "f",
1073 "arguments": 42
1074 }));
1075 assert!(result.is_err());
1076 }
1077
1078 #[test]
1079 fn function_call_rejects_boolean_arguments() {
1080 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
1081 "name": "f",
1082 "arguments": true
1083 }));
1084 assert!(result.is_err());
1085 }
1086
1087 #[test]
1088 fn function_call_rejects_null_arguments() {
1089 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
1090 "name": "f",
1091 "arguments": null
1092 }));
1093 assert!(result.is_err());
1094 }
1095
1096 #[test]
1097 fn function_call_rejects_array_arguments() {
1098 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
1099 "name": "f",
1100 "arguments": [1, 2, 3]
1101 }));
1102 assert!(result.is_err());
1103 }
1104
1105 #[test]
1106 fn function_call_stream_null_arguments_produces_none() {
1107 let fcs: FunctionCallStream = serde_json::from_value(serde_json::json!({
1108 "name": "f",
1109 "arguments": null
1110 }))
1111 .unwrap();
1112 assert_eq!(fcs.arguments, None);
1113 }
1114
1115 #[test]
1116 fn function_call_stream_rejects_integer_arguments() {
1117 let result = serde_json::from_value::<FunctionCallStream>(serde_json::json!({
1118 "name": "f",
1119 "arguments": 42
1120 }));
1121 assert!(result.is_err());
1122 }
1123
1124 #[test]
1125 fn function_call_stream_rejects_boolean_arguments() {
1126 let result = serde_json::from_value::<FunctionCallStream>(serde_json::json!({
1127 "name": "f",
1128 "arguments": true
1129 }));
1130 assert!(result.is_err());
1131 }
1132
1133 #[test]
1134 fn function_call_stream_accepts_dict_arguments() {
1135 let fcs: FunctionCallStream = serde_json::from_value(serde_json::json!({
1136 "name": "get_weather",
1137 "arguments": {"location": "SF"}
1138 }))
1139 .unwrap();
1140 assert_eq!(fcs.arguments.as_deref(), Some("{\"location\":\"SF\"}"));
1141 }
1142
1143 #[test]
1144 fn function_call_stream_accepts_null_arguments() {
1145 let fcs: FunctionCallStream = serde_json::from_value(serde_json::json!({
1146 "name": "get_weather"
1147 }))
1148 .unwrap();
1149 assert_eq!(fcs.arguments, None);
1150 }
1151
1152 #[test]
1153 fn tool_call_with_dict_arguments_roundtrip() {
1154 let tc: ChatCompletionMessageToolCall = serde_json::from_value(serde_json::json!({
1155 "id": "call_abc",
1156 "type": "function",
1157 "function": {
1158 "name": "search",
1159 "arguments": {"query": "hello", "limit": 10}
1160 }
1161 }))
1162 .unwrap();
1163 let parsed: serde_json::Value = serde_json::from_str(&tc.function.arguments).unwrap();
1165 assert_eq!(parsed, serde_json::json!({"query": "hello", "limit": 10}));
1166 let json = serde_json::to_value(&tc).unwrap();
1168 assert!(json["function"]["arguments"].is_string());
1169 }
1170
1171 #[test]
1172 fn stream_delta_function_call_accepts_dict_arguments() {
1173 let delta: ChatCompletionStreamResponseDeltaFunctionCall =
1174 serde_json::from_value(serde_json::json!({
1175 "name": "get_weather",
1176 "arguments": {"location": "SF"}
1177 }))
1178 .unwrap();
1179 assert_eq!(delta.arguments.as_deref(), Some("{\"location\":\"SF\"}"));
1180 }
1181
1182 fn parse_content_part(json: serde_json::Value) -> ChatCompletionRequestUserMessageContentPart {
1183 serde_json::from_value(json).expect("content part deserialization failed")
1184 }
1185
1186 #[test]
1187 fn image_url_url_and_top_level_uuid() {
1188 let part = parse_content_part(serde_json::json!({
1189 "type": "image_url",
1190 "image_url": {"url": "https://x.example/y.png"},
1191 "uuid": "image-123"
1192 }));
1193
1194 match part {
1195 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1196 assert_eq!(part.uuid.as_deref(), Some("image-123"));
1197 assert_eq!(
1198 part.image_url.as_ref().map(|image| image.url.as_str()),
1199 Some("https://x.example/y.png")
1200 );
1201 }
1202 _ => panic!("expected image_url part"),
1203 }
1204 }
1205
1206 #[test]
1207 fn image_url_null_and_top_level_uuid() {
1208 let part = parse_content_part(serde_json::json!({
1209 "type": "image_url",
1210 "image_url": null,
1211 "uuid": "sku-1234-a"
1212 }));
1213
1214 match part {
1215 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1216 assert!(part.image_url.is_none());
1217 assert_eq!(part.uuid.as_deref(), Some("sku-1234-a"));
1218 }
1219 _ => panic!("expected image_url part"),
1220 }
1221 }
1222
1223 #[test]
1224 fn empty_media_urls_deserialize_as_uuid_only() {
1225 for (part_type, media_field, uuid) in [
1226 ("image_url", "image_url", "image-cache-key"),
1227 ("video_url", "video_url", "video-cache-key"),
1228 ("audio_url", "audio_url", "audio-cache-key"),
1229 ] {
1230 let part = parse_content_part(serde_json::json!({
1231 "type": part_type,
1232 (media_field): {"url": ""},
1233 "uuid": uuid
1234 }));
1235 let json = serde_json::to_value(part).unwrap();
1236
1237 assert!(json[media_field].is_null());
1238 assert_eq!(json["uuid"], uuid);
1239 }
1240 }
1241
1242 #[test]
1243 fn image_url_null_without_uuid_deserializes_for_use_site_validation() {
1244 let part = parse_content_part(serde_json::json!({
1245 "type": "image_url",
1246 "image_url": null
1247 }));
1248
1249 match part {
1250 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1251 assert!(part.image_url.is_none());
1252 assert!(part.uuid.is_none());
1253 }
1254 _ => panic!("expected image_url part"),
1255 }
1256 }
1257
1258 #[test]
1259 fn image_url_serialize_uuid_only_uses_null_image_url() {
1260 let part = ChatCompletionRequestMessageContentPartImage {
1261 image_url: None,
1262 uuid: Some("image-123".to_string()),
1263 };
1264 let json = serde_json::to_value(part).unwrap();
1265
1266 assert!(json["image_url"].is_null());
1267 assert_eq!(json["uuid"], "image-123");
1268 }
1269
1270 #[test]
1271 fn cached_media_builders_allow_omitting_urls() {
1272 let image = ChatCompletionRequestMessageContentPartImageArgs::default()
1273 .uuid("image-123")
1274 .build()
1275 .unwrap();
1276 let video = ChatCompletionRequestMessageContentPartVideoArgs::default()
1277 .uuid("video-123")
1278 .build()
1279 .unwrap();
1280 let audio = ChatCompletionRequestMessageContentPartAudioUrlArgs::default()
1281 .uuid("audio-123")
1282 .build()
1283 .unwrap();
1284
1285 let image_json = serde_json::to_value(image).unwrap();
1286 let video_json = serde_json::to_value(video).unwrap();
1287 let audio_json = serde_json::to_value(audio).unwrap();
1288 assert!(image_json["image_url"].is_null());
1289 assert!(video_json["video_url"].is_null());
1290 assert!(audio_json["audio_url"].is_null());
1291 }
1292
1293 #[test]
1294 fn image_url_uuid_accepts_opaque_string() {
1295 let part = parse_content_part(serde_json::json!({
1296 "type": "image_url",
1297 "image_url": {"url": "https://x.example/y.png"},
1298 "uuid": "img-ac3921de680bb217"
1299 }));
1300
1301 match part {
1302 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1303 assert_eq!(part.uuid.as_deref(), Some("img-ac3921de680bb217"));
1304 }
1305 _ => panic!("expected image_url part"),
1306 }
1307 }
1308
1309 #[test]
1310 fn url_conversions_preserve_required_urls() {
1311 let image: ImageUrl = "https://x.example/image.png".into();
1312 let video: VideoUrl = "https://x.example/video.mp4".into();
1313 let audio: AudioUrl = "https://x.example/audio.wav".into();
1314
1315 assert_eq!(image.url.as_str(), "https://x.example/image.png");
1316 assert_eq!(video.url.as_str(), "https://x.example/video.mp4");
1317 assert_eq!(audio.url.as_str(), "https://x.example/audio.wav");
1318 }
1319
1320 #[test]
1321 fn invalid_media_urls_remain_rejected() {
1322 for (part_type, media_field) in [
1323 ("image_url", "image_url"),
1324 ("video_url", "video_url"),
1325 ("audio_url", "audio_url"),
1326 ] {
1327 let result = serde_json::from_value::<ChatCompletionRequestUserMessageContentPart>(
1328 serde_json::json!({
1329 "type": part_type,
1330 (media_field): {"url": "not a url"},
1331 "uuid": "cache-key"
1332 }),
1333 );
1334
1335 assert!(result.is_err(), "{part_type} accepted an invalid URL");
1336 }
1337 }
1338
1339 #[test]
1340 fn legacy_nested_media_uuids_remain_accepted() {
1341 let legacy_uuid = "92b888ad-e64a-478f-b688-5091e16544e3";
1342
1343 for (part_type, media_field, url) in [
1344 ("image_url", "image_url", "https://x.example/image.png"),
1345 ("video_url", "video_url", "https://x.example/video.mp4"),
1346 ("audio_url", "audio_url", "https://x.example/audio.wav"),
1347 ] {
1348 let part = parse_content_part(serde_json::json!({
1349 "type": part_type,
1350 (media_field): {"url": url, "uuid": legacy_uuid}
1351 }));
1352 let json = serde_json::to_value(part).unwrap();
1353
1354 assert_eq!(json[media_field]["url"], url);
1355 assert_eq!(json[media_field]["uuid"], legacy_uuid);
1356 assert!(json.get("uuid").is_none());
1357 }
1358 }
1359
1360 #[test]
1361 fn video_url_null_and_top_level_uuid() {
1362 let part = parse_content_part(serde_json::json!({
1363 "type": "video_url",
1364 "video_url": null,
1365 "uuid": "video-cache-key"
1366 }));
1367
1368 match part {
1369 ChatCompletionRequestUserMessageContentPart::VideoUrl(part) => {
1370 assert!(part.video_url.is_none());
1371 assert_eq!(part.uuid.as_deref(), Some("video-cache-key"));
1372 }
1373 _ => panic!("expected video_url part"),
1374 }
1375 }
1376
1377 #[test]
1378 fn audio_url_null_and_top_level_uuid() {
1379 let part = parse_content_part(serde_json::json!({
1380 "type": "audio_url",
1381 "audio_url": null,
1382 "uuid": "audio-cache-key"
1383 }));
1384
1385 match part {
1386 ChatCompletionRequestUserMessageContentPart::AudioUrl(part) => {
1387 assert!(part.audio_url.is_none());
1388 assert_eq!(part.uuid.as_deref(), Some("audio-cache-key"));
1389 }
1390 _ => panic!("expected audio_url part"),
1391 }
1392 }
1393
1394 #[test]
1395 fn message_content_array_preserves_uuid_alignment() {
1396 let payload = serde_json::json!({
1397 "role": "user",
1398 "content": [
1399 {"type": "text", "text": "describe these"},
1400 {
1401 "type": "image_url",
1402 "image_url": {"url": "https://x.example/img1.png"},
1403 "uuid": "image-1"
1404 },
1405 {"type": "image_url", "image_url": null, "uuid": "image-1"}
1406 ]
1407 });
1408 let message: ChatCompletionRequestUserMessage = serde_json::from_value(payload).unwrap();
1409 let ChatCompletionRequestUserMessageContent::Array(parts) = message.content else {
1410 panic!("expected content array");
1411 };
1412
1413 assert_eq!(parts.len(), 3);
1414 match &parts[1] {
1415 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1416 assert!(
1417 part.image_url
1418 .as_ref()
1419 .map(|image| image.url.as_str())
1420 .is_some()
1421 );
1422 assert_eq!(part.uuid.as_deref(), Some("image-1"));
1423 }
1424 _ => panic!("parts[1] should be image_url"),
1425 }
1426 match &parts[2] {
1427 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1428 assert!(part.image_url.is_none());
1429 assert_eq!(part.uuid.as_deref(), Some("image-1"));
1430 }
1431 _ => panic!("parts[2] should be image_url"),
1432 }
1433 }
1434
1435 #[test]
1436 fn tool_message_accepts_media_content() {
1437 let message: ChatCompletionRequestMessage = serde_json::from_value(serde_json::json!({
1438 "role": "tool",
1439 "tool_call_id": "call_media",
1440 "content": [
1441 {"type": "text", "text": "Screenshot captured"},
1442 {
1443 "type": "image_url",
1444 "image_url": {
1445 "url": "data:image/png;base64,aGVsbG8="
1446 }
1447 },
1448 {
1449 "type": "video_url",
1450 "video_url": {
1451 "url": "https://example.com/clip.mp4"
1452 }
1453 },
1454 {
1455 "type": "audio_url",
1456 "audio_url": {
1457 "url": "https://example.com/audio.wav"
1458 }
1459 }
1460 ]
1461 }))
1462 .unwrap();
1463
1464 let ChatCompletionRequestMessage::Tool(tool) = message else {
1465 panic!("expected tool message");
1466 };
1467 let ChatCompletionRequestToolMessageContent::Array(parts) = tool.content else {
1468 panic!("expected array content");
1469 };
1470 assert!(matches!(
1471 parts[1],
1472 ChatCompletionRequestToolMessageContentPart::ImageUrl(_)
1473 ));
1474 assert!(matches!(
1475 parts[2],
1476 ChatCompletionRequestToolMessageContentPart::VideoUrl(_)
1477 ));
1478 assert!(matches!(
1479 parts[3],
1480 ChatCompletionRequestToolMessageContentPart::AudioUrl(_)
1481 ));
1482 }
1483}