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 ChatCompletionRequestToolMessage,
49 ChatCompletionRequestToolMessageArgs,
50 ChatCompletionRequestToolMessageContent,
51 ChatCompletionRequestToolMessageContentPart,
52 ChatCompletionResponseMessageAudio,
53 ChatCompletionTokenLogprob,
54 Choice,
55 CompletionFinishReason,
56 CompletionTokensDetails,
57 CompletionUsage,
58 FunctionObject,
59 FunctionObjectArgs,
60 ImageDetail,
61 InputAudio,
62 InputAudioFormat,
63 Logprobs,
64 PredictionContent,
65 PredictionContentContent,
66 Prompt,
67 PromptTokensDetails,
68 ResponseFormat,
69 ResponseFormatJsonSchema,
70 Role,
71 ServiceTier,
72 TopLogprobs,
73 WebSearchContextSize,
74 WebSearchLocation,
75 WebSearchOptions,
76 WebSearchUserLocation,
77 WebSearchUserLocationType,
78};
79
80#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
88#[serde(untagged)]
89pub enum Stop {
90 String(String),
91 StringArray(Vec<String>),
92 TokenIdArray(Vec<u32>),
93}
94
95impl Stop {
96 pub fn strings(&self) -> Option<Vec<String>> {
97 match self {
98 Stop::String(s) => Some(vec![s.clone()]),
99 Stop::StringArray(arr) => Some(arr.clone()),
100 Stop::TokenIdArray(_) => None,
101 }
102 }
103
104 pub fn token_ids(&self) -> Option<Vec<u32>> {
105 match self {
106 Stop::TokenIdArray(arr) => Some(arr.clone()),
107 Stop::String(_) | Stop::StringArray(_) => None,
108 }
109 }
110}
111
112impl From<String> for Stop {
113 fn from(value: String) -> Self {
114 Stop::String(value)
115 }
116}
117
118impl From<&str> for Stop {
119 fn from(value: &str) -> Self {
120 Stop::String(value.to_string())
121 }
122}
123
124impl From<Vec<String>> for Stop {
125 fn from(value: Vec<String>) -> Self {
126 Stop::StringArray(value)
127 }
128}
129
130impl From<Vec<u32>> for Stop {
131 fn from(value: Vec<u32>) -> Self {
132 Stop::TokenIdArray(value)
133 }
134}
135
136impl From<async_openai::types::chat::StopConfiguration> for Stop {
137 fn from(value: async_openai::types::chat::StopConfiguration) -> Self {
138 match value {
139 async_openai::types::chat::StopConfiguration::String(value) => Stop::String(value),
140 async_openai::types::chat::StopConfiguration::StringArray(value) => {
141 Stop::StringArray(value)
142 }
143 }
144 }
145}
146
147pub use async_openai::types::chat::FinishReason;
149
150pub use async_openai::types::chat::FunctionType;
153
154#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
160#[serde(rename_all = "lowercase")]
161pub enum ReasoningEffort {
162 None,
163 Minimal,
164 Low,
165 Medium,
166 High,
167 Xhigh,
168 Max,
169}
170
171impl From<async_openai::types::chat::ReasoningEffort> for ReasoningEffort {
172 fn from(value: async_openai::types::chat::ReasoningEffort) -> Self {
173 match value {
174 async_openai::types::chat::ReasoningEffort::None => ReasoningEffort::None,
175 async_openai::types::chat::ReasoningEffort::Minimal => ReasoningEffort::Minimal,
176 async_openai::types::chat::ReasoningEffort::Low => ReasoningEffort::Low,
177 async_openai::types::chat::ReasoningEffort::Medium => ReasoningEffort::Medium,
178 async_openai::types::chat::ReasoningEffort::High => ReasoningEffort::High,
179 async_openai::types::chat::ReasoningEffort::Xhigh => ReasoningEffort::Xhigh,
180 }
181 }
182}
183
184fn deserialize_arguments<'de, D>(deserializer: D) -> Result<String, D::Error>
193where
194 D: serde::Deserializer<'de>,
195{
196 use serde::de::Error;
197 let value = serde_json::Value::deserialize(deserializer)?;
198 match value {
199 serde_json::Value::String(s) => Ok(s),
200 v @ serde_json::Value::Object(_) => {
201 Ok(serde_json::to_string(&v).unwrap())
203 }
204 other => Err(D::Error::custom(format!(
205 "expected string or object for `arguments`, got {other}"
206 ))),
207 }
208}
209
210fn deserialize_arguments_opt<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
211where
212 D: serde::Deserializer<'de>,
213{
214 use serde::de::Error;
215 let value = Option::<serde_json::Value>::deserialize(deserializer)?;
216 match value {
217 None => Ok(None),
218 Some(serde_json::Value::String(s)) => Ok(Some(s)),
219 Some(v @ serde_json::Value::Object(_)) => serde_json::to_string(&v)
220 .map(Some)
221 .map_err(|e| D::Error::custom(e.to_string())),
222 Some(other) => Err(D::Error::custom(format!(
223 "expected string or object for `arguments`, got {other}"
224 ))),
225 }
226}
227
228#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)]
241pub struct FunctionCall {
242 pub name: String,
243 #[serde(deserialize_with = "deserialize_arguments")]
244 pub arguments: String,
245}
246
247#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)]
249pub struct FunctionCallStream {
250 pub name: Option<String>,
251 #[serde(default, deserialize_with = "deserialize_arguments_opt")]
252 pub arguments: Option<String>,
253}
254
255#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)]
261pub struct ChatCompletionMessageToolCallChunk {
262 pub index: u32,
263 pub id: Option<String>,
264 pub r#type: Option<FunctionType>,
265 pub function: Option<FunctionCallStream>,
266}
267
268#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
279#[builder(name = "ChatCompletionRequestMessageContentPartImageArgs")]
280#[builder(pattern = "mutable")]
281#[builder(setter(into, strip_option))]
282#[builder(derive(Debug))]
283#[builder(build_fn(error = "OpenAIError"))]
284pub struct ChatCompletionRequestMessageContentPartImage {
285 #[builder(default)]
286 #[serde(default)]
287 pub image_url: Option<ImageUrl>,
288 #[builder(default)]
289 #[serde(skip_serializing_if = "Option::is_none")]
290 pub uuid: Option<String>,
292}
293
294#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
299#[builder(name = "ImageUrlArgs")]
300#[builder(pattern = "mutable")]
301#[builder(setter(into, strip_option))]
302#[builder(derive(Debug))]
303#[builder(build_fn(error = "OpenAIError"))]
304pub struct ImageUrl {
305 pub url: Url,
306 pub detail: Option<ImageDetail>,
307 #[deprecated(note = "use the content-part `uuid` field for vLLM cache identities")]
308 #[serde(skip_serializing_if = "Option::is_none")]
309 pub uuid: Option<Uuid>,
310}
311
312#[derive(Clone, Serialize, Default, Debug, Deserialize, PartialEq)]
313#[serde(rename_all = "lowercase")]
314pub enum ChatCompletionToolType {
315 #[default]
316 Function,
317}
318
319#[derive(Clone, Serialize, Default, Debug, Deserialize, PartialEq)]
320pub struct FunctionName {
321 pub name: String,
322}
323
324#[derive(Clone, Serialize, Default, Debug, Deserialize, PartialEq)]
325pub struct ChatCompletionNamedToolChoice {
326 pub r#type: ChatCompletionToolType,
327 pub function: FunctionName,
328}
329
330fn default_function_type() -> FunctionType {
331 FunctionType::Function
332}
333
334#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
340pub struct ChatCompletionMessageToolCall {
341 pub id: String,
342 #[serde(default = "default_function_type")]
343 pub r#type: FunctionType,
344 pub function: FunctionCall,
345}
346
347#[derive(Clone, Serialize, Default, Debug, Deserialize, PartialEq)]
349#[serde(rename_all = "lowercase")]
350pub enum ChatCompletionToolChoiceOption {
351 #[default]
352 None,
353 Auto,
354 Required,
355 #[serde(untagged)]
356 Named(ChatCompletionNamedToolChoice),
357}
358
359#[derive(Clone, Serialize, Default, Debug, Builder, Deserialize, PartialEq)]
360#[builder(name = "ChatCompletionToolArgs")]
361#[builder(pattern = "mutable")]
362#[builder(setter(into, strip_option), default)]
363#[builder(derive(Debug))]
364#[builder(build_fn(error = "OpenAIError"))]
365pub struct ChatCompletionTool {
366 #[builder(default = "ChatCompletionToolType::Function")]
367 pub r#type: ChatCompletionToolType,
368 pub function: FunctionObject,
369}
370
371#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
382#[serde(untagged)]
383pub enum StopReason {
384 String(String),
385 Int(i64),
386 IntArray(Vec<i64>),
387}
388
389#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
399#[serde(untagged)]
400pub enum ReasoningContent {
401 Text(String),
403 Segments(Vec<String>),
406}
407
408impl ReasoningContent {
409 pub fn to_flat_string(&self) -> String {
411 match self {
412 ReasoningContent::Text(s) => s.clone(),
413 ReasoningContent::Segments(segs) => segs
414 .iter()
415 .filter(|s| !s.is_empty())
416 .cloned()
417 .collect::<Vec<_>>()
418 .join("\n"),
419 }
420 }
421
422 pub fn segments(&self) -> Option<&[String]> {
424 match self {
425 ReasoningContent::Segments(segs) => Some(segs),
426 ReasoningContent::Text(_) => None,
427 }
428 }
429}
430
431#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
435pub struct ChatCompletionResponseContentPartText {
436 pub text: String,
437}
438
439#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
441pub struct ChatCompletionResponseContentPartImageUrl {
442 pub image_url: ImageUrlResponse,
443}
444
445#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
447pub struct ChatCompletionResponseContentPartVideoUrl {
448 pub video_url: VideoUrlResponse,
449}
450
451#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
453pub struct ChatCompletionResponseContentPartAudioUrl {
454 pub audio_url: AudioUrlResponse,
455}
456
457#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
458pub struct ImageUrlResponse {
459 pub url: String,
460 #[serde(skip_serializing_if = "Option::is_none")]
461 pub detail: Option<String>,
462}
463
464#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
465pub struct VideoUrlResponse {
466 pub url: String,
467}
468
469#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
470pub struct AudioUrlResponse {
471 pub url: String,
472}
473
474#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
476#[serde(tag = "type", rename_all = "snake_case")]
477pub enum ChatCompletionResponseContentPart {
478 Text(ChatCompletionResponseContentPartText),
479 ImageUrl(ChatCompletionResponseContentPartImageUrl),
480 VideoUrl(ChatCompletionResponseContentPartVideoUrl),
481 AudioUrl(ChatCompletionResponseContentPartAudioUrl),
482}
483
484#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
490#[serde(untagged)]
491pub enum ChatCompletionMessageContent {
492 Text(String),
494 Parts(Vec<ChatCompletionResponseContentPart>),
496}
497
498#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
501#[builder(name = "VideoUrlArgs")]
502#[builder(pattern = "mutable")]
503#[builder(setter(into, strip_option))]
504#[builder(derive(Debug))]
505#[builder(build_fn(error = "OpenAIError"))]
506pub struct VideoUrl {
507 pub url: Url,
508 pub detail: Option<ImageDetail>,
509 #[deprecated(note = "use the content-part `uuid` field for vLLM cache identities")]
510 #[serde(skip_serializing_if = "Option::is_none")]
511 pub uuid: Option<Uuid>,
512}
513
514#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
515#[builder(name = "ChatCompletionRequestMessageContentPartVideoArgs")]
516#[builder(pattern = "mutable")]
517#[builder(setter(into, strip_option))]
518#[builder(derive(Debug))]
519#[builder(build_fn(error = "OpenAIError"))]
520pub struct ChatCompletionRequestMessageContentPartVideo {
521 #[builder(default)]
522 #[serde(default)]
523 pub video_url: Option<VideoUrl>,
524 #[builder(default)]
525 #[serde(skip_serializing_if = "Option::is_none")]
526 pub uuid: Option<String>,
528}
529
530#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
531#[builder(name = "AudioUrlArgs")]
532#[builder(pattern = "mutable")]
533#[builder(setter(into, strip_option))]
534#[builder(derive(Debug))]
535#[builder(build_fn(error = "OpenAIError"))]
536pub struct AudioUrl {
537 pub url: Url,
538 #[deprecated(note = "use the content-part `uuid` field for vLLM cache identities")]
539 #[serde(skip_serializing_if = "Option::is_none")]
540 pub uuid: Option<Uuid>,
541}
542
543#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
544#[builder(name = "ChatCompletionRequestMessageContentPartAudioUrlArgs")]
545#[builder(pattern = "mutable")]
546#[builder(setter(into, strip_option))]
547#[builder(derive(Debug))]
548#[builder(build_fn(error = "OpenAIError"))]
549pub struct ChatCompletionRequestMessageContentPartAudioUrl {
550 #[builder(default)]
551 #[serde(default)]
552 pub audio_url: Option<AudioUrl>,
553 #[builder(default)]
554 #[serde(skip_serializing_if = "Option::is_none")]
555 pub uuid: Option<String>,
557}
558
559#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
563#[serde(untagged)]
564pub enum ChatCompletionRequestUserMessageContent {
565 Text(String),
566 Array(Vec<ChatCompletionRequestUserMessageContentPart>),
567}
568
569#[derive(Debug, Serialize, Deserialize, Default, Clone, Builder, PartialEq)]
570#[builder(name = "ChatCompletionRequestUserMessageArgs")]
571#[builder(pattern = "mutable")]
572#[builder(setter(into, strip_option), default)]
573#[builder(derive(Debug))]
574#[builder(build_fn(error = "OpenAIError"))]
575pub struct ChatCompletionRequestUserMessage {
576 pub content: ChatCompletionRequestUserMessageContent,
577 #[serde(skip_serializing_if = "Option::is_none")]
578 pub name: Option<String>,
579}
580
581impl Default for ChatCompletionRequestUserMessageContent {
582 fn default() -> Self {
583 Self::Text(String::new())
584 }
585}
586
587impl From<&str> for ChatCompletionRequestUserMessageContent {
588 fn from(value: &str) -> Self {
589 Self::Text(value.into())
590 }
591}
592
593impl From<String> for ChatCompletionRequestUserMessageContent {
594 fn from(value: String) -> Self {
595 Self::Text(value)
596 }
597}
598
599impl From<Vec<ChatCompletionRequestUserMessageContentPart>>
600 for ChatCompletionRequestUserMessageContent
601{
602 fn from(value: Vec<ChatCompletionRequestUserMessageContentPart>) -> Self {
603 Self::Array(value)
604 }
605}
606
607#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
613#[serde(tag = "type")]
614#[serde(rename_all = "snake_case")]
615pub enum ChatCompletionRequestUserMessageContentPart {
616 Text(ChatCompletionRequestMessageContentPartText),
617 ImageUrl(ChatCompletionRequestMessageContentPartImage),
618 VideoUrl(ChatCompletionRequestMessageContentPartVideo),
619 AudioUrl(ChatCompletionRequestMessageContentPartAudioUrl),
620 InputAudio(ChatCompletionRequestMessageContentPartAudio),
621}
622
623#[derive(Debug, Serialize, Deserialize, Default, Clone, Builder, PartialEq)]
629#[builder(name = "ChatCompletionRequestAssistantMessageArgs")]
630#[builder(pattern = "mutable")]
631#[builder(setter(into, strip_option), default)]
632#[builder(derive(Debug))]
633#[builder(build_fn(error = "OpenAIError"))]
634pub struct ChatCompletionRequestAssistantMessage {
635 #[serde(skip_serializing_if = "Option::is_none")]
636 pub content: Option<ChatCompletionRequestAssistantMessageContent>,
637 #[serde(skip_serializing_if = "Option::is_none")]
639 pub reasoning_content: Option<ReasoningContent>,
640 #[serde(skip_serializing_if = "Option::is_none")]
641 pub refusal: Option<String>,
642 #[serde(skip_serializing_if = "Option::is_none")]
643 pub name: Option<String>,
644 #[serde(skip_serializing_if = "Option::is_none")]
645 pub audio: Option<ChatCompletionRequestAssistantMessageAudio>,
646 #[serde(skip_serializing_if = "Option::is_none")]
647 pub tool_calls: Option<Vec<ChatCompletionMessageToolCall>>,
648 #[deprecated]
649 #[serde(skip_serializing_if = "Option::is_none")]
650 pub function_call: Option<FunctionCall>,
651}
652
653#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
659#[serde(tag = "role")]
660#[serde(rename_all = "lowercase")]
661pub enum ChatCompletionRequestMessage {
662 Developer(ChatCompletionRequestDeveloperMessage),
663 System(ChatCompletionRequestSystemMessage),
664 User(ChatCompletionRequestUserMessage),
665 Assistant(ChatCompletionRequestAssistantMessage),
666 Tool(ChatCompletionRequestToolMessage),
667 Function(ChatCompletionRequestFunctionMessage),
668}
669
670pub type ServiceTierResponse = ServiceTier;
672
673#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
679pub struct ChatCompletionResponseMessage {
680 pub content: Option<ChatCompletionMessageContent>,
684 #[serde(skip_serializing_if = "Option::is_none")]
685 pub refusal: Option<String>,
686 #[serde(skip_serializing_if = "Option::is_none")]
687 pub tool_calls: Option<Vec<ChatCompletionMessageToolCall>>,
688 pub role: Role,
689 #[serde(skip_serializing_if = "Option::is_none")]
690 #[deprecated]
691 pub function_call: Option<FunctionCall>,
692 #[serde(skip_serializing_if = "Option::is_none")]
693 pub audio: Option<ChatCompletionResponseMessageAudio>,
694 pub reasoning_content: Option<String>,
696}
697
698#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
703pub struct ChatCompletionStreamOptions {
704 pub include_usage: bool,
705 #[serde(default)]
708 pub continuous_usage_stats: bool,
709}
710
711#[derive(Clone, Serialize, Default, Debug, Builder, Deserialize, PartialEq)]
718#[builder(name = "CreateChatCompletionRequestArgs")]
719#[builder(pattern = "mutable")]
720#[builder(setter(into, strip_option), default)]
721#[builder(derive(Debug))]
722#[builder(build_fn(error = "OpenAIError"))]
723pub struct CreateChatCompletionRequest {
724 pub messages: Vec<ChatCompletionRequestMessage>,
725 pub model: String,
726 #[serde(skip_serializing_if = "Option::is_none")]
728 pub mm_processor_kwargs: Option<serde_json::Value>,
729 #[serde(skip_serializing_if = "Option::is_none")]
730 pub store: Option<bool>,
731 #[serde(skip_serializing_if = "Option::is_none")]
732 pub reasoning_effort: Option<ReasoningEffort>,
733 #[serde(skip_serializing_if = "Option::is_none")]
734 pub metadata: Option<serde_json::Value>,
735 #[serde(skip_serializing_if = "Option::is_none")]
736 pub frequency_penalty: Option<f32>,
737 #[serde(skip_serializing_if = "Option::is_none")]
738 pub logit_bias: Option<std::collections::HashMap<String, serde_json::Value>>,
739 #[serde(skip_serializing_if = "Option::is_none")]
740 pub logprobs: Option<bool>,
741 #[serde(skip_serializing_if = "Option::is_none")]
742 pub top_logprobs: Option<u8>,
743 #[deprecated]
744 #[serde(skip_serializing_if = "Option::is_none")]
745 pub max_tokens: Option<u32>,
746 #[serde(skip_serializing_if = "Option::is_none")]
747 pub max_completion_tokens: Option<u32>,
748 #[serde(skip_serializing_if = "Option::is_none")]
749 pub n: Option<u8>,
750 #[serde(skip_serializing_if = "Option::is_none")]
751 pub modalities: Option<Vec<async_openai::types::chat::ResponseModalities>>,
752 #[serde(skip_serializing_if = "Option::is_none")]
753 pub prediction: Option<PredictionContent>,
754 #[serde(skip_serializing_if = "Option::is_none")]
755 pub audio: Option<ChatCompletionAudio>,
756 #[serde(skip_serializing_if = "Option::is_none")]
757 pub presence_penalty: Option<f32>,
758 #[serde(skip_serializing_if = "Option::is_none")]
759 pub response_format: Option<ResponseFormat>,
760 #[serde(skip_serializing_if = "Option::is_none")]
761 pub seed: Option<i64>,
762 #[serde(skip_serializing_if = "Option::is_none")]
763 pub service_tier: Option<ServiceTier>,
764 #[serde(skip_serializing_if = "Option::is_none")]
765 pub stop: Option<Stop>,
766 #[serde(default, skip_serializing_if = "Option::is_none")]
767 pub stream: Option<bool>,
768 #[serde(skip_serializing_if = "Option::is_none")]
769 pub stream_options: Option<ChatCompletionStreamOptions>,
770 #[serde(skip_serializing_if = "Option::is_none")]
771 pub temperature: Option<f32>,
772 #[serde(skip_serializing_if = "Option::is_none")]
773 pub top_p: Option<f32>,
774 #[serde(skip_serializing_if = "Option::is_none")]
775 pub tools: Option<Vec<ChatCompletionTool>>,
776 #[serde(skip_serializing_if = "Option::is_none")]
777 pub tool_choice: Option<ChatCompletionToolChoiceOption>,
778 #[serde(skip_serializing_if = "Option::is_none")]
779 pub parallel_tool_calls: Option<bool>,
780 #[serde(skip_serializing_if = "Option::is_none")]
781 pub user: Option<String>,
782 #[deprecated]
783 #[serde(skip_serializing_if = "Option::is_none")]
784 pub function_call: Option<ChatCompletionFunctionCall>,
785 #[deprecated]
786 #[serde(skip_serializing_if = "Option::is_none")]
787 pub functions: Option<Vec<ChatCompletionFunctions>>,
788 #[serde(skip_serializing_if = "Option::is_none")]
789 pub web_search_options: Option<WebSearchOptions>,
790}
791
792#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
796pub struct ChatChoice {
797 pub index: u32,
798 pub message: ChatCompletionResponseMessage,
799 pub finish_reason: Option<FinishReason>,
800 pub logprobs: Option<ChatChoiceLogprobs>,
801}
802
803#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
805pub struct CreateChatCompletionResponse {
806 pub id: String,
807 pub choices: Vec<ChatChoice>,
808 pub created: u32,
809 pub model: String,
810 pub service_tier: Option<ServiceTierResponse>,
811 pub system_fingerprint: Option<String>,
812 pub object: String,
813 pub usage: Option<CompletionUsage>,
814}
815
816pub type ChatCompletionResponseStream =
817 Pin<Box<dyn Stream<Item = Result<CreateChatCompletionStreamResponse, OpenAIError>> + Send>>;
818
819#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
825pub struct ChatCompletionStreamResponseDelta {
826 #[serde(skip_serializing_if = "Option::is_none")]
827 pub content: Option<ChatCompletionMessageContent>,
828 #[serde(skip_serializing_if = "Option::is_none")]
829 pub function_call: Option<ChatCompletionStreamResponseDeltaFunctionCall>,
830 #[serde(skip_serializing_if = "Option::is_none")]
831 pub tool_calls: Option<Vec<ChatCompletionMessageToolCallChunk>>,
832 #[serde(skip_serializing_if = "Option::is_none")]
833 pub role: Option<Role>,
834 #[serde(skip_serializing_if = "Option::is_none")]
835 pub refusal: Option<String>,
836 #[serde(skip_serializing_if = "Option::is_none")]
838 pub reasoning_content: Option<String>,
839}
840
841#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
842pub struct ChatCompletionStreamResponseDeltaFunctionCall {
843 pub name: Option<String>,
844 #[serde(default, deserialize_with = "deserialize_arguments_opt")]
845 pub arguments: Option<String>,
846}
847
848#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
850pub struct ChatChoiceStream {
851 pub index: u32,
852 pub delta: ChatCompletionStreamResponseDelta,
853 pub finish_reason: Option<FinishReason>,
854 pub logprobs: Option<ChatChoiceLogprobs>,
855}
856
857#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
859pub struct CreateChatCompletionStreamResponse {
860 pub id: String,
861 pub choices: Vec<ChatChoiceStream>,
862 pub created: u32,
863 pub model: String,
864 pub service_tier: Option<ServiceTierResponse>,
865 pub system_fingerprint: Option<String>,
866 pub object: String,
867 pub usage: Option<CompletionUsage>,
868}
869
870#[cfg(test)]
871mod tests {
872 use super::*;
873
874 #[test]
875 fn stop_accepts_token_id_array() {
876 let stop: Stop = serde_json::from_value(serde_json::json!([32, 34])).unwrap();
877
878 assert_eq!(stop, Stop::TokenIdArray(vec![32, 34]));
879 }
880
881 #[test]
882 fn stop_accepts_string_and_string_array() {
883 let stop: Stop = serde_json::from_value(serde_json::json!(" The")).unwrap();
884
885 assert_eq!(stop, Stop::String(" The".to_string()));
886
887 let stop: Stop = serde_json::from_value(serde_json::json!(["A", "B"])).unwrap();
888
889 assert_eq!(
890 stop,
891 Stop::StringArray(vec!["A".to_string(), "B".to_string()])
892 );
893 }
894
895 #[test]
896 fn stop_token_id_display_string_remains_string_stop() {
897 let stop: Stop = serde_json::from_value(serde_json::json!("token_id:576")).unwrap();
898
899 assert_eq!(stop, Stop::String("token_id:576".to_string()));
900
901 let stop: Stop = serde_json::from_value(serde_json::json!(["token_id:576"])).unwrap();
902
903 assert_eq!(stop, Stop::StringArray(vec!["token_id:576".to_string()]));
904 }
905
906 #[test]
907 fn stop_rejects_single_token_id() {
908 let result = serde_json::from_value::<Stop>(serde_json::json!(576));
909
910 assert!(result.is_err());
911 }
912
913 #[test]
914 fn stop_converts_from_upstream_stop_configuration() {
915 let upstream =
916 async_openai::types::chat::StopConfiguration::StringArray(vec!["END".to_string()]);
917
918 assert_eq!(
919 Stop::from(upstream),
920 Stop::StringArray(vec!["END".to_string()])
921 );
922 }
923
924 #[test]
925 fn request_builder_accepts_upstream_reasoning_effort() {
926 let request = CreateChatCompletionRequestArgs::default()
927 .reasoning_effort(async_openai::types::chat::ReasoningEffort::High)
928 .build()
929 .unwrap();
930
931 assert_eq!(request.reasoning_effort, Some(ReasoningEffort::High));
932 }
933
934 #[test]
935 fn tool_call_defaults_type_on_deserialize() {
936 let tool_call: ChatCompletionMessageToolCall = serde_json::from_value(serde_json::json!({
937 "id": "call_123",
938 "function": {
939 "name": "get_weather",
940 "arguments": "{\"location\":\"SF\"}"
941 }
942 }))
943 .unwrap();
944
945 assert_eq!(tool_call.r#type, FunctionType::Function);
946 }
947
948 #[test]
949 fn tool_call_serializes_type_for_wire_compat() {
950 let tool_call = ChatCompletionMessageToolCall {
951 id: "call_123".into(),
952 r#type: FunctionType::Function,
953 function: FunctionCall {
954 name: "get_weather".into(),
955 arguments: "{\"location\":\"SF\"}".into(),
956 },
957 };
958
959 let json = serde_json::to_value(tool_call).unwrap();
960 assert_eq!(json["type"], "function");
961 }
962
963 #[test]
966 fn function_call_accepts_string_arguments() {
967 let fc: FunctionCall = serde_json::from_value(serde_json::json!({
968 "name": "get_weather",
969 "arguments": "{\"location\":\"SF\"}"
970 }))
971 .unwrap();
972 assert_eq!(fc.arguments, "{\"location\":\"SF\"}");
973 }
974
975 #[test]
976 fn function_call_accepts_dict_arguments() {
977 let fc: FunctionCall = serde_json::from_value(serde_json::json!({
978 "name": "get_weather",
979 "arguments": {"location": "SF"}
980 }))
981 .unwrap();
982 assert_eq!(fc.arguments, "{\"location\":\"SF\"}");
983 }
984
985 #[test]
986 fn function_call_rejects_integer_arguments() {
987 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
988 "name": "f",
989 "arguments": 42
990 }));
991 assert!(result.is_err());
992 }
993
994 #[test]
995 fn function_call_rejects_boolean_arguments() {
996 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
997 "name": "f",
998 "arguments": true
999 }));
1000 assert!(result.is_err());
1001 }
1002
1003 #[test]
1004 fn function_call_rejects_null_arguments() {
1005 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
1006 "name": "f",
1007 "arguments": null
1008 }));
1009 assert!(result.is_err());
1010 }
1011
1012 #[test]
1013 fn function_call_rejects_array_arguments() {
1014 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
1015 "name": "f",
1016 "arguments": [1, 2, 3]
1017 }));
1018 assert!(result.is_err());
1019 }
1020
1021 #[test]
1022 fn function_call_stream_null_arguments_produces_none() {
1023 let fcs: FunctionCallStream = serde_json::from_value(serde_json::json!({
1024 "name": "f",
1025 "arguments": null
1026 }))
1027 .unwrap();
1028 assert_eq!(fcs.arguments, None);
1029 }
1030
1031 #[test]
1032 fn function_call_stream_rejects_integer_arguments() {
1033 let result = serde_json::from_value::<FunctionCallStream>(serde_json::json!({
1034 "name": "f",
1035 "arguments": 42
1036 }));
1037 assert!(result.is_err());
1038 }
1039
1040 #[test]
1041 fn function_call_stream_rejects_boolean_arguments() {
1042 let result = serde_json::from_value::<FunctionCallStream>(serde_json::json!({
1043 "name": "f",
1044 "arguments": true
1045 }));
1046 assert!(result.is_err());
1047 }
1048
1049 #[test]
1050 fn function_call_stream_accepts_dict_arguments() {
1051 let fcs: FunctionCallStream = serde_json::from_value(serde_json::json!({
1052 "name": "get_weather",
1053 "arguments": {"location": "SF"}
1054 }))
1055 .unwrap();
1056 assert_eq!(fcs.arguments.as_deref(), Some("{\"location\":\"SF\"}"));
1057 }
1058
1059 #[test]
1060 fn function_call_stream_accepts_null_arguments() {
1061 let fcs: FunctionCallStream = serde_json::from_value(serde_json::json!({
1062 "name": "get_weather"
1063 }))
1064 .unwrap();
1065 assert_eq!(fcs.arguments, None);
1066 }
1067
1068 #[test]
1069 fn tool_call_with_dict_arguments_roundtrip() {
1070 let tc: ChatCompletionMessageToolCall = serde_json::from_value(serde_json::json!({
1071 "id": "call_abc",
1072 "type": "function",
1073 "function": {
1074 "name": "search",
1075 "arguments": {"query": "hello", "limit": 10}
1076 }
1077 }))
1078 .unwrap();
1079 let parsed: serde_json::Value = serde_json::from_str(&tc.function.arguments).unwrap();
1081 assert_eq!(parsed, serde_json::json!({"query": "hello", "limit": 10}));
1082 let json = serde_json::to_value(&tc).unwrap();
1084 assert!(json["function"]["arguments"].is_string());
1085 }
1086
1087 #[test]
1088 fn stream_delta_function_call_accepts_dict_arguments() {
1089 let delta: ChatCompletionStreamResponseDeltaFunctionCall =
1090 serde_json::from_value(serde_json::json!({
1091 "name": "get_weather",
1092 "arguments": {"location": "SF"}
1093 }))
1094 .unwrap();
1095 assert_eq!(delta.arguments.as_deref(), Some("{\"location\":\"SF\"}"));
1096 }
1097
1098 fn parse_content_part(json: serde_json::Value) -> ChatCompletionRequestUserMessageContentPart {
1099 serde_json::from_value(json).expect("content part deserialization failed")
1100 }
1101
1102 #[test]
1103 fn image_url_url_and_top_level_uuid() {
1104 let part = parse_content_part(serde_json::json!({
1105 "type": "image_url",
1106 "image_url": {"url": "https://x.example/y.png"},
1107 "uuid": "image-123"
1108 }));
1109
1110 match part {
1111 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1112 assert_eq!(part.uuid.as_deref(), Some("image-123"));
1113 assert_eq!(
1114 part.image_url.as_ref().map(|image| image.url.as_str()),
1115 Some("https://x.example/y.png")
1116 );
1117 }
1118 _ => panic!("expected image_url part"),
1119 }
1120 }
1121
1122 #[test]
1123 fn image_url_null_and_top_level_uuid() {
1124 let part = parse_content_part(serde_json::json!({
1125 "type": "image_url",
1126 "image_url": null,
1127 "uuid": "sku-1234-a"
1128 }));
1129
1130 match part {
1131 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1132 assert!(part.image_url.is_none());
1133 assert_eq!(part.uuid.as_deref(), Some("sku-1234-a"));
1134 }
1135 _ => panic!("expected image_url part"),
1136 }
1137 }
1138
1139 #[test]
1140 fn image_url_null_without_uuid_deserializes_for_use_site_validation() {
1141 let part = parse_content_part(serde_json::json!({
1142 "type": "image_url",
1143 "image_url": null
1144 }));
1145
1146 match part {
1147 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1148 assert!(part.image_url.is_none());
1149 assert!(part.uuid.is_none());
1150 }
1151 _ => panic!("expected image_url part"),
1152 }
1153 }
1154
1155 #[test]
1156 fn image_url_serialize_uuid_only_uses_null_image_url() {
1157 let part = ChatCompletionRequestMessageContentPartImage {
1158 image_url: None,
1159 uuid: Some("image-123".to_string()),
1160 };
1161 let json = serde_json::to_value(part).unwrap();
1162
1163 assert!(json["image_url"].is_null());
1164 assert_eq!(json["uuid"], "image-123");
1165 }
1166
1167 #[test]
1168 fn cached_media_builders_allow_omitting_urls() {
1169 let image = ChatCompletionRequestMessageContentPartImageArgs::default()
1170 .uuid("image-123")
1171 .build()
1172 .unwrap();
1173 let video = ChatCompletionRequestMessageContentPartVideoArgs::default()
1174 .uuid("video-123")
1175 .build()
1176 .unwrap();
1177 let audio = ChatCompletionRequestMessageContentPartAudioUrlArgs::default()
1178 .uuid("audio-123")
1179 .build()
1180 .unwrap();
1181
1182 let image_json = serde_json::to_value(image).unwrap();
1183 let video_json = serde_json::to_value(video).unwrap();
1184 let audio_json = serde_json::to_value(audio).unwrap();
1185 assert!(image_json["image_url"].is_null());
1186 assert!(video_json["video_url"].is_null());
1187 assert!(audio_json["audio_url"].is_null());
1188 }
1189
1190 #[test]
1191 fn image_url_uuid_accepts_opaque_string() {
1192 let part = parse_content_part(serde_json::json!({
1193 "type": "image_url",
1194 "image_url": {"url": "https://x.example/y.png"},
1195 "uuid": "img-ac3921de680bb217"
1196 }));
1197
1198 match part {
1199 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1200 assert_eq!(part.uuid.as_deref(), Some("img-ac3921de680bb217"));
1201 }
1202 _ => panic!("expected image_url part"),
1203 }
1204 }
1205
1206 #[test]
1207 fn url_conversions_preserve_required_urls() {
1208 let image: ImageUrl = "https://x.example/image.png".into();
1209 let video: VideoUrl = "https://x.example/video.mp4".into();
1210 let audio: AudioUrl = "https://x.example/audio.wav".into();
1211
1212 assert_eq!(image.url.as_str(), "https://x.example/image.png");
1213 assert_eq!(video.url.as_str(), "https://x.example/video.mp4");
1214 assert_eq!(audio.url.as_str(), "https://x.example/audio.wav");
1215 }
1216
1217 #[test]
1218 fn legacy_nested_media_uuids_remain_accepted() {
1219 let legacy_uuid = "92b888ad-e64a-478f-b688-5091e16544e3";
1220
1221 for (part_type, media_field, url) in [
1222 ("image_url", "image_url", "https://x.example/image.png"),
1223 ("video_url", "video_url", "https://x.example/video.mp4"),
1224 ("audio_url", "audio_url", "https://x.example/audio.wav"),
1225 ] {
1226 let part = parse_content_part(serde_json::json!({
1227 "type": part_type,
1228 (media_field): {"url": url, "uuid": legacy_uuid}
1229 }));
1230 let json = serde_json::to_value(part).unwrap();
1231
1232 assert_eq!(json[media_field]["url"], url);
1233 assert_eq!(json[media_field]["uuid"], legacy_uuid);
1234 assert!(json.get("uuid").is_none());
1235 }
1236 }
1237
1238 #[test]
1239 fn video_url_null_and_top_level_uuid() {
1240 let part = parse_content_part(serde_json::json!({
1241 "type": "video_url",
1242 "video_url": null,
1243 "uuid": "video-cache-key"
1244 }));
1245
1246 match part {
1247 ChatCompletionRequestUserMessageContentPart::VideoUrl(part) => {
1248 assert!(part.video_url.is_none());
1249 assert_eq!(part.uuid.as_deref(), Some("video-cache-key"));
1250 }
1251 _ => panic!("expected video_url part"),
1252 }
1253 }
1254
1255 #[test]
1256 fn audio_url_null_and_top_level_uuid() {
1257 let part = parse_content_part(serde_json::json!({
1258 "type": "audio_url",
1259 "audio_url": null,
1260 "uuid": "audio-cache-key"
1261 }));
1262
1263 match part {
1264 ChatCompletionRequestUserMessageContentPart::AudioUrl(part) => {
1265 assert!(part.audio_url.is_none());
1266 assert_eq!(part.uuid.as_deref(), Some("audio-cache-key"));
1267 }
1268 _ => panic!("expected audio_url part"),
1269 }
1270 }
1271
1272 #[test]
1273 fn message_content_array_preserves_uuid_alignment() {
1274 let payload = serde_json::json!({
1275 "role": "user",
1276 "content": [
1277 {"type": "text", "text": "describe these"},
1278 {
1279 "type": "image_url",
1280 "image_url": {"url": "https://x.example/img1.png"},
1281 "uuid": "image-1"
1282 },
1283 {"type": "image_url", "image_url": null, "uuid": "image-1"}
1284 ]
1285 });
1286 let message: ChatCompletionRequestUserMessage = serde_json::from_value(payload).unwrap();
1287 let ChatCompletionRequestUserMessageContent::Array(parts) = message.content else {
1288 panic!("expected content array");
1289 };
1290
1291 assert_eq!(parts.len(), 3);
1292 match &parts[1] {
1293 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1294 assert!(
1295 part.image_url
1296 .as_ref()
1297 .map(|image| image.url.as_str())
1298 .is_some()
1299 );
1300 assert_eq!(part.uuid.as_deref(), Some("image-1"));
1301 }
1302 _ => panic!("parts[1] should be image_url"),
1303 }
1304 match &parts[2] {
1305 ChatCompletionRequestUserMessageContentPart::ImageUrl(part) => {
1306 assert!(part.image_url.is_none());
1307 assert_eq!(part.uuid.as_deref(), Some("image-1"));
1308 }
1309 _ => panic!("parts[2] should be image_url"),
1310 }
1311 }
1312}