Python: Prevent duplicate system instructions in Python telemetry - #5981
Conversation
|
Copilot The relevant rule in the OTEL semantic conventions is: Please make sure there are test case to ensure this is the case. Also test the case where the users input messages already contain a system message and system_insrtructions is also supplied, the span should contain: |
... Addressed in 5b12184. I added explicit coverage for separate framework instructions vs chat-history system messages, including the mixed case where |
... Addressed in 5e039d1. The observability tests cover both cases now: separate framework/options instructions stay in |
There was a problem hiding this comment.
Pull request overview
This PR updates Python OpenTelemetry chat/agent telemetry so framework-level system instructions are only recorded in gen_ai.system_instructions and are no longer duplicated into gen_ai.input.messages, making gen_ai.input.messages reflect only the actual conversation history.
Changes:
- Update
_capture_messagesto serialize/log only normalized chat-history messages (no longer prepending system instructions into the message list used for span attributes). - Update and expand observability tests to assert
gen_ai.input.messagesexcludes framework instructions while preserving non-ASCII system instructions and preserving chat-history system messages.
Show a summary per file
| File | Description |
|---|---|
| python/packages/core/agent_framework/observability.py | Changes message capture to avoid injecting framework instructions into gen_ai.input.messages payloads. |
| python/packages/core/tests/core/test_observability.py | Updates/adds tests asserting the new telemetry shape (no duplicate system instructions in input messages). |
Copilot's findings
Comments suppressed due to low confidence (1)
python/packages/core/agent_framework/observability.py:2176
_capture_messagesassumesfinish_reasonis always one of the keys inFINISH_REASON_MAPand doesFINISH_REASON_MAP[finish_reason]. ButFinishReasonis defined asNewType(str)and explicitly allows arbitrary strings for extensibility; passing an unknown finish reason will raiseKeyErrorand can breakget_response()telemetry wrapping. Consider usingFINISH_REASON_MAP.get(finish_reason, str(finish_reason))(or guarding withif finish_reason in FINISH_REASON_MAP) so unknown reasons are recorded without throwing.
if finish_reason:
otel_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)
- Files reviewed: 2/2 changed files
- Comments generated: 1
Motivation and Context
Python observability currently records system instructions twice on the same span: once in
gen_ai.system_instructionsand again ingen_ai.input.messages. This change keeps system instructions in their dedicated attribute and limits input messages to the actual conversation history.Description
Telemetry serialization
gen_ai.input.messages.gen_ai.system_instructionsemission.Behavioral scope
gen_ai.input.messagesreflects user/assistant/tool history.Coverage
Contribution Checklist