From 0ec6ec5b82dbde812861f704ca7e6c792b39a6c8 Mon Sep 17 00:00:00 2001 From: yaodong-shen Date: Mon, 27 Jul 2026 01:38:31 +0800 Subject: [PATCH] fix(openai): preserve response output content index Use the parent output item's completion index for nested message content so reasoning outputs cannot absorb the following message text. Add coverage with real OpenAI response models. --- .../providers/openai/attributes/response.py | 4 +-- .../openai_core/test_response_attributes.py | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/agentops/instrumentation/providers/openai/attributes/response.py b/agentops/instrumentation/providers/openai/attributes/response.py index 9cf8f4b80..0b07dad83 100644 --- a/agentops/instrumentation/providers/openai/attributes/response.py +++ b/agentops/instrumentation/providers/openai/attributes/response.py @@ -462,9 +462,9 @@ def get_response_output_message_attributes(index: int, message: "ResponseOutputM attributes = _extract_attributes_from_mapping_with_index(message, RESPONSE_OUTPUT_MESSAGE_ATTRIBUTES, index) if message.content: - for i, content in enumerate(message.content): + for content in message.content: if isinstance(content, ResponseOutputText): - attributes.update(get_response_output_text_attributes(content, i)) + attributes.update(get_response_output_text_attributes(content, index)) else: logger.debug( diff --git a/tests/unit/instrumentation/openai_core/test_response_attributes.py b/tests/unit/instrumentation/openai_core/test_response_attributes.py index 24809423f..250138683 100644 --- a/tests/unit/instrumentation/openai_core/test_response_attributes.py +++ b/tests/unit/instrumentation/openai_core/test_response_attributes.py @@ -10,6 +10,8 @@ import os from unittest.mock import MagicMock, patch +from openai.types.responses import ResponseOutputMessage, ResponseOutputText, ResponseReasoningItem + from agentops.instrumentation.providers.openai.attributes.response import ( get_response_kwarg_attributes, get_response_response_attributes, @@ -400,6 +402,35 @@ def test_get_response_output_message_attributes(self): # Verify basic expected attributes assert isinstance(result, dict) + def test_response_message_content_uses_parent_output_index(self): + """Message text must share the parent output item's completion index.""" + reasoning = ResponseReasoningItem( + id="reasoning_1", + summary=[], + type="reasoning", + status="completed", + ) + message = ResponseOutputMessage( + id="msg_1", + content=[ + ResponseOutputText( + annotations=[], + text="answer", + type="output_text", + ) + ], + role="assistant", + status="completed", + type="message", + ) + + attributes = get_response_output_attributes([reasoning, message]) + + assert attributes[MessageAttributes.COMPLETION_ID.format(i=0)] == "reasoning_1" + assert MessageAttributes.COMPLETION_CONTENT.format(i=0) not in attributes + assert attributes[MessageAttributes.COMPLETION_ID.format(i=1)] == "msg_1" + assert attributes[MessageAttributes.COMPLETION_CONTENT.format(i=1)] == "answer" + def test_get_response_output_text_attributes(self): """Test extraction of attributes from output text""" # Create a mock text content