mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
perf(otel/v2): resolve the caller's org only when a destination is org-scoped
The org fallback loads the team, which on a cache miss is a Prisma read on the authentication path. It cannot change which destinations are selected unless some access names an org, so it now runs only then; the logging credentials are also parsed once instead of twice.
This commit is contained in:
parent
b5a51e72cd
commit
d31b4a3fde
2 changed files with 57 additions and 16 deletions
|
|
@ -149,7 +149,6 @@ _ENABLE_TEAM_STALE_ALIAS_BYPASS: bool | None = None
|
|||
|
||||
if TYPE_CHECKING:
|
||||
from litellm.integrations.otel.model.destination import OtelDestination
|
||||
from litellm.models.credentials import CredentialItem
|
||||
from litellm.proxy.proxy_server import ProxyConfig as _ProxyConfig
|
||||
from litellm.types.proxy.policy_engine import PolicyMatchContext
|
||||
from litellm.types.utils import OtelDestinationParams
|
||||
|
|
@ -751,6 +750,10 @@ async def _resolve_logging_exporters(
|
|||
``access.global``). Each survivor is built via ``destination_for_credential`` and deduped on
|
||||
(endpoint, headers, resource attributes). Returns ([], []) when nothing is selected
|
||||
(default-deny).
|
||||
|
||||
The caller's org is resolved only when some destination is org-scoped: the fallback
|
||||
loads the team, which on a cache miss is a Prisma read on the authentication path,
|
||||
and it cannot change the outcome when no ``access`` names an org.
|
||||
"""
|
||||
from litellm.integrations.otel.model.config import is_otel_v2_enabled
|
||||
from litellm.proxy.management_endpoints.logging_exporter_access import (
|
||||
|
|
@ -766,26 +769,26 @@ async def _resolve_logging_exporters(
|
|||
if not is_otel_v2_enabled():
|
||||
return (), ()
|
||||
|
||||
if not any(
|
||||
(info := parse_credential_info(credential.credential_info)) is not None and info.credential_type == "logging"
|
||||
logging_credentials = tuple(
|
||||
(credential, info)
|
||||
for credential in litellm.credential_list
|
||||
):
|
||||
if (info := parse_credential_info(credential.credential_info)) is not None and info.credential_type == "logging"
|
||||
)
|
||||
if not logging_credentials:
|
||||
return (), ()
|
||||
|
||||
team_id = user_api_key_dict.team_id
|
||||
org_id = await _effective_org_id(user_api_key_dict)
|
||||
org_id = (
|
||||
await _effective_org_id(user_api_key_dict)
|
||||
if any(info.access is not None and info.access.orgs for _, info in logging_credentials)
|
||||
else None
|
||||
)
|
||||
team_ids, org_ids = identity_scope(team_id, org_id)
|
||||
|
||||
def _selected(credential: "CredentialItem") -> bool:
|
||||
info = parse_credential_info(credential.credential_info)
|
||||
if info is None or info.credential_type != "logging":
|
||||
return False
|
||||
return access_grants(info.access, team_ids, org_ids)
|
||||
|
||||
built = tuple(
|
||||
result
|
||||
for credential in litellm.credential_list
|
||||
if _selected(credential)
|
||||
for credential, info in logging_credentials
|
||||
if access_grants(info.access, team_ids, org_ids)
|
||||
if (result := destination_for_credential(credential)) is not None
|
||||
)
|
||||
deduped = {
|
||||
|
|
|
|||
|
|
@ -6724,9 +6724,11 @@ async def test_resolve_logging_exporters_short_circuits_without_destinations(mon
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_logging_exporters_runs_lookup_when_a_destination_exists(monkeypatch):
|
||||
async def test_resolve_logging_exporters_resolves_a_global_destination_without_an_org_lookup(monkeypatch):
|
||||
"""The short-circuit must not skip resolution when a destination exists: a global
|
||||
destination is still resolved for a team-scoped key, and the org lookup runs."""
|
||||
destination is still resolved for a team-scoped key. No destination is org-scoped
|
||||
here, so the org fallback (a team load, and on a cache miss a Prisma read on the
|
||||
auth path) cannot change the outcome and must not run."""
|
||||
from litellm.integrations.otel.model.config import is_otel_v2_enabled
|
||||
from litellm.proxy import litellm_pre_call_utils as pcu
|
||||
|
||||
|
|
@ -6755,10 +6757,46 @@ async def test_resolve_logging_exporters_runs_lookup_when_a_destination_exists(m
|
|||
key = UserAPIKeyAuth(api_key="k", team_id="t1")
|
||||
destinations, backends = await pcu._resolve_logging_exporters(key)
|
||||
|
||||
assert lookups["org"] == 1 # a destination exists, so the resolver runs the lookup
|
||||
assert lookups["org"] == 0 # nothing is org-scoped, so the lookup is skipped
|
||||
assert "generic" in backends # global access grants the team key
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_logging_exporters_runs_the_org_lookup_for_an_org_scoped_destination(monkeypatch):
|
||||
"""An org-scoped destination is invisible without the caller's org, so the fallback
|
||||
still runs; a team key carrying no org_id resolves it through its team."""
|
||||
from litellm.integrations.otel.model.config import is_otel_v2_enabled
|
||||
from litellm.proxy import litellm_pre_call_utils as pcu
|
||||
|
||||
monkeypatch.setenv("LITELLM_OTEL_V2", "true")
|
||||
is_otel_v2_enabled.cache_clear()
|
||||
|
||||
monkeypatch.setattr(
|
||||
litellm,
|
||||
"credential_list",
|
||||
[
|
||||
CredentialItem(
|
||||
credential_name="d-org",
|
||||
credential_values={"otel_endpoint": "https://collector/v1/traces"},
|
||||
credential_info={"credential_type": "logging", "description": "generic", "access": {"orgs": ["o1"]}},
|
||||
)
|
||||
],
|
||||
)
|
||||
lookups = {"org": 0}
|
||||
|
||||
async def _spy_effective_org_id(user_api_key_dict):
|
||||
lookups["org"] += 1
|
||||
return "o1"
|
||||
|
||||
monkeypatch.setattr(pcu, "_effective_org_id", _spy_effective_org_id)
|
||||
|
||||
key = UserAPIKeyAuth(api_key="k", team_id="t1")
|
||||
destinations, backends = await pcu._resolve_logging_exporters(key)
|
||||
|
||||
assert lookups["org"] == 1 # an org-scoped destination needs the caller's org
|
||||
assert "generic" in backends # and the org grant then selects it
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_logging_exporters_noop_when_flag_off(monkeypatch):
|
||||
"""LITELLM_OTEL_V2 is the sole activation gate: with the flag off, the resolver
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue