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 ReasoningEffort,
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
154fn deserialize_arguments<'de, D>(deserializer: D) -> Result<String, D::Error>
163where
164 D: serde::Deserializer<'de>,
165{
166 use serde::de::Error;
167 let value = serde_json::Value::deserialize(deserializer)?;
168 match value {
169 serde_json::Value::String(s) => Ok(s),
170 v @ serde_json::Value::Object(_) => {
171 Ok(serde_json::to_string(&v).unwrap())
173 }
174 other => Err(D::Error::custom(format!(
175 "expected string or object for `arguments`, got {other}"
176 ))),
177 }
178}
179
180fn deserialize_arguments_opt<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
181where
182 D: serde::Deserializer<'de>,
183{
184 use serde::de::Error;
185 let value = Option::<serde_json::Value>::deserialize(deserializer)?;
186 match value {
187 None => Ok(None),
188 Some(serde_json::Value::String(s)) => Ok(Some(s)),
189 Some(v @ serde_json::Value::Object(_)) => serde_json::to_string(&v)
190 .map(Some)
191 .map_err(|e| D::Error::custom(e.to_string())),
192 Some(other) => Err(D::Error::custom(format!(
193 "expected string or object for `arguments`, got {other}"
194 ))),
195 }
196}
197
198#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)]
211pub struct FunctionCall {
212 pub name: String,
213 #[serde(deserialize_with = "deserialize_arguments")]
214 pub arguments: String,
215}
216
217#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)]
219pub struct FunctionCallStream {
220 pub name: Option<String>,
221 #[serde(default, deserialize_with = "deserialize_arguments_opt")]
222 pub arguments: Option<String>,
223}
224
225#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)]
231pub struct ChatCompletionMessageToolCallChunk {
232 pub index: u32,
233 pub id: Option<String>,
234 pub r#type: Option<FunctionType>,
235 pub function: Option<FunctionCallStream>,
236}
237
238#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq)]
244#[serde(rename_all = "lowercase")]
245pub enum ImageDetail {
246 #[default]
247 Auto,
248 Low,
249 High,
250}
251
252#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
254#[builder(name = "ChatCompletionRequestMessageContentPartImageArgs")]
255#[builder(pattern = "mutable")]
256#[builder(setter(into, strip_option))]
257#[builder(derive(Debug))]
258#[builder(build_fn(error = "OpenAIError"))]
259pub struct ChatCompletionRequestMessageContentPartImage {
260 pub image_url: ImageUrl,
261}
262
263#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
268#[builder(name = "ImageUrlArgs")]
269#[builder(pattern = "mutable")]
270#[builder(setter(into, strip_option))]
271#[builder(derive(Debug))]
272#[builder(build_fn(error = "OpenAIError"))]
273pub struct ImageUrl {
274 pub url: Url,
275 pub detail: Option<ImageDetail>,
276 #[serde(skip_serializing_if = "Option::is_none")]
277 pub uuid: Option<Uuid>,
278}
279
280#[derive(Clone, Serialize, Default, Debug, Deserialize, PartialEq)]
281#[serde(rename_all = "lowercase")]
282pub enum ChatCompletionToolType {
283 #[default]
284 Function,
285}
286
287#[derive(Clone, Serialize, Default, Debug, Deserialize, PartialEq)]
288pub struct FunctionName {
289 pub name: String,
290}
291
292#[derive(Clone, Serialize, Default, Debug, Deserialize, PartialEq)]
293pub struct ChatCompletionNamedToolChoice {
294 pub r#type: ChatCompletionToolType,
295 pub function: FunctionName,
296}
297
298fn default_function_type() -> FunctionType {
299 FunctionType::Function
300}
301
302#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
308pub struct ChatCompletionMessageToolCall {
309 pub id: String,
310 #[serde(default = "default_function_type")]
311 pub r#type: FunctionType,
312 pub function: FunctionCall,
313}
314
315#[derive(Clone, Serialize, Default, Debug, Deserialize, PartialEq)]
317#[serde(rename_all = "lowercase")]
318pub enum ChatCompletionToolChoiceOption {
319 #[default]
320 None,
321 Auto,
322 Required,
323 #[serde(untagged)]
324 Named(ChatCompletionNamedToolChoice),
325}
326
327#[derive(Clone, Serialize, Default, Debug, Builder, Deserialize, PartialEq)]
328#[builder(name = "ChatCompletionToolArgs")]
329#[builder(pattern = "mutable")]
330#[builder(setter(into, strip_option), default)]
331#[builder(derive(Debug))]
332#[builder(build_fn(error = "OpenAIError"))]
333pub struct ChatCompletionTool {
334 #[builder(default = "ChatCompletionToolType::Function")]
335 pub r#type: ChatCompletionToolType,
336 pub function: FunctionObject,
337}
338
339#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
350#[serde(untagged)]
351pub enum StopReason {
352 String(String),
353 Int(i64),
354 IntArray(Vec<i64>),
355}
356
357#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
367#[serde(untagged)]
368pub enum ReasoningContent {
369 Text(String),
371 Segments(Vec<String>),
374}
375
376impl ReasoningContent {
377 pub fn to_flat_string(&self) -> String {
379 match self {
380 ReasoningContent::Text(s) => s.clone(),
381 ReasoningContent::Segments(segs) => segs
382 .iter()
383 .filter(|s| !s.is_empty())
384 .cloned()
385 .collect::<Vec<_>>()
386 .join("\n"),
387 }
388 }
389
390 pub fn segments(&self) -> Option<&[String]> {
392 match self {
393 ReasoningContent::Segments(segs) => Some(segs),
394 ReasoningContent::Text(_) => None,
395 }
396 }
397}
398
399#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
403pub struct ChatCompletionResponseContentPartText {
404 pub text: String,
405}
406
407#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
409pub struct ChatCompletionResponseContentPartImageUrl {
410 pub image_url: ImageUrlResponse,
411}
412
413#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
415pub struct ChatCompletionResponseContentPartVideoUrl {
416 pub video_url: VideoUrlResponse,
417}
418
419#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
421pub struct ChatCompletionResponseContentPartAudioUrl {
422 pub audio_url: AudioUrlResponse,
423}
424
425#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
426pub struct ImageUrlResponse {
427 pub url: String,
428 #[serde(skip_serializing_if = "Option::is_none")]
429 pub detail: Option<String>,
430}
431
432#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
433pub struct VideoUrlResponse {
434 pub url: String,
435}
436
437#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
438pub struct AudioUrlResponse {
439 pub url: String,
440}
441
442#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
444#[serde(tag = "type", rename_all = "snake_case")]
445pub enum ChatCompletionResponseContentPart {
446 Text(ChatCompletionResponseContentPartText),
447 ImageUrl(ChatCompletionResponseContentPartImageUrl),
448 VideoUrl(ChatCompletionResponseContentPartVideoUrl),
449 AudioUrl(ChatCompletionResponseContentPartAudioUrl),
450}
451
452#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
458#[serde(untagged)]
459pub enum ChatCompletionMessageContent {
460 Text(String),
462 Parts(Vec<ChatCompletionResponseContentPart>),
464}
465
466#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
469#[builder(name = "VideoUrlArgs")]
470#[builder(pattern = "mutable")]
471#[builder(setter(into, strip_option))]
472#[builder(derive(Debug))]
473#[builder(build_fn(error = "OpenAIError"))]
474pub struct VideoUrl {
475 pub url: Url,
476 pub detail: Option<ImageDetail>,
477 #[serde(skip_serializing_if = "Option::is_none")]
478 pub uuid: Option<Uuid>,
479}
480
481#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
482#[builder(name = "ChatCompletionRequestMessageContentPartVideoArgs")]
483#[builder(pattern = "mutable")]
484#[builder(setter(into, strip_option))]
485#[builder(derive(Debug))]
486#[builder(build_fn(error = "OpenAIError"))]
487pub struct ChatCompletionRequestMessageContentPartVideo {
488 pub video_url: VideoUrl,
489}
490
491#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
492#[builder(name = "AudioUrlArgs")]
493#[builder(pattern = "mutable")]
494#[builder(setter(into, strip_option))]
495#[builder(derive(Debug))]
496#[builder(build_fn(error = "OpenAIError"))]
497pub struct AudioUrl {
498 pub url: Url,
499 #[serde(skip_serializing_if = "Option::is_none")]
500 pub uuid: Option<Uuid>,
501}
502
503#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
504#[builder(name = "ChatCompletionRequestMessageContentPartAudioUrlArgs")]
505#[builder(pattern = "mutable")]
506#[builder(setter(into, strip_option))]
507#[builder(derive(Debug))]
508#[builder(build_fn(error = "OpenAIError"))]
509pub struct ChatCompletionRequestMessageContentPartAudioUrl {
510 pub audio_url: AudioUrl,
511}
512
513#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
517#[serde(untagged)]
518pub enum ChatCompletionRequestUserMessageContent {
519 Text(String),
520 Array(Vec<ChatCompletionRequestUserMessageContentPart>),
521}
522
523#[derive(Debug, Serialize, Deserialize, Default, Clone, Builder, PartialEq)]
524#[builder(name = "ChatCompletionRequestUserMessageArgs")]
525#[builder(pattern = "mutable")]
526#[builder(setter(into, strip_option), default)]
527#[builder(derive(Debug))]
528#[builder(build_fn(error = "OpenAIError"))]
529pub struct ChatCompletionRequestUserMessage {
530 pub content: ChatCompletionRequestUserMessageContent,
531 #[serde(skip_serializing_if = "Option::is_none")]
532 pub name: Option<String>,
533}
534
535impl Default for ChatCompletionRequestUserMessageContent {
536 fn default() -> Self {
537 Self::Text(String::new())
538 }
539}
540
541impl From<&str> for ChatCompletionRequestUserMessageContent {
542 fn from(value: &str) -> Self {
543 Self::Text(value.into())
544 }
545}
546
547impl From<String> for ChatCompletionRequestUserMessageContent {
548 fn from(value: String) -> Self {
549 Self::Text(value)
550 }
551}
552
553impl From<Vec<ChatCompletionRequestUserMessageContentPart>>
554 for ChatCompletionRequestUserMessageContent
555{
556 fn from(value: Vec<ChatCompletionRequestUserMessageContentPart>) -> Self {
557 Self::Array(value)
558 }
559}
560
561#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
567#[serde(tag = "type")]
568#[serde(rename_all = "snake_case")]
569pub enum ChatCompletionRequestUserMessageContentPart {
570 Text(ChatCompletionRequestMessageContentPartText),
571 ImageUrl(ChatCompletionRequestMessageContentPartImage),
572 VideoUrl(ChatCompletionRequestMessageContentPartVideo),
573 AudioUrl(ChatCompletionRequestMessageContentPartAudioUrl),
574 InputAudio(ChatCompletionRequestMessageContentPartAudio),
575}
576
577#[derive(Debug, Serialize, Deserialize, Default, Clone, Builder, PartialEq)]
583#[builder(name = "ChatCompletionRequestAssistantMessageArgs")]
584#[builder(pattern = "mutable")]
585#[builder(setter(into, strip_option), default)]
586#[builder(derive(Debug))]
587#[builder(build_fn(error = "OpenAIError"))]
588pub struct ChatCompletionRequestAssistantMessage {
589 #[serde(skip_serializing_if = "Option::is_none")]
590 pub content: Option<ChatCompletionRequestAssistantMessageContent>,
591 #[serde(skip_serializing_if = "Option::is_none")]
593 pub reasoning_content: Option<ReasoningContent>,
594 #[serde(skip_serializing_if = "Option::is_none")]
595 pub refusal: Option<String>,
596 #[serde(skip_serializing_if = "Option::is_none")]
597 pub name: Option<String>,
598 #[serde(skip_serializing_if = "Option::is_none")]
599 pub audio: Option<ChatCompletionRequestAssistantMessageAudio>,
600 #[serde(skip_serializing_if = "Option::is_none")]
601 pub tool_calls: Option<Vec<ChatCompletionMessageToolCall>>,
602 #[deprecated]
603 #[serde(skip_serializing_if = "Option::is_none")]
604 pub function_call: Option<FunctionCall>,
605}
606
607#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
613#[serde(tag = "role")]
614#[serde(rename_all = "lowercase")]
615pub enum ChatCompletionRequestMessage {
616 Developer(ChatCompletionRequestDeveloperMessage),
617 System(ChatCompletionRequestSystemMessage),
618 User(ChatCompletionRequestUserMessage),
619 Assistant(ChatCompletionRequestAssistantMessage),
620 Tool(ChatCompletionRequestToolMessage),
621 Function(ChatCompletionRequestFunctionMessage),
622}
623
624#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
628#[serde(rename_all = "lowercase")]
629pub enum ServiceTierResponse {
630 Scale,
631 Default,
632 Flex,
633 Priority,
634}
635
636#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
642pub struct ChatCompletionResponseMessage {
643 pub content: Option<ChatCompletionMessageContent>,
647 #[serde(skip_serializing_if = "Option::is_none")]
648 pub refusal: Option<String>,
649 #[serde(skip_serializing_if = "Option::is_none")]
650 pub tool_calls: Option<Vec<ChatCompletionMessageToolCall>>,
651 pub role: Role,
652 #[serde(skip_serializing_if = "Option::is_none")]
653 #[deprecated]
654 pub function_call: Option<FunctionCall>,
655 #[serde(skip_serializing_if = "Option::is_none")]
656 pub audio: Option<ChatCompletionResponseMessageAudio>,
657 pub reasoning_content: Option<String>,
659}
660
661#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
666pub struct ChatCompletionStreamOptions {
667 pub include_usage: bool,
668 #[serde(default)]
671 pub continuous_usage_stats: bool,
672}
673
674#[derive(Clone, Serialize, Default, Debug, Builder, Deserialize, PartialEq)]
681#[builder(name = "CreateChatCompletionRequestArgs")]
682#[builder(pattern = "mutable")]
683#[builder(setter(into, strip_option), default)]
684#[builder(derive(Debug))]
685#[builder(build_fn(error = "OpenAIError"))]
686pub struct CreateChatCompletionRequest {
687 pub messages: Vec<ChatCompletionRequestMessage>,
688 pub model: String,
689 #[serde(skip_serializing_if = "Option::is_none")]
691 pub mm_processor_kwargs: Option<serde_json::Value>,
692 #[serde(skip_serializing_if = "Option::is_none")]
693 pub store: Option<bool>,
694 #[serde(skip_serializing_if = "Option::is_none")]
695 pub reasoning_effort: Option<ReasoningEffort>,
696 #[serde(skip_serializing_if = "Option::is_none")]
697 pub metadata: Option<serde_json::Value>,
698 #[serde(skip_serializing_if = "Option::is_none")]
699 pub frequency_penalty: Option<f32>,
700 #[serde(skip_serializing_if = "Option::is_none")]
701 pub logit_bias: Option<std::collections::HashMap<String, serde_json::Value>>,
702 #[serde(skip_serializing_if = "Option::is_none")]
703 pub logprobs: Option<bool>,
704 #[serde(skip_serializing_if = "Option::is_none")]
705 pub top_logprobs: Option<u8>,
706 #[deprecated]
707 #[serde(skip_serializing_if = "Option::is_none")]
708 pub max_tokens: Option<u32>,
709 #[serde(skip_serializing_if = "Option::is_none")]
710 pub max_completion_tokens: Option<u32>,
711 #[serde(skip_serializing_if = "Option::is_none")]
712 pub n: Option<u8>,
713 #[serde(skip_serializing_if = "Option::is_none")]
714 pub modalities: Option<Vec<async_openai::types::chat::ResponseModalities>>,
715 #[serde(skip_serializing_if = "Option::is_none")]
716 pub prediction: Option<PredictionContent>,
717 #[serde(skip_serializing_if = "Option::is_none")]
718 pub audio: Option<ChatCompletionAudio>,
719 #[serde(skip_serializing_if = "Option::is_none")]
720 pub presence_penalty: Option<f32>,
721 #[serde(skip_serializing_if = "Option::is_none")]
722 pub response_format: Option<ResponseFormat>,
723 #[serde(skip_serializing_if = "Option::is_none")]
724 pub seed: Option<i64>,
725 #[serde(skip_serializing_if = "Option::is_none")]
726 pub service_tier: Option<ServiceTier>,
727 #[serde(skip_serializing_if = "Option::is_none")]
728 pub stop: Option<Stop>,
729 #[serde(default, skip_serializing_if = "Option::is_none")]
730 pub stream: Option<bool>,
731 #[serde(skip_serializing_if = "Option::is_none")]
732 pub stream_options: Option<ChatCompletionStreamOptions>,
733 #[serde(skip_serializing_if = "Option::is_none")]
734 pub temperature: Option<f32>,
735 #[serde(skip_serializing_if = "Option::is_none")]
736 pub top_p: Option<f32>,
737 #[serde(skip_serializing_if = "Option::is_none")]
738 pub tools: Option<Vec<ChatCompletionTool>>,
739 #[serde(skip_serializing_if = "Option::is_none")]
740 pub tool_choice: Option<ChatCompletionToolChoiceOption>,
741 #[serde(skip_serializing_if = "Option::is_none")]
742 pub parallel_tool_calls: Option<bool>,
743 #[serde(skip_serializing_if = "Option::is_none")]
744 pub user: Option<String>,
745 #[deprecated]
746 #[serde(skip_serializing_if = "Option::is_none")]
747 pub function_call: Option<ChatCompletionFunctionCall>,
748 #[deprecated]
749 #[serde(skip_serializing_if = "Option::is_none")]
750 pub functions: Option<Vec<ChatCompletionFunctions>>,
751 #[serde(skip_serializing_if = "Option::is_none")]
752 pub web_search_options: Option<WebSearchOptions>,
753}
754
755#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
759pub struct ChatChoice {
760 pub index: u32,
761 pub message: ChatCompletionResponseMessage,
762 pub finish_reason: Option<FinishReason>,
763 pub logprobs: Option<ChatChoiceLogprobs>,
764}
765
766#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
768pub struct CreateChatCompletionResponse {
769 pub id: String,
770 pub choices: Vec<ChatChoice>,
771 pub created: u32,
772 pub model: String,
773 pub service_tier: Option<ServiceTierResponse>,
774 pub system_fingerprint: Option<String>,
775 pub object: String,
776 pub usage: Option<CompletionUsage>,
777}
778
779pub type ChatCompletionResponseStream =
780 Pin<Box<dyn Stream<Item = Result<CreateChatCompletionStreamResponse, OpenAIError>> + Send>>;
781
782#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
788pub struct ChatCompletionStreamResponseDelta {
789 #[serde(skip_serializing_if = "Option::is_none")]
790 pub content: Option<ChatCompletionMessageContent>,
791 #[serde(skip_serializing_if = "Option::is_none")]
792 pub function_call: Option<ChatCompletionStreamResponseDeltaFunctionCall>,
793 #[serde(skip_serializing_if = "Option::is_none")]
794 pub tool_calls: Option<Vec<ChatCompletionMessageToolCallChunk>>,
795 #[serde(skip_serializing_if = "Option::is_none")]
796 pub role: Option<Role>,
797 #[serde(skip_serializing_if = "Option::is_none")]
798 pub refusal: Option<String>,
799 #[serde(skip_serializing_if = "Option::is_none")]
801 pub reasoning_content: Option<String>,
802}
803
804#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
805pub struct ChatCompletionStreamResponseDeltaFunctionCall {
806 pub name: Option<String>,
807 #[serde(default, deserialize_with = "deserialize_arguments_opt")]
808 pub arguments: Option<String>,
809}
810
811#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
813pub struct ChatChoiceStream {
814 pub index: u32,
815 pub delta: ChatCompletionStreamResponseDelta,
816 pub finish_reason: Option<FinishReason>,
817 pub logprobs: Option<ChatChoiceLogprobs>,
818}
819
820#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
822pub struct CreateChatCompletionStreamResponse {
823 pub id: String,
824 pub choices: Vec<ChatChoiceStream>,
825 pub created: u32,
826 pub model: String,
827 pub service_tier: Option<ServiceTierResponse>,
828 pub system_fingerprint: Option<String>,
829 pub object: String,
830 pub usage: Option<CompletionUsage>,
831}
832
833#[cfg(test)]
834mod tests {
835 use super::*;
836
837 #[test]
838 fn stop_accepts_token_id_array() {
839 let stop: Stop = serde_json::from_value(serde_json::json!([32, 34])).unwrap();
840
841 assert_eq!(stop, Stop::TokenIdArray(vec![32, 34]));
842 }
843
844 #[test]
845 fn stop_accepts_string_and_string_array() {
846 let stop: Stop = serde_json::from_value(serde_json::json!(" The")).unwrap();
847
848 assert_eq!(stop, Stop::String(" The".to_string()));
849
850 let stop: Stop = serde_json::from_value(serde_json::json!(["A", "B"])).unwrap();
851
852 assert_eq!(
853 stop,
854 Stop::StringArray(vec!["A".to_string(), "B".to_string()])
855 );
856 }
857
858 #[test]
859 fn stop_token_id_display_string_remains_string_stop() {
860 let stop: Stop = serde_json::from_value(serde_json::json!("token_id:576")).unwrap();
861
862 assert_eq!(stop, Stop::String("token_id:576".to_string()));
863
864 let stop: Stop = serde_json::from_value(serde_json::json!(["token_id:576"])).unwrap();
865
866 assert_eq!(stop, Stop::StringArray(vec!["token_id:576".to_string()]));
867 }
868
869 #[test]
870 fn stop_rejects_single_token_id() {
871 let result = serde_json::from_value::<Stop>(serde_json::json!(576));
872
873 assert!(result.is_err());
874 }
875
876 #[test]
877 fn stop_converts_from_upstream_stop_configuration() {
878 let upstream =
879 async_openai::types::chat::StopConfiguration::StringArray(vec!["END".to_string()]);
880
881 assert_eq!(
882 Stop::from(upstream),
883 Stop::StringArray(vec!["END".to_string()])
884 );
885 }
886
887 #[test]
888 fn tool_call_defaults_type_on_deserialize() {
889 let tool_call: ChatCompletionMessageToolCall = serde_json::from_value(serde_json::json!({
890 "id": "call_123",
891 "function": {
892 "name": "get_weather",
893 "arguments": "{\"location\":\"SF\"}"
894 }
895 }))
896 .unwrap();
897
898 assert_eq!(tool_call.r#type, FunctionType::Function);
899 }
900
901 #[test]
902 fn tool_call_serializes_type_for_wire_compat() {
903 let tool_call = ChatCompletionMessageToolCall {
904 id: "call_123".into(),
905 r#type: FunctionType::Function,
906 function: FunctionCall {
907 name: "get_weather".into(),
908 arguments: "{\"location\":\"SF\"}".into(),
909 },
910 };
911
912 let json = serde_json::to_value(tool_call).unwrap();
913 assert_eq!(json["type"], "function");
914 }
915
916 #[test]
919 fn function_call_accepts_string_arguments() {
920 let fc: FunctionCall = serde_json::from_value(serde_json::json!({
921 "name": "get_weather",
922 "arguments": "{\"location\":\"SF\"}"
923 }))
924 .unwrap();
925 assert_eq!(fc.arguments, "{\"location\":\"SF\"}");
926 }
927
928 #[test]
929 fn function_call_accepts_dict_arguments() {
930 let fc: FunctionCall = serde_json::from_value(serde_json::json!({
931 "name": "get_weather",
932 "arguments": {"location": "SF"}
933 }))
934 .unwrap();
935 assert_eq!(fc.arguments, "{\"location\":\"SF\"}");
936 }
937
938 #[test]
939 fn function_call_rejects_integer_arguments() {
940 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
941 "name": "f",
942 "arguments": 42
943 }));
944 assert!(result.is_err());
945 }
946
947 #[test]
948 fn function_call_rejects_boolean_arguments() {
949 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
950 "name": "f",
951 "arguments": true
952 }));
953 assert!(result.is_err());
954 }
955
956 #[test]
957 fn function_call_rejects_null_arguments() {
958 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
959 "name": "f",
960 "arguments": null
961 }));
962 assert!(result.is_err());
963 }
964
965 #[test]
966 fn function_call_rejects_array_arguments() {
967 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
968 "name": "f",
969 "arguments": [1, 2, 3]
970 }));
971 assert!(result.is_err());
972 }
973
974 #[test]
975 fn function_call_stream_null_arguments_produces_none() {
976 let fcs: FunctionCallStream = serde_json::from_value(serde_json::json!({
977 "name": "f",
978 "arguments": null
979 }))
980 .unwrap();
981 assert_eq!(fcs.arguments, None);
982 }
983
984 #[test]
985 fn function_call_stream_rejects_integer_arguments() {
986 let result = serde_json::from_value::<FunctionCallStream>(serde_json::json!({
987 "name": "f",
988 "arguments": 42
989 }));
990 assert!(result.is_err());
991 }
992
993 #[test]
994 fn function_call_stream_rejects_boolean_arguments() {
995 let result = serde_json::from_value::<FunctionCallStream>(serde_json::json!({
996 "name": "f",
997 "arguments": true
998 }));
999 assert!(result.is_err());
1000 }
1001
1002 #[test]
1003 fn function_call_stream_accepts_dict_arguments() {
1004 let fcs: FunctionCallStream = serde_json::from_value(serde_json::json!({
1005 "name": "get_weather",
1006 "arguments": {"location": "SF"}
1007 }))
1008 .unwrap();
1009 assert_eq!(fcs.arguments.as_deref(), Some("{\"location\":\"SF\"}"));
1010 }
1011
1012 #[test]
1013 fn function_call_stream_accepts_null_arguments() {
1014 let fcs: FunctionCallStream = serde_json::from_value(serde_json::json!({
1015 "name": "get_weather"
1016 }))
1017 .unwrap();
1018 assert_eq!(fcs.arguments, None);
1019 }
1020
1021 #[test]
1022 fn tool_call_with_dict_arguments_roundtrip() {
1023 let tc: ChatCompletionMessageToolCall = serde_json::from_value(serde_json::json!({
1024 "id": "call_abc",
1025 "type": "function",
1026 "function": {
1027 "name": "search",
1028 "arguments": {"query": "hello", "limit": 10}
1029 }
1030 }))
1031 .unwrap();
1032 let parsed: serde_json::Value = serde_json::from_str(&tc.function.arguments).unwrap();
1034 assert_eq!(parsed, serde_json::json!({"query": "hello", "limit": 10}));
1035 let json = serde_json::to_value(&tc).unwrap();
1037 assert!(json["function"]["arguments"].is_string());
1038 }
1039
1040 #[test]
1041 fn stream_delta_function_call_accepts_dict_arguments() {
1042 let delta: ChatCompletionStreamResponseDeltaFunctionCall =
1043 serde_json::from_value(serde_json::json!({
1044 "name": "get_weather",
1045 "arguments": {"location": "SF"}
1046 }))
1047 .unwrap();
1048 assert_eq!(delta.arguments.as_deref(), Some("{\"location\":\"SF\"}"));
1049 }
1050}