This commit is contained in:
devin-ai-integration[bot] 2026-08-27 19:03:08 -05:00 committed by GitHub
commit dc9cbcef4c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 66 additions and 7 deletions

View file

@ -181,7 +181,12 @@ nothing here imports outside it:
free-form metadata is promoted until each sub-key is explicitly allowlisted.
- [`baggage.py`](./model/baggage.py) — the single definition of which request-identity
values are promoted into Baggage (so child spans inherit them) and under which
attribute keys.
attribute keys. The requested model is promoted as the vendor
`litellm.request.model`, never canonical `gen_ai.request.model`: baggage lands
on every span of the request, and a canonical GenAI attribute on an HTTP, DB or
guardrail span makes GenAI-aware backends read that span as an LLM generation.
`gen_ai.request.model` stays on the LLM-call span only, where the `genai`
mapper stamps it.
- [`utils.py`](./model/utils.py) — value coercion, JSON serialization, and
extractor-table application, shared across the package.

View file

@ -32,19 +32,25 @@ _PROMOTABLE: Final[dict[str, Callable[[RequestIdentity, str | None, tuple[str, .
),
LiteLLM.KEY_HASH: lambda identity, model, team_metadata_keys: identity.key_hash,
LiteLLM.END_USER: lambda identity, model, team_metadata_keys: identity.end_user,
LiteLLM.REQUEST_MODEL: lambda identity, model, team_metadata_keys: model,
GenAI.REQUEST_MODEL: lambda identity, model, team_metadata_keys: model,
LiteLLM.PROVIDER_MODEL: lambda identity, model, team_metadata_keys: identity.provider_model,
}
# Keys promoted by default (a subset of ``_PROMOTABLE``). ``END_USER`` is
# promotable but off by default — it identifies an individual user, so stamping
# it onto every span is opt-in via ``config.baggage_promoted_keys``.
# it onto every span is opt-in via ``config.baggage_promoted_keys``. The
# requested model is promoted as the vendor ``litellm.request.model``: baggage
# lands on every span of the request, and canonical ``gen_ai.request.model`` on
# an HTTP/DB/guardrail span makes GenAI-aware backends (e.g. Langfuse) render
# that span as an LLM generation. ``GenAI.REQUEST_MODEL`` stays promotable for
# operators who deliberately want the canonical key everywhere.
BAGGAGE_PROMOTED_KEYS: Final[tuple[str, ...]] = (
LiteLLM.TEAM_ID,
LiteLLM.TEAM_ALIAS,
LiteLLM.TEAM_METADATA,
LiteLLM.KEY_HASH,
GenAI.REQUEST_MODEL,
LiteLLM.REQUEST_MODEL,
LiteLLM.PROVIDER_MODEL,
)

View file

@ -292,6 +292,11 @@ class LiteLLM:
# The model string litellm actually sent to the provider (the deployment's
# ``litellm_params.model``), distinct from the user-facing ``gen_ai.request.model``.
PROVIDER_MODEL: Final = "litellm.provider.model"
# The requested model, promoted onto non-GenAI spans (server, DB, guardrail)
# for correlation. Canonical ``gen_ai.request.model`` stays on GenAI spans
# only, so a GenAI-aware backend can't read an HTTP or DB span as an LLM
# generation.
REQUEST_MODEL: Final = "litellm.request.model"
REQUEST_STREAMING: Final = "litellm.request.streaming"
TOOLS_DECLARED: Final = "litellm.request.tools.declared"
GUARDRAIL_NAME: Final = "litellm.guardrail.name"

View file

@ -74,13 +74,54 @@ def test_identity_promoted_onto_every_span():
for span in spans:
assert span.attributes.get(LiteLLM.TEAM_ID) == "t1"
assert span.attributes.get(LiteLLM.TEAM_ALIAS) == "team one"
assert span.attributes.get(GenAI.REQUEST_MODEL) == "gpt-4o"
assert span.attributes.get(LiteLLM.REQUEST_MODEL) == "gpt-4o"
def test_canonical_request_model_stays_off_non_llm_spans():
"""The requested model rides Baggage as the vendor ``litellm.request.model``.
Canonical ``gen_ai.request.model`` must appear only on the LLM-call span,
which the mapper stamps it on; a GenAI-aware backend reading it off the
SERVER, guardrail or service span would render those as LLM generations."""
engine, exporter = _engine_and_exporter()
data = LLMCallSpanData.from_standard_logging_payload(_payload())
bag = promoted_baggage(data.identity, data.request_model, BAGGAGE_PROMOTED_KEYS)
assert GenAI.REQUEST_MODEL not in bag
ctx = ctx_mod.set_request_baggage(bag)
root = engine.start_span(SpanRole.PROXY_REQUEST, "POST /chat/completions", ctx)
root_ctx = ctx_mod.context_from_span(root, ctx)
engine.emit(SpanRole.LLM_CALL, data, parent_context=root_ctx)
engine.emit(
SpanRole.GUARDRAIL, GuardrailSpanData("presidio", status="success"), root_ctx
)
engine.emit(SpanRole.SERVICE, ServiceSpanData("redis", call_type="set"), root_ctx)
root.end()
spans = {span.name: span for span in exporter.get_finished_spans()}
llm = spans["chat gpt-4o"]
non_llm = [span for name, span in spans.items() if name != "chat gpt-4o"]
assert len(non_llm) == 3
assert llm.attributes.get(GenAI.REQUEST_MODEL) == "gpt-4o"
for span in non_llm:
assert GenAI.REQUEST_MODEL not in span.attributes
assert span.attributes.get(LiteLLM.REQUEST_MODEL) == "gpt-4o"
def test_canonical_request_model_promotable_when_explicitly_allowlisted():
"""Operators who want the canonical key everywhere can still opt in by
listing it in ``baggage_promoted_keys``."""
data = LLMCallSpanData.from_standard_logging_payload(_payload())
bag = promoted_baggage(
data.identity, data.request_model, (GenAI.REQUEST_MODEL, LiteLLM.TEAM_ID)
)
assert bag[GenAI.REQUEST_MODEL] == "gpt-4o"
assert LiteLLM.REQUEST_MODEL not in bag
def test_team_metadata_promoted_only_for_allowlisted_subkeys():
"""Allowlisted team-metadata sub-keys are promoted (JSON) onto every span;
non-allowlisted sub-keys are excluded, alongside the provider/underlying
model name and the user-facing ``gen_ai.request.model``."""
model name and the user-facing ``litellm.request.model``."""
import json
engine, exporter = _engine_and_exporter()
@ -99,7 +140,7 @@ def test_team_metadata_promoted_only_for_allowlisted_subkeys():
assert json.loads(span.attributes[LiteLLM.TEAM_METADATA]) == {"tier": "gold"}
# provider model is distinct from the user-facing request model
assert span.attributes.get(LiteLLM.PROVIDER_MODEL) == "azure/my-deployment"
assert span.attributes.get(GenAI.REQUEST_MODEL) == "gpt-4o"
assert span.attributes.get(LiteLLM.REQUEST_MODEL) == "gpt-4o"
def test_team_metadata_not_promoted_by_default():

View file

@ -895,7 +895,9 @@ def test_promoted_baggage_is_bounded_allowlist():
promoted = promoted_baggage(identity, "gpt-4o", BAGGAGE_PROMOTED_KEYS)
assert promoted[LiteLLM.TEAM_ID] == "t1"
assert promoted[LiteLLM.TEAM_ALIAS] == "team one"
assert promoted[GenAI.REQUEST_MODEL] == "gpt-4o"
assert promoted[LiteLLM.REQUEST_MODEL] == "gpt-4o"
# canonical gen_ai.* is not promoted: it would mark non-GenAI spans as LLM ops
assert GenAI.REQUEST_MODEL not in promoted
# allowlisted metadata sub-key is promoted under the litellm.metadata.* prefix
assert promoted[f"{LiteLLM.METADATA_PREFIX}user_api_key_org_id"] == "org1"
# full metadata blob is NOT promoted