Update message format to align with OTel spec#256
Conversation
Remove the versioned wrapper `{ version, messages }` envelope from message
serialization. Messages now serialize as a plain JSON array per OTel gen-ai
semantic conventions. Output messages default finish_reason to "stop" when
created from plain strings via toOutputMessages/normalizeOutputMessages.
Port of microsoft/Agent365-dotnet#253.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the observability message serialization format to match OpenTelemetry gen-ai semantic conventions by removing the versioned { version, messages } envelope and emitting a plain JSON array of messages, while also defaulting finish_reason to "stop" for output messages created from plain strings and tightening wrapper detection.
Changes:
- Serialize
gen_ai.{input,output}.messagesas a plain JSON array instead of a versioned wrapper object. - Default
finish_reasonto"stop"for output messages produced from string / string[] inputs. - Update wrapper type guards, contracts, exporter truncation logic, and tests to reflect the new format.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/observability/tracing/message-utils.test.ts | Updates unit tests for wrapper detection, normalization, and array-based serialization; adds finish_reason expectations. |
| tests/observability/integration/openai-agent-instrument.test.ts | Adjusts integration assertions to expect JSON arrays for input/output message attributes. |
| tests/observability/integration/langchain-agent-instrument.test.ts | Adjusts integration assertions to expect JSON arrays for input/output message attributes. |
| tests/observability/integration/helpers/span-validators.ts | Updates span validators to treat message attributes as arrays (not {version,messages}). |
| tests/observability/extension/openai/OpenAIMessageContract.test.ts | Updates OpenAI extension contract tests to index into the serialized array. |
| tests/observability/extension/openai/OpenAIAgentsTraceProcessor.test.ts | Updates processor tests to validate array serialization and indexing. |
| tests/observability/extension/langchain/LangChainObservabilityAttributes.test.ts | Updates LangChain extension attribute assertions to expect array JSON. |
| tests/observability/extension/langchain/LangChainMessageContract.test.ts | Updates LangChain message contract tests for array JSON format. |
| tests/observability/extension/hosting/scope-utils.test.ts | Updates hosting scope tests to expect array serialization for input messages. |
| tests/observability/extension/hosting/output-logging-middleware.test.ts | Updates middleware tests to expect array output messages plus default finish_reason. |
| tests/observability/extension/helpers/message-schema-validator.ts | Updates shared validator to validate “plain array of messages” payloads. |
| tests/observability/core/scopes.test.ts | Updates core scope tests to validate array-based attributes and finish_reason defaults. |
| tests/observability/core/scope-messages.test.ts | Updates scope message recording tests for array format and finish_reason defaults. |
| tests/observability/core/output-scope.test.ts | Updates OutputScope tests to validate array output messages and finish_reason defaults. |
| tests/observability/core/agent365-exporter.test.ts | Updates exporter truncation tests to validate array-based message attributes. |
| packages/agents-a365-observability/src/tracing/scopes/OutputScope.ts | Removes versioned wrapping and refines raw-dict detection for output message recording. |
| packages/agents-a365-observability/src/tracing/message-utils.ts | Implements array serialization, default finish_reason, and tighter wrapper guard. |
| packages/agents-a365-observability/src/tracing/exporter/utils.ts | Updates truncation logic to parse/flush message attributes as arrays and adjust overflow sentinel generation. |
| packages/agents-a365-observability/src/tracing/contracts.ts | Removes version from message wrapper contracts and introduces DEFAULT_FINISH_REASON. |
| packages/agents-a365-observability/src/index.ts | Updates public exports to expose DEFAULT_FINISH_REASON instead of the schema version constant. |
| packages/agents-a365-observability-extensions-openai/src/Utils.ts | Updates OpenAI extension helpers to build wrappers without version and default finish_reason for assistant. |
| packages/agents-a365-observability-extensions-langchain/src/Utils.ts | Updates LangChain extension to wrap messages without version before serializing. |
| InputMessages, | ||
| OutputMessages, | ||
| A365_MESSAGE_SCHEMA_VERSION, | ||
| DEFAULT_FINISH_REASON, | ||
| } from '@microsoft/agents-a365-observability'; |
There was a problem hiding this comment.
Fixed in 3854c7d — removed the unused import.
| ], | ||
| finish_reason: 'error' |
There was a problem hiding this comment.
Good catch. Fixed in 3854c7d — removed \ from the serialization fallback since it's used for both input and output attributes.
| ], | ||
| finish_reason: 'error' |
There was a problem hiding this comment.
Fixed in 3854c7d — removed finish_reason from the overflow sentinel for the same reason.
| export interface InputMessages { | ||
| version: typeof A365_MESSAGE_SCHEMA_VERSION; | ||
| messages: ChatMessage[]; | ||
| } | ||
| /** | ||
| * An output message produced by a model (OTEL gen-ai semantic conventions). | ||
| * `finish_reason` defaults to `"stop"` per OTel spec when not provided. | ||
| */ | ||
| export interface OutputMessage extends ChatMessage { | ||
| finish_reason?: FinishReason | string; | ||
| } | ||
|
|
||
| export interface OutputMessages { | ||
| version: typeof A365_MESSAGE_SCHEMA_VERSION; | ||
| messages: OutputMessage[]; | ||
| } | ||
|
|
||
| export const A365_MESSAGE_SCHEMA_VERSION = '0.1.0' as const; | ||
| /** Default finish reason applied when none is provided (per OTel spec). */ | ||
| export const DEFAULT_FINISH_REASON = 'stop' as const; |
There was a problem hiding this comment.
Intentional breaking change — matches the dotnet approach in microsoft/Agent365-dotnet#253, which also deleted MessageConstants.SchemaVersion and the Version property outright with no deprecation shim.
- Remove unused DEFAULT_FINISH_REASON import from test file - Remove finish_reason from serialization fallback and overflow sentinel (finish_reason is output-only per OTel spec, sentinels are used for both input and output attributes) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The "validate error.type on failing tool" test was flaky because it used waitForSpans with a generic count of 2, which could resolve before the tool span was emitted. Now polls specifically for the execute_tool span by name with a 15s timeout. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| export function isWrappedMessages(input: InputMessagesParam | OutputMessagesParam): input is InputMessages | OutputMessages { | ||
| return !Array.isArray(input) && typeof input === 'object' && input !== null && 'version' in input && 'messages' in input; | ||
| return !Array.isArray(input) && typeof input === 'object' && input !== null && 'messages' in input && Array.isArray((input as InputMessages).messages); | ||
| } |
| private _isRawDict(messages: ResponseMessagesParam): messages is Record<string, unknown> { | ||
| return typeof messages === 'object' && messages !== null | ||
| && !Array.isArray(messages) | ||
| && !('version' in messages && 'messages' in messages); | ||
| && !('messages' in messages && Array.isArray((messages as OutputMessages).messages)); | ||
| } |
| return { | ||
| version: A365_MESSAGE_SCHEMA_VERSION, | ||
| messages: [{ role, parts: [{ type: 'text', content }] }], | ||
| messages: [{ role, parts: [{ type: 'text', content }], finish_reason: role === MessageRole.ASSISTANT ? DEFAULT_FINISH_REASON : undefined }], |
Summary
{ version: "0.1.0", messages: [...] }envelope from message serialization — messages now serialize as a plain JSON array[...]per OTel gen-ai semantic conventionsfinish_reasonto"stop"on output messages created from plain strings viatoOutputMessages/normalizeOutputMessagesisWrappedMessagestype guard withArray.isArraycheck to prevent false positivesPort of microsoft/Agent365-dotnet#253.
Test plan
🤖 Generated with Claude Code