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)]
283#[builder(name = "ChatCompletionRequestMessageContentPartImageArgs")]
284#[builder(pattern = "mutable")]
285#[builder(setter(into, strip_option))]
286#[builder(derive(Debug))]
287#[builder(build_fn(error = "OpenAIError"))]
288pub struct ChatCompletionRequestMessageContentPartImage {
289 pub image_url: ImageUrl,
290}
291
292#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
297#[builder(name = "ImageUrlArgs")]
298#[builder(pattern = "mutable")]
299#[builder(setter(into, strip_option))]
300#[builder(derive(Debug))]
301#[builder(build_fn(error = "OpenAIError"))]
302pub struct ImageUrl {
303 pub url: Url,
304 pub detail: Option<ImageDetail>,
305 #[serde(skip_serializing_if = "Option::is_none")]
306 pub uuid: Option<Uuid>,
307}
308
309#[derive(Clone, Serialize, Default, Debug, Deserialize, PartialEq)]
310#[serde(rename_all = "lowercase")]
311pub enum ChatCompletionToolType {
312 #[default]
313 Function,
314}
315
316#[derive(Clone, Serialize, Default, Debug, Deserialize, PartialEq)]
317pub struct FunctionName {
318 pub name: String,
319}
320
321#[derive(Clone, Serialize, Default, Debug, Deserialize, PartialEq)]
322pub struct ChatCompletionNamedToolChoice {
323 pub r#type: ChatCompletionToolType,
324 pub function: FunctionName,
325}
326
327fn default_function_type() -> FunctionType {
328 FunctionType::Function
329}
330
331#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
337pub struct ChatCompletionMessageToolCall {
338 pub id: String,
339 #[serde(default = "default_function_type")]
340 pub r#type: FunctionType,
341 pub function: FunctionCall,
342}
343
344#[derive(Clone, Serialize, Default, Debug, Deserialize, PartialEq)]
346#[serde(rename_all = "lowercase")]
347pub enum ChatCompletionToolChoiceOption {
348 #[default]
349 None,
350 Auto,
351 Required,
352 #[serde(untagged)]
353 Named(ChatCompletionNamedToolChoice),
354}
355
356#[derive(Clone, Serialize, Default, Debug, Builder, Deserialize, PartialEq)]
357#[builder(name = "ChatCompletionToolArgs")]
358#[builder(pattern = "mutable")]
359#[builder(setter(into, strip_option), default)]
360#[builder(derive(Debug))]
361#[builder(build_fn(error = "OpenAIError"))]
362pub struct ChatCompletionTool {
363 #[builder(default = "ChatCompletionToolType::Function")]
364 pub r#type: ChatCompletionToolType,
365 pub function: FunctionObject,
366}
367
368#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
379#[serde(untagged)]
380pub enum StopReason {
381 String(String),
382 Int(i64),
383 IntArray(Vec<i64>),
384}
385
386#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
396#[serde(untagged)]
397pub enum ReasoningContent {
398 Text(String),
400 Segments(Vec<String>),
403}
404
405impl ReasoningContent {
406 pub fn to_flat_string(&self) -> String {
408 match self {
409 ReasoningContent::Text(s) => s.clone(),
410 ReasoningContent::Segments(segs) => segs
411 .iter()
412 .filter(|s| !s.is_empty())
413 .cloned()
414 .collect::<Vec<_>>()
415 .join("\n"),
416 }
417 }
418
419 pub fn segments(&self) -> Option<&[String]> {
421 match self {
422 ReasoningContent::Segments(segs) => Some(segs),
423 ReasoningContent::Text(_) => None,
424 }
425 }
426}
427
428#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
432pub struct ChatCompletionResponseContentPartText {
433 pub text: String,
434}
435
436#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
438pub struct ChatCompletionResponseContentPartImageUrl {
439 pub image_url: ImageUrlResponse,
440}
441
442#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
444pub struct ChatCompletionResponseContentPartVideoUrl {
445 pub video_url: VideoUrlResponse,
446}
447
448#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
450pub struct ChatCompletionResponseContentPartAudioUrl {
451 pub audio_url: AudioUrlResponse,
452}
453
454#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
455pub struct ImageUrlResponse {
456 pub url: String,
457 #[serde(skip_serializing_if = "Option::is_none")]
458 pub detail: Option<String>,
459}
460
461#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
462pub struct VideoUrlResponse {
463 pub url: String,
464}
465
466#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
467pub struct AudioUrlResponse {
468 pub url: String,
469}
470
471#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
473#[serde(tag = "type", rename_all = "snake_case")]
474pub enum ChatCompletionResponseContentPart {
475 Text(ChatCompletionResponseContentPartText),
476 ImageUrl(ChatCompletionResponseContentPartImageUrl),
477 VideoUrl(ChatCompletionResponseContentPartVideoUrl),
478 AudioUrl(ChatCompletionResponseContentPartAudioUrl),
479}
480
481#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
487#[serde(untagged)]
488pub enum ChatCompletionMessageContent {
489 Text(String),
491 Parts(Vec<ChatCompletionResponseContentPart>),
493}
494
495#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
498#[builder(name = "VideoUrlArgs")]
499#[builder(pattern = "mutable")]
500#[builder(setter(into, strip_option))]
501#[builder(derive(Debug))]
502#[builder(build_fn(error = "OpenAIError"))]
503pub struct VideoUrl {
504 pub url: Url,
505 pub detail: Option<ImageDetail>,
506 #[serde(skip_serializing_if = "Option::is_none")]
507 pub uuid: Option<Uuid>,
508}
509
510#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
511#[builder(name = "ChatCompletionRequestMessageContentPartVideoArgs")]
512#[builder(pattern = "mutable")]
513#[builder(setter(into, strip_option))]
514#[builder(derive(Debug))]
515#[builder(build_fn(error = "OpenAIError"))]
516pub struct ChatCompletionRequestMessageContentPartVideo {
517 pub video_url: VideoUrl,
518}
519
520#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
521#[builder(name = "AudioUrlArgs")]
522#[builder(pattern = "mutable")]
523#[builder(setter(into, strip_option))]
524#[builder(derive(Debug))]
525#[builder(build_fn(error = "OpenAIError"))]
526pub struct AudioUrl {
527 pub url: Url,
528 #[serde(skip_serializing_if = "Option::is_none")]
529 pub uuid: Option<Uuid>,
530}
531
532#[derive(Debug, Serialize, Deserialize, Clone, Builder, PartialEq)]
533#[builder(name = "ChatCompletionRequestMessageContentPartAudioUrlArgs")]
534#[builder(pattern = "mutable")]
535#[builder(setter(into, strip_option))]
536#[builder(derive(Debug))]
537#[builder(build_fn(error = "OpenAIError"))]
538pub struct ChatCompletionRequestMessageContentPartAudioUrl {
539 pub audio_url: AudioUrl,
540}
541
542#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
546#[serde(untagged)]
547pub enum ChatCompletionRequestUserMessageContent {
548 Text(String),
549 Array(Vec<ChatCompletionRequestUserMessageContentPart>),
550}
551
552#[derive(Debug, Serialize, Deserialize, Default, Clone, Builder, PartialEq)]
553#[builder(name = "ChatCompletionRequestUserMessageArgs")]
554#[builder(pattern = "mutable")]
555#[builder(setter(into, strip_option), default)]
556#[builder(derive(Debug))]
557#[builder(build_fn(error = "OpenAIError"))]
558pub struct ChatCompletionRequestUserMessage {
559 pub content: ChatCompletionRequestUserMessageContent,
560 #[serde(skip_serializing_if = "Option::is_none")]
561 pub name: Option<String>,
562}
563
564impl Default for ChatCompletionRequestUserMessageContent {
565 fn default() -> Self {
566 Self::Text(String::new())
567 }
568}
569
570impl From<&str> for ChatCompletionRequestUserMessageContent {
571 fn from(value: &str) -> Self {
572 Self::Text(value.into())
573 }
574}
575
576impl From<String> for ChatCompletionRequestUserMessageContent {
577 fn from(value: String) -> Self {
578 Self::Text(value)
579 }
580}
581
582impl From<Vec<ChatCompletionRequestUserMessageContentPart>>
583 for ChatCompletionRequestUserMessageContent
584{
585 fn from(value: Vec<ChatCompletionRequestUserMessageContentPart>) -> Self {
586 Self::Array(value)
587 }
588}
589
590#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
596#[serde(tag = "type")]
597#[serde(rename_all = "snake_case")]
598pub enum ChatCompletionRequestUserMessageContentPart {
599 Text(ChatCompletionRequestMessageContentPartText),
600 ImageUrl(ChatCompletionRequestMessageContentPartImage),
601 VideoUrl(ChatCompletionRequestMessageContentPartVideo),
602 AudioUrl(ChatCompletionRequestMessageContentPartAudioUrl),
603 InputAudio(ChatCompletionRequestMessageContentPartAudio),
604}
605
606#[derive(Debug, Serialize, Deserialize, Default, Clone, Builder, PartialEq)]
612#[builder(name = "ChatCompletionRequestAssistantMessageArgs")]
613#[builder(pattern = "mutable")]
614#[builder(setter(into, strip_option), default)]
615#[builder(derive(Debug))]
616#[builder(build_fn(error = "OpenAIError"))]
617pub struct ChatCompletionRequestAssistantMessage {
618 #[serde(skip_serializing_if = "Option::is_none")]
619 pub content: Option<ChatCompletionRequestAssistantMessageContent>,
620 #[serde(skip_serializing_if = "Option::is_none")]
622 pub reasoning_content: Option<ReasoningContent>,
623 #[serde(skip_serializing_if = "Option::is_none")]
624 pub refusal: Option<String>,
625 #[serde(skip_serializing_if = "Option::is_none")]
626 pub name: Option<String>,
627 #[serde(skip_serializing_if = "Option::is_none")]
628 pub audio: Option<ChatCompletionRequestAssistantMessageAudio>,
629 #[serde(skip_serializing_if = "Option::is_none")]
630 pub tool_calls: Option<Vec<ChatCompletionMessageToolCall>>,
631 #[deprecated]
632 #[serde(skip_serializing_if = "Option::is_none")]
633 pub function_call: Option<FunctionCall>,
634}
635
636#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
642#[serde(tag = "role")]
643#[serde(rename_all = "lowercase")]
644pub enum ChatCompletionRequestMessage {
645 Developer(ChatCompletionRequestDeveloperMessage),
646 System(ChatCompletionRequestSystemMessage),
647 User(ChatCompletionRequestUserMessage),
648 Assistant(ChatCompletionRequestAssistantMessage),
649 Tool(ChatCompletionRequestToolMessage),
650 Function(ChatCompletionRequestFunctionMessage),
651}
652
653#[derive(Clone, Serialize, Debug, Deserialize, PartialEq)]
657#[serde(rename_all = "lowercase")]
658pub enum ServiceTierResponse {
659 Scale,
660 Default,
661 Flex,
662 Priority,
663}
664
665#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
671pub struct ChatCompletionResponseMessage {
672 pub content: Option<ChatCompletionMessageContent>,
676 #[serde(skip_serializing_if = "Option::is_none")]
677 pub refusal: Option<String>,
678 #[serde(skip_serializing_if = "Option::is_none")]
679 pub tool_calls: Option<Vec<ChatCompletionMessageToolCall>>,
680 pub role: Role,
681 #[serde(skip_serializing_if = "Option::is_none")]
682 #[deprecated]
683 pub function_call: Option<FunctionCall>,
684 #[serde(skip_serializing_if = "Option::is_none")]
685 pub audio: Option<ChatCompletionResponseMessageAudio>,
686 pub reasoning_content: Option<String>,
688}
689
690#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
695pub struct ChatCompletionStreamOptions {
696 pub include_usage: bool,
697 #[serde(default)]
700 pub continuous_usage_stats: bool,
701}
702
703#[derive(Clone, Serialize, Default, Debug, Builder, Deserialize, PartialEq)]
710#[builder(name = "CreateChatCompletionRequestArgs")]
711#[builder(pattern = "mutable")]
712#[builder(setter(into, strip_option), default)]
713#[builder(derive(Debug))]
714#[builder(build_fn(error = "OpenAIError"))]
715pub struct CreateChatCompletionRequest {
716 pub messages: Vec<ChatCompletionRequestMessage>,
717 pub model: String,
718 #[serde(skip_serializing_if = "Option::is_none")]
720 pub mm_processor_kwargs: Option<serde_json::Value>,
721 #[serde(skip_serializing_if = "Option::is_none")]
722 pub store: Option<bool>,
723 #[serde(skip_serializing_if = "Option::is_none")]
724 pub reasoning_effort: Option<ReasoningEffort>,
725 #[serde(skip_serializing_if = "Option::is_none")]
726 pub metadata: Option<serde_json::Value>,
727 #[serde(skip_serializing_if = "Option::is_none")]
728 pub frequency_penalty: Option<f32>,
729 #[serde(skip_serializing_if = "Option::is_none")]
730 pub logit_bias: Option<std::collections::HashMap<String, serde_json::Value>>,
731 #[serde(skip_serializing_if = "Option::is_none")]
732 pub logprobs: Option<bool>,
733 #[serde(skip_serializing_if = "Option::is_none")]
734 pub top_logprobs: Option<u8>,
735 #[deprecated]
736 #[serde(skip_serializing_if = "Option::is_none")]
737 pub max_tokens: Option<u32>,
738 #[serde(skip_serializing_if = "Option::is_none")]
739 pub max_completion_tokens: Option<u32>,
740 #[serde(skip_serializing_if = "Option::is_none")]
741 pub n: Option<u8>,
742 #[serde(skip_serializing_if = "Option::is_none")]
743 pub modalities: Option<Vec<async_openai::types::chat::ResponseModalities>>,
744 #[serde(skip_serializing_if = "Option::is_none")]
745 pub prediction: Option<PredictionContent>,
746 #[serde(skip_serializing_if = "Option::is_none")]
747 pub audio: Option<ChatCompletionAudio>,
748 #[serde(skip_serializing_if = "Option::is_none")]
749 pub presence_penalty: Option<f32>,
750 #[serde(skip_serializing_if = "Option::is_none")]
751 pub response_format: Option<ResponseFormat>,
752 #[serde(skip_serializing_if = "Option::is_none")]
753 pub seed: Option<i64>,
754 #[serde(skip_serializing_if = "Option::is_none")]
755 pub service_tier: Option<ServiceTier>,
756 #[serde(skip_serializing_if = "Option::is_none")]
757 pub stop: Option<Stop>,
758 #[serde(default, skip_serializing_if = "Option::is_none")]
759 pub stream: Option<bool>,
760 #[serde(skip_serializing_if = "Option::is_none")]
761 pub stream_options: Option<ChatCompletionStreamOptions>,
762 #[serde(skip_serializing_if = "Option::is_none")]
763 pub temperature: Option<f32>,
764 #[serde(skip_serializing_if = "Option::is_none")]
765 pub top_p: Option<f32>,
766 #[serde(skip_serializing_if = "Option::is_none")]
767 pub tools: Option<Vec<ChatCompletionTool>>,
768 #[serde(skip_serializing_if = "Option::is_none")]
769 pub tool_choice: Option<ChatCompletionToolChoiceOption>,
770 #[serde(skip_serializing_if = "Option::is_none")]
771 pub parallel_tool_calls: Option<bool>,
772 #[serde(skip_serializing_if = "Option::is_none")]
773 pub user: Option<String>,
774 #[deprecated]
775 #[serde(skip_serializing_if = "Option::is_none")]
776 pub function_call: Option<ChatCompletionFunctionCall>,
777 #[deprecated]
778 #[serde(skip_serializing_if = "Option::is_none")]
779 pub functions: Option<Vec<ChatCompletionFunctions>>,
780 #[serde(skip_serializing_if = "Option::is_none")]
781 pub web_search_options: Option<WebSearchOptions>,
782}
783
784#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
788pub struct ChatChoice {
789 pub index: u32,
790 pub message: ChatCompletionResponseMessage,
791 pub finish_reason: Option<FinishReason>,
792 pub logprobs: Option<ChatChoiceLogprobs>,
793}
794
795#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
797pub struct CreateChatCompletionResponse {
798 pub id: String,
799 pub choices: Vec<ChatChoice>,
800 pub created: u32,
801 pub model: String,
802 pub service_tier: Option<ServiceTierResponse>,
803 pub system_fingerprint: Option<String>,
804 pub object: String,
805 pub usage: Option<CompletionUsage>,
806}
807
808pub type ChatCompletionResponseStream =
809 Pin<Box<dyn Stream<Item = Result<CreateChatCompletionStreamResponse, OpenAIError>> + Send>>;
810
811#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
817pub struct ChatCompletionStreamResponseDelta {
818 #[serde(skip_serializing_if = "Option::is_none")]
819 pub content: Option<ChatCompletionMessageContent>,
820 #[serde(skip_serializing_if = "Option::is_none")]
821 pub function_call: Option<ChatCompletionStreamResponseDeltaFunctionCall>,
822 #[serde(skip_serializing_if = "Option::is_none")]
823 pub tool_calls: Option<Vec<ChatCompletionMessageToolCallChunk>>,
824 #[serde(skip_serializing_if = "Option::is_none")]
825 pub role: Option<Role>,
826 #[serde(skip_serializing_if = "Option::is_none")]
827 pub refusal: Option<String>,
828 #[serde(skip_serializing_if = "Option::is_none")]
830 pub reasoning_content: Option<String>,
831}
832
833#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
834pub struct ChatCompletionStreamResponseDeltaFunctionCall {
835 pub name: Option<String>,
836 #[serde(default, deserialize_with = "deserialize_arguments_opt")]
837 pub arguments: Option<String>,
838}
839
840#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
842pub struct ChatChoiceStream {
843 pub index: u32,
844 pub delta: ChatCompletionStreamResponseDelta,
845 pub finish_reason: Option<FinishReason>,
846 pub logprobs: Option<ChatChoiceLogprobs>,
847}
848
849#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
851pub struct CreateChatCompletionStreamResponse {
852 pub id: String,
853 pub choices: Vec<ChatChoiceStream>,
854 pub created: u32,
855 pub model: String,
856 pub service_tier: Option<ServiceTierResponse>,
857 pub system_fingerprint: Option<String>,
858 pub object: String,
859 pub usage: Option<CompletionUsage>,
860}
861
862#[cfg(test)]
863mod tests {
864 use super::*;
865
866 #[test]
867 fn stop_accepts_token_id_array() {
868 let stop: Stop = serde_json::from_value(serde_json::json!([32, 34])).unwrap();
869
870 assert_eq!(stop, Stop::TokenIdArray(vec![32, 34]));
871 }
872
873 #[test]
874 fn stop_accepts_string_and_string_array() {
875 let stop: Stop = serde_json::from_value(serde_json::json!(" The")).unwrap();
876
877 assert_eq!(stop, Stop::String(" The".to_string()));
878
879 let stop: Stop = serde_json::from_value(serde_json::json!(["A", "B"])).unwrap();
880
881 assert_eq!(
882 stop,
883 Stop::StringArray(vec!["A".to_string(), "B".to_string()])
884 );
885 }
886
887 #[test]
888 fn stop_token_id_display_string_remains_string_stop() {
889 let stop: Stop = serde_json::from_value(serde_json::json!("token_id:576")).unwrap();
890
891 assert_eq!(stop, Stop::String("token_id:576".to_string()));
892
893 let stop: Stop = serde_json::from_value(serde_json::json!(["token_id:576"])).unwrap();
894
895 assert_eq!(stop, Stop::StringArray(vec!["token_id:576".to_string()]));
896 }
897
898 #[test]
899 fn stop_rejects_single_token_id() {
900 let result = serde_json::from_value::<Stop>(serde_json::json!(576));
901
902 assert!(result.is_err());
903 }
904
905 #[test]
906 fn stop_converts_from_upstream_stop_configuration() {
907 let upstream =
908 async_openai::types::chat::StopConfiguration::StringArray(vec!["END".to_string()]);
909
910 assert_eq!(
911 Stop::from(upstream),
912 Stop::StringArray(vec!["END".to_string()])
913 );
914 }
915
916 #[test]
917 fn request_builder_accepts_upstream_reasoning_effort() {
918 let request = CreateChatCompletionRequestArgs::default()
919 .reasoning_effort(async_openai::types::chat::ReasoningEffort::High)
920 .build()
921 .unwrap();
922
923 assert_eq!(request.reasoning_effort, Some(ReasoningEffort::High));
924 }
925
926 #[test]
927 fn tool_call_defaults_type_on_deserialize() {
928 let tool_call: ChatCompletionMessageToolCall = serde_json::from_value(serde_json::json!({
929 "id": "call_123",
930 "function": {
931 "name": "get_weather",
932 "arguments": "{\"location\":\"SF\"}"
933 }
934 }))
935 .unwrap();
936
937 assert_eq!(tool_call.r#type, FunctionType::Function);
938 }
939
940 #[test]
941 fn tool_call_serializes_type_for_wire_compat() {
942 let tool_call = ChatCompletionMessageToolCall {
943 id: "call_123".into(),
944 r#type: FunctionType::Function,
945 function: FunctionCall {
946 name: "get_weather".into(),
947 arguments: "{\"location\":\"SF\"}".into(),
948 },
949 };
950
951 let json = serde_json::to_value(tool_call).unwrap();
952 assert_eq!(json["type"], "function");
953 }
954
955 #[test]
958 fn function_call_accepts_string_arguments() {
959 let fc: FunctionCall = serde_json::from_value(serde_json::json!({
960 "name": "get_weather",
961 "arguments": "{\"location\":\"SF\"}"
962 }))
963 .unwrap();
964 assert_eq!(fc.arguments, "{\"location\":\"SF\"}");
965 }
966
967 #[test]
968 fn function_call_accepts_dict_arguments() {
969 let fc: FunctionCall = serde_json::from_value(serde_json::json!({
970 "name": "get_weather",
971 "arguments": {"location": "SF"}
972 }))
973 .unwrap();
974 assert_eq!(fc.arguments, "{\"location\":\"SF\"}");
975 }
976
977 #[test]
978 fn function_call_rejects_integer_arguments() {
979 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
980 "name": "f",
981 "arguments": 42
982 }));
983 assert!(result.is_err());
984 }
985
986 #[test]
987 fn function_call_rejects_boolean_arguments() {
988 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
989 "name": "f",
990 "arguments": true
991 }));
992 assert!(result.is_err());
993 }
994
995 #[test]
996 fn function_call_rejects_null_arguments() {
997 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
998 "name": "f",
999 "arguments": null
1000 }));
1001 assert!(result.is_err());
1002 }
1003
1004 #[test]
1005 fn function_call_rejects_array_arguments() {
1006 let result = serde_json::from_value::<FunctionCall>(serde_json::json!({
1007 "name": "f",
1008 "arguments": [1, 2, 3]
1009 }));
1010 assert!(result.is_err());
1011 }
1012
1013 #[test]
1014 fn function_call_stream_null_arguments_produces_none() {
1015 let fcs: FunctionCallStream = serde_json::from_value(serde_json::json!({
1016 "name": "f",
1017 "arguments": null
1018 }))
1019 .unwrap();
1020 assert_eq!(fcs.arguments, None);
1021 }
1022
1023 #[test]
1024 fn function_call_stream_rejects_integer_arguments() {
1025 let result = serde_json::from_value::<FunctionCallStream>(serde_json::json!({
1026 "name": "f",
1027 "arguments": 42
1028 }));
1029 assert!(result.is_err());
1030 }
1031
1032 #[test]
1033 fn function_call_stream_rejects_boolean_arguments() {
1034 let result = serde_json::from_value::<FunctionCallStream>(serde_json::json!({
1035 "name": "f",
1036 "arguments": true
1037 }));
1038 assert!(result.is_err());
1039 }
1040
1041 #[test]
1042 fn function_call_stream_accepts_dict_arguments() {
1043 let fcs: FunctionCallStream = serde_json::from_value(serde_json::json!({
1044 "name": "get_weather",
1045 "arguments": {"location": "SF"}
1046 }))
1047 .unwrap();
1048 assert_eq!(fcs.arguments.as_deref(), Some("{\"location\":\"SF\"}"));
1049 }
1050
1051 #[test]
1052 fn function_call_stream_accepts_null_arguments() {
1053 let fcs: FunctionCallStream = serde_json::from_value(serde_json::json!({
1054 "name": "get_weather"
1055 }))
1056 .unwrap();
1057 assert_eq!(fcs.arguments, None);
1058 }
1059
1060 #[test]
1061 fn tool_call_with_dict_arguments_roundtrip() {
1062 let tc: ChatCompletionMessageToolCall = serde_json::from_value(serde_json::json!({
1063 "id": "call_abc",
1064 "type": "function",
1065 "function": {
1066 "name": "search",
1067 "arguments": {"query": "hello", "limit": 10}
1068 }
1069 }))
1070 .unwrap();
1071 let parsed: serde_json::Value = serde_json::from_str(&tc.function.arguments).unwrap();
1073 assert_eq!(parsed, serde_json::json!({"query": "hello", "limit": 10}));
1074 let json = serde_json::to_value(&tc).unwrap();
1076 assert!(json["function"]["arguments"].is_string());
1077 }
1078
1079 #[test]
1080 fn stream_delta_function_call_accepts_dict_arguments() {
1081 let delta: ChatCompletionStreamResponseDeltaFunctionCall =
1082 serde_json::from_value(serde_json::json!({
1083 "name": "get_weather",
1084 "arguments": {"location": "SF"}
1085 }))
1086 .unwrap();
1087 assert_eq!(delta.arguments.as_deref(), Some("{\"location\":\"SF\"}"));
1088 }
1089}