Python: record gen_ai.client.operation.duration for failed chat and embedding calls - #7819
Open
Venkat Ramachandran (venkat-uk) wants to merge 1 commit into
Open
Conversation
The GenAI semantic conventions define gen_ai.client.operation.duration for failed operations as well as successful ones, with error.type set to the class of the error. ChatTelemetryLayer and EmbeddingTelemetryLayer only recorded the histogram on the success path, so error latency never reached the metric and the metric carried no error rate. _capture_response already had the branch that copies error.type into the metric attributes, but no caller ever put error.type into the attribute dict, so that branch was unreachable in production. Add _capture_operation_error and call it from the three chat and embedding failure paths: the streaming setup failure, the mid-stream failure, and the non-streaming failure. The metric attribute set is filtered through GEN_AI_METRIC_ATTRIBUTES, the same filter the success path uses, so no high-cardinality attribute reaches the metric.
Venkat Ramachandran (venkat-uk)
deployed
to
github-app-auth
August 22, 2026 12:56 — with
GitHub Actions
Active
Venkat Ramachandran (venkat-uk)
deployed
to
github-app-auth
August 22, 2026 12:56 — with
GitHub Actions
Active
Venkat Ramachandran (venkat-uk)
had a problem deploying
to
github-app-auth
August 22, 2026 12:56 — with
GitHub Actions
Error
Venkat Ramachandran (venkat-uk)
deployed
to
github-app-auth
August 22, 2026 12:56 — with
GitHub Actions
Active
Copilot started reviewing on behalf of
Venkat Ramachandran (venkat-uk)
August 22, 2026 12:56
View session
Contributor
There was a problem hiding this comment.
Pull request overview
Records failed chat and embedding operation durations with OpenTelemetry error attributes.
Changes:
- Adds
_capture_operation_error. - Instruments chat and embedding failure paths.
- Adds helper and chat failure tests.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
python/packages/core/agent_framework/observability.py |
Records duration metrics for failed operations. |
python/packages/core/tests/core/test_observability.py |
Tests error metric filtering and chat failures. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+1922
to
+1927
| _capture_operation_error( | ||
| attributes=attributes, | ||
| exception=exception, | ||
| operation_duration_histogram=getattr(self, "duration_histogram", None), | ||
| duration=perf_counter() - start_time_stamp, | ||
| ) |
Comment on lines
+1712
to
+1717
| _capture_operation_error( | ||
| attributes=attributes, | ||
| exception=exception, | ||
| operation_duration_histogram=getattr(self, "duration_histogram", None), | ||
| duration=perf_counter() - start_time, | ||
| ) |
Author
|
@microsoft-github-policy-service agree |
Author
|
I have sole ownership of the intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer |
Venkat Ramachandran (venkat-uk)
force-pushed
the
fix/otel-duration-metric-on-error
branch
from
August 22, 2026 13:16
7713f56 to
a29a82f
Compare
Venkat Ramachandran (venkat-uk)
deployed
to
github-app-auth
August 22, 2026 13:16 — with
GitHub Actions
Active
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation & Context
gen_ai.client.operation.durationis only recorded when a call succeeds. Every failedchat call and every failed embedding call is missing from the metric.
The OpenTelemetry GenAI semantic conventions define this metric for failed operations too,
with
error.typeset to the class of the error.error.typeis listed as conditionallyrequired, "if the operation ended in an error".
The framework already knows this.
_capture_responseinpython/packages/core/agent_framework/observability.pyhas a branch that copieserror.typefrom the attribute dictionary into the metric attributes:No caller ever puts
OtelAttr.ERROR_TYPEinto that dictionary, and_capture_responseisonly reached on the success path, so the branch is unreachable in production. The only
place that covers it is
test_capture_response_with_error_type, which calls the helperdirectly.
FunctionTool.invokein_tools.pyalready does this correctly. It setsattributes[OtelAttr.ERROR_TYPE]in itsexceptblock and records the duration histogramin a
finallyblock, so failed tool calls do reachagent_framework.function.invocation.duration. The chat and embedding layers do not.What this costs an operator: a dashboard built on
gen_ai.client.operation.durationshowsonly healthy traffic. A provider outage that makes every call time out after 30 seconds
looks like a drop in request volume, not a latency or error problem. There is no error rate
to alert on, because no error series exists.
Description & Review Guide
What are the major changes?
A new helper,
_capture_operation_error, records the duration histogram for a failedcall. It filters the request attributes through
GEN_AI_METRIC_ATTRIBUTES, the samefilter
_capture_responseuses on the success path, then addserror.type = type(exception).__name__.It is called from three failure paths:
ChatTelemetryLayer, streaming, when the underlying client raises while the stream isbeing set up.
ChatTelemetryLayer, streaming, when the stream raises during iteration. The durationcomes from
duration_state, which the existing_record_durationcleanup hook fillsin on both success and failure.
ChatTelemetryLayer, non-streaming, andEmbeddingTelemetryLayer.What is the impact of these changes?
The metric now has a series for failed calls. Nothing that is recorded today changes:
the success path, span attributes, the token usage histogram, and the exception recorded
on the span are all untouched. No public API changes. No signature changes.
AgentTelemetryLayeris deliberately left alone. It calls_capture_responsewithout ahistogram, so it does not emit this metric at all today, on success or on failure.
Making it emit one would be a new feature rather than a fix.
What do you want reviewers to focus on?
Two things. First, whether
EmbeddingTelemetryLayershould be in the same PR. It sharesthe metric and the same gap, so I included it, but it is proved only by the helper's unit
test and by the existing openai embedding tests staying green, not by a new end-to-end
embedding test. Second, whether the mid-stream case should record a duration when
duration_stateis empty. Today it records nothing in that case, which I kept, because azero would be wrong.
How tested
The two chat tests were written first and fail on
d9d3fb6with:Four tests added to
packages/core/tests/core/test_observability.py:test_capture_operation_error_keeps_only_metric_attributesgen_ai.conversation.idis dropped,error.typeis addedtest_capture_operation_error_without_histogram_or_durationtest_chat_client_records_duration_on_errorerror.typetest_chat_client_records_duration_on_streaming_errorerror.typeCommands run from
python/:uv run poe pyright -P corereports 16 errors onobservability.pyand_workflows/_viz.py.All 16 are missing optional imports (
opentelemetry.exporter.otlp.proto.grpc.*,graphviz)and all 16 reproduce on a clean checkout of
d9d3fb6with the same environment. None of themare on changed lines.
The openai embedding suite caught a real problem in the first version of this change:
OpenAIEmbeddingClientdoes not always haveduration_histogramset. The embedding callsite now uses
getattr(self, "duration_histogram", None), which is what the chat layeralready does.
Tradeoffs
estimate of one failure in a thousand calls this is a rounding error. On a provider
outage it is one point per failed call, the same rate the success path already writes.
error.typeis the exception class name. On a wrapper exception that is the wrapper'sname, not the underlying provider status code. This matches how
capture_exceptionalready labels the span, so the metric and the trace agree with each other.
gen_ai.client.operation.durationwill see a newset of series appear, one per distinct exception class. The cardinality is bounded by the
number of exception types, and the attribute set is otherwise the same filtered set the
success path uses.
helper's unit test and by the existing openai and anthropic suites staying green.
Related Issue
Fixes #7818
Contribution Checklist