[Infra] Promote interal staging to main - #26298
Conversation
Use wildcard-aware deployment lookup when building order-based fallback levels so requests like openai/gpt-4.1-mini can advance from order=1 to order=2, and add a regression test for wildcard routing. Made-with: Cursor
allow model routing to improve based on conversation signals ensures router is picking best model for task
Remove _experimental/out/ changes from this PR — these are auto-generated Next.js build outputs, not part of the adaptive router feature. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Unrelated timestamp and version drift was showing in the PR diff. This PR adds no new deps — keep uv.lock identical to main. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Router coverage check flagged this method as untested. Adds two cases: - initializes AdaptiveRouter from model_list and is idempotent on re-entry - no-op when no adaptive deployments are configured Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Use LoggingCallbackManager.add_litellm_callback instead of litellm.callbacks.append (required by callback_manager_test) - init_adaptive_router_deployment now uses model_name_to_deployment_indices for O(k) lookup instead of scanning model_list - Rephrase comment in set_model_list to avoid the 'in self.model_list' substring that the linear-scan test greps for - Whitelist _finalize_adaptive_router_if_configured in test_no_linear_scans_in_router — prefix match on 'auto_router/adaptive_router' has no supporting index; runs once at init Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Use 'auto_router/adaptive_router' prefix in example yaml, docs, and README — the old 'adaptive_router/...' and 'openai/gpt-4o-mini' values silently skipped adaptive-router init because detection requires the 'auto_router/adaptive_router' prefix. - Read x-litellm-min-quality-tier from request headers (and the 'min_quality_tier' metadata key as fallback) in async_pre_routing_hook. Previously the documented header was defined but never extracted, so the quality-floor feature was inert. - Evict expired entries from _session_states. The cache grew without bound — added a parallel expiry map (same TTL as _owner_cache) and an opportunistic bulk sweep when the cache crosses a size threshold. - Align adaptive-router migration SQL with Prisma schema: all count columns and the 'clean_credit_awarded' / 'last_processed_turn' fields are NOT NULL in the data model, so the migration now declares them NOT NULL. Fixes test_aaaasschema_migration_check. Tests: 8 new covering header/metadata/precedence/invalid-value paths for min_quality_tier and TTL-based eviction of _session_states. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ires
The post-call hook was hardcoding tool_results=[] on every Turn, so the
failure detector never saw tool errors and the bandit only learned from
satisfaction — never from negative tool outcomes.
Added _recent_tool_results(messages): walks the request messages from the
tail and collects the contiguous run of role=='tool' entries — those are
the results from the most recent assistant tool_calls round. Normalizes
each to {content, is_error}, the only fields signals._detect_failure /
_detect_exhaustion read.
Tests: 6 new covering empty input, trailing-run extraction, is_error
propagation, boundary at first non-tool message, no-trailing-tool case,
and the end-to-end path from hook -> Turn.tool_results.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Six tests in test_hooks.py were written against an older API and had been failing in CI. Updated: - test_resolve_session_key_* (4 tests): _resolve_session_key now requires at least SIGNAL_GATE_MIN_MESSAGES messages before deriving a hash (it returns None on shorter convos to match the signal-processing gate). Switched the tests to use _long_messages() so they hit the hash path. - test_post_call_success_hook_* (2 tests): the hook was migrated from async_post_call_success_hook (mutates response._hidden_params) to async_post_call_response_headers_hook (returns a headers dict) because the former fires too late for streaming responses. Rewrote the tests against the new API; added a metadata-not-dict noop case. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…empty tool output - SessionState now carries clean_credit_awarded + last_processed_turn (matching the DB schema). Satisfaction only fires once per session AND only after MIN_TURNS_FOR_CLEAN_CREDIT turns of context — early "thanks" no longer inflates alpha. - _detect_failure no longer treats empty content as failure. Many tools legitimately return empty output (zero-result searches, silent bash); penalizing those corrupted the bandit posterior. Only is_error fires now. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…redact PII - _owner_cache now opportunistically sweeps expired entries past _OWNER_CACHE_SWEEP_THRESHOLD live entries. Previously sessions that never came back piled up forever. - flush_session_to_db strips session_id/router_name/model_name from the update payload. Prisma rejects writes to @@id fields. - record_turn no longer persists last_user_content / last_assistant_content / tool_call_history / pending_tool_calls. Those are needed only in-memory for the next turn's signal detection; writing user prompts and tool payloads to the DB would store PII for every conversation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds total_spend column to LiteLLM_TeamMembership that accumulates continuously and is not zeroed by the budget cycle reset job. This enables UI surfaces to distinguish current-cycle spend (the existing spend column, which resets) from lifetime spend per team member. Also exposes budget_reset_at on LiteLLM_BudgetTable so /team/info callers can see when a member's budget window next resets. The field was already stored in the DB but stripped by the response Pydantic model. Includes regression tests that: - Guard the reset job against ever writing total_spend: 0 - Verify the spend writer increments both spend and total_spend in one UPDATE statement.
Made-with: Cursor
The uv migration added PRISMA_BINARY_CACHE_DIR=/app/.cache/... and XDG_CACHE_HOME=/app/.cache to the runtime stages of Dockerfile and Dockerfile.database. BINARY_PATHS in the generated prisma client was baked to point into /app/.cache, so any deployment that mounts a volume there (common with securityContext.readOnlyRootFilesystem: true and an emptyDir/tmpfs for a writable cache) wipes the pre-downloaded query engine at pod startup, producing BinaryNotFoundError during connect(). Before the uv migration, prisma-python defaulted to $HOME/.cache = /root/.cache (runtime stage runs as root), which was unaffected by any /app/* volume mounts. Restore that behaviour: drop the env vars from the runtime stage, re-run prisma generate there so the query engine AND the baked BINARY_PATHS both land in /root/.cache, and remove the stale builder-stage /app/.cache (~800 MB). Dockerfile.non_root is intentionally left alone — its /app/.cache location is by design for the hardened offline-install flow.
LiteLLM_BudgetTable is documented as "user-controllable params" and its model_fields.keys() is used as the allowlist for extracting budget fields from incoming API request bodies (management_helpers/utils.py:88, organization_endpoints.py:112/255/537/549, project_endpoints.py:197/245/632, customer_endpoints.py:598). Request models like NewOrganizationRequest inherit from LiteLLM_BudgetTable, so anything on the base class becomes user-settable — a caller could set budget_reset_at far in the future and evade budget cycling. Move budget_reset_at from the base class to LiteLLM_BudgetTableFull so it appears on API responses without becoming writable, and type LiteLLM_TeamMembership.litellm_budget_table as Union[Full, Base] so Pydantic picks Full when the data has server-managed fields (/team/info reads Prisma rows that include budget_reset_at and created_at) and Base when callers construct with only user-settable fields (existing auth tests and caches).
Follow-up on review feedback: the previous commit had the builder download the query engine into /app/.cache, then threw it away in the runtime stage and re-downloaded into /root/.cache. That doubled the build-time network fetch. Remove PRISMA_BINARY_CACHE_DIR and XDG_CACHE_HOME from the builder stage as well, so its prisma generate lands in /root/.cache with the correct path layout on its own. Drop the runtime-stage prisma generate and instead COPY --from=builder /root/.cache /root/.cache. Single download, smaller image.
…itellm_prismaCacheRuntime
- Mark last_updated_at (AdaptiveRouterState) and last_activity_at (AdaptiveRouterSession) with @updatedat so Prisma refreshes the timestamps on every write. Without this the fields stayed frozen at INSERT time and the last_activity_at index was misleading for any future TTL/eviction logic. Applied to all three schema.prisma copies; no migration SQL change needed (Prisma @updatedat is a client-side annotation that doesn't touch DDL). - get_state_snapshot: report cell.total_samples instead of alpha+beta for the 'samples' field. The previous value inflated every cell by the COLD_START_MASS prior (e.g. showed 10.0 before any real traffic arrived), which confused operators reading /adaptive_router/.../state. Updated docs + the snapshot test to match. Also fixes two pre-existing merge-break syntax errors in router.py (missing ')' on the AdaptiveRouter TYPE_CHECKING import; truncated async_pre_routing_hook dispatch call for the adaptive router branch) that were masking the rest of the file from the interpreter. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P1: start the adaptive-router flusher loop unconditionally at proxy boot
instead of gating on 'adaptive_routers is non-empty'. Adaptive routers
added via /config/reload after boot now have their queues drained.
State is lazy-loaded per router on first flush tick (new _state_loaded
flag on AdaptiveRouter) so hot-reloaded routers still get their
persisted priors.
P2: _finalize_adaptive_router_if_configured now prunes stale
AdaptiveRouterPostCallHook callbacks from every litellm callback list
before registering new ones. Without this, every Router replacement
left the old hooks wired up in litellm.callbacks and double-fired
signal recording for every request. Uses
logging_callback_manager.remove_callbacks_by_type (same pattern as the
semantic tool filter).
CI fixes:
- black --check failure: reformatted litellm/router.py
- schema migration diff: aligned @@index with the explicit index name
('idx_adaptive_router_session_activity') from the original migration
by adding 'map:' to all three schema.prisma copies. No new migration
needed.
Tests: 1 new covering the prune-on-hot-reload path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The prevent_key_leaks_in_exceptions CI check forbids '{args}' in
f-strings because it's a common shape for accidental API key leaks
in exception messages. _signature() uses an entirely local variable
named 'args' for tool-call arguments (loop-detection signatures, no
exception path), but the grep is substring-based. Rename to 'call_args'.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Dedupe names in add_guardrail_to_applied_guardrails_header (matches policies). - Inline unified during_call condition so mypy narrows UserAPIKeyAuth. - Extend bedrock guardrails test mock for logging_event_type. Made-with: Cursor
Principle: GHA handles work that doesn't need external API keys; CCI
stays for integration tests that hit real API endpoints.
Four CCI jobs moved to new or extended GHA workflows:
1. check_code_and_doc_quality (was 25 runs: ruff + import-safety +
21 code_coverage_tests + 3 documentation_tests + circular-imports).
- The 21 tests/code_coverage_tests/*.py scripts and the 3
tests/documentation_tests/*.py scripts run in the new
.github/workflows/test-code-quality.yml workflow.
- ruff, import-safety, and circular-imports were already run by
.github/workflows/test-linting.yml — no new migration needed.
- The 3 documentation_tests scripts read
docs/my-website/docs/proxy/config_settings.md. Since docs have
moved to BerriAI/litellm-docs, the GHA workflow checks out that
repo and symlinks docs/my-website -> the checkout so the
existing hardcoded paths resolve without touching the scripts.
The stale local docs/my-website/ copy in this repo will be
removed in a separate PR.
2. semgrep (custom-rule SAST against .semgrep/rules).
- New .github/workflows/test-semgrep.yml.
3. installing_litellm_on_python + installing_litellm_on_python_3_13
(pip install compat checks on Python 3.12 and 3.13).
- New .github/workflows/test-install-litellm.yml as a matrix job.
- 3.12 run also verifies litellm_enterprise import; 3.13 run
skips that check (matches previous CCI behavior).
- installing_litellm_on_python_v2_migration_resolver stays in CCI
because it requires a postgres service.
CCI .circleci/config.yml: -112 lines, 4 jobs and their workflow refs
removed.
Replace the calibration step (one request + 10-minute poll) with an independent ground truth computed from response usage via litellm.cost_per_token. All N requests are made up front, so a single dropped Redis write no longer kills the test. Add /health/readiness checks at test start and on poll timeout so the failure message surfaces proxy state (db, cache) instead of "calibration timed out". Set PROXY_BATCH_WRITE_AT=2 in the spend tracking CI job to shorten the scheduler flush window.
store_in_memory_spend_updates_in_redis drained the in-memory queues into local variables before the rpush pipeline. If rpush raised (cloud Redis hiccup, timeout, connection blip), those already-drained transactions were garbage-collected with the scheduler job, silently losing all spend aggregated during that tick. Wrap the rpush in try/except. On failure, re-enqueue the aggregated transactions into their respective in-memory queues so the next scheduler tick retries. Add a unit test that seeds real queues, simulates an rpush failure, and asserts the transactions land back in-memory.
Replace recursive `_walk` helper with a stack-based traversal so the recursive_detector CI check passes without adding to the ignore list, and avoid Python recursion limits on deeply nested payloads. Made-with: Cursor
The daily queue parameter types on _restore_spend_updates_to_in_memory_queues were narrowed to specific subtypes (DailyUserSpendTransaction, etc), but the caller passes Dict[str, BaseDailySpendTransaction] — the return type of flush_and_get_aggregated_daily_spend_update_transactions. Widen the parameters to the base type. Also replace dynamic TypedDict key lookup (which returned object) with explicit literal-keyed get() calls so mypy can type-narrow each field.
…d_logging_reapply fix(proxy): Bedrock guardrail spend logs - hook mode, match redaction, streaming request_data
[Infra] Migrate more CI jobs from CircleCI to GitHub Actions
[Fix] Stabilize flaky spend accuracy tests + patch Redis buffer data-loss path
fix(router): wildcard order fallback to higher-order deployments
… errors Two changes, both test-only: - Configure the aiohttp session with TCPConnector(force_close=True) and an explicit ClientTimeout(total=30, connect=10). Prevents reuse of idle TCP connections that the proxy/kernel may have closed during the long window between setup POSTs and the later poll loop, and surfaces a blocked proxy event loop quickly instead of hanging on aiohttp's 5-minute default. - In poll_key_spend_until, catch aiohttp.ClientError and asyncio.TimeoutError around the single /key/info call. A transient transport hiccup now logs and retries on the next tick instead of failing the entire polling loop. Addresses the ConnectionTimeoutError observed on the first /key/info call after the 20 chat completions.
Track per-member total spend on team memberships
[Fix] Stabilize spend accuracy test transport flakes
[Infra] bump versions
|
|
|
|
||
| // ---- session --------------------------------------------------------- | ||
| function newSession() { | ||
| STATE.sessionId = "chat-" + Math.random().toString(36).slice(2, 10); |
|
|
||
| // ---- persistence ----------------------------------------------------- | ||
| function ssGet(k) { try { return sessionStorage.getItem(k) || ""; } catch { return ""; } } | ||
| function ssSet(k, v) { try { sessionStorage.setItem(k, v); } catch {} } |
| return d.toTimeString().slice(0, 8); | ||
| } | ||
| function ssGet(k) { try { return sessionStorage.getItem(k) || ""; } catch { return ""; } } | ||
| function ssSet(k, v) { try { sessionStorage.setItem(k, v); } catch {} } |
| renderInfo(); | ||
| renderGateStatus(); | ||
|
|
||
| const thinkingBubble = appendThinking(); |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Greptile SummaryThis PR promotes the internal staging branch to main, landing a substantial set of changes: a new Adaptive Router strategy (Thompson-sampling bandit with per-session signal detection, DB persistence, and an admin introspection endpoint), a The new feature is well-tested with comprehensive mocked unit and e2e tests, and the DB migrations are additive. All remaining findings are P2 quality/correctness suggestions. Confidence Score: 5/5Safe to merge; all findings are P2 quality/hardening suggestions that do not block correctness on the happy path. No P0/P1 issues found. The adaptive router, bandit logic, DB migrations, and guardrail refactoring are well-scoped. The three P2 findings (overly broad redaction in base guardrail class, _state_loaded prematurely set on DB unavailability at startup, unreferenced asyncio task) are edge-case degradations rather than bugs on the primary request path. litellm/integrations/custom_guardrail.py (broad redact scope), litellm/proxy/proxy_server.py (_state_loaded + task reference), litellm/router_strategy/adaptive_router/adaptive_router.py (min_quality_tier ValueError)
|
| Filename | Overview |
|---|---|
| litellm/router_strategy/adaptive_router/adaptive_router.py | New AdaptiveRouter class: Thompson-sampling bandit with owner-cache and session-state management. Well-structured, asyncio-safe (no yields between cell read+write), comprehensive tests. Minor: no graceful fallback when min_quality_tier eliminates all models. |
| litellm/router_strategy/adaptive_router/bandit.py | New Thompson-sampling bandit utilities. pick_best correctly builds costs from the same eligible set as cells. apply_delta respects SAMPLE_CAP. Normalization handles zero-spread edge case. |
| litellm/router_strategy/adaptive_router/hooks.py | New post-call hook. Session key derivation, tool-result extraction, and logical-model lookup from metadata are clean. Exception swallowing prevents signal failures from breaking requests. |
| litellm/router_strategy/adaptive_router/update_queue.py | In-memory aggregator queue for state and session upserts. Lock usage is correct; last-write-wins session semantics properly handle concurrent flushers via atomic upsert. |
| litellm/proxy/proxy_server.py | Adds adaptive-router flusher loop and /adaptive_router/state admin endpoint. Two issues: _state_loaded set True even when prisma_client is None, and asyncio.create_task result not stored. |
| litellm/integrations/custom_guardrail.py | Adds use_native_during_call_hook ClassVar and applies redact_nested_match_and_regex_keys globally to all guardrail log payloads. Overly broad redaction could affect non-Bedrock guardrails. |
| litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py | Refactors _redact_pii_matches to delegate to shared utility, adds logging_event_type parameter to fix pre/during/post_call attribution in spend logs, and sets use_native_during_call_hook=True on BedrockGuardrail. |
| litellm/proxy/db/db_transaction_queue/redis_update_buffer.py | Adds Redis rpush failure recovery by restoring drained transactions back to in-memory queues. Correctly handles both DBSpendUpdateTransactions entities and daily spend transaction pairs. |
| litellm-proxy-extras/litellm_proxy_extras/migrations/20260418000000_add_adaptive_router_tables/migration.sql | New migration adds LiteLLM_AdaptiveRouterState and LiteLLM_AdaptiveRouterSession tables with appropriate indexes. Schema is additive, correctly documented. |
| litellm/router.py | Integrates adaptive router into Router lifecycle: deferred finalization after all deployments are registered, hook cleanup on reload, and pre-routing dispatch. Wildcard-aware deployment lookup fix included. |
Sequence Diagram
sequenceDiagram
participant C as Client
participant PS as ProxyServer
participant R as Router
participant AR as AdaptiveRouter
participant BN as Bandit
participant H as PostCallHook
participant Q as UpdateQueue
participant FL as FlusherLoop
participant DB as Postgres
PS->>PS: startup: load_state_from_db
DB-->>AR: persisted BanditCells
PS->>PS: create_task(flusher_loop)
C->>PS: POST /chat/completions
PS->>R: async_pre_routing_hook(model)
R->>AR: async_pre_routing_hook
AR->>BN: pick_best(cells, costs)
BN-->>AR: chosen_model
AR-->>R: PreRoutingHookResponse(model=chosen)
R-->>PS: route to chosen_model
PS-->>C: response + x-litellm-adaptive-router-model header
note over H: async_log_success_event
H->>H: _resolve_session_key
H->>AR: claim_or_check_owner
AR-->>H: True/False
H->>AR: record_turn(session_id, model, rt, turn)
AR->>AR: apply_turn(session_state)
AR->>Q: add_session_state(snapshot)
AR->>AR: update _cells[cell_key]
AR->>Q: add_state_delta(delta)
loop every 10s
FL->>Q: flush_state_to_db(prisma)
Q->>DB: upsert LiteLLM_AdaptiveRouterState
FL->>Q: flush_session_to_db(prisma)
Q->>DB: upsert LiteLLM_AdaptiveRouterSession
end
Reviews (1): Last reviewed commit: "Merge pull request #26295 from BerriAI/y..." | Re-trigger Greptile
| # Default-safe behavior: never persist raw matched spans in standard | ||
| # guardrail logging payloads (single shared implementation; Bedrock hooks pass | ||
| # raw provider JSON so redaction is not duplicated upstream). | ||
| clean_guardrail_response = redact_nested_match_and_regex_keys( | ||
| clean_guardrail_response |
There was a problem hiding this comment.
Overly broad redaction may affect non-Bedrock guardrail logs
redact_nested_match_and_regex_keys is now applied to clean_guardrail_response in the shared base class path, meaning it silently replaces any key named match or regex (at any nesting depth) across ALL guardrail provider responses — not just Bedrock PII assessments. If a custom guardrail returns a response dict where match or regex is a semantically meaningful, non-PII field (e.g., a regex pattern for an allow-list, a match count from a semantic similarity check), that data is permanently lost from the standard logging payload without warning. The original _redact_pii_matches was intentionally scoped to the specific Bedrock sensitiveInformationPolicy / wordPolicy paths for this reason.
| # may be added later via `/config/reload`, and the flusher is a no-op when | ||
| # `llm_router.adaptive_routers` is empty. Per-router DB state is loaded | ||
| # lazily by the flusher on first tick (see `_state_loaded` flag) so | ||
| # hot-reloaded routers also get their persisted priors. | ||
| if llm_router is not None and getattr(llm_router, "adaptive_routers", None): | ||
| for _ar in llm_router.adaptive_routers.values(): | ||
| await _ar.load_state_from_db(prisma_client) |
There was a problem hiding this comment.
_state_loaded set to True even when prisma_client is None
load_state_from_db silently returns early when prisma_client is None, but _ar._state_loaded = True is unconditionally set immediately after. If the DB connection is not yet established at startup time, the adaptive router starts with cold-start priors and the flusher's lazy-load path will never retry:
if not getattr(ar, "_state_loaded", False): # already True → skipped
await ar.load_state_from_db(prisma_client)Any persisted bandit priors from a previous deployment are permanently ignored. Consider only setting _state_loaded = True when prisma_client is not None, so the flusher's lazy-load path can still hydrate state once the DB becomes available.
| if llm_router is not None and getattr(llm_router, "adaptive_routers", None): | ||
| for _ar in llm_router.adaptive_routers.values(): | ||
| await _ar.load_state_from_db(prisma_client) | ||
| _ar._state_loaded = True |
There was a problem hiding this comment.
Background task reference not retained
asyncio.create_task(...) returns a Task object that should be stored to prevent accidental garbage collection. While CPython's event loop holds a strong reference to all running tasks, the reference should still be stored (PEP guidance + avoids subtle bugs on other implementations and during shutdown):
_adaptive_router_flusher_task = asyncio.create_task(_adaptive_router_flusher_loop())The other background health-check uses asyncio.ensure_future(...) without storing the result as well, but adding a reference here avoids the antipattern for the new code.
|
|
||
| Classifies the last user message, picks a logical model via the bandit, | ||
| and stashes the chosen model on `request_kwargs["metadata"]` so the | ||
| post-call hook can surface it as a response header. | ||
|
|
||
| Routing is stateless per-turn: every call Thompson-samples fresh, | ||
| regardless of any prior pick for the same session. Cross-turn | ||
| attribution is enforced post-call via the owner cache (see |
There was a problem hiding this comment.
pick_model raises unguarded ValueError on misconfigured min_quality_tier
If a caller passes a min_quality_tier value that is higher than every model's tier (e.g., min_quality_tier=3 when all models are tier 1/2), _eligible_models returns [] and pick_model raises:
ValueError: AdaptiveRouter[…]: no models meet min_quality_tier=…
This exception would propagate up through async_pre_routing_hook and surface as a 500 error to the end user. Consider falling back to the highest-tier eligible model (or the full model set) with a warning log rather than raising, so a misconfigured header doesn't take down traffic.
[Infra] Promote interal staging to main
Relevant issues
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
tests/test_litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake test-unit@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewDelays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
CI (LiteLLM team)
Branch creation CI run
Link:
CI run for the last commit
Link:
Merge / cherry-pick CI run
Links:
Screenshots / Proof of Fix
Type
🚄 Infrastructure
Changes