mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-06 08:16:43 +00:00
Merge 6dbaf34005 into 49affa7c01
This commit is contained in:
commit
dc9cbcef4c
5 changed files with 66 additions and 7 deletions
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue