Skip to main content

chat_responses/api/
completion.rs

1use crate::api::types::error::handle_responses_error;
2use crate::api::types::request::{ResponsesRequest, ResponsesRequestConfig};
3use crate::api::types::response::ResponsesApiResponse;
4use crate::client::ResponsesClient;
5use chat_core::error::{ChatError, ChatFailure};
6use chat_core::traits::CompletionProvider;
7use chat_core::transport::Transport;
8use chat_core::types::messages::Messages;
9use chat_core::types::options::ChatOptions;
10use chat_core::types::provider_meta::ProviderMeta;
11use chat_core::types::response::ChatResponse;
12use chat_core::types::tools::ToolDeclarations;
13
14#[async_trait::async_trait]
15impl<T: Transport> CompletionProvider for ResponsesClient<T> {
16    async fn complete(
17        &mut self,
18        messages: &mut Messages,
19        tool_declarations: Option<&dyn ToolDeclarations>,
20        options: Option<&ChatOptions>,
21        structured_output: Option<&schemars::Schema>,
22    ) -> Result<ChatResponse, ChatFailure> {
23        let previous_response_id = if self.use_previous_response_id {
24            self.last_response_id.clone()
25        } else {
26            None
27        };
28
29        let request_body = ResponsesRequest::from_core(ResponsesRequestConfig {
30            model_name: &self.model_name,
31            messages,
32            tool_declarations,
33            extra_tool_declarations: &self.extra_tool_declarations,
34            reasoning_effort: self.reasoning_effort.clone(),
35            options,
36            output_shape: structured_output,
37            previous_response_id,
38            store: self.store,
39        })
40        .map_err(ChatFailure::from_err)?;
41
42        let body = serde_json::to_vec(&request_body)
43            .map_err(|e| ChatFailure::from_err(ChatError::InvalidResponse(e.to_string())))?;
44
45        let req = chat_core::transport::Request {
46            scheme: self.scheme.clone(),
47            host: self.host.clone(),
48            path: format!("{}/responses", self.base_path),
49            headers: vec![
50                ("Authorization".into(), format!("Bearer {}", self.api_key)),
51                ("Content-Type".into(), "application/json".into()),
52            ],
53            body,
54        };
55
56        let res = self
57            .transport
58            .send(req)
59            .await
60            .map_err(ChatFailure::from_err)?;
61
62        let res = handle_responses_error(res)?;
63
64        let oai_data: ResponsesApiResponse = serde_json::from_slice(&res.body)
65            .map_err(|e| ChatFailure::from_err(ChatError::InvalidResponse(e.to_string())))?;
66
67        let (response, response_id) = oai_data
68            .into_core_chat_response()
69            .map_err(ChatFailure::from_err)?;
70
71        if self.use_previous_response_id
72            && let Some(id) = response_id
73        {
74            self.last_response_id = Some(id);
75        }
76
77        Ok(response)
78    }
79
80    fn metadata(&self) -> Option<&ProviderMeta> {
81        Some(&self.meta)
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn test_from_core_without_previous_response_id() {
91        let messages = chat_core::types::messages::from_user(vec!["hello"]);
92        let req = ResponsesRequest::from_core(ResponsesRequestConfig {
93            model_name: "gpt-4o",
94            messages: &messages,
95            tool_declarations: None,
96            extra_tool_declarations: &[],
97            reasoning_effort: None,
98            options: None,
99            output_shape: None,
100            previous_response_id: None,
101            store: None,
102        })
103        .unwrap();
104
105        let json = serde_json::to_value(&req).unwrap();
106        assert!(json.get("previous_response_id").is_none());
107    }
108
109    #[test]
110    fn test_from_core_with_previous_response_id() {
111        let messages = chat_core::types::messages::from_user(vec!["hello"]);
112        let req = ResponsesRequest::from_core(ResponsesRequestConfig {
113            model_name: "gpt-4o",
114            messages: &messages,
115            tool_declarations: None,
116            extra_tool_declarations: &[],
117            reasoning_effort: None,
118            options: None,
119            output_shape: None,
120            previous_response_id: Some("resp_abc".to_string()),
121            store: None,
122        })
123        .unwrap();
124
125        let json = serde_json::to_value(&req).unwrap();
126        assert_eq!(json["previous_response_id"], "resp_abc");
127    }
128}