From 30174f9a5c53bd418c284ff86982d8a849c4dec0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 May 2026 21:17:37 +0000 Subject: [PATCH] Fix duplicated system instructions in Python telemetry --- .../packages/core/agent_framework/observability.py | 14 ++++++++------ .../packages/core/tests/core/test_observability.py | 10 ++++++++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/python/packages/core/agent_framework/observability.py b/python/packages/core/agent_framework/observability.py index 022008c05b..71dbf316ec 100644 --- a/python/packages/core/agent_framework/observability.py +++ b/python/packages/core/agent_framework/observability.py @@ -2155,13 +2155,15 @@ def _capture_messages( """Log messages with extra information.""" from ._types import normalize_messages, prepend_instructions_to_messages - prepped = prepend_instructions_to_messages(normalize_messages(messages), system_instructions) - otel_messages: list[dict[str, Any]] = [] + normalized_messages = normalize_messages(messages) + prepped = prepend_instructions_to_messages(normalized_messages, system_instructions) + span_messages: list[dict[str, Any]] = [] for index, message in enumerate(prepped): + otel_message = _to_otel_message(message) + if index >= len(prepped) - len(normalized_messages): + span_messages.append(otel_message) # Reuse the otel message representation for logging instead of calling to_dict() # to avoid expensive Pydantic serialization overhead - otel_message = _to_otel_message(message) - otel_messages.append(otel_message) logger.info( otel_message, extra={ @@ -2171,9 +2173,9 @@ def _capture_messages( }, ) if finish_reason: - otel_messages[-1]["finish_reason"] = FINISH_REASON_MAP[finish_reason] + span_messages[-1]["finish_reason"] = FINISH_REASON_MAP[finish_reason] span.set_attribute( - OtelAttr.OUTPUT_MESSAGES if output else OtelAttr.INPUT_MESSAGES, json.dumps(otel_messages, ensure_ascii=False) + OtelAttr.OUTPUT_MESSAGES if output else OtelAttr.INPUT_MESSAGES, json.dumps(span_messages, ensure_ascii=False) ) if system_instructions: if not isinstance(system_instructions, list): diff --git a/python/packages/core/tests/core/test_observability.py b/python/packages/core/tests/core/test_observability.py index 6185bccd44..7674c70800 100644 --- a/python/packages/core/tests/core/test_observability.py +++ b/python/packages/core/tests/core/test_observability.py @@ -290,9 +290,9 @@ async def test_chat_client_observability_with_instructions( assert len(system_instructions) == 1 assert system_instructions[0]["content"] == "You are a helpful assistant." - # Verify input_messages contains system message + # Verify input_messages excludes system instructions input_messages = json.loads(span.attributes[OtelAttr.INPUT_MESSAGES]) - assert any(msg.get("role") == "system" for msg in input_messages) + assert [msg.get("role") for msg in input_messages] == ["user"] @pytest.mark.parametrize("enable_sensitive_data", [True], indirect=True) @@ -2981,6 +2981,9 @@ async def test_system_instructions_preserves_non_ascii_characters(span_exporter: system_instructions = json.loads(system_instructions_json) assert system_instructions[0]["content"] == chinese_text + input_messages = json.loads(span.attributes[OtelAttr.INPUT_MESSAGES]) + assert [msg.get("role") for msg in input_messages] == ["user"] + @pytest.mark.parametrize("enable_sensitive_data", [True], indirect=True) async def test_tool_arguments_preserves_non_ascii_characters(span_exporter: InMemorySpanExporter): @@ -3104,6 +3107,9 @@ async def test_agent_instructions_from_default_options( assert len(system_instructions) == 1 assert system_instructions[0]["content"] == "Default system instructions." + input_messages = json.loads(span.attributes[OtelAttr.INPUT_MESSAGES]) + assert [msg.get("role") for msg in input_messages] == ["user"] + @pytest.mark.parametrize("enable_sensitive_data", [True], indirect=True) async def test_agent_instructions_from_options_override(