Skip to content

Add model observation telemetry attributes - #282

Merged
Pujarini Mohapatra (biswapm) merged 4 commits into
mainfrom
users/pmohapatra/model-activity-otel
Oct 19, 2026
Merged

Pujarini Mohapatra (biswapm) merged 4 commits into
mainfrom
users/pmohapatra/model-activity-otel

Conversation

@biswapm

Copy link
Copy Markdown
Contributor

Summary

  • normalize manually emitted inference spans to lowercase chat so Maven model observation ingests them
  • add response-model, cache read/write token, agent type, and integer endpoint-port attributes to the core API
  • map served-model and cache-token data from OpenAI Agents and LangChain without fabricating unavailable endpoint metadata
  • document the model observation schema and add focused coverage

Validation

  • 44 passed, 2 subtests passed across core inference, baggage, OpenAI, and LangChain observability tests
  • Ruff formatting and import/undefined-name checks passed
  • Python package compilation passed

Emit normalized inference operations, response model identity, cache token usage, agent type, and typed endpoint ports across core and supported framework mappings.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@biswapm
Pujarini Mohapatra (biswapm) requested a review from a team as a code owner October 12, 2026 13:58
Copilot AI balanced review requested due to automatic review settings October 12, 2026 13:58
@github-actions

Copy link
Copy Markdown

⚠️ Deprecation Warning: The deny-licenses option is deprecated for possible removal in the next major release. For more information, see issue 997.

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

New dataclass fields break positional-call compatibility, and whitespace model values can produce invalid telemetry.

2 open findings
What changed in this PR

Adds model-observation telemetry across core inference scopes, OpenAI Agents, and LangChain integrations.

Changes:

  • Adds served-model, cache-token, agent-type, and integer endpoint-port attributes.
  • Normalizes inference operations to chat.
  • Expands documentation and focused tests.
File Description
tests/​observability/​extensions/​openai/​test_wrapper_openaiagents.py Tests OpenAI model and cache-token mapping.
tests/​observability/​extensions/​langchain/​test_wrapper_langchain.py Tests LangChain model and cache-token mapping.
tests/​observability/​core/​test_inference_scope.py Tests inference model-observation attributes.
tests/​observability/​core/​test_baggage_builder.py Tests agent-type baggage.
libraries/​microsoft-agents-a365-observability-extensions-openai/​.../​utils.py Maps response model and cached tokens.
libraries/​microsoft-agents-a365-observability-extensions-langchain/​.../​utils.py Maps requested/served models and cache tokens.
libraries/​microsoft-agents-a365-observability-core/​.../​trace_processor/​util.py Propagates agent-type baggage.
libraries/​microsoft-agents-a365-observability-core/​.../​opentelemetry_scope.py Emits agent type on spans.
libraries/​microsoft-agents-a365-observability-core/​.../​middleware/​baggage_builder.py Adds agent-type baggage API.
libraries/​microsoft-agents-a365-observability-core/​.../​inference_scope.py Emits normalized inference telemetry.
libraries/​microsoft-agents-a365-observability-core/​.../​inference_call_details.py Adds response-model and cache-token fields.
libraries/​microsoft-agents-a365-observability-core/​.../​constants.py Defines new telemetry keys.
libraries/​microsoft-agents-a365-observability-core/​.../​agent_details.py Adds agent-type metadata.
libraries/​microsoft-agents-a365-observability-core/​docs/​design.md Documents the updated schema and API.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping shared attribute definitions in core and framework-specific extraction in the adapters is a sound, focused approach that fits the existing architecture.

Four minor comments inline.

Outside the changed lines:

  • Minor: Integration guide still says InferenceScope emits Chat/TextCompletion/GenerateContent (docs/integrating-with-existing-opentelemetry.md:96). After this change InferenceScope always emits gen_ai.operation.name = chat and names spans chat <model>. This guide still says: - Manual instrumentation uses the capitalized InferenceOperationType.value, and manual and auto-instrumentation are 'inconsistent today' (line 96). - Users should filter their backend on Chat / TextCompletion / GenerateContent, and the casing difference is a tracked SDK issue (line 99). - Users should look for Chat (or chat) when verifying the integration (line 113). Anyone who builds dashboards or filters from this page will match on values the S...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The separation between shared telemetry definitions and framework-specific extraction is clear and consistent with the existing architecture. The changes are narrowly scoped and straightforward to follow.

One minor comment inline.

Since my last review:

  • Still open: Read cache details from the generated message's UsageMetadata (libraries/microsoft-agents-a365-observability-extensions-langchain/microsoft_agents_a365/observability/extensions/langchain/utils.py). libraries/microsoft-agents-a365-observability-extensions-langchain/microsoft_agents_a365/observability/extensions/langchain/utils.py:329-330,395-418 still obtains usage exclusively from llm_output.tok
  • Still open: Whitespace model is emitted as the request model and span name (libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/inference_scope.py). libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/inference_call_details.py:28-30 accepts model=' ' when responseModel='gpt-4.1'. In the same package, inferen
  • Still open: Integration guide still says InferenceScope emits Chat/TextCompletion/GenerateContent (docs/integrating-with-existing-opentelemetry.md). docs/integrating-with-existing-opentelemetry.md:96,99,113 still describes capitalized manual inference operations and recommends matching them. libraries/microsoft-agents-a365-observability-core/micro
  • Still open: New agent_type is a plain string, but design.md documents it as the AgentType enum (libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/agent_details.py). libraries/microsoft-agents-a365-observability-core/docs/design.md:289 still documents agent_type as AgentType | None. The implementation declares Optional[str] in agent_details.py:33, while models/age

…g, cache tokens

Addresses 6 unresolved review threads on PR #282:

1. AgentDetails.agent_type: moved to the end of the dataclass so existing
   positional callers aren't shifted; type widened to Optional[Union[AgentType, str]].
2. InferenceCallDetails: moved responseModel/cacheReadInputTokens/
   cacheWriteInputTokens to the end of the dataclass to preserve positional
   constructor compatibility.
3. LangChain utils.token_counts(): now reads normalized cache token details
   from the generated AIMessage's usage_metadata (both direct attribute and
   dumpd kwargs-serialized forms) before falling back to llm_output-based
   token_usage, and also handles Anthropic's flat raw
   cache_read_input_tokens/cache_creation_input_tokens fields. Added tests
   using the real message shape instead of a synthetic llm_output shape.
4. InferenceCallDetails.__post_init__: normalizes model/responseModel by
   stripping whitespace and treating blank values as None, so a
   whitespace-only model no longer produces a blank gen_ai.request.model /
   malformed span name when responseModel is set.
5. AgentType enum handling: OpenTelemetryScope and BaggageBuilder.agent_type
   now accept AgentType | str | None and emit .value for enum members so
   OTel doesn't silently drop AgentType.FOUNDRY-style values.
6. InferenceScope operation naming: added InferenceOperationType.canonical_name
   (maps CHAT/TEXT_COMPLETION/GENERATE_CONTENT to their canonical lowercase
   semconv spelling) and use it for both the span name prefix and
   gen_ai.operation.name, replacing the hardcoded "chat" value. Updated the
   Agent365 exporter's GEN_AI_OPERATION_NAMES allowlist to include the
   canonical lowercase names (kept legacy PascalCase entries), fixed the
   PR's own test that wrongly asserted "chat" for a TEXT_COMPLETION input,
   and updated docs/design.md accordingly.

Also added CHANGELOG.md entries for both affected packages.

All changes verified with the full core + langchain test suites
(278 passed, plus the 5 newly added tests) via `pytest tests/observability/core/
tests/observability/extensions/langchain/ -m "not integration"`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 15, 2026 11:06
@biswapm

Copy link
Copy Markdown
Contributor Author

Review feedback addressed (commit 4eaf0a0)

Fixed all 6 unresolved review threads:

  1. agent_details.py (dataclass field order) — Moved the new agent_type field to the end of AgentDetails, after all pre-existing fields, so existing positional callers (e.g. 8th positional arg for tenant_id) are no longer shifted.

  2. inference_call_details.py (dataclass field order) — Moved responseModel, cacheReadInputTokens, and cacheWriteInputTokens to the end of InferenceCallDetails, restoring the pre-PR positional field order so a call like InferenceCallDetails(op, model, provider, 100, 50, ...) binds correctly again.

  3. langchain/utils.py (cache token extraction, minor) — token_counts() now reads normalized usage_metadata.input_token_details/output_token_details from the generated AIMessage (handling both a live BaseMessage object and the dumpd message.kwargs.usage_metadata serialized form) before falling back to provider-specific llm_output lookups, and also handles Anthropic's flat raw cache_read_input_tokens/cache_creation_input_tokens. Added test_cache_token_counts_read_from_generated_message_usage_metadata and test_cache_token_counts_fall_back_to_raw_anthropic_flat_fields, which use the real message shape (not a synthetic llm_output shape).

  4. inference_scope.py (whitespace model, minor) — InferenceCallDetails.__post_init__ now strips model/responseModel and normalizes blank/whitespace-only values to None before validation, so a whitespace-only model with a valid responseModel no longer leaks into the span name or gen_ai.request.model. Added test_whitespace_only_model_is_normalized_when_response_model_present.

  5. agent_details.py / opentelemetry_scope.py / baggage_builder.py (AgentType enum, minor) — Widened the agent_type type hint to Optional[Union[AgentType, str]] and updated OpenTelemetryScope and BaggageBuilder.agent_type to emit .value when given an AgentType enum member, so AgentType.FOUNDRY no longer gets silently dropped by OTel. Added test_agent_type_enum_member_emits_string_value.

  6. inference_scope.py (operation name hardcoding, significant) — Added InferenceOperationType.canonical_name (maps CHAT/TEXT_COMPLETION/GENERATE_CONTENT to their canonical lowercase semconv spelling) and replaced the hardcoded CHAT_OPERATION_NAME usage in both the span-name prefix and gen_ai.operation.name with details.operationName.canonical_name. Updated the Agent365 exporter's GEN_AI_OPERATION_NAMES allowlist in exporters/utils.py to include the lowercase canonical names (chat, text_completion, generate_content) in addition to the legacy PascalCase aliases, since spans can now genuinely emit those values. Fixed the PR's own test that incorrectly asserted "chat" for a TEXT_COMPLETION input (now asserts "text_completion"), added test_inference_scope_operation_name_mapping_per_type covering all three enum values, and updated docs/design.md to describe the per-operation canonical mapping instead of a chat-only description.

Also added CHANGELOG.md entries under [Unreleased] in both microsoft-agents-a365-observability-core and microsoft-agents-a365-observability-extensions-langchain.

Test results

pytest tests/observability/core/ tests/observability/extensions/langchain/ -m "not integration" → 278 passed (plus the 5 new tests listed above), 4 integration tests deselected, 0 failures.

Pushed to users/pmohapatra/model-activity-otel as commit 4eaf0a0. All 6 review threads resolved.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

LangChain usage mapping can replace batch totals with per-message counts and emit reasoning-token counts as finish reasons.

2 open findings
2 resolved since last review

🧠 Review effort: Balanced

…ain utils

- Remove erroneous mapping of output_token_details.reasoning (a token
  count) to GEN_AI_RESPONSE_FINISH_REASONS_KEY in token_counts(); that
  attribute is reserved for actual finish reasons.
- Rewrite _message_usage_metadata() to aggregate usage across ALL batch
  entries in outputs["generations"] instead of only the first, so
  batched LLM calls no longer silently drop usage for later inputs.
  Only the first completion with usage data per batch entry is used to
  avoid double-counting alternative completions (n > 1).
- Add _merge_usage_metadata() helper to recursively sum numeric usage
  fields (including nested detail dicts) across batch entries.
- Add regression tests for both fixes.
- Reformat utils.py and test_wrapper_langchain.py with ruff to fix CI
  format-check failure (ruff format --check .).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 15, 2026 11:28
@biswapm

Copy link
Copy Markdown
Contributor Author

Fixed CI failure and the 2 new review comments (commit f722e88):

CI fix:

  • Python SDK (3.11) job was failing the format tox env (ruff format --check .) on utils.py and test_wrapper_langchain.py. Reformatted both files; verified locally against the CI-pinned ruff==0.15.0.

Review comment fixes:

  1. Reasoning tokens mislabeled as finish reasons (utils.py line 407): removed the erroneous output_token_details.reasoning → GEN_AI_RESPONSE_FINISH_REASONS_KEY mapping that I introduced in the previous round — that's a token count, not a finish reason, and there's no dedicated reasoning-tokens attribute defined yet.
  2. Batched usage only read the first input (utils.py line 413, _message_usage_metadata): rewrote it to aggregate usage across all batch entries in outputs["generations"] instead of only generations[0], so multi-input batched LLM calls no longer silently drop usage for later inputs. Added a _merge_usage_metadata() helper that sums numeric fields (incl. nested input_token_details/output_token_details) across batch entries, while still only using the first completion-with-usage per batch entry to avoid double-counting alternative completions when n > 1.

Added regression tests for both:

  • test_reasoning_tokens_from_message_usage_metadata_are_not_emitted_as_finish_reasons
  • test_token_counts_are_aggregated_across_batch_entries

All 280 tests pass locally (tests/observability/core + tests/observability/extensions/langchain), and ruff format --check / ruff check --preview both pass on the touched files with the CI-pinned ruff version.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Partial LangChain message metadata can still replace complete batch token totals and underreport usage.

0 open findings

2 resolved since last review
Previously missed (2)

In code that hasn't changed since last review

Low severity Document cache metric names with dotted attribute paths

libraries/​microsoft-agents-a365-observability-extensions-langchain/​CHANGELOG.md:11

The documented cache attribute names use underscores where the emitted constants use dots. Queries using these names will miss the exported counts. Document gen_ai.usage.cache_read.input_tokens and gen_ai.usage.cache_write.input_tokens instead.

Low severity Replace Any usage values with concrete token types

libraries/​microsoft-agents-a365-observability-extensions-langchain/​microsoft_agents_a365/​observability/​extensions/​langchain/​utils.py:445

The new _merge_usage_metadata and _message_usage_metadata helpers use Any for usage values and the accumulator, leaving recursive token addition unchecked. Use concrete usage/token-detail types for the normalized result and accumulator, and narrow serialized inputs from object at the extraction boundary. Update both helpers together so downstream token values remain type checked.

🧠 Review effort: Balanced

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping common telemetry definitions in core and framework-specific extraction in the adapters is a good fit for this SDK. The changes are focused and generally straightforward to follow.

Three minor comments inline.

Since my last review:

  • Still open: Integration guide still says InferenceScope emits Chat/TextCompletion/GenerateContent (docs/integrating-with-existing-opentelemetry.md). docs/integrating-with-existing-opentelemetry.md:96,99,113 still describes PascalCase manual operation names and recommends matching them. libraries/microsoft-agents-a365-observability-core/microsoft_a

Address review feedback on canonical operation and span-name changes, model identity validation, request/response model separation, and normalized cache-inclusive input-token totals. Update the existing OpenTelemetry integration guide without changing runtime behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 19, 2026 11:08
@biswapm
Pujarini Mohapatra (biswapm) requested a review from a team as a code owner October 19, 2026 11:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Partial LangChain message metadata can replace complete batch totals and underreport token usage.

1 open issue
1 previously missed issue

In code that hasn't changed since last review

Low severity Replace Any usage types with validated concrete types

libraries/​microsoft-agents-a365-observability-extensions-langchain/​microsoft_agents_a365/​observability/​extensions/​langchain/​utils.py:445

The new _merge_usage_metadata() and _message_usage_metadata() helpers introduce Any in their parameters, return type, and accumulator, contrary to the repository's typing requirement. This leaves recursive merging and accumulator addition unchecked. Use LangChain's UsageMetadata for normalized usage, and concrete types or object with narrowing for serialized data; narrow accumulator values before adding them.

Review effort: Balanced

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The separation between shared telemetry definitions and framework-specific extraction fits the SDK well. The changes are focused, and the additive APIs follow the existing tracing abstractions.

One minor comment inline.

Since my last review:

  • Fixed: Integration guide still says InferenceScope emits Chat/TextCompletion/GenerateContent (docs/integrating-with-existing-opentelemetry.md)
  • Fixed: Operation-name casing change is filed under Fixed and misstates the previous behavior (libraries/microsoft-agents-a365-observability-core/CHANGELOG.md)
  • Fixed: InferenceCallDetails now raises on a missing or blank model, and the CHANGELOG doesn't say so (libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/inference_call_details.py)
  • Fixed: LangChain CHANGELOG omits the request/response model split and the input-token change (libraries/microsoft-agents-a365-observability-extensions-langchain/CHANGELOG.md)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. These are non-blocking, but please address them before merge:

Should-fix

  • langchain/utils.py ~L365-369: the OpenAI completion_tokens_details.reasoning_tokens value is still mapped to gen_ai.response.finish_reasons. f722e88 removed the same mapping from the usage_metadata loop but missed this one, so spans still get an integer token count in finish_reasons. Suggest dropping this entry. No test currently covers it.
  • openai/utils.py get_attributes_from_response: obj.model now goes to gen_ai.response.model instead of gen_ai.request.model. Nothing else sets the request model on ResponseSpanData spans, so those spans lose gen_ai.request.model entirely. Please add an OpenAI extension CHANGELOG entry for this breaking change and for the new cache_read attribute, or keep emitting the request model as well.
  • langchain/CHANGELOG.md: "Batched generations retain aggregate llm_output usage rather than replacing it with a single message's usage" doesn't match the code, which sums message.usage_metadata across batch entries and prefers that over llm_output.

Consider

  • InferenceCallDetails.__post_init__ raising ValueError means telemetry setup can now break a user's request path. Logging a warning and falling back may be safer for an observability SDK.
  • text_completion and generate_content spans are now exported to Agent 365. Please confirm the ingest service accepts them.
  • token_counts can yield input/cache_read tokens twice (once from llm_output, once from usage_metadata), and the last value wins. That's correct today but fragile.

@biswapm
Pujarini Mohapatra (biswapm) merged commit 200daf0 into main Oct 19, 2026
10 checks passed
@biswapm
Pujarini Mohapatra (biswapm) deleted the users/pmohapatra/model-activity-otel branch October 19, 2026 15:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants