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