feat(telemetry): emit gen_ai.response.model from Output.metadata["model"]#275
Merged
Merged
Conversation
…el"]
`output_attributes` now emits `gen_ai.response.model` set to
`output.metadata["model"]` — the model id celeste already populates on
every Output. Both `_predict` (via `ModalityClient._build_metadata`) and
`_stream` (via the `stream_metadata` constructor arg) write `model` to
metadata, so the field is universally present; no guards needed.
For providers that echo a different served snapshot in their response
(Anthropic latest-tag → dated, OpenAI alias → snapshot), provider mixins
can opt in to overwrite `metadata["model"]` with the served value. None
do that today — `metadata["model"]` is the request id everywhere — and
the emitted attribute equals `gen_ai.request.model` in practice. That's
semconv-clean for the common case where served = requested.
Test fixture `TelemetryStream` now defaults `stream_metadata={"model":
"test-model"}` to mirror production wiring; one updated test passes the
metadata explicitly. New positive test asserts `gen_ai.response.model`
reads from `metadata["model"]`.
Real-call validated against running chat-backend (Gemini text + image
streaming): every GenAI span now carries `gen_ai.response.model`.
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
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.
Summary
output_attributesnow emitsgen_ai.response.modelset tooutput.metadata["model"]— the model id celeste already populates on every Output. Closes the GenAI semconvgen_ai.response.modelgap.Why
GenAI semconv defines two distinct attributes:
gen_ai.request.model— the id the caller requestedgen_ai.response.model— the id the provider actually servedPre-PR celeste only emitted the request side. For routing/aliasing providers (Anthropic latest-tag → dated snapshot, OpenAI alias → snapshot) the response side can differ, and dashboards need it to attribute cost/latency/errors to the actually-served variant.
Mechanism
One-line change in
output_attributes:metadata["model"]is universally populated:ModalityClient._build_metadatawrites it (client.py:415).client.py:_streampassesstream_metadata={"model": self.model.id, ...}to the Stream constructor;Stream._build_stream_metadataspreads it into the Output's metadata.For providers that don't echo a different served model, the emitted attribute equals
gen_ai.request.model. That's accurate for the common case where served = requested.Why no provider mixin changes
Provider mixins COULD overwrite
metadata["model"]with the response's served value (Anthropicresponse_data["model"], GeminimodelVersion, etc.) to surface routing differences. They don't today; this PR is intentionally minimal. Aliasing-provider users who want the served snapshot can override_build_metadatain their provider mixin in a follow-up — the data flow is already there.Tests
TelemetryStreamtest fixture defaultsstream_metadata={"model": "test-model"}to mirror production wiring.test_off_spec_usage_emitted_under_celeste_namespaceupdated to construct Output withmetadata={"model": ...}.test_response_model_emitted_from_metadataasserts the attribute is read from metadata correctly.631 tests pass. Real-call validated against running chat-backend (Gemini text streaming + non-streaming + image-gen): every GenAI span carries
gen_ai.response.model.Out of scope
metadata["model"]with the served snapshot. Easy follow-up; doesn't belong in this minimal landing.Output.modelas a typed field.metadata["model"]is the existing surface; promoting it would expand the public Output API for one optional string.Test plan
make lint,make format --check,make typecheck— clean.uv run pytest tests/unit_tests/ -m "not integration" -q— 631 passed.gen_ai.response.modelpresent on every span.