Repository navigation
Conversation
cpsievert
marked this pull request as ready for review
October 20, 2026 21:50
Ports the OpenTelemetry stack ellmer merged on 2026-10-20 (tidyverse/ellmer#1143, #1147, #1148, #1149, #1174, #1175, building on #1142 and #1160): - Metrics alongside spans: gen_ai.client.inference.* histograms (duration, input/output tokens, time to first chunk, time per output chunk) and monotonic usage counters (input/output/cache-read/reasoning tokens, tagged with gen_ai.token.modality), gen_ai.execute_tool.duration, and gen_ai.invoke_agent.{duration,inference_calls,tool_calls}. - Richer chat spans: server.address/server.port, gen_ai.request.stream, gen_ai.output.type, request params actually sent (gen_ai.request.*, including reasoning effort on a best-effort basis), finish reasons (gen_ai.response.finish_reasons as a proper string array), cache-read and reasoning token counts, time-to-first-chunk, and (with content capture enabled) gen_ai.tool.definitions, including the private structured-output tool used by Anthropic/Bedrock. - Well-known gen_ai.provider.name values (e.g. "anthropic", "openai", "gcp.gemini") via a display-name mapping. - invoke_agent is now an internal-kind span and records duration, inference/tool call counts, and total token usage as attributes. - Successful requests are marked with an OK span status; failed, cancelled (StreamController.cancel()), and interrupted (e.g. abandoned streams, keyboard interrupt) requests report an error status and error.type on spans and the duration metrics. Tool results holding Content values (e.g. images) now convert to otel parts instead of stringifying. Provider gains value_reasoning_tokens() (OpenAI Responses + Completions) and structured_output_tool() (Anthropic, Bedrock Converse) hooks to feed the new attributes.
…fail The OTel SDK treats Ok as final (the spec's Ok > Error > Unset total order): it overrides any prior Error and ignores any later one. Recording the OK status before the final turn was built meant a late failure -- e.g. a truncated data_model response raising during turn assembly -- produced a self-contradictory span: status OK alongside an exception event, error.type, and an "error" finish reason. The OK stamp now happens at the end of the request path, gated on a flag set only after turn assembly and response cleanup complete, so a late error always lands. record_chat_span_status() keeps the ellmer structure (metrics + attributes + error status for cancelled requests) but defers the success stamp to record_chat_span_ok(). ellmer has the same latent masking in record_chat_otel_span_status().
…ised histogram buckets Follow-ups on top of Provider.base_url: - server.address/server.port now come from Provider.base_url uniformly, covering Google, Bedrock (all flavors), Posit, Databricks, and Snowflake in addition to the OpenAI/Anthropic SDK clients. - gen_ai.request.reasoning.level also recognizes Google's config.thinking_config.thinking_level (ThinkingLevel enum). - Histograms are created with the GenAI semconv's advised explicit bucket boundaries (durations 0.01..81.92s, agent duration 0.1..409.6s, token counts 1..67108864, call counts 1..128) when the installed opentelemetry-api supports explicit_bucket_boundaries_advisory; detected by signature so older API versions keep working with SDK default buckets.
botocore reads region from AWS config/AWS_DEFAULT_REGION, not AWS_REGION, so the test failed in config-less CI environments.
cpsievert
force-pushed
the
otel-genai-semconv
branch
from
October 21, 2026 16:39
a1490ac to
0c6f6ae
Compare
…irectly Bumping the opentelemetry-api/sdk floor from 1.12 to 1.30 (the release that added explicit_bucket_boundaries_advisory) lets the semconv-advised bucket boundaries be passed unconditionally, dropping the inspect.signature runtime probe and the **kwargs indirection in _get_histogram().
This branch has not been deployed
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.
Ports the OpenTelemetry stack ellmer merged on 2026-10-20: tidyverse/ellmer#1143, tidyverse/ellmer#1147, tidyverse/ellmer#1148, tidyverse/ellmer#1149, tidyverse/ellmer#1174, tidyverse/ellmer#1175, plus the prerequisites they build on (tidyverse/ellmer#1142 time-to-first-token, tidyverse/ellmer#1160 Content tool results as otel parts).
Summary
Metrics alongside spans (all from the GenAI semantic conventions):
gen_ai.client.inference.duration/operation.input_tokens/operation.output_tokenshistograms,gen_ai.client.inference.usage.*monotonic counters (input/output/cache-read/reasoning tokens, taggedgen_ai.token.modality="unknown"),gen_ai.client.inference.time_to_first_chunkandtime_per_output_chunkhistograms for streams,gen_ai.execute_tool.duration, andgen_ai.invoke_agent.{duration,inference_calls,tool_calls}. Histograms are created with the semconv's advised explicit bucket boundaries when the installed opentelemetry-api supportsexplicit_bucket_boundaries_advisory(detected by signature, so older versions keep working). Instruments are created lazily from a module-level meter, so everything is a no-op without an SDK configured.Richer spans. Chat spans now record
server.address/server.port(via the newProvider.base_url), the request parameters actually sent (gen_ai.request.*, includinggen_ai.request.streamand a best-effort reasoning level across OpenAI/Anthropic/completions/Google kwargs shapes),gen_ai.output.typefor structured output, finish reasons (gen_ai.response.finish_reasonsas a properstring[]— Python OTel supports array attributes where R's otel could not), cache-read/reasoning token counts, time to first chunk, and (with content capture enabled)gen_ai.tool.definitions, including the private structured-output tool used by Anthropic/Bedrock (newProvider.structured_output_tool()hook).gen_ai.provider.namenow maps to the well-known semconv values. Theinvoke_agentspan is nowinternalkind and records duration, inference/tool call counts, and token totals.Reliability of statuses and errors. Successful chat spans get an
OKstatus — but stamped lazily, only once turn assembly and cleanup have completed, because the SDK treats Ok as final (the spec'sOk > Error > Unsettotal order) and an earlier stamp would mask a late failure such as a truncateddata_modelresponse (regression tests included; ellmer'srecord_chat_otel_span_status()has the same latent masking and could use the same fix). Failed, cancelled (StreamController.cancel()), and interrupted (e.g. abandoned streams, keyboard interrupt) requests now report an error status anderror.typeon spans and the duration metrics; the sync agent loop now closes its inner generator deterministically (mirroring the async path) so abandoned streams unwind child-before-parent on all Python implementations. Tool results holdingContentvalues (e.g. images) convert to otel parts instead of stringifying.Also:
Provider.value_reasoning_tokens()hook (OpenAI Responses + Completions implementations).Verification
19 new OpenTelemetry tests in
tests/test_otel.py(metrics viaInMemoryMetricReader, span attributes, span kinds, cancel/interrupt/error paths, tool definitions, structured-output tool modes, advised bucket boundaries) plus updates to the existing suite. Full test suite (1370 passed), ruff, and pyright pass locally;docs/get-started/monitor.qmdgains a Metrics section and updated attribute lists.Deliberate divergences from ellmer
gen_ai.response.finish_reasonsis a properstring[](ellmer emits a scalar, blocked on No way to record a length-1 character vector as an array attribute r-lib/otel#48).chat_performsucceeds; chatlas drops unsupported params at set-time, so no request-time filtering is needed).data_modelis set (chatlas sends both in the request; ellmer suppresses user tools for structured-output requests).