@@ -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