@@ -109,10 +109,94 @@ async def get_response(
109109
110110 response = await self .original_model .get_response (** kwargs )
111111
112- # Set span output
113- if span :
112+ # Set span output with structured data
113+ if span and response :
114+ new_items = []
115+ final_output = None
116+
117+ # Extract final output text from response
118+ response_final_output = getattr (response , 'final_output' , None )
119+ if response_final_output :
120+ final_output = response_final_output
121+
122+ # Extract items from the response output
123+ response_output = getattr (response , 'output' , None )
124+ if response_output :
125+ output_items = response_output if isinstance (response_output , list ) else [response_output ]
126+
127+ for item in output_items :
128+ item_type = getattr (item , 'type' , None )
129+
130+ # Handle reasoning items
131+ if item_type == 'reasoning' :
132+ reasoning_summary = []
133+ summary = getattr (item , 'summary' , None )
134+ if summary :
135+ for summary_part in summary :
136+ text = getattr (summary_part , 'text' , None )
137+ if text :
138+ reasoning_summary .append ({
139+ "text" : text ,
140+ "type" : "summary_text"
141+ })
142+
143+ new_items .append ({
144+ "id" : getattr (item , 'id' , None ),
145+ "type" : "reasoning" ,
146+ "status" : getattr (item , 'status' , None ),
147+ "content" : None ,
148+ "summary" : reasoning_summary if reasoning_summary else None ,
149+ })
150+
151+ # Handle tool call items
152+ elif item_type == 'function_call' :
153+ new_items .append ({
154+ "id" : getattr (item , 'id' , None ),
155+ "name" : getattr (item , 'name' , None ),
156+ "type" : "function_call" ,
157+ "status" : getattr (item , 'status' , 'completed' ),
158+ "call_id" : getattr (item , 'call_id' , None ),
159+ "arguments" : getattr (item , 'arguments' , None ),
160+ })
161+
162+ # Handle tool output items
163+ elif item_type == 'function_call_output' :
164+ new_items .append ({
165+ "type" : "function_call_output" ,
166+ "output" : getattr (item , 'output' , None ),
167+ "call_id" : getattr (item , 'call_id' , None ),
168+ })
169+
170+ # Handle message items
171+ elif item_type == 'message' :
172+ content = []
173+ message_text = ""
174+ item_content = getattr (item , 'content' , None )
175+ if item_content :
176+ for content_part in item_content :
177+ text = getattr (content_part , 'text' , None )
178+ if text :
179+ content .append ({
180+ "text" : text ,
181+ "type" : "output_text" ,
182+ })
183+ message_text = text
184+
185+ new_items .append ({
186+ "id" : getattr (item , 'id' , None ),
187+ "role" : getattr (item , 'role' , 'assistant' ),
188+ "type" : "message" ,
189+ "status" : getattr (item , 'status' , 'completed' ),
190+ "content" : content ,
191+ })
192+
193+ # Use message text as final output if we have it
194+ if message_text and not final_output :
195+ final_output = message_text
196+
114197 span .output = {
115- "response" : str (response ) if response else None ,
198+ "new_items" : new_items ,
199+ "final_output" : final_output ,
116200 }
117201
118202 return response
@@ -160,7 +244,9 @@ async def stream_response(
160244 # Wrap the streaming in a tracing span if tracer is available
161245 if self .tracer and self .trace_id :
162246 trace = self .tracer .trace (self .trace_id )
163- async with trace .span (
247+
248+ # Manually start the span instead of using context manager
249+ span = await trace .start_span (
164250 parent_id = self .parent_span_id ,
165251 name = "run_agent_streamed" ,
166252 input = {
@@ -172,7 +258,9 @@ async def stream_response(
172258 "handoffs" : [str (h ) for h in handoffs ] if handoffs else [],
173259 "previous_response_id" : previous_response_id ,
174260 },
175- ) as span :
261+ )
262+
263+ try :
176264 # Get the stream from the original model
177265 stream_kwargs = {
178266 "system_instructions" : system_instructions ,
@@ -193,23 +281,170 @@ async def stream_response(
193281 # Get the stream response from the original model and yield each event
194282 stream_response = self .original_model .stream_response (** stream_kwargs )
195283
196- # Pass through each event from the original stream
197- event_count = 0
198- final_output = None
284+ # Pass through each event from the original stream and track items
285+ new_items = []
286+ final_response_text = ""
287+ current_text_item = None
288+ tool_call_map = {} # Map call_id to tool name
289+
199290 async for event in stream_response :
200- event_count += 1
201- # Track the final output if available
202- if hasattr (event , 'type' ) and event .type == 'raw_response_event' :
203- if hasattr (event .data , 'output' ):
204- final_output = event .data .output
291+ event_type = getattr (event , 'type' , 'no-type' )
292+
293+ # Handle response.output_item.done events which contain completed items
294+ if event_type == 'response.output_item.done' :
295+ item = getattr (event , 'item' , None )
296+ if item is not None :
297+ item_type = getattr (item , 'type' , None )
298+
299+ # Handle function call (tool request)
300+ if item_type == 'function_call' :
301+ call_id = getattr (item , 'call_id' , None )
302+ name = getattr (item , 'name' , None )
303+ new_items .append ({
304+ "id" : getattr (item , 'id' , None ),
305+ "name" : name ,
306+ "type" : "function_call" ,
307+ "status" : getattr (item , 'status' , 'completed' ),
308+ "call_id" : call_id ,
309+ "arguments" : getattr (item , 'arguments' , None ),
310+ })
311+ if call_id and name :
312+ tool_call_map [call_id ] = name
313+
314+ # Handle completed message items (final text output)
315+ elif item_type == 'message' :
316+ content = []
317+ message_text = ""
318+ item_content = getattr (item , 'content' , None )
319+ if item_content :
320+ for content_part in item_content :
321+ text = getattr (content_part , 'text' , None )
322+ if text :
323+ content .append ({
324+ "text" : text ,
325+ "type" : "output_text" ,
326+ })
327+ # Use the complete text from the message as final_output
328+ message_text = text
329+
330+ new_items .append ({
331+ "id" : getattr (item , 'id' , None ),
332+ "role" : getattr (item , 'role' , 'assistant' ),
333+ "type" : "message" ,
334+ "status" : getattr (item , 'status' , 'completed' ),
335+ "content" : content ,
336+ })
337+
338+ # Update final_response_text with the complete message text
339+ if message_text :
340+ final_response_text = message_text
341+
342+ # Track reasoning, tool calls, and responses from run_item_stream_event (kept for compatibility)
343+ if event_type == 'run_item_stream_event' :
344+ item = getattr (event , 'item' , None )
345+ if item is not None :
346+ item_type = getattr (item , 'type' , None )
347+
348+ # Handle reasoning items
349+ if item_type == 'reasoning_item' :
350+ reasoning_summary = []
351+ raw_item = getattr (item , 'raw_item' , None )
352+ if raw_item is not None :
353+ summary = getattr (raw_item , 'summary' , None )
354+ if summary :
355+ for summary_part in summary :
356+ text = getattr (summary_part , 'text' , None )
357+ if text :
358+ reasoning_summary .append ({
359+ "text" : text ,
360+ "type" : "summary_text"
361+ })
362+
363+ new_items .append ({
364+ "id" : getattr (raw_item , 'id' , None ),
365+ "type" : "reasoning" ,
366+ "status" : getattr (raw_item , 'status' , None ),
367+ "content" : None ,
368+ "summary" : reasoning_summary if reasoning_summary else None ,
369+ })
370+
371+ # Handle tool call items
372+ elif item_type == 'tool_call_item' :
373+ raw_item = getattr (item , 'raw_item' , None )
374+ if raw_item is not None :
375+ call_id , tool_name , tool_arguments = _extract_tool_call_info (raw_item )
376+ tool_call_map [call_id ] = tool_name
377+
378+ new_items .append ({
379+ "id" : getattr (raw_item , 'id' , None ),
380+ "name" : tool_name ,
381+ "type" : "function_call" ,
382+ "status" : getattr (raw_item , 'status' , 'completed' ),
383+ "call_id" : call_id ,
384+ "arguments" : str (tool_arguments ) if isinstance (tool_arguments , dict ) else tool_arguments ,
385+ })
386+
387+ # Handle tool output items
388+ elif item_type == 'tool_call_output_item' :
389+ raw_item = getattr (item , 'raw_item' , None )
390+ if raw_item is not None :
391+ call_id , tool_name , content = _extract_tool_response_info (tool_call_map , raw_item )
392+
393+ new_items .append ({
394+ "type" : "function_call_output" ,
395+ "output" : content ,
396+ "call_id" : call_id ,
397+ })
398+
399+ # Accumulate text deltas to build final response
400+ # Note: OpenAI Agents SDK can emit events in different formats
401+ if hasattr (event , 'type' ) and event .type == 'response.output_text.delta' :
402+ # Direct event type from OpenAI Agents SDK (observed in practice)
403+ if hasattr (event , 'delta' ):
404+ final_response_text += event .delta
405+
406+ # Handle raw_response_event wrapper (alternative event format, kept for compatibility)
407+ elif hasattr (event , 'type' ) and event .type == 'raw_response_event' :
408+ if hasattr (event , 'data' ):
409+ raw_event = event .data
410+
411+ # Track when output items are added
412+ if isinstance (raw_event , ResponseOutputItemAddedEvent ):
413+ if hasattr (raw_event , 'item' ) and raw_event .item .type == 'message' :
414+ current_text_item = {
415+ "id" : getattr (raw_event .item , 'id' , None ),
416+ "role" : getattr (raw_event .item , 'role' , 'assistant' ),
417+ "type" : "message" ,
418+ "status" : "in_progress" ,
419+ "content" : []
420+ }
421+
422+ # Check if this is a text delta event
423+ elif isinstance (raw_event , ResponseTextDeltaEvent ):
424+ if hasattr (raw_event , 'delta' ) and raw_event .delta :
425+ final_response_text += raw_event .delta
426+
427+ # Track when output items are done
428+ elif isinstance (raw_event , ResponseOutputItemDoneEvent ):
429+ if current_text_item and final_response_text :
430+ current_text_item ["status" ] = "completed"
431+ current_text_item ["content" ] = [{
432+ "text" : final_response_text ,
433+ "type" : "output_text" ,
434+ }]
435+ new_items .append (current_text_item )
436+ current_text_item = None
437+
205438 yield event
206439
207- # Set span output
208- if span :
209- span .output = {
210- "event_count" : event_count ,
211- "final_output" : str (final_output ) if final_output else None ,
212- }
440+ # Set span output with structured data including tool calls and final response
441+ span .output = {
442+ "new_items" : new_items ,
443+ "final_output" : final_response_text if final_response_text else None ,
444+ }
445+ finally :
446+ # End the span after all events have been yielded
447+ await trace .end_span (span )
213448 else :
214449 # No tracing, just stream normally
215450 # Get the stream from the original model
0 commit comments