1use super::{Client, responses_api::streaming::StreamingCompletionResponse};
11use super::{ImageUrl, InputAudio, SystemContent};
12use crate::completion::CompletionError;
13use crate::json_utils;
14use crate::message::{AudioMediaType, Document, MessageError, Text};
15use crate::one_or_many::string_or_one_or_many;
16
17use crate::{OneOrMany, completion, message};
18use serde::{Deserialize, Serialize};
19use serde_json::{Map, Value};
20
21use std::convert::Infallible;
22use std::ops::Add;
23use std::str::FromStr;
24
25pub mod streaming;
26
27#[derive(Debug, Deserialize, Serialize, Clone)]
30pub struct CompletionRequest {
31 pub input: OneOrMany<InputItem>,
33 pub model: String,
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub instructions: Option<String>,
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub max_output_tokens: Option<u64>,
41 #[serde(skip_serializing_if = "Option::is_none")]
43 pub stream: Option<bool>,
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub temperature: Option<f64>,
47 #[serde(skip_serializing_if = "Vec::is_empty")]
51 pub tools: Vec<ResponsesToolDefinition>,
52 #[serde(flatten)]
54 pub additional_parameters: AdditionalParameters,
55}
56
57impl CompletionRequest {
58 pub fn with_structured_outputs<S>(mut self, schema_name: S, schema: serde_json::Value) -> Self
59 where
60 S: Into<String>,
61 {
62 self.additional_parameters.text = Some(TextConfig::structured_output(schema_name, schema));
63
64 self
65 }
66
67 pub fn with_reasoning(mut self, reasoning: Reasoning) -> Self {
68 self.additional_parameters.reasoning = Some(reasoning);
69
70 self
71 }
72}
73
74#[derive(Debug, Deserialize, Serialize, Clone)]
76pub struct InputItem {
77 #[serde(skip_serializing_if = "Option::is_none")]
81 role: Option<Role>,
82 #[serde(flatten)]
84 input: InputContent,
85}
86
87#[derive(Debug, Deserialize, Serialize, Clone)]
89#[serde(rename_all = "lowercase")]
90pub enum Role {
91 User,
92 Assistant,
93 System,
94}
95
96#[derive(Debug, Deserialize, Serialize, Clone)]
98#[serde(tag = "type", rename_all = "snake_case")]
99pub enum InputContent {
100 Message(Message),
101 Reasoning(OpenAIReasoning),
102 FunctionCall(OutputFunctionCall),
103 FunctionCallOutput(ToolResult),
104}
105
106#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
107pub struct OpenAIReasoning {
108 id: String,
109 pub summary: Vec<ReasoningSummary>,
110 pub encrypted_content: Option<String>,
111 #[serde(skip_serializing_if = "Option::is_none")]
112 pub status: Option<ToolStatus>,
113}
114
115#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
116#[serde(tag = "type", rename_all = "snake_case")]
117pub enum ReasoningSummary {
118 SummaryText { text: String },
119}
120
121impl ReasoningSummary {
122 fn new(input: &str) -> Self {
123 Self::SummaryText {
124 text: input.to_string(),
125 }
126 }
127
128 pub fn text(&self) -> String {
129 let ReasoningSummary::SummaryText { text } = self;
130 text.clone()
131 }
132}
133
134#[derive(Debug, Deserialize, Serialize, Clone)]
136pub struct ToolResult {
137 call_id: String,
139 output: String,
141 status: ToolStatus,
143}
144
145impl From<Message> for InputItem {
146 fn from(value: Message) -> Self {
147 match value {
148 Message::User { .. } => Self {
149 role: Some(Role::User),
150 input: InputContent::Message(value),
151 },
152 Message::Assistant { ref content, .. } => {
153 let role = if content
154 .clone()
155 .iter()
156 .any(|x| matches!(x, AssistantContentType::Reasoning(_)))
157 {
158 None
159 } else {
160 Some(Role::Assistant)
161 };
162 Self {
163 role,
164 input: InputContent::Message(value),
165 }
166 }
167 Message::System { .. } => Self {
168 role: Some(Role::System),
169 input: InputContent::Message(value),
170 },
171 Message::ToolResult {
172 tool_call_id,
173 output,
174 } => Self {
175 role: None,
176 input: InputContent::FunctionCallOutput(ToolResult {
177 call_id: tool_call_id,
178 output,
179 status: ToolStatus::Completed,
180 }),
181 },
182 }
183 }
184}
185
186impl TryFrom<crate::completion::Message> for Vec<InputItem> {
187 type Error = CompletionError;
188
189 fn try_from(value: crate::completion::Message) -> Result<Self, Self::Error> {
190 match value {
191 crate::completion::Message::User { content } => {
192 let mut items = Vec::new();
193
194 for user_content in content {
195 match user_content {
196 crate::message::UserContent::Text(Text { text }) => {
197 items.push(InputItem {
198 role: Some(Role::User),
199 input: InputContent::Message(Message::User {
200 content: OneOrMany::one(UserContent::InputText { text }),
201 name: None,
202 }),
203 });
204 }
205 crate::message::UserContent::ToolResult(
206 crate::completion::message::ToolResult {
207 call_id,
208 content: tool_content,
209 ..
210 },
211 ) => {
212 for tool_result_content in tool_content {
213 let crate::completion::message::ToolResultContent::Text(Text {
214 text,
215 }) = tool_result_content
216 else {
217 return Err(CompletionError::ProviderError(
218 "This thing only supports text!".to_string(),
219 ));
220 };
221 items.push(InputItem {
223 role: None,
224 input: InputContent::FunctionCallOutput(ToolResult {
225 call_id: call_id
226 .clone()
227 .expect("The call ID of this tool should exist!"),
228 output: text,
229 status: ToolStatus::Completed,
230 }),
231 });
232 }
233 }
234 crate::message::UserContent::Document(Document { data, .. }) => {
236 items.push(InputItem {
237 role: Some(Role::User),
238 input: InputContent::Message(Message::User {
239 content: OneOrMany::one(UserContent::InputText { text: data }),
240 name: None,
241 }),
242 })
243 }
244 _ => {
245 return Err(CompletionError::ProviderError(
246 "This API only supports text and tool results at the moment"
247 .to_string(),
248 ));
249 }
250 }
251 }
252
253 Ok(items)
254 }
255 crate::completion::Message::Assistant { id, content } => {
256 let mut items = Vec::new();
257
258 for assistant_content in content {
259 match assistant_content {
260 crate::message::AssistantContent::Text(Text { text }) => {
261 let id = id.as_ref().unwrap_or(&String::default()).clone();
262 items.push(InputItem {
263 role: Some(Role::Assistant),
264 input: InputContent::Message(Message::Assistant {
265 content: OneOrMany::one(AssistantContentType::Text(
266 AssistantContent::OutputText(Text { text }),
267 )),
268 id,
269 name: None,
270 status: ToolStatus::Completed,
271 }),
272 });
273 }
274 crate::message::AssistantContent::ToolCall(crate::message::ToolCall {
275 id: tool_id,
276 call_id,
277 function,
278 }) => {
279 items.push(InputItem {
280 role: None,
281 input: InputContent::FunctionCall(OutputFunctionCall {
282 arguments: function.arguments,
283 call_id: call_id.expect("The tool call ID should exist!"),
284 id: tool_id,
285 name: function.name,
286 status: ToolStatus::Completed,
287 }),
288 });
289 }
290 crate::message::AssistantContent::Reasoning(
291 crate::message::Reasoning { id, reasoning },
292 ) => {
293 items.push(InputItem {
294 role: None,
295 input: InputContent::Reasoning(OpenAIReasoning {
296 id: id
297 .expect("An OpenAI-generated ID is required when using OpenAI reasoning items"),
298 summary: reasoning.into_iter().map(|x| ReasoningSummary::new(&x)).collect(),
299 encrypted_content: None,
300 status: None,
301 }),
302 });
303 }
304 }
305 }
306
307 Ok(items)
308 }
309 }
310 }
311}
312
313impl From<OneOrMany<String>> for Vec<ReasoningSummary> {
314 fn from(value: OneOrMany<String>) -> Self {
315 value.iter().map(|x| ReasoningSummary::new(x)).collect()
316 }
317}
318
319#[derive(Debug, Deserialize, Serialize, Clone)]
321pub struct ResponsesToolDefinition {
322 pub name: String,
324 pub parameters: serde_json::Value,
326 pub strict: bool,
328 #[serde(rename = "type")]
330 pub kind: String,
331 pub description: String,
333}
334
335fn add_props_false(schema: &mut serde_json::Value) {
339 if let Value::Object(obj) = schema {
340 let is_object_schema = obj.get("type") == Some(&Value::String("object".to_string()))
341 || obj.contains_key("properties");
342
343 if is_object_schema && !obj.contains_key("additionalProperties") {
344 obj.insert("additionalProperties".to_string(), Value::Bool(false));
345 }
346
347 if let Some(defs) = obj.get_mut("$defs")
348 && let Value::Object(defs_obj) = defs
349 {
350 for (_, def_schema) in defs_obj.iter_mut() {
351 add_props_false(def_schema);
352 }
353 }
354
355 if let Some(properties) = obj.get_mut("properties")
356 && let Value::Object(props) = properties
357 {
358 for (_, prop_value) in props.iter_mut() {
359 add_props_false(prop_value);
360 }
361 }
362
363 if let Some(items) = obj.get_mut("items") {
364 add_props_false(items);
365 }
366
367 for key in ["anyOf", "oneOf", "allOf"] {
369 if let Some(variants) = obj.get_mut(key)
370 && let Value::Array(variants_array) = variants
371 {
372 for variant in variants_array.iter_mut() {
373 add_props_false(variant);
374 }
375 }
376 }
377 }
378}
379
380impl From<completion::ToolDefinition> for ResponsesToolDefinition {
381 fn from(value: completion::ToolDefinition) -> Self {
382 let completion::ToolDefinition {
383 name,
384 mut parameters,
385 description,
386 } = value;
387
388 add_props_false(&mut parameters);
389
390 Self {
391 name,
392 parameters,
393 description,
394 kind: "function".to_string(),
395 strict: true,
396 }
397 }
398}
399
400#[derive(Clone, Debug, Serialize, Deserialize)]
403pub struct ResponsesUsage {
404 pub input_tokens: u64,
406 #[serde(skip_serializing_if = "Option::is_none")]
408 pub input_tokens_details: Option<InputTokensDetails>,
409 pub output_tokens: u64,
411 pub output_tokens_details: OutputTokensDetails,
413 pub total_tokens: u64,
415}
416
417impl ResponsesUsage {
418 pub(crate) fn new() -> Self {
420 Self {
421 input_tokens: 0,
422 input_tokens_details: Some(InputTokensDetails::new()),
423 output_tokens: 0,
424 output_tokens_details: OutputTokensDetails::new(),
425 total_tokens: 0,
426 }
427 }
428}
429
430impl Add for ResponsesUsage {
431 type Output = Self;
432
433 fn add(self, rhs: Self) -> Self::Output {
434 let input_tokens = self.input_tokens + rhs.input_tokens;
435 let input_tokens_details = self.input_tokens_details.map(|lhs| {
436 if let Some(tokens) = rhs.input_tokens_details {
437 lhs + tokens
438 } else {
439 lhs
440 }
441 });
442 let output_tokens = self.output_tokens + rhs.output_tokens;
443 let output_tokens_details = self.output_tokens_details + rhs.output_tokens_details;
444 let total_tokens = self.total_tokens + rhs.total_tokens;
445 Self {
446 input_tokens,
447 input_tokens_details,
448 output_tokens,
449 output_tokens_details,
450 total_tokens,
451 }
452 }
453}
454
455#[derive(Clone, Debug, Serialize, Deserialize)]
457pub struct InputTokensDetails {
458 pub cached_tokens: u64,
460}
461
462impl InputTokensDetails {
463 pub(crate) fn new() -> Self {
464 Self { cached_tokens: 0 }
465 }
466}
467
468impl Add for InputTokensDetails {
469 type Output = Self;
470 fn add(self, rhs: Self) -> Self::Output {
471 Self {
472 cached_tokens: self.cached_tokens + rhs.cached_tokens,
473 }
474 }
475}
476
477#[derive(Clone, Debug, Serialize, Deserialize)]
479pub struct OutputTokensDetails {
480 pub reasoning_tokens: u64,
482}
483
484impl OutputTokensDetails {
485 pub(crate) fn new() -> Self {
486 Self {
487 reasoning_tokens: 0,
488 }
489 }
490}
491
492impl Add for OutputTokensDetails {
493 type Output = Self;
494 fn add(self, rhs: Self) -> Self::Output {
495 Self {
496 reasoning_tokens: self.reasoning_tokens + rhs.reasoning_tokens,
497 }
498 }
499}
500
501#[derive(Clone, Debug, Default, Serialize, Deserialize)]
503pub struct IncompleteDetailsReason {
504 pub reason: String,
506}
507
508#[derive(Clone, Debug, Default, Serialize, Deserialize)]
510pub struct ResponseError {
511 pub code: String,
513 pub message: String,
515}
516
517#[derive(Clone, Debug, Deserialize, Serialize)]
519#[serde(rename_all = "snake_case")]
520pub enum ResponseObject {
521 Response,
522}
523
524#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
526#[serde(rename_all = "snake_case")]
527pub enum ResponseStatus {
528 InProgress,
529 Completed,
530 Failed,
531 Cancelled,
532 Queued,
533 Incomplete,
534}
535
536impl TryFrom<(String, crate::completion::CompletionRequest)> for CompletionRequest {
538 type Error = CompletionError;
539 fn try_from(
540 (model, req): (String, crate::completion::CompletionRequest),
541 ) -> Result<Self, Self::Error> {
542 let input = {
543 let mut partial_history = vec![];
544 if let Some(docs) = req.normalized_documents() {
545 partial_history.push(docs);
546 }
547 partial_history.extend(req.chat_history);
548
549 let mut full_history: Vec<InputItem> = Vec::new();
551
552 full_history.extend(
554 partial_history
555 .into_iter()
556 .map(|x| <Vec<InputItem>>::try_from(x).unwrap())
557 .collect::<Vec<Vec<InputItem>>>()
558 .into_iter()
559 .flatten()
560 .collect::<Vec<InputItem>>(),
561 );
562
563 full_history
564 };
565
566 let input = OneOrMany::many(input)
567 .expect("This should never panic - if it does, please file a bug report");
568
569 let stream = req
570 .additional_params
571 .clone()
572 .unwrap_or(Value::Null)
573 .as_bool();
574
575 let additional_parameters = if let Some(map) = req.additional_params {
576 serde_json::from_value::<AdditionalParameters>(map).expect("Converting additional parameters to AdditionalParameters should never fail as every field is an Option")
577 } else {
578 AdditionalParameters::default()
580 };
581
582 Ok(Self {
583 input,
584 model,
585 instructions: req.preamble,
586 max_output_tokens: req.max_tokens,
587 stream,
588 tools: req
589 .tools
590 .into_iter()
591 .map(ResponsesToolDefinition::from)
592 .collect(),
593 temperature: req.temperature,
594 additional_parameters,
595 })
596 }
597}
598
599#[derive(Clone)]
601pub struct ResponsesCompletionModel {
602 pub(crate) client: Client,
604 pub model: String,
606}
607
608impl ResponsesCompletionModel {
609 pub fn new(client: Client, model: &str) -> Self {
611 Self {
612 client,
613 model: model.to_string(),
614 }
615 }
616
617 pub fn completions_api(self) -> crate::providers::openai::completion::CompletionModel {
619 crate::providers::openai::completion::CompletionModel::new(self.client, &self.model)
620 }
621
622 pub(crate) fn create_completion_request(
624 &self,
625 completion_request: crate::completion::CompletionRequest,
626 ) -> Result<CompletionRequest, CompletionError> {
627 let req = CompletionRequest::try_from((self.model.clone(), completion_request))?;
628
629 Ok(req)
630 }
631}
632
633#[derive(Clone, Debug, Serialize, Deserialize)]
635pub struct CompletionResponse {
636 pub id: String,
638 pub object: ResponseObject,
640 pub created_at: u64,
642 pub status: ResponseStatus,
644 pub error: Option<ResponseError>,
646 pub incomplete_details: Option<IncompleteDetailsReason>,
648 pub instructions: Option<String>,
650 pub max_output_tokens: Option<u64>,
652 pub model: String,
654 pub usage: Option<ResponsesUsage>,
656 pub output: Vec<Output>,
658 pub tools: Vec<ResponsesToolDefinition>,
660 #[serde(flatten)]
662 pub additional_parameters: AdditionalParameters,
663}
664
665#[derive(Clone, Debug, Deserialize, Serialize, Default)]
668pub struct AdditionalParameters {
669 #[serde(skip_serializing_if = "Option::is_none")]
671 pub background: Option<bool>,
672 #[serde(skip_serializing_if = "Option::is_none")]
674 pub text: Option<TextConfig>,
675 #[serde(skip_serializing_if = "Option::is_none")]
677 pub include: Option<Vec<Include>>,
678 #[serde(skip_serializing_if = "Option::is_none")]
680 pub top_p: Option<f64>,
681 #[serde(skip_serializing_if = "Option::is_none")]
683 pub truncation: Option<TruncationStrategy>,
684 #[serde(skip_serializing_if = "Option::is_none")]
686 pub user: Option<String>,
687 #[serde(skip_serializing_if = "Map::is_empty", default)]
689 pub metadata: serde_json::Map<String, serde_json::Value>,
690 #[serde(skip_serializing_if = "Option::is_none")]
692 pub parallel_tool_calls: Option<bool>,
693 #[serde(skip_serializing_if = "Option::is_none")]
695 pub previous_response_id: Option<String>,
696 #[serde(skip_serializing_if = "Option::is_none")]
698 pub reasoning: Option<Reasoning>,
699 #[serde(skip_serializing_if = "Option::is_none")]
701 pub service_tier: Option<OpenAIServiceTier>,
702 #[serde(skip_serializing_if = "Option::is_none")]
704 pub store: Option<bool>,
705}
706
707impl AdditionalParameters {
708 pub fn to_json(self) -> serde_json::Value {
709 serde_json::to_value(self).expect("this should never fail since a struct that impls Deserialize will always be valid JSON")
710 }
711}
712
713#[derive(Clone, Debug, Default, Serialize, Deserialize)]
717#[serde(rename_all = "snake_case")]
718pub enum TruncationStrategy {
719 Auto,
720 #[default]
721 Disabled,
722}
723
724#[derive(Clone, Debug, Serialize, Deserialize)]
727pub struct TextConfig {
728 pub format: TextFormat,
729}
730
731impl TextConfig {
732 pub(crate) fn structured_output<S>(name: S, schema: serde_json::Value) -> Self
733 where
734 S: Into<String>,
735 {
736 Self {
737 format: TextFormat::JsonSchema(StructuredOutputsInput {
738 name: name.into(),
739 schema,
740 strict: true,
741 }),
742 }
743 }
744}
745
746#[derive(Clone, Debug, Serialize, Deserialize, Default)]
749#[serde(tag = "type")]
750#[serde(rename_all = "snake_case")]
751pub enum TextFormat {
752 JsonSchema(StructuredOutputsInput),
753 #[default]
754 Text,
755}
756
757#[derive(Clone, Debug, Serialize, Deserialize)]
759pub struct StructuredOutputsInput {
760 pub name: String,
762 pub schema: serde_json::Value,
764 pub strict: bool,
766}
767
768#[derive(Clone, Debug, Default, Serialize, Deserialize)]
770pub struct Reasoning {
771 pub effort: Option<ReasoningEffort>,
773 #[serde(skip_serializing_if = "Option::is_none")]
775 pub summary: Option<ReasoningSummaryLevel>,
776}
777
778impl Reasoning {
779 pub fn new() -> Self {
781 Self {
782 effort: None,
783 summary: None,
784 }
785 }
786
787 pub fn with_effort(mut self, reasoning_effort: ReasoningEffort) -> Self {
789 self.effort = Some(reasoning_effort);
790
791 self
792 }
793
794 pub fn with_summary_level(mut self, reasoning_summary_level: ReasoningSummaryLevel) -> Self {
796 self.summary = Some(reasoning_summary_level);
797
798 self
799 }
800}
801
802#[derive(Clone, Debug, Default, Serialize, Deserialize)]
804#[serde(rename_all = "snake_case")]
805pub enum OpenAIServiceTier {
806 #[default]
807 Auto,
808 Default,
809 Flex,
810}
811
812#[derive(Clone, Debug, Default, Serialize, Deserialize)]
814#[serde(rename_all = "snake_case")]
815pub enum ReasoningEffort {
816 Minimal,
817 Low,
818 #[default]
819 Medium,
820 High,
821}
822
823#[derive(Clone, Debug, Default, Serialize, Deserialize)]
825#[serde(rename_all = "snake_case")]
826pub enum ReasoningSummaryLevel {
827 #[default]
828 Auto,
829 Concise,
830 Detailed,
831}
832
833#[derive(Clone, Debug, Deserialize, Serialize)]
836pub enum Include {
837 #[serde(rename = "file_search_call.results")]
838 FileSearchCallResults,
839 #[serde(rename = "message.input_image.image_url")]
840 MessageInputImageImageUrl,
841 #[serde(rename = "computer_call.output.image_url")]
842 ComputerCallOutputOutputImageUrl,
843 #[serde(rename = "reasoning.encrypted_content")]
844 ReasoningEncryptedContent,
845 #[serde(rename = "code_interpreter_call.outputs")]
846 CodeInterpreterCallOutputs,
847}
848
849#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
851#[serde(tag = "type")]
852#[serde(rename_all = "snake_case")]
853pub enum Output {
854 Message(OutputMessage),
855 #[serde(alias = "function_call")]
856 FunctionCall(OutputFunctionCall),
857 Reasoning {
858 id: String,
859 summary: Vec<ReasoningSummary>,
860 },
861}
862
863impl From<Output> for Vec<completion::AssistantContent> {
864 fn from(value: Output) -> Self {
865 let res: Vec<completion::AssistantContent> = match value {
866 Output::Message(OutputMessage { content, .. }) => content
867 .into_iter()
868 .map(completion::AssistantContent::from)
869 .collect(),
870 Output::FunctionCall(OutputFunctionCall {
871 id,
872 arguments,
873 call_id,
874 name,
875 ..
876 }) => vec![completion::AssistantContent::tool_call_with_call_id(
877 id, call_id, name, arguments,
878 )],
879 Output::Reasoning { id, summary } => {
880 let summary: Vec<String> = summary.into_iter().map(|x| x.text()).collect();
881
882 vec![completion::AssistantContent::Reasoning(
883 message::Reasoning::multi(summary).with_id(id),
884 )]
885 }
886 };
887
888 res
889 }
890}
891
892#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
893pub struct OutputReasoning {
894 id: String,
895 summary: Vec<ReasoningSummary>,
896 status: ToolStatus,
897}
898
899#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
901pub struct OutputFunctionCall {
902 pub id: String,
903 #[serde(with = "json_utils::stringified_json")]
904 pub arguments: serde_json::Value,
905 pub call_id: String,
906 pub name: String,
907 pub status: ToolStatus,
908}
909
910#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
912#[serde(rename_all = "snake_case")]
913pub enum ToolStatus {
914 InProgress,
915 Completed,
916 Incomplete,
917}
918
919#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
921pub struct OutputMessage {
922 pub id: String,
924 pub role: OutputRole,
926 pub status: ResponseStatus,
928 pub content: Vec<AssistantContent>,
930}
931
932#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
934#[serde(rename_all = "snake_case")]
935pub enum OutputRole {
936 Assistant,
937}
938
939impl completion::CompletionModel for ResponsesCompletionModel {
940 type Response = CompletionResponse;
941 type StreamingResponse = StreamingCompletionResponse;
942
943 #[cfg_attr(feature = "worker", worker::send)]
944 async fn completion(
945 &self,
946 completion_request: crate::completion::CompletionRequest,
947 ) -> Result<completion::CompletionResponse<Self::Response>, CompletionError> {
948 let request = self.create_completion_request(completion_request)?;
949 let request = serde_json::to_value(request)?;
950
951 tracing::debug!("OpenAI input: {}", serde_json::to_string_pretty(&request)?);
952
953 let response = self.client.post("/responses").json(&request).send().await?;
954
955 if response.status().is_success() {
956 let t = response.text().await?;
957 tracing::debug!(target: "rig", "OpenAI response: {}", t);
958
959 let response = serde_json::from_str::<Self::Response>(&t)?;
960 response.try_into()
961 } else {
962 Err(CompletionError::ProviderError(response.text().await?))
963 }
964 }
965
966 #[cfg_attr(feature = "worker", worker::send)]
967 async fn stream(
968 &self,
969 request: crate::completion::CompletionRequest,
970 ) -> Result<
971 crate::streaming::StreamingCompletionResponse<Self::StreamingResponse>,
972 CompletionError,
973 > {
974 Self::stream(self, request).await
975 }
976}
977
978impl TryFrom<CompletionResponse> for completion::CompletionResponse<CompletionResponse> {
979 type Error = CompletionError;
980
981 fn try_from(response: CompletionResponse) -> Result<Self, Self::Error> {
982 if response.output.is_empty() {
983 return Err(CompletionError::ResponseError(
984 "Response contained no parts".to_owned(),
985 ));
986 }
987
988 let content: Vec<completion::AssistantContent> = response
989 .output
990 .iter()
991 .cloned()
992 .flat_map(<Vec<completion::AssistantContent>>::from)
993 .collect();
994
995 let choice = OneOrMany::many(content).map_err(|_| {
996 CompletionError::ResponseError(
997 "Response contained no message or tool call (empty)".to_owned(),
998 )
999 })?;
1000
1001 let usage = response
1002 .usage
1003 .as_ref()
1004 .map(|usage| completion::Usage {
1005 input_tokens: usage.input_tokens,
1006 output_tokens: usage.output_tokens,
1007 total_tokens: usage.total_tokens,
1008 })
1009 .unwrap_or_default();
1010
1011 Ok(completion::CompletionResponse {
1012 choice,
1013 usage,
1014 raw_response: response,
1015 })
1016 }
1017}
1018
1019#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
1021#[serde(tag = "role", rename_all = "lowercase")]
1022pub enum Message {
1023 #[serde(alias = "developer")]
1024 System {
1025 #[serde(deserialize_with = "string_or_one_or_many")]
1026 content: OneOrMany<SystemContent>,
1027 #[serde(skip_serializing_if = "Option::is_none")]
1028 name: Option<String>,
1029 },
1030 User {
1031 #[serde(deserialize_with = "string_or_one_or_many")]
1032 content: OneOrMany<UserContent>,
1033 #[serde(skip_serializing_if = "Option::is_none")]
1034 name: Option<String>,
1035 },
1036 Assistant {
1037 content: OneOrMany<AssistantContentType>,
1038 #[serde(skip_serializing_if = "String::is_empty")]
1039 id: String,
1040 #[serde(skip_serializing_if = "Option::is_none")]
1041 name: Option<String>,
1042 status: ToolStatus,
1043 },
1044 #[serde(rename = "tool")]
1045 ToolResult {
1046 tool_call_id: String,
1047 output: String,
1048 },
1049}
1050
1051#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone)]
1053#[serde(rename_all = "lowercase")]
1054pub enum ToolResultContentType {
1055 #[default]
1056 Text,
1057}
1058
1059impl Message {
1060 pub fn system(content: &str) -> Self {
1061 Message::System {
1062 content: OneOrMany::one(content.to_owned().into()),
1063 name: None,
1064 }
1065 }
1066}
1067
1068#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
1071#[serde(tag = "type", rename_all = "snake_case")]
1072pub enum AssistantContent {
1073 OutputText(Text),
1074 Refusal { refusal: String },
1075}
1076
1077impl From<AssistantContent> for completion::AssistantContent {
1078 fn from(value: AssistantContent) -> Self {
1079 match value {
1080 AssistantContent::Refusal { refusal } => {
1081 completion::AssistantContent::Text(Text { text: refusal })
1082 }
1083 AssistantContent::OutputText(Text { text }) => {
1084 completion::AssistantContent::Text(Text { text })
1085 }
1086 }
1087 }
1088}
1089
1090#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
1092#[serde(untagged)]
1093pub enum AssistantContentType {
1094 Text(AssistantContent),
1095 ToolCall(OutputFunctionCall),
1096 Reasoning(OpenAIReasoning),
1097}
1098
1099#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
1101#[serde(tag = "type", rename_all = "snake_case")]
1102pub enum UserContent {
1103 InputText {
1104 text: String,
1105 },
1106 #[serde(rename = "image_url")]
1107 Image {
1108 image_url: ImageUrl,
1109 },
1110 Audio {
1111 input_audio: InputAudio,
1112 },
1113 #[serde(rename = "tool")]
1114 ToolResult {
1115 tool_call_id: String,
1116 output: String,
1117 },
1118}
1119
1120impl TryFrom<message::Message> for Vec<Message> {
1121 type Error = message::MessageError;
1122
1123 fn try_from(message: message::Message) -> Result<Self, Self::Error> {
1124 match message {
1125 message::Message::User { content } => {
1126 let (tool_results, other_content): (Vec<_>, Vec<_>) = content
1127 .into_iter()
1128 .partition(|content| matches!(content, message::UserContent::ToolResult(_)));
1129
1130 if !tool_results.is_empty() {
1133 tool_results
1134 .into_iter()
1135 .map(|content| match content {
1136 message::UserContent::ToolResult(message::ToolResult {
1137 call_id,
1138 content,
1139 ..
1140 }) => Ok::<_, message::MessageError>(Message::ToolResult {
1141 tool_call_id: call_id.expect("The tool call ID should exist"),
1142 output: {
1143 let res = content.first();
1144 match res {
1145 completion::message::ToolResultContent::Text(Text {
1146 text,
1147 }) => text,
1148 _ => return Err(MessageError::ConversionError("This API only currently supports text tool results".into()))
1149 }
1150 },
1151 }),
1152 _ => unreachable!(),
1153 })
1154 .collect::<Result<Vec<_>, _>>()
1155 } else {
1156 let other_content = OneOrMany::many(other_content).expect(
1157 "There must be other content here if there were no tool result content",
1158 );
1159
1160 Ok(vec![Message::User {
1161 content: other_content.map(|content| match content {
1162 message::UserContent::Text(message::Text { text }) => {
1163 UserContent::InputText { text }
1164 }
1165 message::UserContent::Image(message::Image {
1166 data, detail, ..
1167 }) => UserContent::Image {
1168 image_url: ImageUrl {
1169 url: data,
1170 detail: detail.unwrap_or_default(),
1171 },
1172 },
1173 message::UserContent::Document(message::Document { data, .. }) => {
1174 UserContent::InputText { text: data }
1175 }
1176 message::UserContent::Audio(message::Audio {
1177 data,
1178 media_type,
1179 ..
1180 }) => UserContent::Audio {
1181 input_audio: InputAudio {
1182 data,
1183 format: match media_type {
1184 Some(media_type) => media_type,
1185 None => AudioMediaType::MP3,
1186 },
1187 },
1188 },
1189 _ => unreachable!(),
1190 }),
1191 name: None,
1192 }])
1193 }
1194 }
1195 message::Message::Assistant { content, id } => {
1196 let assistant_message_id = id;
1197
1198 match content.first() {
1199 crate::message::AssistantContent::Text(Text { text }) => {
1200 Ok(vec![Message::Assistant {
1201 id: assistant_message_id
1202 .expect("The assistant message ID should exist"),
1203 status: ToolStatus::Completed,
1204 content: OneOrMany::one(AssistantContentType::Text(
1205 AssistantContent::OutputText(Text { text }),
1206 )),
1207 name: None,
1208 }])
1209 }
1210 crate::message::AssistantContent::ToolCall(crate::message::ToolCall {
1211 id,
1212 call_id,
1213 function,
1214 }) => Ok(vec![Message::Assistant {
1215 content: OneOrMany::one(AssistantContentType::ToolCall(
1216 OutputFunctionCall {
1217 call_id: call_id.expect("The call ID should exist"),
1218 arguments: function.arguments,
1219 id,
1220 name: function.name,
1221 status: ToolStatus::Completed,
1222 },
1223 )),
1224 id: assistant_message_id.expect("The assistant message ID should exist!"),
1225 name: None,
1226 status: ToolStatus::Completed,
1227 }]),
1228 crate::message::AssistantContent::Reasoning(crate::message::Reasoning {
1229 id,
1230 reasoning,
1231 }) => Ok(vec![Message::Assistant {
1232 content: OneOrMany::one(AssistantContentType::Reasoning(OpenAIReasoning {
1233 id: id.expect("An OpenAI-generated ID is required when using OpenAI reasoning items"),
1234 summary: reasoning.into_iter().map(|x| ReasoningSummary::SummaryText { text: x }).collect(),
1235 encrypted_content: None,
1236 status: Some(ToolStatus::Completed),
1237 })),
1238 id: assistant_message_id.expect("The assistant message ID should exist!"),
1239 name: None,
1240 status: (ToolStatus::Completed),
1241 }]),
1242 }
1243 }
1244 }
1245 }
1246}
1247
1248impl FromStr for UserContent {
1249 type Err = Infallible;
1250
1251 fn from_str(s: &str) -> Result<Self, Self::Err> {
1252 Ok(UserContent::InputText {
1253 text: s.to_string(),
1254 })
1255 }
1256}