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
224#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)]
237pub struct FunctionCall {
238 pub name: String,
239 #[serde(deserialize_with = "deserialize_arguments")]
240 pub arguments: String,
241}
242
243#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)]
245pub struct FunctionCallStream {
246 pub name: Option<String>,
247 #[serde(default, deserialize_with = "deserialize_arguments_opt")]
248 pub arguments: Option<String>,
249}
250
251#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)]
257pub struct ChatCompletionMessageToolCallChunk {
258 pub index: u32,
259 pub id: Option<String>,
260 pub r#type: Option<FunctionType>,
261 pub function: Option<FunctionCallStream>,
262}
263
264#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
275#[builder(name = "ChatCompletionRequestMessageContentPartImageArgs")]
276#[builder(pattern = "mutable")]
277#[builder(setter(into, strip_option))]
278#[builder(derive(Debug))]
279#[builder(build_fn(error = "OpenAIError"))]
280pub struct ChatCompletionRequestMessageContentPartImage {
281 #[builder(default)]
282 #[serde(default)]
283 pub image_url: Option<ImageUrl>,
284 #[builder(default)]
285 #[serde(skip_serializing_if = "Option::is_none")]
286 pub uuid: Option<String>,
288}
289
290#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
295#[builder(name = "ImageUrlArgs")]
296#[builder(pattern = "mutable")]
297#[builder(setter(into, strip_option))]
298#[builder(derive(Debug))]
299#[builder(build_fn(error = "OpenAIError"))]
300pub struct ImageUrl {
301 pub url: Url,
302 pub detail: Option<ImageDetail>,
303 #[deprecated(note = "use the content-part `uuid` field for vLLM cache identities")]
304 #[serde(skip_serializing_if = "Option::is_none")]
305 pub uuid: Option<Uuid>,
306}
307
308#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
314#[serde(tag = "type")]
315#[serde(rename_all = "snake_case")]
316pub enum ChatCompletionRequestToolMessageContentPart {
317 Text(ChatCompletionRequestMessageContentPartText),
318 ImageUrl(ChatCompletionRequestMessageContentPartImage),
319 VideoUrl(ChatCompletionRequestMessageContentPartVideo),
320 AudioUrl(ChatCompletionRequestMessageContentPartAudioUrl),
321}
322
323#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
325#[serde(untagged)]
326pub enum ChatCompletionRequestToolMessageContent {
327 Text(String),
328 Array(Vec<ChatCompletionRequestToolMessageContentPart>),
329}
330
331impl Default for ChatCompletionRequestToolMessageContent {
332 fn default() -> Self {
333 Self::Text(String::new())
334 }
335}
336
337impl From<&str> for ChatCompletionRequestToolMessageContent {
338 fn from(value: &str) -> Self {
339 Self::Text(value.into())
340 }
341}
342
343impl From<String> for ChatCompletionRequestToolMessageContent {
344 fn from(value: String) -> Self {
345 Self::Text(value)
346 }
347}
348
349#[derive(Debug, Serialize, Deserialize, Default, Clone, Builder, PartialEq)]
351#[builder(name = "ChatCompletionRequestToolMessageArgs")]
352#[builder(pattern = "mutable")]
353#[builder(setter(into, strip_option), default)]
354#[builder(derive(Debug))]
355#[builder(build_fn(error = "OpenAIError"))]
356pub struct ChatCompletionRequestToolMessage {
357 pub content: ChatCompletionRequestToolMessageContent,
358 pub tool_call_id: String,
359}
360
361#[derive(Clone, Serialize, Default, Debug, Deserialize, PartialEq)]
362#[serde(rename_all = "lowercase")]
363pub enum ChatCompletionToolType {
364 #[default]
365 Function,
366}
367
368#[derive(Clone, Serialize, Default, Debug, Deserialize, PartialEq)]
369pub struct FunctionName {
370 pub name: String,
371}
372
373#[derive(Clone, Serialize, Default, Debug, Deserialize, PartialEq)]
374pub struct ChatCompletionNamedToolChoice {
375 pub r#type: ChatCompletionToolType,
376 pub function: FunctionName,
377}
378
379fn default_function_type() -> FunctionType {
380 FunctionType::Function
381}
382
383#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
389pub struct ChatCompletionMessageToolCall {
390 pub id: String,
391 #[serde(default = "default_function_type")]
392 pub r#type: FunctionType,
393 pub function: FunctionCall,
394}
395
396#[derive(Clone, Serialize, Default, Debug, Deserialize, PartialEq)]
398#[serde(rename_all = "lowercase")]
399pub enum ChatCompletionToolChoiceOption {
400 #[default]
401 None,
402 Auto,
403 Required,
404 #[serde(untagged)]
405 Named(ChatCompletionNamedToolChoice),
406}
407
408#[derive(Clone, Serialize, Default, Debug, Builder, Deserialize, PartialEq)]
409#[builder(name = "ChatCompletionToolArgs")]
410#[builder(pattern = "mutable")]
411#[builder(setter(into, strip_option), default)]
412#[builder(derive(Debug))]
413#[builder(build_fn(error = "OpenAIError"))]
414pub struct ChatCompletionTool {
415 #[builder(default = "ChatCompletionToolType::Function")]
416 pub r#type: ChatCompletionToolType,
417 pub function: FunctionObject,
418}
419
420#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
431#[serde(untagged)]
432pub enum StopReason {
433 String(String),
434 Int(i64),
435 IntArray(Vec<i64>),
436}
437
438#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
448#[serde(untagged)]
449pub enum ReasoningContent {
450 Text(String),
452 Segments(Vec<String>),
455}
456
457impl ReasoningContent {
458 pub fn to_flat_string(&self) -> String {
460 match self {
461 ReasoningContent::Text(s) => s.clone(),
462 ReasoningContent::Segments(segs) => segs
463 .iter()
464 .filter(|s| !s.is_empty())
465 .cloned()
466 .collect::<Vec<_>>()
467 .join("\n"),
468 }
469 }
470
471 pub fn segments(&self) -> Option<&[String]> {
473 match self {
474 ReasoningContent::Segments(segs) => Some(segs),
475 ReasoningContent::Text(_) => None,
476 }
477 }
478}
479
480#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
484pub struct ChatCompletionResponseContentPartText {
485 pub text: String,
486}
487
488#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
490pub struct ChatCompletionResponseContentPartImageUrl {
491 pub image_url: ImageUrlResponse,
492}
493
494#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
496pub struct ChatCompletionResponseContentPartVideoUrl {
497 pub video_url: VideoUrlResponse,
498}
499
500#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
502pub struct ChatCompletionResponseContentPartAudioUrl {
503 pub audio_url: AudioUrlResponse,
504}
505
506#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
507pub struct ImageUrlResponse {
508 pub url: String,
509 #[serde(skip_serializing_if = "Option::is_none")]
510 pub detail: Option<String>,
511}
512
513#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
514pub struct VideoUrlResponse {
515 pub url: String,
516}
517
518#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
519pub struct AudioUrlResponse {
520 pub url: String,
521}
522
523#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
525#[serde(tag = "type", rename_all = "snake_case")]
526pub enum ChatCompletionResponseContentPart {
527 Text(ChatCompletionResponseContentPartText),
528 ImageUrl(ChatCompletionResponseContentPartImageUrl),
529 VideoUrl(ChatCompletionResponseContentPartVideoUrl),
530 AudioUrl(ChatCompletionResponseContentPartAudioUrl),
531}
532
533#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
539#[serde(untagged)]
540pub enum ChatCompletionMessageContent {
541 Text(String),
543 Parts(Vec<ChatCompletionResponseContentPart>),
545}
546
547#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
550#[builder(name = "VideoUrlArgs")]
551#[builder(pattern = "mutable")]
552#[builder(setter(into, strip_option))]
553#[builder(derive(Debug))]
554#[builder(build_fn(error = "OpenAIError"))]
555pub struct VideoUrl {
556 pub url: Url,
557 pub detail: Option<ImageDetail>,
558 #[deprecated(note = "use the content-part `uuid` field for vLLM cache identities")]
559 #[serde(skip_serializing_if = "Option::is_none")]
560 pub uuid: Option<Uuid>,
561}
562
563#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
564#[builder(name = "ChatCompletionRequestMessageContentPartVideoArgs")]
565#[builder(pattern = "mutable")]
566#[builder(setter(into, strip_option))]
567#[builder(derive(Debug))]
568#[builder(build_fn(error = "OpenAIError"))]
569pub struct ChatCompletionRequestMessageContentPartVideo {
570 #[builder(default)]
571 #[serde(default)]
572 pub video_url: Option<VideoUrl>,
573 #[builder(default)]
574 #[serde(skip_serializing_if = "Option::is_none")]
575 pub uuid: Option<String>,
577}
578
579#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
580#[builder(name = "AudioUrlArgs")]
581#[builder(pattern = "mutable")]
582#[builder(setter(into, strip_option))]
583#[builder(derive(Debug))]
584#[builder(build_fn(error = "OpenAIError"))]
585pub struct AudioUrl {
586 pub url: Url,
587 #[deprecated(note = "use the content-part `uuid` field for vLLM cache identities")]
588 #[serde(skip_serializing_if = "Option::is_none")]
589 pub uuid: Option<Uuid>,
590}
591
592#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
593#[builder(name = "ChatCompletionRequestMessageContentPartAudioUrlArgs")]
594#[builder(pattern = "mutable")]
595#[builder(setter(into, strip_option))]
596#[builder(derive(Debug))]
597#[builder(build_fn(error = "OpenAIError"))]
598pub struct ChatCompletionRequestMessageContentPartAudioUrl {
599 #[builder(default)]
600 #[serde(default)]
601 pub audio_url: Option<AudioUrl>,
602 #[builder(default)]
603 #[serde(skip_serializing_if = "Option::is_none")]
604 pub uuid: Option<String>,
606}
607
608#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
612#[serde(untagged)]
613pub enum ChatCompletionRequestUserMessageContent {
614 Text(String),
615 Array(Vec<ChatCompletionRequestUserMessageContentPart>),
616}
617
618#[derive(Debug, Serialize, Deserialize, Default, Clone, Builder, PartialEq)]
619#[builder(name = "ChatCompletionRequestUserMessageArgs")]
620#[builder(pattern = "mutable")]
621#[builder(setter(into, strip_option), default)]
622#[builder(derive(Debug))]
623#[builder(build_fn(error = "OpenAIError"))]
624pub struct ChatCompletionRequestUserMessage {
625 pub content: ChatCompletionRequestUserMessageContent,
626 #[serde(skip_serializing_if = "Option::is_none")]
627 pub name: Option<String>,
628}
629
630impl Default for ChatCompletionRequestUserMessageContent {
631 fn default() -> Self {
632 Self::Text(String::new())
633 }
634}
635
636impl From<&str> for ChatCompletionRequestUserMessageContent {
637 fn from(value: &str) -> Self {
638 Self::Text(value.into())
639 }
640}
641
642impl From<String> for ChatCompletionRequestUserMessageContent {
643 fn from(value: String) -> Self {
644 Self::Text(value)
645 }
646}
647
648impl From<Vec<ChatCompletionRequestUserMessageContentPart>>
649 for ChatCompletionRequestUserMessageContent
650{
651 fn from(value: Vec<ChatCompletionRequestUserMessageContentPart>) -> Self {
652 Self::Array(value)
653 }
654}
655
656#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
662#[serde(tag = "type")]
663#[serde(rename_all = "snake_case")]
664pub enum ChatCompletionRequestUserMessageContentPart {
665 Text(ChatCompletionRequestMessageContentPartText),
666 ImageUrl(ChatCompletionRequestMessageContentPartImage),
667 VideoUrl(ChatCompletionRequestMessageContentPartVideo),
668 AudioUrl(ChatCompletionRequestMessageContentPartAudioUrl),
669 InputAudio(ChatCompletionRequestMessageContentPartAudio),
670}
671
672#[derive(Debug, Serialize, Deserialize, Default, Clone, Builder, PartialEq)]
678#[builder(name = "ChatCompletionRequestAssistantMessageArgs")]
679#[builder(pattern = "mutable")]
680#[builder(setter(into, strip_option), default)]
681#[builder(derive(Debug))]
682#[builder(build_fn(error = "OpenAIError"))]
683pub struct ChatCompletionRequestAssistantMessage {
684 #[serde(skip_serializing_if = "Option::is_none")]
685 pub content: Option<ChatCompletionRequestAssistantMessageContent>,
686 #[serde(skip_serializing_if = "Option::is_none")]
688 pub reasoning_content: Option<ReasoningContent>,
689 #[serde(skip_serializing_if = "Option::is_none")]
690 pub refusal: Option<String>,
691 #[serde(skip_serializing_if = "Option::is_none")]
692 pub name: Option<String>,
693 #[serde(skip_serializing_if = "Option::is_none")]
694 pub audio: Option<ChatCompletionRequestAssistantMessageAudio>,
695 #[serde(skip_serializing_if = "Option::is_none")]
696 pub tool_calls: Option<Vec<ChatCompletionMessageToolCall>>,
697 #[deprecated]
698 #[serde(skip_serializing_if = "Option::is_none")]
699 pub function_call: Option<FunctionCall>,
700}
701
702#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
708#[serde(tag = "role")]
709#[serde(rename_all = "lowercase")]
710pub enum ChatCompletionRequestMessage {
711 Developer(ChatCompletionRequestDeveloperMessage),
712 System(ChatCompletionRequestSystemMessage),
713 User(ChatCompletionRequestUserMessage),
714 Assistant(ChatCompletionRequestAssistantMessage),
715 Tool(ChatCompletionRequestToolMessage),
716 Function(ChatCompletionRequestFunctionMessage),
717}
718
719pub type ServiceTierResponse = ServiceTier;
721
722#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
728pub struct ChatCompletionResponseMessage {
729 pub content: Option<ChatCompletionMessageContent>,
733 #[serde(skip_serializing_if = "Option::is_none")]
734 pub refusal: Option<String>,
735 #[serde(skip_serializing_if = "Option::is_none")]
736 pub tool_calls: Option<Vec<ChatCompletionMessageToolCall>>,
737 pub role: Role,
738 #[serde(skip_serializing_if = "Option::is_none")]
739 #[deprecated]
740 pub function_call: Option<FunctionCall>,
741 #[serde(skip_serializing_if = "Option::is_none")]
742 pub audio: Option<ChatCompletionResponseMessageAudio>,
743 pub reasoning_content: Option<String>,
745}
746
747#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
752pub struct ChatCompletionStreamOptions {
753 pub include_usage: bool,
754 #[serde(default)]
757 pub continuous_usage_stats: bool,
758}
759
760#[derive(Clone, Serialize, Default, Debug, Builder, Deserialize, PartialEq)]
767#[builder(name = "CreateChatCompletionRequestArgs")]
768#[builder(pattern = "mutable")]
769#[builder(setter(into, strip_option), default)]
770#[builder(derive(Debug))]
771#[builder(build_fn(error = "OpenAIError"))]
772pub struct CreateChatCompletionRequest {
773 pub messages: Vec<ChatCompletionRequestMessage>,
774 pub model: String,
775 #[serde(skip_serializing_if = "Option::is_none")]
777 pub mm_processor_kwargs: Option<serde_json::Value>,
778 #[serde(skip_serializing_if = "Option::is_none")]
779 pub store: Option<bool>,
780 #[serde(skip_serializing_if = "Option::is_none")]
781 pub reasoning_effort: Option<ReasoningEffort>,
782 #[serde(skip_serializing_if = "Option::is_none")]
783 pub metadata: Option<serde_json::Value>,
784 #[serde(skip_serializing_if = "Option::is_none")]
785 pub frequency_penalty: Option<f32>,
786 #[serde(skip_serializing_if = "Option::is_none")]
787 pub logit_bias: Option<std::collections::HashMap<String, serde_json::Value>>,
788 #[serde(skip_serializing_if = "Option::is_none")]
789 pub logprobs: Option<bool>,
790 #[serde(skip_serializing_if = "Option::is_none")]
791 pub top_logprobs: Option<u8>,
792 #[deprecated]
793 #[serde(skip_serializing_if = "Option::is_none")]
794 pub max_tokens: Option<u32>,
795 #[serde(skip_serializing_if = "Option::is_none")]
796 pub max_completion_tokens: Option<u32>,
797 #[serde(skip_serializing_if = "Option::is_none")]
798 pub n: Option<u8>,
799 #[serde(skip_serializing_if = "Option::is_none")]
800 pub modalities: Option<Vec<async_openai::types::chat::ResponseModalities>>,
801 #[serde(skip_serializing_if = "Option::is_none")]
802 pub prediction: Option<PredictionContent>,
803 #[serde(skip_serializing_if = "Option::is_none")]
804 pub audio: Option<ChatCompletionAudio>,
805 #[serde(skip_serializing_if = "Option::is_none")]
806 pub presence_penalty: Option<f32>,
807 #[serde(skip_serializing_if = "Option::is_none")]
808 pub response_format: Option<ResponseFormat>,
809 #[serde(skip_serializing_if = "Option::is_none")]
810 pub seed: Option<i64>,
811 #[serde(skip_serializing_if = "Option::is_none")]
812 pub service_tier: Option<ServiceTier>,
813 #[serde(skip_serializing_if = "Option::is_none")]
814 pub stop: Option<Stop>,
815 #[serde(default, skip_serializing_if = "Option::is_none")]
816 pub stream: Option<bool>,
817 #[serde(skip_serializing_if = "Option::is_none")]
818 pub stream_options: Option<ChatCompletionStreamOptions>,
819 #[serde(skip_serializing_if = "Option::is_none")]
820 pub temperature: Option<f32>,
821 #[serde(skip_serializing_if = "Option::is_none")]
822 pub top_p: Option<f32>,
823 #[serde(skip_serializing_if = "Option::is_none")]
824 pub tools: Option<Vec<ChatCompletionTool>>,
825 #[serde(skip_serializing_if = "Option::is_none")]
826 pub tool_choice: Option<ChatCompletionToolChoiceOption>,
827 #[serde(skip_serializing_if = "Option::is_none")]
828 pub parallel_tool_calls: Option<bool>,
829 #[serde(skip_serializing_if = "Option::is_none")]
830 pub user: Option<String>,
831 #[deprecated]
832 #[serde(skip_serializing_if = "Option::is_none")]
833 pub function_call: Option<ChatCompletionFunctionCall>,
834 #[deprecated]
835 #[serde(skip_serializing_if = "Option::is_none")]
836 pub functions: Option<Vec<ChatCompletionFunctions>>,
837 #[serde(skip_serializing_if = "Option::is_none")]
838 pub web_search_options: Option<WebSearchOptions>,
839}
840
841#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
845pub struct ChatChoice {
846 pub index: u32,
847 pub message: ChatCompletionResponseMessage,
848 pub finish_reason: Option<FinishReason>,
849 pub logprobs: Option<ChatChoiceLogprobs>,
850}
851
852#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
854pub struct CreateChatCompletionResponse {
855 pub id: String,
856 pub choices: Vec<ChatChoice>,
857 pub created: u32,
858 pub model: String,
859 pub service_tier: Option<ServiceTierResponse>,
860 pub system_fingerprint: Option<String>,
861 pub object: String,
862 pub usage: Option<CompletionUsage>,
863}
864
865pub type ChatCompletionResponseStream =
866 Pin<Box<dyn Stream<Item = Result<CreateChatCompletionStreamResponse, OpenAIError>> + Send>>;
867
868#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
874pub struct ChatCompletionStreamResponseDelta {
875 #[serde(skip_serializing_if = "Option::is_none")]
876 pub content: Option<ChatCompletionMessageContent>,
877 #[serde(skip_serializing_if = "Option::is_none")]
878 pub function_call: Option<ChatCompletionStreamResponseDeltaFunctionCall>,
879 #[serde(skip_serializing_if = "Option::is_none")]
880 pub tool_calls: Option<Vec<ChatCompletionMessageToolCallChunk>>,
881 #[serde(skip_serializing_if = "Option::is_none")]
882 pub role: Option<Role>,
883 #[serde(skip_serializing_if = "Option::is_none")]
884 pub refusal: Option<String>,
885 #[serde(skip_serializing_if = "Option::is_none")]
887 pub reasoning_content: Option<String>,
888}
889
890#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
891pub struct ChatCompletionStreamResponseDeltaFunctionCall {
892 pub name: Option<String>,
893 #[serde(default, deserialize_with = "deserialize_arguments_opt")]
894 pub arguments: Option<String>,
895}
896
897#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
899pub struct ChatChoiceStream {
900 pub index: u32,
901 pub delta: ChatCompletionStreamResponseDelta,
902 pub finish_reason: Option<FinishReason>,
903 pub logprobs: Option<ChatChoiceLogprobs>,
904}
905
906#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
908pub struct CreateChatCompletionStreamResponse {
909 pub id: String,
910 pub choices: Vec<ChatChoiceStream>,
911 pub created: u32,
912 pub model: String,
913 pub service_tier: Option<ServiceTierResponse>,
914 pub system_fingerprint: Option<String>,
915 pub object: String,
916 pub usage: Option<CompletionUsage>,
917}
918
919#[cfg(test)]
920mod tests {
921 use super::*;
922
923 #[test]
924 fn stop_accepts_token_id_array() {
925 let stop: Stop = serde_json::from_value(serde_json::json!([32, 34])).unwrap();
926
927 assert_eq!(stop, Stop::TokenIdArray(vec![32, 34]));
928 }
929
930 #[test]
931 fn stop_accepts_string_and_string_array() {
932 let stop: Stop = serde_json::from_value(serde_json::json!(" The")).unwrap();
933
934 assert_eq!(stop, Stop::String(" The".to_string()));
935
936 let stop: Stop = serde_json::from_value(serde_json::json!(["A", "B"])).unwrap();
937
938 assert_eq!(
939 stop,
940 Stop::StringArray(vec!["A".to_string(), "B".to_string()])
941 );
942 }
943
944 #[test]
945 fn stop_token_id_display_string_remains_string_stop() {
946 let stop: Stop = serde_json::from_value(serde_json::json!("token_id:576")).unwrap();
947
948 assert_eq!(stop, Stop::String("token_id:576".to_string()));
949
950 let stop: Stop = serde_json::from_value(serde_json::json!(["token_id:576"])).unwrap();
951
952 assert_eq!(stop, Stop::StringArray(vec!["token_id:576".to_string()]));
953 }
954
955 #[test]
956 fn stop_rejects_single_token_id() {
957 let result = serde_json::from_value::<Stop>(serde_json::json!(576));
958
959 assert!(result.is_err());
960 }
961
962 #[test]
963 fn stop_converts_from_upstream_stop_configuration() {
964 let upstream =
965 async_openai::types::chat::StopConfiguration::StringArray(vec!["END".to_string()]);
966
967 assert_eq!(
968 Stop::from(upstream),
969 Stop::StringArray(vec!["END".to_string()])
970 );
971 }
972
973 #[test]
974 fn request_builder_accepts_upstream_reasoning_effort() {
975 let request = CreateChatCompletionRequestArgs::default()
976 .reasoning_effort(async_openai::types::chat::ReasoningEffort::High)
977 .build()
978 .unwrap();
979
980 assert_eq!(request.reasoning_effort, Some(ReasoningEffort::High));
981 }
982
983 #[test]
984 fn tool_call_defaults_type_on_deserialize() {
985 let tool_call: ChatCompletionMessageToolCall = serde_json::from_value(serde_json::json!({
986 "id": "call_123",
987 "function": {
988 "name": "get_weather",
989 "arguments": "{\"location\":\"SF\"}"
990 }
991 }))
992 .unwrap();
993
994 assert_eq!(tool_call.r#type, FunctionType::Function);
995 }
996
997 #[test]
998 fn tool_call_serializes_type_for_wire_compat() {
999 let tool_call = ChatCompletionMessageToolCall {
1000 id: "call_123".into(),
1001 r#type: FunctionType::Function,
1002 function: FunctionCall {
1003 name: "get_weather".into(),
1004 arguments: "{\"location\":\"SF\"}".into(),
1005 },
1006 };
1007
1008 let json = serde_json::to_value(tool_call).unwrap();
1009 assert_eq!(json["type"], "function");
1010 }
1011
1012 #[test]
1015 fn function_call_accepts_string_arguments() {
1016 let fc: FunctionCall = serde_json::from_value(serde_json::json!({
1017 "name": "get_weather",
1018 "arguments": "{\"location\":\"SF\"}"
1019 }))
1020 .unwrap();
1021 assert_eq!(fc.arguments, "{\"location\":\"SF\"}");
1022 }
1023
1024 #[test]
1025 fn function_call_accepts_dict_arguments() {
1026 let fc: FunctionCall = serde_json::from_value(serde_json::json!({
1027 "name": "get_weather",
1028 "arguments": {"location": "SF"}
1029 }))
1030 .unwrap();
1031 assert_eq!(fc.arguments, "{\"location\":\"SF\"}");
1032 }
1033
1034 #[test]
1035 fn function_call_rejects_integer_arguments() {
1036 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
1037 "name": "f",
1038 "arguments": 42
1039 }));
1040 assert!(result.is_err());
1041 }
1042
1043 #[test]
1044 fn function_call_rejects_boolean_arguments() {
1045 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
1046 "name": "f",
1047 "arguments": true
1048 }));
1049 assert!(result.is_err());
1050 }
1051
1052 #[test]
1053 fn function_call_rejects_null_arguments() {
1054 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
1055 "name": "f",
1056 "arguments": null
1057 }));
1058 assert!(result.is_err());
1059 }
1060
1061 #[test]
1062 fn function_call_rejects_array_arguments() {
1063 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
1064 "name": "f",
1065 "arguments": [1, 2, 3]
1066 }));
1067 assert!(result.is_err());
1068 }
1069
1070 #[test]
1071 fn function_call_stream_null_arguments_produces_none() {
1072 let fcs: FunctionCallStream = serde_json::from_value(serde_json::json!({
1073 "name": "f",
1074 "arguments": null
1075 }))
1076 .unwrap();
1077 assert_eq!(fcs.arguments, None);
1078 }
1079
1080 #[test]
1081 fn function_call_stream_rejects_integer_arguments() {
1082 let result = serde_json::from_value::<FunctionCallStream>(serde_json::json!({
1083 "name": "f",
1084 "arguments": 42
1085 }));
1086 assert!(result.is_err());
1087 }
1088
1089 #[test]
1090 fn function_call_stream_rejects_boolean_arguments() {
1091 let result = serde_json::from_value::<FunctionCallStream>(serde_json::json!({
1092 "name": "f",
1093 "arguments": true
1094 }));
1095 assert!(result.is_err());
1096 }
1097
1098 #[test]
1099 fn function_call_stream_accepts_dict_arguments() {
1100 let fcs: FunctionCallStream = serde_json::from_value(serde_json::json!({
1101 "name": "get_weather",
1102 "arguments": {"location": "SF"}
1103 }))
1104 .unwrap();
1105 assert_eq!(fcs.arguments.as_deref(), Some("{\"location\":\"SF\"}"));
1106 }
1107
1108 #[test]
1109 fn function_call_stream_accepts_null_arguments() {
1110 let fcs: FunctionCallStream = serde_json::from_value(serde_json::json!({
1111 "name": "get_weather"
1112 }))
1113 .unwrap();
1114 assert_eq!(fcs.arguments, None);
1115 }
1116
1117 #[test]
1118 fn tool_call_with_dict_arguments_roundtrip() {
1119 let tc: ChatCompletionMessageToolCall = serde_json::from_value(serde_json::json!({
1120 "id": "call_abc",
1121 "type": "function",
1122 "function": {
1123 "name": "search",
1124 "arguments": {"query": "hello", "limit": 10}
1125 }
1126 }))
1127 .unwrap();
1128 let parsed: serde_json::Value = serde_json::from_str(&tc.function.arguments).unwrap();
1130 assert_eq!(parsed, serde_json::json!({"query": "hello", "limit": 10}));
1131 let json = serde_json::to_value(&tc).unwrap();
1133 assert!(json["function"]["arguments"].is_string());
1134 }
1135
1136 #[test]
1137 fn stream_delta_function_call_accepts_dict_arguments() {
1138 let delta: ChatCompletionStreamResponseDeltaFunctionCall =
1139 serde_json::from_value(serde_json::json!({
1140 "name": "get_weather",
1141 "arguments": {"location": "SF"}
1142 }))
1143 .unwrap();
1144 assert_eq!(delta.arguments.as_deref(), Some("{\"location\":\"SF\"}"));
1145 }
1146
1147 fn parse_content_part(json: serde_json::Value) -> ChatCompletionRequestUserMessageContentPart {
1148 serde_json::from_value(json).expect("content part deserialization failed")
1149 }
1150
1151 #[test]
1152 fn image_url_url_and_top_level_uuid() {
1153 let part = parse_content_part(serde_json::json!({
1154 "type": "image_url",
1155 "image_url": {"url": "https://x.example/y.png"},
1156 "uuid": "image-123"
1157 }));
1158
1159 match part {
1160 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1161 assert_eq!(part.uuid.as_deref(), Some("image-123"));
1162 assert_eq!(
1163 part.image_url.as_ref().map(|image| image.url.as_str()),
1164 Some("https://x.example/y.png")
1165 );
1166 }
1167 _ => panic!("expected image_url part"),
1168 }
1169 }
1170
1171 #[test]
1172 fn image_url_null_and_top_level_uuid() {
1173 let part = parse_content_part(serde_json::json!({
1174 "type": "image_url",
1175 "image_url": null,
1176 "uuid": "sku-1234-a"
1177 }));
1178
1179 match part {
1180 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1181 assert!(part.image_url.is_none());
1182 assert_eq!(part.uuid.as_deref(), Some("sku-1234-a"));
1183 }
1184 _ => panic!("expected image_url part"),
1185 }
1186 }
1187
1188 #[test]
1189 fn image_url_null_without_uuid_deserializes_for_use_site_validation() {
1190 let part = parse_content_part(serde_json::json!({
1191 "type": "image_url",
1192 "image_url": null
1193 }));
1194
1195 match part {
1196 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1197 assert!(part.image_url.is_none());
1198 assert!(part.uuid.is_none());
1199 }
1200 _ => panic!("expected image_url part"),
1201 }
1202 }
1203
1204 #[test]
1205 fn image_url_serialize_uuid_only_uses_null_image_url() {
1206 let part = ChatCompletionRequestMessageContentPartImage {
1207 image_url: None,
1208 uuid: Some("image-123".to_string()),
1209 };
1210 let json = serde_json::to_value(part).unwrap();
1211
1212 assert!(json["image_url"].is_null());
1213 assert_eq!(json["uuid"], "image-123");
1214 }
1215
1216 #[test]
1217 fn cached_media_builders_allow_omitting_urls() {
1218 let image = ChatCompletionRequestMessageContentPartImageArgs::default()
1219 .uuid("image-123")
1220 .build()
1221 .unwrap();
1222 let video = ChatCompletionRequestMessageContentPartVideoArgs::default()
1223 .uuid("video-123")
1224 .build()
1225 .unwrap();
1226 let audio = ChatCompletionRequestMessageContentPartAudioUrlArgs::default()
1227 .uuid("audio-123")
1228 .build()
1229 .unwrap();
1230
1231 let image_json = serde_json::to_value(image).unwrap();
1232 let video_json = serde_json::to_value(video).unwrap();
1233 let audio_json = serde_json::to_value(audio).unwrap();
1234 assert!(image_json["image_url"].is_null());
1235 assert!(video_json["video_url"].is_null());
1236 assert!(audio_json["audio_url"].is_null());
1237 }
1238
1239 #[test]
1240 fn image_url_uuid_accepts_opaque_string() {
1241 let part = parse_content_part(serde_json::json!({
1242 "type": "image_url",
1243 "image_url": {"url": "https://x.example/y.png"},
1244 "uuid": "img-ac3921de680bb217"
1245 }));
1246
1247 match part {
1248 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1249 assert_eq!(part.uuid.as_deref(), Some("img-ac3921de680bb217"));
1250 }
1251 _ => panic!("expected image_url part"),
1252 }
1253 }
1254
1255 #[test]
1256 fn url_conversions_preserve_required_urls() {
1257 let image: ImageUrl = "https://x.example/image.png".into();
1258 let video: VideoUrl = "https://x.example/video.mp4".into();
1259 let audio: AudioUrl = "https://x.example/audio.wav".into();
1260
1261 assert_eq!(image.url.as_str(), "https://x.example/image.png");
1262 assert_eq!(video.url.as_str(), "https://x.example/video.mp4");
1263 assert_eq!(audio.url.as_str(), "https://x.example/audio.wav");
1264 }
1265
1266 #[test]
1267 fn legacy_nested_media_uuids_remain_accepted() {
1268 let legacy_uuid = "92b888ad-e64a-478f-b688-5091e16544e3";
1269
1270 for (part_type, media_field, url) in [
1271 ("image_url", "image_url", "https://x.example/image.png"),
1272 ("video_url", "video_url", "https://x.example/video.mp4"),
1273 ("audio_url", "audio_url", "https://x.example/audio.wav"),
1274 ] {
1275 let part = parse_content_part(serde_json::json!({
1276 "type": part_type,
1277 (media_field): {"url": url, "uuid": legacy_uuid}
1278 }));
1279 let json = serde_json::to_value(part).unwrap();
1280
1281 assert_eq!(json[media_field]["url"], url);
1282 assert_eq!(json[media_field]["uuid"], legacy_uuid);
1283 assert!(json.get("uuid").is_none());
1284 }
1285 }
1286
1287 #[test]
1288 fn video_url_null_and_top_level_uuid() {
1289 let part = parse_content_part(serde_json::json!({
1290 "type": "video_url",
1291 "video_url": null,
1292 "uuid": "video-cache-key"
1293 }));
1294
1295 match part {
1296 ChatCompletionRequestUserMessageContentPart::VideoUrl(part) => {
1297 assert!(part.video_url.is_none());
1298 assert_eq!(part.uuid.as_deref(), Some("video-cache-key"));
1299 }
1300 _ => panic!("expected video_url part"),
1301 }
1302 }
1303
1304 #[test]
1305 fn audio_url_null_and_top_level_uuid() {
1306 let part = parse_content_part(serde_json::json!({
1307 "type": "audio_url",
1308 "audio_url": null,
1309 "uuid": "audio-cache-key"
1310 }));
1311
1312 match part {
1313 ChatCompletionRequestUserMessageContentPart::AudioUrl(part) => {
1314 assert!(part.audio_url.is_none());
1315 assert_eq!(part.uuid.as_deref(), Some("audio-cache-key"));
1316 }
1317 _ => panic!("expected audio_url part"),
1318 }
1319 }
1320
1321 #[test]
1322 fn message_content_array_preserves_uuid_alignment() {
1323 let payload = serde_json::json!({
1324 "role": "user",
1325 "content": [
1326 {"type": "text", "text": "describe these"},
1327 {
1328 "type": "image_url",
1329 "image_url": {"url": "https://x.example/img1.png"},
1330 "uuid": "image-1"
1331 },
1332 {"type": "image_url", "image_url": null, "uuid": "image-1"}
1333 ]
1334 });
1335 let message: ChatCompletionRequestUserMessage = serde_json::from_value(payload).unwrap();
1336 let ChatCompletionRequestUserMessageContent::Array(parts) = message.content else {
1337 panic!("expected content array");
1338 };
1339
1340 assert_eq!(parts.len(), 3);
1341 match &parts[1] {
1342 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1343 assert!(
1344 part.image_url
1345 .as_ref()
1346 .map(|image| image.url.as_str())
1347 .is_some()
1348 );
1349 assert_eq!(part.uuid.as_deref(), Some("image-1"));
1350 }
1351 _ => panic!("parts[1] should be image_url"),
1352 }
1353 match &parts[2] {
1354 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1355 assert!(part.image_url.is_none());
1356 assert_eq!(part.uuid.as_deref(), Some("image-1"));
1357 }
1358 _ => panic!("parts[2] should be image_url"),
1359 }
1360 }
1361
1362 #[test]
1363 fn tool_message_accepts_media_content() {
1364 let message: ChatCompletionRequestMessage = serde_json::from_value(serde_json::json!({
1365 "role": "tool",
1366 "tool_call_id": "call_media",
1367 "content": [
1368 {"type": "text", "text": "Screenshot captured"},
1369 {
1370 "type": "image_url",
1371 "image_url": {
1372 "url": "data:image/png;base64,aGVsbG8="
1373 }
1374 },
1375 {
1376 "type": "video_url",
1377 "video_url": {
1378 "url": "https://example.com/clip.mp4"
1379 }
1380 },
1381 {
1382 "type": "audio_url",
1383 "audio_url": {
1384 "url": "https://example.com/audio.wav"
1385 }
1386 }
1387 ]
1388 }))
1389 .unwrap();
1390
1391 let ChatCompletionRequestMessage::Tool(tool) = message else {
1392 panic!("expected tool message");
1393 };
1394 let ChatCompletionRequestToolMessageContent::Array(parts) = tool.content else {
1395 panic!("expected array content");
1396 };
1397 assert!(matches!(
1398 parts[1],
1399 ChatCompletionRequestToolMessageContentPart::ImageUrl(_)
1400 ));
1401 assert!(matches!(
1402 parts[2],
1403 ChatCompletionRequestToolMessageContentPart::VideoUrl(_)
1404 ));
1405 assert!(matches!(
1406 parts[3],
1407 ChatCompletionRequestToolMessageContentPart::AudioUrl(_)
1408 ));
1409 }
1410}