@@ -430,6 +430,75 @@ def test_structured_output_parsed_in_final_response
430430    end 
431431  end 
432432
433+   class  CalendarEvent  < OpenAI ::BaseModel 
434+     required  :name ,  String 
435+     required  :date ,  String 
436+     required  :location ,  String 
437+   end 
438+ 
439+   class  LookupCalendar  < OpenAI ::BaseModel 
440+     required  :first_name ,  String 
441+     required  :last_name ,  String 
442+   end 
443+ 
444+   def  test_stream_with_both_text_and_tools 
445+     stub_request ( :post ,  "http://localhost/responses" ) 
446+       . to_return ( 
447+         status : 200 , 
448+         headers : { "Content-Type"  =>  "text/event-stream" } , 
449+         body : text_and_tools_sse_response 
450+       ) 
451+ 
452+     stream  =  @client . responses . stream ( 
453+       model : "gpt-4o-2024-08-06" , 
454+       input : [ 
455+         { role : :system ,  content : "Extract event info and look up attendees." } , 
456+         { role : :user ,  content : "Ada Lovelace is going to a conference on Friday at the Convention Center." } 
457+       ] , 
458+       text : CalendarEvent , 
459+       tools : [ LookupCalendar ] 
460+     ) 
461+ 
462+     events  =  stream . to_a 
463+ 
464+     text_done  =  events . find  {  |e | e . type  == :"response.output_text.done"  } 
465+     assert_pattern  do 
466+       text_done  =>  OpenAI ::Streaming ::ResponseTextDoneEvent [ 
467+         parsed : CalendarEvent [ 
468+           name : "Conference" , 
469+           date : "Friday" , 
470+           location : "Convention Center" 
471+         ] 
472+       ] 
473+     end 
474+ 
475+     function_done  =  events . find  {  |e | e . type  == :"response.function_call_arguments.done"  } 
476+     assert_equal ( '{"first_name":"Ada","last_name":"Lovelace"}' ,  function_done . arguments ) 
477+ 
478+     final_response  =  stream . get_final_response 
479+ 
480+     text_output  =  final_response . output . find  {  |o | o . is_a? ( OpenAI ::Models ::Responses ::ResponseOutputMessage )  } 
481+     text_content  =  text_output . content . find  {  |c | c [ :type ]  == :output_text  } 
482+     assert_pattern  do 
483+       text_content [ :parsed ]  =>  CalendarEvent [ 
484+         name : "Conference" , 
485+         date : "Friday" , 
486+         location : "Convention Center" 
487+       ] 
488+     end 
489+ 
490+     tool_call  =  final_response . output . find  {  |o | o . is_a? ( OpenAI ::Models ::Responses ::ResponseFunctionToolCall )  } 
491+     assert_pattern  do 
492+       tool_call  =>  OpenAI ::Models ::Responses ::ResponseFunctionToolCall [ 
493+         name : "LookupCalendar" , 
494+         parsed : LookupCalendar [ 
495+           first_name : "Ada" , 
496+           last_name : "Lovelace" 
497+         ] 
498+       ] 
499+     end 
500+   end 
501+ 
433502  private 
434503
435504  def  function_tool_params 
@@ -742,4 +811,51 @@ def error_sse_response
742811
743812    SSE 
744813  end 
814+ 
815+   def  text_and_tools_sse_response 
816+     <<~SSE 
817+       event: response.created 
818+       data: {"type":"response.created","sequence_number":1,"response":{"id":"resp_stream_001","object":"realtime.response","status":"in_progress","output":[],"usage":null}} 
819+ 
820+       event: response.output_item.added 
821+       data: {"type":"response.output_item.added","sequence_number":2,"response_id":"resp_stream_001","output_index":0,"item":{"id":"msg_001","object":"realtime.item","type":"message","status":"in_progress","role":"assistant","content":[]}} 
822+ 
823+       event: response.content_part.added 
824+       data: {"type":"response.content_part.added","sequence_number":3,"response_id":"resp_stream_001","item_id":"msg_001","output_index":0,"content_index":0,"part":{"type":"output_text","text":""}} 
825+ 
826+       event: response.output_text.delta 
827+       data: {"type":"response.output_text.delta","sequence_number":4,"response_id":"resp_stream_001","item_id":"msg_001","output_index":0,"content_index":0,"delta":"{\\ "name\\ ":\\ "Conference\\ ","} 
828+ 
829+       event: response.output_text.delta 
830+       data: {"type":"response.output_text.delta","sequence_number":5,"response_id":"resp_stream_001","item_id":"msg_001","output_index":0,"content_index":0,"delta":"\\ "date\\ ":\\ "Friday\\ ",\\ "location\\ ":\\ "Convention Center\\ "}"} 
831+ 
832+       event: response.output_text.done 
833+       data: {"type":"response.output_text.done","sequence_number":6,"response_id":"resp_stream_001","item_id":"msg_001","output_index":0,"content_index":0,"text":"{\\ "name\\ ":\\ "Conference\\ ",\\ "date\\ ":\\ "Friday\\ ",\\ "location\\ ":\\ "Convention Center\\ "}"} 
834+ 
835+       event: response.content_part.done 
836+       data: {"type":"response.content_part.done","sequence_number":7,"response_id":"resp_stream_001","item_id":"msg_001","output_index":0,"content_index":0,"part":{"type":"output_text","text":"{\\ "name\\ ":\\ "Conference\\ ",\\ "date\\ ":\\ "Friday\\ ",\\ "location\\ ":\\ "Convention Center\\ "}"}} 
837+ 
838+       event: response.output_item.done 
839+       data: {"type":"response.output_item.done","sequence_number":8,"response_id":"resp_stream_001","item_id":"msg_001","output_index":0,"item":{"id":"msg_001","object":"realtime.item","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"{\\ "name\\ ":\\ "Conference\\ ",\\ "date\\ ":\\ "Friday\\ ",\\ "location\\ ":\\ "Convention Center\\ "}"}]}} 
840+ 
841+       event: response.output_item.added 
842+       data: {"type":"response.output_item.added","sequence_number":9,"response_id":"resp_stream_001","output_index":1,"item":{"id":"call_001","object":"realtime.item","type":"function_call","status":"in_progress","name":"LookupCalendar","arguments":"","call_id":"call_001"}} 
843+ 
844+       event: response.function_call_arguments.delta 
845+       data: {"type":"response.function_call_arguments.delta","sequence_number":10,"item_id":"call_001","output_index":1,"delta":"{\\ "first_name\\ ":\\ "Ada\\ ","} 
846+ 
847+       event: response.function_call_arguments.delta 
848+       data: {"type":"response.function_call_arguments.delta","sequence_number":11,"item_id":"call_001","output_index":1,"delta":"\\ "last_name\\ ":\\ "Lovelace\\ "}"} 
849+ 
850+       event: response.function_call_arguments.done 
851+       data: {"type":"response.function_call_arguments.done","sequence_number":12,"item_id":"call_001","output_index":1,"arguments":"{\\ "first_name\\ ":\\ "Ada\\ ",\\ "last_name\\ ":\\ "Lovelace\\ "}"} 
852+ 
853+       event: response.output_item.done 
854+       data: {"type":"response.output_item.done","sequence_number":13,"response_id":"resp_stream_001","item_id":"call_001","output_index":1,"item":{"id":"call_001","object":"realtime.item","type":"function_call","status":"completed","name":"LookupCalendar","arguments":"{\\ "first_name\\ ":\\ "Ada\\ ",\\ "last_name\\ ":\\ "Lovelace\\ "}","call_id":"call_001"}} 
855+ 
856+       event: response.completed 
857+       data: {"type":"response.completed","sequence_number":14,"response":{"id":"resp_stream_001","object":"realtime.response","status":"completed","output":[{"id":"msg_001","object":"realtime.item","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"{\\ "name\\ ":\\ "Conference\\ ",\\ "date\\ ":\\ "Friday\\ ",\\ "location\\ ":\\ "Convention Center\\ "}"}]},{"id":"call_001","object":"realtime.item","type":"function_call","status":"completed","name":"LookupCalendar","arguments":"{\\ "first_name\\ ":\\ "Ada\\ ",\\ "last_name\\ ":\\ "Lovelace\\ "}","call_id":"call_001"}],"usage":{"total_tokens":50,"input_tokens":30,"output_tokens":20}}} 
858+ 
859+     SSE 
860+   end 
745861end 
0 commit comments