fix(proxy): carry project_id through key metadata enrichment and drop docstrings

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
ryan 2026-09-16 02:28:38 +00:00
parent b00cd15bd7
commit b20f1422eb
8 changed files with 5 additions and 19 deletions

View file

@ -307,12 +307,10 @@ def model_access_group_spend_counter_key(access_group_name: str) -> str:
def project_cache_key(project_id: str) -> str:
"""Cache key one project row is stored under; shared by auth, spend tracking and the spend writer."""
return f"project_id:{project_id}"
def project_spend_counter_key(project_id: str) -> str:
"""Spend counter key for one project; the reservation, cost callback, auth and reseed paths all read it."""
return f"spend:project:{project_id}"

View file

@ -504,6 +504,8 @@ class _ProxyDBLogger(CustomLogger):
metadata["user_api_key_team_id"] = key_obj.team_id
if metadata.get("user_api_key_org_id") is None:
metadata["user_api_key_org_id"] = key_obj.org_id
if metadata.get("user_api_key_project_id") is None:
metadata["user_api_key_project_id"] = key_obj.project_id
except Exception:
verbose_proxy_logger.debug(
"Failed to enrich failure metadata with key info for api_key=%s",

View file

@ -7161,9 +7161,6 @@ def _project_with_budget(spend: float, max_budget: float):
],
)
async def test_project_max_budget_check_reads_live_spend_counter(counter_spend, db_spend, blocks):
"""LIT-3269: project budget enforcement must read the cross-pod
``spend:project:{id}`` counter first and only fall back to the cached row's
spend, matching key/team/org checks. The boundary is inclusive (>=)."""
from litellm.caching.dual_cache import DualCache
from litellm.proxy.auth.auth_checks import _project_max_budget_check

View file

@ -1666,7 +1666,6 @@ def test_budget_table_reset_invalidates_every_access_group_not_just_the_first(
def test_project_reset_zeroes_spend_on_due_tiers(reset_budget_job, mock_prisma_client, monkeypatch):
"""A project linked to an expiring budget tier has its spend zeroed in the same cascade transaction."""
_make_counter_invalidation_job(monkeypatch)
mock_prisma_client.data["budget"] = [_budget_row(budget_id="budget-due", budget_duration="7d")]
mock_prisma_client.db.litellm_projecttable.set_find_many_results(

View file

@ -1062,10 +1062,6 @@ async def test_batch_database_updates_queues_org_member_spend_for_the_request_us
@pytest.mark.asyncio
async def test_project_spend_is_persisted_to_project_table_and_project_cache_is_evicted():
"""Regression for LIT-3269: a request made with a project-scoped key must
increment LiteLLM_ProjectTable.spend, otherwise /project/info stays at 0
and the project budget never blocks. The cached project row is evicted so
the next auth check reads the fresh spend."""
db_writer: Final = DBSpendUpdateWriter()
await db_writer._batch_database_updates(
response_cost=0.25,

View file

@ -432,8 +432,6 @@ async def test_from_db_bounds_in_flight_prisma_requests_across_counter_keys():
@pytest.mark.asyncio
async def test_from_db_reseeds_project_counter_from_the_project_row():
"""LIT-3269: a cold ``spend:project:{id}`` counter seeds from LiteLLM_ProjectTable.spend,
so a fresh pod enforces the project budget against persisted spend rather than 0."""
prisma: Final = _FakePrismaClient(project_row=SimpleNamespace(project_id="proj-1", spend=7.25))
assert await SpendCounterReseed.from_db(prisma_client=prisma, counter_key="spend:project:proj-1") == 7.25

View file

@ -1372,6 +1372,7 @@ async def test_enrich_failure_metadata_with_full_key_lookup():
mock_key_obj.user_id = "fetched-user-id"
mock_key_obj.team_id = "fetched-team-id"
mock_key_obj.org_id = "fetched-org-id"
mock_key_obj.project_id = "fetched-project-id"
mock_team_obj = MagicMock()
mock_team_obj.team_alias = "fetched-team-alias"
@ -1395,12 +1396,14 @@ async def test_enrich_failure_metadata_with_full_key_lookup():
"user_api_key_team_id": None,
"user_api_key_team_alias": None,
"user_api_key_org_id": None,
"user_api_key_project_id": None,
}
result = await _ProxyDBLogger._enrich_failure_metadata_with_key_info(metadata)
assert result["user_api_key_alias"] == "fetched-key-alias"
assert result["user_api_key_user_id"] == "fetched-user-id"
assert result["user_api_key_team_id"] == "fetched-team-id"
assert result["user_api_key_org_id"] == "fetched-org-id"
assert result["user_api_key_project_id"] == "fetched-project-id"
assert result["user_api_key_team_alias"] == "fetched-team-alias"

View file

@ -672,10 +672,6 @@ async def _seed_project_scoped_budgets(
@pytest.mark.asyncio
async def test_should_reserve_project_and_team_member_counters_for_project_scoped_key(spend_counter_state):
"""LIT-3269: a key carrying user_id, team_id and project_id reserves against
both the team member counter and the project counter; neither replaces the
other. After the call the project counter reflects the real cost once, not
the reservation plus the post-call increment."""
counter_cache, key_cache = spend_counter_state
proxy_logging_obj = ProxyLogging(user_api_key_cache=key_cache)
await _seed_project_scoped_budgets(
@ -724,8 +720,6 @@ async def test_should_reserve_project_and_team_member_counters_for_project_scope
@pytest.mark.asyncio
async def test_exhausted_team_member_budget_still_blocks_project_scoped_key(spend_counter_state):
"""LIT-3269: the project budget is additive. A project with plenty of
headroom must not let a key through once its team member budget is spent."""
counter_cache, key_cache = spend_counter_state
proxy_logging_obj = ProxyLogging(user_api_key_cache=key_cache)
await _seed_project_scoped_budgets(
@ -755,7 +749,6 @@ async def test_exhausted_team_member_budget_still_blocks_project_scoped_key(spen
@pytest.mark.asyncio
async def test_exhausted_project_budget_blocks_project_scoped_key(spend_counter_state):
"""LIT-3269: with team member headroom left, the project budget alone blocks the key."""
counter_cache, key_cache = spend_counter_state
proxy_logging_obj = ProxyLogging(user_api_key_cache=key_cache)
await _seed_project_scoped_budgets(