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
335impl From<completion::ToolDefinition> for ResponsesToolDefinition {
336 fn from(value: completion::ToolDefinition) -> Self {
337 let completion::ToolDefinition {
338 name,
339 mut parameters,
340 description,
341 } = value;
342
343 let parameters = parameters
344 .as_object_mut()
345 .expect("parameters should be a JSON object");
346 parameters.insert(
347 "additionalProperties".to_string(),
348 serde_json::Value::Bool(false),
349 );
350
351 let parameters = serde_json::Value::Object(parameters.clone());
352
353 Self {
354 name,
355 parameters,
356 description,
357 kind: "function".to_string(),
358 strict: true,
359 }
360 }
361}
362
363#[derive(Clone, Debug, Serialize, Deserialize)]
366pub struct ResponsesUsage {
367 pub input_tokens: u64,
369 #[serde(skip_serializing_if = "Option::is_none")]
371 pub input_tokens_details: Option<InputTokensDetails>,
372 pub output_tokens: u64,
374 pub output_tokens_details: OutputTokensDetails,
376 pub total_tokens: u64,
378}
379
380impl ResponsesUsage {
381 pub(crate) fn new() -> Self {
383 Self {
384 input_tokens: 0,
385 input_tokens_details: Some(InputTokensDetails::new()),
386 output_tokens: 0,
387 output_tokens_details: OutputTokensDetails::new(),
388 total_tokens: 0,
389 }
390 }
391}
392
393impl Add for ResponsesUsage {
394 type Output = Self;
395
396 fn add(self, rhs: Self) -> Self::Output {
397 let input_tokens = self.input_tokens + rhs.input_tokens;
398 let input_tokens_details = self.input_tokens_details.map(|lhs| {
399 if let Some(tokens) = rhs.input_tokens_details {
400 lhs + tokens
401 } else {
402 lhs
403 }
404 });
405 let output_tokens = self.output_tokens + rhs.output_tokens;
406 let output_tokens_details = self.output_tokens_details + rhs.output_tokens_details;
407 let total_tokens = self.total_tokens + rhs.total_tokens;
408 Self {
409 input_tokens,
410 input_tokens_details,
411 output_tokens,
412 output_tokens_details,
413 total_tokens,
414 }
415 }
416}
417
418#[derive(Clone, Debug, Serialize, Deserialize)]
420pub struct InputTokensDetails {
421 pub cached_tokens: u64,
423}
424
425impl InputTokensDetails {
426 pub(crate) fn new() -> Self {
427 Self { cached_tokens: 0 }
428 }
429}
430
431impl Add for InputTokensDetails {
432 type Output = Self;
433 fn add(self, rhs: Self) -> Self::Output {
434 Self {
435 cached_tokens: self.cached_tokens + rhs.cached_tokens,
436 }
437 }
438}
439
440#[derive(Clone, Debug, Serialize, Deserialize)]
442pub struct OutputTokensDetails {
443 pub reasoning_tokens: u64,
445}
446
447impl OutputTokensDetails {
448 pub(crate) fn new() -> Self {
449 Self {
450 reasoning_tokens: 0,
451 }
452 }
453}
454
455impl Add for OutputTokensDetails {
456 type Output = Self;
457 fn add(self, rhs: Self) -> Self::Output {
458 Self {
459 reasoning_tokens: self.reasoning_tokens + rhs.reasoning_tokens,
460 }
461 }
462}
463
464#[derive(Clone, Debug, Default, Serialize, Deserialize)]
466pub struct IncompleteDetailsReason {
467 pub reason: String,
469}
470
471#[derive(Clone, Debug, Default, Serialize, Deserialize)]
473pub struct ResponseError {
474 pub code: String,
476 pub message: String,
478}
479
480#[derive(Clone, Debug, Deserialize, Serialize)]
482#[serde(rename_all = "snake_case")]
483pub enum ResponseObject {
484 Response,
485}
486
487#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
489#[serde(rename_all = "snake_case")]
490pub enum ResponseStatus {
491 InProgress,
492 Completed,
493 Failed,
494 Cancelled,
495 Queued,
496 Incomplete,
497}
498
499impl TryFrom<(String, crate::completion::CompletionRequest)> for CompletionRequest {
501 type Error = CompletionError;
502 fn try_from(
503 (model, req): (String, crate::completion::CompletionRequest),
504 ) -> Result<Self, Self::Error> {
505 let input = {
506 let mut partial_history = vec![];
507 if let Some(docs) = req.normalized_documents() {
508 partial_history.push(docs);
509 }
510 partial_history.extend(req.chat_history);
511
512 let mut full_history: Vec<InputItem> = Vec::new();
514
515 full_history.extend(
517 partial_history
518 .into_iter()
519 .map(|x| <Vec<InputItem>>::try_from(x).unwrap())
520 .collect::<Vec<Vec<InputItem>>>()
521 .into_iter()
522 .flatten()
523 .collect::<Vec<InputItem>>(),
524 );
525
526 full_history
527 };
528
529 let input = OneOrMany::many(input)
530 .expect("This should never panic - if it does, please file a bug report");
531
532 let stream = req
533 .additional_params
534 .clone()
535 .unwrap_or(Value::Null)
536 .as_bool();
537
538 let additional_parameters = if let Some(map) = req.additional_params {
539 serde_json::from_value::<AdditionalParameters>(map).expect("Converting additional parameters to AdditionalParameters should never fail as every field is an Option")
540 } else {
541 AdditionalParameters::default()
543 };
544
545 Ok(Self {
546 input,
547 model,
548 instructions: req.preamble,
549 max_output_tokens: req.max_tokens,
550 stream,
551 tools: req
552 .tools
553 .into_iter()
554 .map(ResponsesToolDefinition::from)
555 .collect(),
556 temperature: req.temperature,
557 additional_parameters,
558 })
559 }
560}
561
562#[derive(Clone)]
564pub struct ResponsesCompletionModel {
565 pub(crate) client: Client,
567 pub model: String,
569}
570
571impl ResponsesCompletionModel {
572 pub fn new(client: Client, model: &str) -> Self {
574 Self {
575 client,
576 model: model.to_string(),
577 }
578 }
579
580 pub fn completions_api(self) -> crate::providers::openai::completion::CompletionModel {
582 crate::providers::openai::completion::CompletionModel::new(self.client, &self.model)
583 }
584
585 pub(crate) fn create_completion_request(
587 &self,
588 completion_request: crate::completion::CompletionRequest,
589 ) -> Result<CompletionRequest, CompletionError> {
590 let req = CompletionRequest::try_from((self.model.clone(), completion_request))?;
591
592 Ok(req)
593 }
594}
595
596#[derive(Clone, Debug, Serialize, Deserialize)]
598pub struct CompletionResponse {
599 pub id: String,
601 pub object: ResponseObject,
603 pub created_at: u64,
605 pub status: ResponseStatus,
607 pub error: Option<ResponseError>,
609 pub incomplete_details: Option<IncompleteDetailsReason>,
611 pub instructions: Option<String>,
613 pub max_output_tokens: Option<u64>,
615 pub model: String,
617 pub usage: Option<ResponsesUsage>,
619 pub output: Vec<Output>,
621 pub tools: Vec<ResponsesToolDefinition>,
623 #[serde(flatten)]
625 pub additional_parameters: AdditionalParameters,
626}
627
628#[derive(Clone, Debug, Deserialize, Serialize, Default)]
631pub struct AdditionalParameters {
632 #[serde(skip_serializing_if = "Option::is_none")]
634 pub background: Option<bool>,
635 #[serde(skip_serializing_if = "Option::is_none")]
637 pub text: Option<TextConfig>,
638 #[serde(skip_serializing_if = "Option::is_none")]
640 pub include: Option<Vec<Include>>,
641 #[serde(skip_serializing_if = "Option::is_none")]
643 pub top_p: Option<f64>,
644 #[serde(skip_serializing_if = "Option::is_none")]
646 pub truncation: Option<TruncationStrategy>,
647 #[serde(skip_serializing_if = "Option::is_none")]
649 pub user: Option<String>,
650 #[serde(skip_serializing_if = "Map::is_empty", default)]
652 pub metadata: serde_json::Map<String, serde_json::Value>,
653 #[serde(skip_serializing_if = "Option::is_none")]
655 pub parallel_tool_calls: Option<bool>,
656 #[serde(skip_serializing_if = "Option::is_none")]
658 pub previous_response_id: Option<String>,
659 #[serde(skip_serializing_if = "Option::is_none")]
661 pub reasoning: Option<Reasoning>,
662 #[serde(skip_serializing_if = "Option::is_none")]
664 pub service_tier: Option<OpenAIServiceTier>,
665 #[serde(skip_serializing_if = "Option::is_none")]
667 pub store: Option<bool>,
668}
669
670impl AdditionalParameters {
671 pub fn to_json(self) -> serde_json::Value {
672 serde_json::to_value(self).expect("this should never fail since a struct that impls Deserialize will always be valid JSON")
673 }
674}
675
676#[derive(Clone, Debug, Default, Serialize, Deserialize)]
680#[serde(rename_all = "snake_case")]
681pub enum TruncationStrategy {
682 Auto,
683 #[default]
684 Disabled,
685}
686
687#[derive(Clone, Debug, Serialize, Deserialize)]
690pub struct TextConfig {
691 pub format: TextFormat,
692}
693
694impl TextConfig {
695 pub(crate) fn structured_output<S>(name: S, schema: serde_json::Value) -> Self
696 where
697 S: Into<String>,
698 {
699 Self {
700 format: TextFormat::JsonSchema(StructuredOutputsInput {
701 name: name.into(),
702 schema,
703 strict: true,
704 }),
705 }
706 }
707}
708
709#[derive(Clone, Debug, Serialize, Deserialize, Default)]
712#[serde(tag = "type")]
713#[serde(rename_all = "snake_case")]
714pub enum TextFormat {
715 JsonSchema(StructuredOutputsInput),
716 #[default]
717 Text,
718}
719
720#[derive(Clone, Debug, Serialize, Deserialize)]
722pub struct StructuredOutputsInput {
723 pub name: String,
725 pub schema: serde_json::Value,
727 pub strict: bool,
729}
730
731#[derive(Clone, Debug, Default, Serialize, Deserialize)]
733pub struct Reasoning {
734 pub effort: Option<ReasoningEffort>,
736 #[serde(skip_serializing_if = "Option::is_none")]
738 pub summary: Option<ReasoningSummaryLevel>,
739}
740
741impl Reasoning {
742 pub fn new() -> Self {
744 Self {
745 effort: None,
746 summary: None,
747 }
748 }
749
750 pub fn with_effort(mut self, reasoning_effort: ReasoningEffort) -> Self {
752 self.effort = Some(reasoning_effort);
753
754 self
755 }
756
757 pub fn with_summary_level(mut self, reasoning_summary_level: ReasoningSummaryLevel) -> Self {
759 self.summary = Some(reasoning_summary_level);
760
761 self
762 }
763}
764
765#[derive(Clone, Debug, Default, Serialize, Deserialize)]
767#[serde(rename_all = "snake_case")]
768pub enum OpenAIServiceTier {
769 #[default]
770 Auto,
771 Default,
772 Flex,
773}
774
775#[derive(Clone, Debug, Default, Serialize, Deserialize)]
777#[serde(rename_all = "snake_case")]
778pub enum ReasoningEffort {
779 Low,
780 #[default]
781 Medium,
782 High,
783}
784
785#[derive(Clone, Debug, Default, Serialize, Deserialize)]
787#[serde(rename_all = "snake_case")]
788pub enum ReasoningSummaryLevel {
789 #[default]
790 Auto,
791 Concise,
792 Detailed,
793}
794
795#[derive(Clone, Debug, Deserialize, Serialize)]
798pub enum Include {
799 #[serde(rename = "file_search_call.results")]
800 FileSearchCallResults,
801 #[serde(rename = "message.input_image.image_url")]
802 MessageInputImageImageUrl,
803 #[serde(rename = "computer_call.output.image_url")]
804 ComputerCallOutputOutputImageUrl,
805 #[serde(rename = "reasoning.encrypted_content")]
806 ReasoningEncryptedContent,
807 #[serde(rename = "code_interpreter_call.outputs")]
808 CodeInterpreterCallOutputs,
809}
810
811#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
813#[serde(tag = "type")]
814#[serde(rename_all = "snake_case")]
815pub enum Output {
816 Message(OutputMessage),
817 #[serde(alias = "function_call")]
818 FunctionCall(OutputFunctionCall),
819 Reasoning {
820 id: String,
821 summary: Vec<ReasoningSummary>,
822 },
823}
824
825impl From<Output> for Vec<completion::AssistantContent> {
826 fn from(value: Output) -> Self {
827 let res: Vec<completion::AssistantContent> = match value {
828 Output::Message(OutputMessage { content, .. }) => content
829 .into_iter()
830 .map(completion::AssistantContent::from)
831 .collect(),
832 Output::FunctionCall(OutputFunctionCall {
833 id,
834 arguments,
835 call_id,
836 name,
837 ..
838 }) => vec![completion::AssistantContent::tool_call_with_call_id(
839 id, call_id, name, arguments,
840 )],
841 Output::Reasoning { id, summary } => {
842 let summary: Vec<String> = summary.into_iter().map(|x| x.text()).collect();
843
844 vec![completion::AssistantContent::Reasoning(
845 message::Reasoning::multi(summary).with_id(id),
846 )]
847 }
848 };
849
850 res
851 }
852}
853
854#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
855pub struct OutputReasoning {
856 id: String,
857 summary: Vec<ReasoningSummary>,
858 status: ToolStatus,
859}
860
861#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
863pub struct OutputFunctionCall {
864 pub id: String,
865 #[serde(with = "json_utils::stringified_json")]
866 pub arguments: serde_json::Value,
867 pub call_id: String,
868 pub name: String,
869 pub status: ToolStatus,
870}
871
872#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
874#[serde(rename_all = "snake_case")]
875pub enum ToolStatus {
876 InProgress,
877 Completed,
878 Incomplete,
879}
880
881#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
883pub struct OutputMessage {
884 pub id: String,
886 pub role: OutputRole,
888 pub status: ResponseStatus,
890 pub content: Vec<AssistantContent>,
892}
893
894#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
896#[serde(rename_all = "snake_case")]
897pub enum OutputRole {
898 Assistant,
899}
900
901impl completion::CompletionModel for ResponsesCompletionModel {
902 type Response = CompletionResponse;
903 type StreamingResponse = StreamingCompletionResponse;
904
905 #[cfg_attr(feature = "worker", worker::send)]
906 async fn completion(
907 &self,
908 completion_request: crate::completion::CompletionRequest,
909 ) -> Result<completion::CompletionResponse<Self::Response>, CompletionError> {
910 let request = self.create_completion_request(completion_request)?;
911 let request = serde_json::to_value(request)?;
912
913 tracing::debug!("OpenAI input: {}", serde_json::to_string_pretty(&request)?);
914
915 let response = self.client.post("/responses").json(&request).send().await?;
916
917 if response.status().is_success() {
918 let t = response.text().await?;
919 tracing::debug!(target: "rig", "OpenAI response: {}", t);
920
921 let response = serde_json::from_str::<Self::Response>(&t)?;
922 response.try_into()
923 } else {
924 Err(CompletionError::ProviderError(response.text().await?))
925 }
926 }
927
928 #[cfg_attr(feature = "worker", worker::send)]
929 async fn stream(
930 &self,
931 request: crate::completion::CompletionRequest,
932 ) -> Result<
933 crate::streaming::StreamingCompletionResponse<Self::StreamingResponse>,
934 CompletionError,
935 > {
936 Self::stream(self, request).await
937 }
938}
939
940impl TryFrom<CompletionResponse> for completion::CompletionResponse<CompletionResponse> {
941 type Error = CompletionError;
942
943 fn try_from(response: CompletionResponse) -> Result<Self, Self::Error> {
944 if response.output.is_empty() {
945 return Err(CompletionError::ResponseError(
946 "Response contained no parts".to_owned(),
947 ));
948 }
949
950 let content: Vec<completion::AssistantContent> = response
951 .output
952 .iter()
953 .cloned()
954 .flat_map(<Vec<completion::AssistantContent>>::from)
955 .collect();
956
957 let choice = OneOrMany::many(content).map_err(|_| {
958 CompletionError::ResponseError(
959 "Response contained no message or tool call (empty)".to_owned(),
960 )
961 })?;
962
963 let usage = response
964 .usage
965 .as_ref()
966 .map(|usage| completion::Usage {
967 input_tokens: usage.input_tokens,
968 output_tokens: usage.output_tokens,
969 total_tokens: usage.total_tokens,
970 })
971 .unwrap_or_default();
972
973 Ok(completion::CompletionResponse {
974 choice,
975 usage,
976 raw_response: response,
977 })
978 }
979}
980
981#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
983#[serde(tag = "role", rename_all = "lowercase")]
984pub enum Message {
985 #[serde(alias = "developer")]
986 System {
987 #[serde(deserialize_with = "string_or_one_or_many")]
988 content: OneOrMany<SystemContent>,
989 #[serde(skip_serializing_if = "Option::is_none")]
990 name: Option<String>,
991 },
992 User {
993 #[serde(deserialize_with = "string_or_one_or_many")]
994 content: OneOrMany<UserContent>,
995 #[serde(skip_serializing_if = "Option::is_none")]
996 name: Option<String>,
997 },
998 Assistant {
999 content: OneOrMany<AssistantContentType>,
1000 #[serde(skip_serializing_if = "String::is_empty")]
1001 id: String,
1002 #[serde(skip_serializing_if = "Option::is_none")]
1003 name: Option<String>,
1004 status: ToolStatus,
1005 },
1006 #[serde(rename = "tool")]
1007 ToolResult {
1008 tool_call_id: String,
1009 output: String,
1010 },
1011}
1012
1013#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone)]
1015#[serde(rename_all = "lowercase")]
1016pub enum ToolResultContentType {
1017 #[default]
1018 Text,
1019}
1020
1021impl Message {
1022 pub fn system(content: &str) -> Self {
1023 Message::System {
1024 content: OneOrMany::one(content.to_owned().into()),
1025 name: None,
1026 }
1027 }
1028}
1029
1030#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
1033#[serde(tag = "type", rename_all = "snake_case")]
1034pub enum AssistantContent {
1035 OutputText(Text),
1036 Refusal { refusal: String },
1037}
1038
1039impl From<AssistantContent> for completion::AssistantContent {
1040 fn from(value: AssistantContent) -> Self {
1041 match value {
1042 AssistantContent::Refusal { refusal } => {
1043 completion::AssistantContent::Text(Text { text: refusal })
1044 }
1045 AssistantContent::OutputText(Text { text }) => {
1046 completion::AssistantContent::Text(Text { text })
1047 }
1048 }
1049 }
1050}
1051
1052#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
1054#[serde(untagged)]
1055pub enum AssistantContentType {
1056 Text(AssistantContent),
1057 ToolCall(OutputFunctionCall),
1058 Reasoning(OpenAIReasoning),
1059}
1060
1061#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
1063#[serde(tag = "type", rename_all = "snake_case")]
1064pub enum UserContent {
1065 InputText {
1066 text: String,
1067 },
1068 #[serde(rename = "image_url")]
1069 Image {
1070 image_url: ImageUrl,
1071 },
1072 Audio {
1073 input_audio: InputAudio,
1074 },
1075 #[serde(rename = "tool")]
1076 ToolResult {
1077 tool_call_id: String,
1078 output: String,
1079 },
1080}
1081
1082impl TryFrom<message::Message> for Vec<Message> {
1083 type Error = message::MessageError;
1084
1085 fn try_from(message: message::Message) -> Result<Self, Self::Error> {
1086 match message {
1087 message::Message::User { content } => {
1088 let (tool_results, other_content): (Vec<_>, Vec<_>) = content
1089 .into_iter()
1090 .partition(|content| matches!(content, message::UserContent::ToolResult(_)));
1091
1092 if !tool_results.is_empty() {
1095 tool_results
1096 .into_iter()
1097 .map(|content| match content {
1098 message::UserContent::ToolResult(message::ToolResult {
1099 call_id,
1100 content,
1101 ..
1102 }) => Ok::<_, message::MessageError>(Message::ToolResult {
1103 tool_call_id: call_id.expect("The tool call ID should exist"),
1104 output: {
1105 let res = content.first();
1106 match res {
1107 completion::message::ToolResultContent::Text(Text {
1108 text,
1109 }) => text,
1110 _ => return Err(MessageError::ConversionError("This API only currently supports text tool results".into()))
1111 }
1112 },
1113 }),
1114 _ => unreachable!(),
1115 })
1116 .collect::<Result<Vec<_>, _>>()
1117 } else {
1118 let other_content = OneOrMany::many(other_content).expect(
1119 "There must be other content here if there were no tool result content",
1120 );
1121
1122 Ok(vec![Message::User {
1123 content: other_content.map(|content| match content {
1124 message::UserContent::Text(message::Text { text }) => {
1125 UserContent::InputText { text }
1126 }
1127 message::UserContent::Image(message::Image {
1128 data, detail, ..
1129 }) => UserContent::Image {
1130 image_url: ImageUrl {
1131 url: data,
1132 detail: detail.unwrap_or_default(),
1133 },
1134 },
1135 message::UserContent::Document(message::Document { data, .. }) => {
1136 UserContent::InputText { text: data }
1137 }
1138 message::UserContent::Audio(message::Audio {
1139 data,
1140 media_type,
1141 ..
1142 }) => UserContent::Audio {
1143 input_audio: InputAudio {
1144 data,
1145 format: match media_type {
1146 Some(media_type) => media_type,
1147 None => AudioMediaType::MP3,
1148 },
1149 },
1150 },
1151 _ => unreachable!(),
1152 }),
1153 name: None,
1154 }])
1155 }
1156 }
1157 message::Message::Assistant { content, id } => {
1158 let assistant_message_id = id;
1159
1160 match content.first() {
1161 crate::message::AssistantContent::Text(Text { text }) => {
1162 Ok(vec![Message::Assistant {
1163 id: assistant_message_id
1164 .expect("The assistant message ID should exist"),
1165 status: ToolStatus::Completed,
1166 content: OneOrMany::one(AssistantContentType::Text(
1167 AssistantContent::OutputText(Text { text }),
1168 )),
1169 name: None,
1170 }])
1171 }
1172 crate::message::AssistantContent::ToolCall(crate::message::ToolCall {
1173 id,
1174 call_id,
1175 function,
1176 }) => Ok(vec![Message::Assistant {
1177 content: OneOrMany::one(AssistantContentType::ToolCall(
1178 OutputFunctionCall {
1179 call_id: call_id.expect("The call ID should exist"),
1180 arguments: function.arguments,
1181 id,
1182 name: function.name,
1183 status: ToolStatus::Completed,
1184 },
1185 )),
1186 id: assistant_message_id.expect("The assistant message ID should exist!"),
1187 name: None,
1188 status: ToolStatus::Completed,
1189 }]),
1190 crate::message::AssistantContent::Reasoning(crate::message::Reasoning {
1191 id,
1192 reasoning,
1193 }) => Ok(vec![Message::Assistant {
1194 content: OneOrMany::one(AssistantContentType::Reasoning(OpenAIReasoning {
1195 id: id.expect("An OpenAI-generated ID is required when using OpenAI reasoning items"),
1196 summary: reasoning.into_iter().map(|x| ReasoningSummary::SummaryText { text: x }).collect(),
1197 encrypted_content: None,
1198 status: Some(ToolStatus::Completed),
1199 })),
1200 id: assistant_message_id.expect("The assistant message ID should exist!"),
1201 name: None,
1202 status: (ToolStatus::Completed),
1203 }]),
1204 }
1205 }
1206 }
1207 }
1208}
1209
1210impl FromStr for UserContent {
1211 type Err = Infallible;
1212
1213 fn from_str(s: &str) -> Result<Self, Self::Err> {
1214 Ok(UserContent::InputText {
1215 text: s.to_string(),
1216 })
1217 }
1218}