diff --git a/litellm/integrations/shadow_eval_logger.py b/litellm/integrations/shadow_eval_logger.py index 059141b3c57..df0e7366689 100644 --- a/litellm/integrations/shadow_eval_logger.py +++ b/litellm/integrations/shadow_eval_logger.py @@ -605,8 +605,10 @@ class ShadowEvalLogger(CustomLogger): # attributed to, and budget-checked against, the key whose traffic it copies. # The router's pre-routing hook also writes its routing decision into this dict; # read it back after the call for tier attribution. - shadow_metadata: Final[dict[str, object]] = sanitized_forwardable_call_metadata( # mutable-ok: router writes back - parent_metadata, SHADOW_EVAL_ROUTER_CALL_ORIGIN + shadow_metadata: Final[dict[str, object]] = ( + sanitized_forwardable_call_metadata( # mutable-ok: router writes back + parent_metadata, SHADOW_EVAL_ROUTER_CALL_ORIGIN + ) ) shadow_params: Final = { # mutable-ok: splatted as kwargs k: v for k, v in model_parameters.items() if k not in ("stream", "metadata") diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.activity.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.activity.test.tsx index 4a8af9eff22..04dad46a522 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.activity.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.activity.test.tsx @@ -3,11 +3,14 @@ import { describe, expect, it, vi } from "vitest"; const mockUserDailyActivityCall = vi.fn(); -vi.mock("@/components/networking", () => ({ - userDailyActivityCall: (...args: unknown[]) => mockUserDailyActivityCall(...args), - getToolSpend: vi.fn().mockResolvedValue({ by_tool: [], daily: [], start_date: null, end_date: null }), - getGeneralSettingsCall: vi.fn().mockResolvedValue([]), -})); +vi.mock("@/components/networking", () => { + const emptyToolSpend = { by_tool: [], daily: [], start_date: null, end_date: null }; + return { + userDailyActivityCall: (...args: unknown[]) => mockUserDailyActivityCall(...args), + getToolSpend: vi.fn().mockResolvedValue(emptyToolSpend), + getGeneralSettingsCall: vi.fn().mockResolvedValue([]), + }; +}); vi.mock("@/components/shared/advanced_date_picker", () => ({ __esModule: true, diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/ShadowEvalSection.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/ShadowEvalSection.tsx index 5677173f01f..163acf38989 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/ShadowEvalSection.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/ShadowEvalSection.tsx @@ -265,6 +265,15 @@ const VerdictBar: React.FC<{ results: NonNullable }> = ); }; +const JobFailureBanner: React.FC<{ job: ShadowEvalJob }> = ({ job }) => { + if (job.failed_count === 0 || !job.last_error) return null; + return ( +

+ Last failure: {job.last_error} +

+ ); +}; + const JobResults: React.FC<{ job: ShadowEvalJob; onStop: () => void; @@ -298,11 +307,7 @@ const JobResults: React.FC<{ ) : null} - {job.failed_count > 0 && job.last_error ? ( -

- Last failure: {job.last_error} -

- ) : null} + {results && results.groups.length > 0 ? ( <> @@ -633,6 +638,8 @@ const ShadowEvalSection: React.FC = ({ accessToken }) => const latest = useMemo(() => jobs?.[0] ?? null, [jobs]); const previous = useMemo(() => jobs?.slice(1) ?? [], [jobs]); const { data: latestDetail } = useShadowEvalJob(accessToken, latest?.job_id ?? null); + const latestJobIsFinished = + latestDetail != null && latestDetail.status !== "pending" && latestDetail.status !== "running"; if (error instanceof ApiError && error.status === 403) return null; // admin-only section @@ -657,9 +664,7 @@ const ShadowEvalSection: React.FC = ({ accessToken }) => /> ) : null} - {!latest || (latestDetail && latestDetail.status !== "pending" && latestDetail.status !== "running") ? ( - - ) : null} + {!latest || latestJobIsFinished ? : null} diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index 4f9ac085774..965cc5ee55c 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -886,7 +886,7 @@ export interface paths { put?: never; /** * Stop Shadow Eval Job - * @description Stop an active shadow eval job. Existing verdicts are kept; sampling halts within ~30s. + * @description Stop an active shadow eval job. Existing verdicts are kept; sampling halts within ~10s. */ post: operations["stop_shadow_eval_job_auto_router_shadow_eval__job_id__stop_post"]; delete?: never;