mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-13 23:11:40 +00:00
fix: guardrail usage logs action-filter pagination and partial-stream empty-choices chunks
- usage_endpoints.py: when action filter is set, fetch all rows and filter in Python before slicing to page — action is derived from SpendLog JSON metadata, not an index column, so DB-level skip/take cannot be used when filtering by it. Unfiltered path retains efficient DB pagination unchanged. - common_request_processing.py: strip trailing empty-choices chunks (e.g. OpenAI final SSE usage chunk) before stream_chunk_builder so it does not IndexError on an empty choices list when reading finish_reason from the last chunk.
This commit is contained in:
parent
7fc09d6b7e
commit
4d51bebf96
2 changed files with 44 additions and 4 deletions
|
|
@ -2465,6 +2465,17 @@ class ProxyBaseLLMRequestProcessing:
|
|||
return
|
||||
elif not isinstance(first_chunk, str) and not hasattr(first_chunk, "choices"):
|
||||
return
|
||||
# Strip trailing usage-only / empty-choices chunks (e.g. OpenAI's final SSE
|
||||
# `data: {"choices":[],"usage":{...}}`) so stream_chunk_builder can safely
|
||||
# inspect the last chunk's finish_reason without an IndexError.
|
||||
def _has_empty_choices(c: Any) -> bool:
|
||||
if isinstance(c, dict):
|
||||
return c.get("choices") == []
|
||||
return bool(hasattr(c, "choices") and getattr(c, "choices") == [])
|
||||
|
||||
chunks = [c for c in chunks if not _has_empty_choices(c)]
|
||||
if not chunks:
|
||||
return
|
||||
# Optimization, not a correctness guard: dispatch_success_handlers is the
|
||||
# authoritative de-dup via has_dispatched_final_stream_success. This just
|
||||
# skips the stream_chunk_builder assembly when completion already logged.
|
||||
|
|
|
|||
|
|
@ -608,6 +608,35 @@ async def guardrails_usage_logs(
|
|||
effective_guardrail_ids.append(logical_name)
|
||||
|
||||
where = _build_usage_logs_where(effective_guardrail_ids or None, policy_id, start_date, end_date)
|
||||
|
||||
if action:
|
||||
# action is derived from SpendLog JSON metadata, not an index column, so we
|
||||
# cannot push the filter to the DB. Fetch all matching index rows, join with
|
||||
# SpendLogs, filter in Python, then slice to the requested page.
|
||||
all_index_rows = await SpendLogGuardrailIndexRepository(prisma_client).table.find_many(
|
||||
where=where,
|
||||
order={"start_time": "desc"},
|
||||
)
|
||||
if not all_index_rows:
|
||||
return UsageLogsResponse(logs=[], total=0, page=page, page_size=page_size)
|
||||
all_request_ids = [r.request_id for r in all_index_rows]
|
||||
spend_logs = await SpendLogsRepository(prisma_client).table.find_many(
|
||||
where={"request_id": {"in": all_request_ids}}
|
||||
)
|
||||
log_by_id = {s.request_id: s for s in spend_logs}
|
||||
all_entries: List[UsageLogEntry] = []
|
||||
for r in all_index_rows:
|
||||
sl = log_by_id.get(r.request_id)
|
||||
if not sl:
|
||||
continue
|
||||
entry = _usage_log_entry_from_row(r, sl, action)
|
||||
if entry is not None:
|
||||
all_entries.append(entry)
|
||||
total = len(all_entries)
|
||||
start_idx = (page - 1) * page_size
|
||||
logs_out = all_entries[start_idx : start_idx + page_size]
|
||||
return UsageLogsResponse(logs=logs_out, total=total, page=page, page_size=page_size)
|
||||
|
||||
index_rows = await SpendLogGuardrailIndexRepository(prisma_client).table.find_many(
|
||||
where=where,
|
||||
order={"start_time": "desc"},
|
||||
|
|
@ -620,15 +649,15 @@ async def guardrails_usage_logs(
|
|||
return UsageLogsResponse(logs=[], total=total, page=page, page_size=page_size)
|
||||
spend_logs = await SpendLogsRepository(prisma_client).table.find_many(where={"request_id": {"in": request_ids}})
|
||||
log_by_id = {s.request_id: s for s in spend_logs}
|
||||
logs_out: List[UsageLogEntry] = []
|
||||
logs_out_unfiltered: List[UsageLogEntry] = []
|
||||
for r in index_rows[:page_size]:
|
||||
sl = log_by_id.get(r.request_id)
|
||||
if not sl:
|
||||
continue
|
||||
entry = _usage_log_entry_from_row(r, sl, action)
|
||||
entry = _usage_log_entry_from_row(r, sl, None)
|
||||
if entry is not None:
|
||||
logs_out.append(entry)
|
||||
return UsageLogsResponse(logs=logs_out, total=total, page=page, page_size=page_size)
|
||||
logs_out_unfiltered.append(entry)
|
||||
return UsageLogsResponse(logs=logs_out_unfiltered, total=total, page=page, page_size=page_size)
|
||||
except Exception as e:
|
||||
from litellm.proxy.utils import handle_exception_on_proxy
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue