@@ -21,6 +21,15 @@ struct OpenAIRequest {
2121 max_tokens : usize ,
2222}
2323
24+ #[ derive( Serialize ) ]
25+ struct OpenAIResponsesRequest {
26+ model : String ,
27+ input : String ,
28+ instructions : String ,
29+ temperature : f32 ,
30+ max_output_tokens : usize ,
31+ }
32+
2433#[ derive( Serialize , Deserialize ) ]
2534struct Message {
2635 role : String ,
@@ -34,6 +43,29 @@ struct OpenAIResponse {
3443 model : String ,
3544}
3645
46+ #[ derive( Deserialize ) ]
47+ struct OpenAIResponsesResponse {
48+ output : Vec < OpenAIResponseOutput > ,
49+ model : String ,
50+ #[ serde( default ) ]
51+ usage : Option < OpenAIResponsesUsage > ,
52+ }
53+
54+ #[ derive( Deserialize ) ]
55+ struct OpenAIResponseOutput {
56+ #[ serde( rename = "type" ) ]
57+ output_type : String ,
58+ #[ serde( default ) ]
59+ content : Vec < OpenAIResponseContent > ,
60+ }
61+
62+ #[ derive( Deserialize ) ]
63+ struct OpenAIResponseContent {
64+ #[ serde( rename = "type" ) ]
65+ content_type : String ,
66+ text : Option < String > ,
67+ }
68+
3769#[ derive( Deserialize ) ]
3870struct Choice {
3971 message : Message ,
@@ -46,6 +78,13 @@ struct OpenAIUsage {
4678 total_tokens : usize ,
4779}
4880
81+ #[ derive( Deserialize ) ]
82+ struct OpenAIResponsesUsage {
83+ input_tokens : usize ,
84+ output_tokens : usize ,
85+ total_tokens : usize ,
86+ }
87+
4988impl OpenAIAdapter {
5089 pub fn new ( config : ModelConfig ) -> Result < Self > {
5190 let api_key = config. api_key . clone ( )
@@ -109,6 +148,32 @@ impl OpenAIAdapter {
109148#[ async_trait]
110149impl LLMAdapter for OpenAIAdapter {
111150 async fn complete ( & self , request : LLMRequest ) -> Result < LLMResponse > {
151+ if should_use_responses_api ( & self . config ) {
152+ return self . complete_responses ( request) . await ;
153+ }
154+
155+ self . complete_chat_completions ( request) . await
156+ }
157+
158+ fn _model_name ( & self ) -> & str {
159+ & self . config . model_name
160+ }
161+ }
162+
163+ fn is_retryable_status ( status : StatusCode ) -> bool {
164+ status == StatusCode :: TOO_MANY_REQUESTS || status. is_server_error ( )
165+ }
166+
167+ fn should_use_responses_api ( config : & ModelConfig ) -> bool {
168+ if let Some ( flag) = config. openai_use_responses {
169+ return flag;
170+ }
171+
172+ !config. model_name . starts_with ( "gpt-3.5" )
173+ }
174+
175+ impl OpenAIAdapter {
176+ async fn complete_chat_completions ( & self , request : LLMRequest ) -> Result < LLMResponse > {
112177 let messages = vec ! [
113178 Message {
114179 role: "system" . to_string( ) ,
@@ -161,11 +226,65 @@ impl LLMAdapter for OpenAIAdapter {
161226 } )
162227 }
163228
164- fn _model_name ( & self ) -> & str {
165- & self . config . model_name
229+ async fn complete_responses ( & self , request : LLMRequest ) -> Result < LLMResponse > {
230+ let openai_request = OpenAIResponsesRequest {
231+ model : self . config . model_name . clone ( ) ,
232+ input : request. user_prompt ,
233+ instructions : request. system_prompt ,
234+ temperature : request. temperature . unwrap_or ( self . config . temperature ) ,
235+ max_output_tokens : request. max_tokens . unwrap_or ( self . config . max_tokens ) ,
236+ } ;
237+
238+ let url = format ! ( "{}/responses" , self . base_url) ;
239+ let response = self
240+ . send_with_retry ( || {
241+ self . client
242+ . post ( & url)
243+ . header ( "Authorization" , format ! ( "Bearer {}" , self . api_key) )
244+ . header ( "Content-Type" , "application/json" )
245+ . json ( & openai_request)
246+ } )
247+ . await
248+ . context ( "Failed to send request to OpenAI" ) ?;
249+
250+ let openai_response: OpenAIResponsesResponse = response
251+ . json ( )
252+ . await
253+ . context ( "Failed to parse OpenAI response" ) ?;
254+
255+ let content = extract_response_text ( & openai_response) ;
256+ let usage = openai_response. usage . map ( |usage| Usage {
257+ prompt_tokens : usage. input_tokens ,
258+ completion_tokens : usage. output_tokens ,
259+ total_tokens : usage. total_tokens ,
260+ } ) ;
261+
262+ Ok ( LLMResponse {
263+ content,
264+ model : openai_response. model ,
265+ usage,
266+ } )
166267 }
167268}
168269
169- fn is_retryable_status ( status : StatusCode ) -> bool {
170- status == StatusCode :: TOO_MANY_REQUESTS || status. is_server_error ( )
270+ fn extract_response_text ( response : & OpenAIResponsesResponse ) -> String {
271+ let mut combined = String :: new ( ) ;
272+
273+ for item in & response. output {
274+ if item. output_type != "message" {
275+ continue ;
276+ }
277+ for content in & item. content {
278+ if content. content_type == "output_text" {
279+ if let Some ( text) = & content. text {
280+ if !combined. is_empty ( ) {
281+ combined. push ( '\n' ) ;
282+ }
283+ combined. push_str ( text) ;
284+ }
285+ }
286+ }
287+ }
288+
289+ combined
171290}
0 commit comments