7676from utils .query import (
7777 build_multimodal_input ,
7878 consume_query_tokens ,
79+ extract_provider_and_model_from_model_id ,
7980 store_query_results ,
8081)
8182from utils .quota_utils import get_available_quotas
9495type AgentDispatchEvent = AgentStreamEvent | AgentRunResultEvent
9596
9697logger = get_logger (__name__ )
98+ tracer = trace .get_tracer (__name__ )
9799
98100DEFAULT_REFUSAL_RESPONSE : Final [str ] = (
99101 "I cannot process this request due to policy restrictions."
@@ -212,9 +214,9 @@ async def generate_agent_response( # pylint: disable=too-many-statements
212214 responses_params : ResponsesApiParams ,
213215 turn_summary : TurnSummary ,
214216 background_topic_summary_tasks : list [asyncio .Task [None ]],
217+ root_span : trace .Span ,
215218 emit_start : bool = True ,
216219 original_input : Optional [ResponseInput ] = None ,
217- root_span : Optional [trace .Span ] = None ,
218220 context_status : ContextStatus = "full" ,
219221) -> AsyncIterator [str ]:
220222 """Wrap an agent SSE generator with cleanup logic.
@@ -229,12 +231,12 @@ async def generate_agent_response( # pylint: disable=too-many-statements
229231 turn_summary: TurnSummary populated during streaming.
230232 background_topic_summary_tasks: Mutable list tracking fire-and-forget
231233 topic summary tasks for graceful shutdown.
234+ root_span: OpenTelemetry root span for this request.
232235 emit_start: Whether to emit the SSE start event. False when the caller
233236 (the compaction-aware wrapper) has already emitted it.
234237 original_input: In compacted mode, the original user input before the
235238 explicit-input rewrite. Used to persist the completed turn with its
236239 structured input (preserving attachments); ``None`` otherwise.
237- root_span: OpenTelemetry root span for this request.
238240 context_status: Whether the conversation context was sent in full
239241 ("full") or older turns were replaced by a summary ("summarized").
240242 Reported to the client in the SSE end event.
@@ -260,8 +262,11 @@ async def generate_agent_response( # pylint: disable=too-many-statements
260262 media_type ,
261263 )
262264 try :
263- async for event in generator :
264- yield event
265+ with trace .use_span ( # pylint: disable=not-context-manager
266+ root_span , end_on_exit = False
267+ ):
268+ async for event in generator :
269+ yield event
265270
266271 stream_completed = True
267272
@@ -301,8 +306,7 @@ async def generate_agent_response( # pylint: disable=too-many-statements
301306 deregister_stream (context .request_id )
302307
303308 if not stream_completed :
304- if root_span is not None :
305- root_span .end ()
309+ root_span .end ()
306310 return
307311
308312 await _persist_compacted_turn (
@@ -314,12 +318,15 @@ async def generate_agent_response( # pylint: disable=too-many-statements
314318 and bool (context .query_request .generate_topic_summary )
315319 )
316320 try :
317- topic_summary = await maybe_get_topic_summary (
318- generate_topic_summary = should_generate_topic_summary ,
319- input_text = context .query_request .query ,
320- client = context .client ,
321- model_id = responses_params .model ,
322- )
321+ with trace .use_span ( # pylint: disable=not-context-manager
322+ root_span , end_on_exit = False
323+ ):
324+ topic_summary = await maybe_get_topic_summary (
325+ generate_topic_summary = should_generate_topic_summary ,
326+ input_text = context .query_request .query ,
327+ client = context .client ,
328+ model_id = responses_params .model ,
329+ )
323330 except HTTPException as exc :
324331 logger .warning (
325332 "Topic summary failed for request %s: %s" ,
@@ -335,8 +342,7 @@ async def generate_agent_response( # pylint: disable=too-many-statements
335342 ),
336343 media_type ,
337344 )
338- if root_span is not None :
339- root_span .end ()
345+ root_span .end ()
340346 return
341347 logger .info ("Consuming tokens" )
342348 consume_query_tokens (
@@ -373,37 +379,22 @@ async def generate_agent_response( # pylint: disable=too-many-statements
373379 )
374380
375381 # Set final OTEL span attributes
376- if root_span is not None :
377- add_span_event (root_span , SpanEvents .TURN_PERSISTED )
378- if turn_summary .tool_calls :
379- tool_names = [tc .name for tc in turn_summary .tool_calls ]
380- set_span_attributes (
381- root_span ,
382- {
383- SpanAttributes .TOOL_CALLS_COUNT : len (tool_names ),
384- SpanAttributes .TOOL_CALLS_NAMES : tool_names ,
385- },
386- )
387- add_span_event (
388- root_span ,
389- SpanEvents .TOOL_EXECUTION_COMPLETED ,
390- {"tool.calls" : ", " .join (tool_names )},
391- )
392- set_span_attributes (
393- root_span ,
394- {
395- SpanAttributes .SESSION_ID : context .conversation_id ,
396- SpanAttributes .LLM_USAGE_INPUT_TOKENS : (
397- turn_summary .token_usage .input_tokens
398- ),
399- SpanAttributes .LLM_USAGE_OUTPUT_TOKENS : (
400- turn_summary .token_usage .output_tokens
401- ),
402- SpanAttributes .OUTPUT : turn_summary .llm_response ,
403- },
404- )
405- add_span_event (root_span , SpanEvents .LLM_RESPONSE_COMPLETED )
406- root_span .end ()
382+ add_span_event (root_span , SpanEvents .TURN_PERSISTED )
383+ set_span_attributes (
384+ root_span ,
385+ {
386+ SpanAttributes .SESSION_ID : context .conversation_id ,
387+ SpanAttributes .LLM_USAGE_INPUT_TOKENS : (
388+ turn_summary .token_usage .input_tokens
389+ ),
390+ SpanAttributes .LLM_USAGE_OUTPUT_TOKENS : (
391+ turn_summary .token_usage .output_tokens
392+ ),
393+ SpanAttributes .OUTPUT : turn_summary .llm_response ,
394+ },
395+ )
396+ add_span_event (root_span , SpanEvents .LLM_RESPONSE_COMPLETED )
397+ root_span .end ()
407398
408399 logger .info ("Agent streaming complete" )
409400
@@ -429,57 +420,97 @@ async def agent_response_generator(
429420 Yields:
430421 Serialized SSE event strings.
431422 """
432- media_type = context .query_request .media_type or MEDIA_TYPE_JSON
433- dispatch_state = AgentTurnAccumulator (
434- vector_store_ids = context .vector_store_ids ,
435- rag_id_mapping = context .rag_id_mapping ,
436- turn_summary = turn_summary ,
437- )
438- reject_image_attachments_in_compacted_mode (responses_params , image_attachments )
439- if image_attachments :
440- prompt = build_multimodal_input (
441- agent_prompt_text (responses_params ),
442- image_attachments ,
423+ with tracer .start_as_current_span ("llm.inference" ) as span :
424+ provider_id , model_id = extract_provider_and_model_from_model_id (
425+ responses_params .model
443426 )
444- else :
445- prompt = agent_prompt_text (responses_params )
427+ set_span_attributes (
428+ span ,
429+ {
430+ SpanAttributes .LLM_MODEL_ID : model_id ,
431+ SpanAttributes .LLM_PROVIDER_ID : provider_id ,
432+ },
433+ )
434+ add_span_event (span , SpanEvents .LLM_INFERENCE_STARTED )
446435
447- logger .debug ("Starting agent streaming response processing" )
448- async with agent .run_stream_events (prompt ) as stream :
449- async for event in stream :
450- if payload := dispatch_stream_event (event , dispatch_state ):
451- yield serialize_event (payload , media_type )
436+ media_type = context .query_request .media_type or MEDIA_TYPE_JSON
437+ dispatch_state = AgentTurnAccumulator (
438+ vector_store_ids = context .vector_store_ids ,
439+ rag_id_mapping = context .rag_id_mapping ,
440+ turn_summary = turn_summary ,
441+ )
442+ reject_image_attachments_in_compacted_mode (responses_params , image_attachments )
443+ if image_attachments :
444+ prompt = build_multimodal_input (
445+ agent_prompt_text (responses_params ),
446+ image_attachments ,
447+ )
448+ else :
449+ prompt = agent_prompt_text (responses_params )
450+
451+ logger .debug ("Starting agent streaming response processing" )
452+ async with agent .run_stream_events (prompt ) as stream :
453+ async for event in stream :
454+ if payload := dispatch_stream_event (event , dispatch_state ):
455+ yield serialize_event (payload , media_type )
456+
457+ # Capture the structured output items OGX returned so compacted mode can
458+ # persist the turn exactly as OGX would have (LCORE-3883).
459+ turn_summary .output_items = captured_output_items (agent )
460+
461+ if dispatch_state .run_result is None :
462+ logger .error ("No final result received from agent run" )
463+ return
464+
465+ run_result = dispatch_state .run_result
466+ turn_summary .token_usage = extract_agent_token_usage (
467+ run_result .usage ,
468+ responses_params .model ,
469+ endpoint_path ,
470+ )
452471
453- # Capture the structured output items OGX returned so compacted mode can
454- # persist the turn exactly as OGX would have (LCORE-3883).
455- turn_summary .output_items = captured_output_items (agent )
472+ set_span_attributes (
473+ span ,
474+ {
475+ SpanAttributes .LLM_USAGE_INPUT_TOKENS : run_result .usage .input_tokens ,
476+ SpanAttributes .LLM_USAGE_OUTPUT_TOKENS : run_result .usage .output_tokens ,
477+ },
478+ )
456479
457- if dispatch_state .run_result is None :
458- logger .error ("No final result received from agent run" )
459- return
480+ if turn_summary .tool_calls :
481+ tool_names = [tc .name for tc in turn_summary .tool_calls ]
482+ set_span_attributes (
483+ span ,
484+ {
485+ SpanAttributes .TOOL_CALLS_COUNT : len (tool_names ),
486+ SpanAttributes .TOOL_CALLS_NAMES : tool_names ,
487+ },
488+ )
489+ add_span_event (
490+ span ,
491+ SpanEvents .TOOL_EXECUTION_COMPLETED ,
492+ {"tool.calls" : ", " .join (tool_names )},
493+ )
460494
461- run_result = dispatch_state .run_result
462- turn_summary .token_usage = extract_agent_token_usage (
463- run_result .usage ,
464- responses_params .model ,
465- endpoint_path ,
466- )
495+ add_span_event (span , SpanEvents .LLM_INFERENCE_COMPLETED )
467496
468- finish_reason = get_agent_finish_reason (run_result .response )
469- if finish_reason != AgentFinishReason .SUCCESS :
470- error_response = get_finish_reason_error (finish_reason , responses_params .model )
471- yield serialize_event (
472- ErrorStreamPayload .from_error_response (error_response ),
473- media_type ,
474- )
497+ finish_reason = get_agent_finish_reason (run_result .response )
498+ if finish_reason != AgentFinishReason .SUCCESS :
499+ error_response = get_finish_reason_error (
500+ finish_reason , responses_params .model
501+ )
502+ yield serialize_event (
503+ ErrorStreamPayload .from_error_response (error_response ),
504+ media_type ,
505+ )
475506
476- turn_summary .referenced_documents = deduplicate_referenced_documents (
477- context .inline_rag_context .referenced_documents
478- + turn_summary .referenced_documents
479- )
480- turn_summary .rag_chunks = (
481- context .inline_rag_context .rag_chunks + turn_summary .rag_chunks
482- )
507+ turn_summary .referenced_documents = deduplicate_referenced_documents (
508+ context .inline_rag_context .referenced_documents
509+ + turn_summary .referenced_documents
510+ )
511+ turn_summary .rag_chunks = (
512+ context .inline_rag_context .rag_chunks + turn_summary .rag_chunks
513+ )
483514
484515
485516def serialize_event (
0 commit comments