diff --git a/skills/litellm-setup/SKILL.md b/.agents/skills/litellm-setup/SKILL.md
similarity index 100%
rename from skills/litellm-setup/SKILL.md
rename to .agents/skills/litellm-setup/SKILL.md
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
deleted file mode 100644
index 50253186c01..00000000000
--- a/.devcontainer/devcontainer.json
+++ /dev/null
@@ -1,57 +0,0 @@
-{
- "name": "Python 3.11",
- // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
- "image": "mcr.microsoft.com/devcontainers/python:3.11-bookworm",
- // https://github.com/devcontainers/images/tree/main/src/python
- // https://mcr.microsoft.com/en-us/product/devcontainers/python/tags
-
- // "build": {
- // "dockerfile": "Dockerfile",
- // "context": ".."
- // },
-
- // Features to add to the dev container. More info: https://containers.dev/features.
- "features": {
- "ghcr.io/devcontainers/features/node:1": {
- "version": "lts"
- },
- "ghcr.io/devcontainers/features/docker-in-docker:2": {}
- },
-
- // Configure tool-specific properties.
- "customizations": {
- // Configure properties specific to VS Code.
- "vscode": {
- "settings": {},
- "extensions": [
- "ms-python.python",
- "ms-python.vscode-pylance",
- "GitHub.copilot",
- "GitHub.copilot-chat",
- "ms-python.autopep8"
- ]
- }
- },
-
- // Use 'forwardPorts' to make a list of ports inside the container available locally.
- "forwardPorts": [4000],
-
- "containerEnv": {
- "LITELLM_LOG": "DEBUG"
- },
-
- // Use 'portsAttributes' to set default properties for specific forwarded ports.
- // More info: https://containers.dev/implementors/json_reference/#port-attributes
- "portsAttributes": {
- "4000": {
- "label": "LiteLLM Server",
- "onAutoForward": "notify"
- }
- },
-
- // More info: https://aka.ms/dev-containers-non-root.
- // "remoteUser": "litellm",
-
- // Use 'postCreateCommand' to run commands after the container is created.
- "postCreateCommand": "bash ./.devcontainer/post-create.sh"
-}
\ No newline at end of file
diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh
deleted file mode 100644
index 78f857d55d6..00000000000
--- a/.devcontainer/post-create.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/usr/bin/env bash
-set -e
-
-echo "[post-create] Installing uv"
-curl -LsSf https://astral.sh/uv/0.10.9/install.sh | env UV_NO_MODIFY_PATH=1 sh
-export PATH="$HOME/.local/bin:$PATH"
-
-echo "[post-create] Installing Python dependencies (uv)"
-uv sync --frozen --group proxy-dev --extra proxy
-
-echo "[post-create] Generating Prisma client"
-uv run --no-sync prisma generate
-
-echo "[post-create] Installing npm dependencies"
-cd ui/litellm-dashboard && npm ci
-
-echo "[post-create] Done"
diff --git a/.env.example b/.env.example
deleted file mode 100644
index dc1fd5a6ccb..00000000000
--- a/.env.example
+++ /dev/null
@@ -1,32 +0,0 @@
-# OpenAI
-OPENAI_API_KEY = ""
-OPENAI_BASE_URL = ""
-# Cohere
-COHERE_API_KEY = ""
-# OpenRouter
-OR_SITE_URL = ""
-OR_APP_NAME = "LiteLLM Example app"
-OR_API_KEY = ""
-# Azure API base URL
-AZURE_API_BASE = ""
-# Azure API version
-AZURE_API_VERSION = ""
-# Azure API key
-AZURE_API_KEY = ""
-# Replicate
-REPLICATE_API_KEY = ""
-REPLICATE_API_TOKEN = ""
-# Anthropic
-ANTHROPIC_API_KEY = ""
-# Infisical
-INFISICAL_TOKEN = ""
-# Novita AI
-NOVITA_API_KEY = ""
-# INFINITY
-INFINITY_API_KEY = ""
-
-# Development Configs
-# Generate one with: echo "LITELLM_MASTER_KEY=sk-$(openssl rand -hex 32)"
-LITELLM_MASTER_KEY = ""
-DATABASE_URL = "postgresql://llmproxy:dbpassword9090@db:5432/litellm"
-STORE_MODEL_IN_DB = "True"
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
deleted file mode 100644
index a0943cff53d..00000000000
--- a/.git-blame-ignore-revs
+++ /dev/null
@@ -1,40 +0,0 @@
-# Add the commit hash of any commit you want to ignore in `git blame` here.
-# One commit hash per line.
-#
-# The GitHub Blame UI will use this file automatically!
-#
-# Run this command to always ignore formatting commits in `git blame`
-# git config blame.ignoreRevsFile .git-blame-ignore-revs
-
-# Update pydantic code to fix warnings (GH-3600)
-876840e9957bc7e9f7d6a2b58c4d7c53dad16481
-
-# style(ui): run prettier --write across the dashboard (#29622)
-7edf3a9cb55548b143df1692f4ed7c4681d7fcf7
-
-# style: reformat litellm/ with ruff format (#31317)
-17bfd415aeb5a57fb646b5cc67da1c730aa7c50b
-
-# style: unify ruff format width on 120 (#31518)
-48b5a5a0cc5a694a11219416ee0b6eb6e620e74e
-
-# refactor(imports): move collections.abc names out of typing (#35495)
-397e8e4918777e4e60a7f5e88699e0a9a7dabb3d
-
-# refactor(lint): apply every safe ruff autofix and zero 28 strict-rule budgets (#35495)
-b604e2b20c6db2099085a2f0e59b7e99e87eed6f
-
-# refactor(logging): drop redundant !s conversion flags from f-strings (#35546)
-7b2d3440cba3160277470f7a0180098ae9b87864
-
-# perf: build log messages lazily so filtered-out log records cost nothing (#35703)
-c9887a1f94bc1e7e4bdfe64d640f0509a0bc19dd
-
-# feat(lint): enforce Final on locals and freeze function parameters (#35807)
-2708620d6a599cc73c1950a942d26ac26a7ed3d4
-
-# chore(lint): remove litellm/types from the ruff lint exclusion (#35926)
-4e32a8bf6a1e1af1e04b67c759841ccef44b2235
-
-# chore(lint): strip inert type: ignore comments and zero LIT009/LIT010/LIT011 headroom (#35928)
-338e411103ad5d7003e97f34f04fa36bca542dbe
diff --git a/.gitattributes b/.gitattributes
deleted file mode 100644
index 5c9061f52ac..00000000000
--- a/.gitattributes
+++ /dev/null
@@ -1,2 +0,0 @@
-*.ipynb linguist-vendored
-ui/litellm-dashboard/src/lib/http/schema.d.ts linguist-generated
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md
similarity index 90%
rename from CONTRIBUTING.md
rename to .github/CONTRIBUTING.md
index 426cf986d4c..6297cd9a7c0 100644
--- a/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -276,15 +276,15 @@ When the provider reports its own usage or spend numbers, compare the proxy's nu
Provider work (a new provider, a new parameter, a new usage or cost field) is the most common kind of contribution, and it is where PRs get reworked most often. Follow these rules
-**Preserve zero prices.** In [`model_prices_and_context_window.json`](model_prices_and_context_window.json), an explicit `0.0` means that the priced operation is free. A missing field or `null` means no rate is specified, so the relevant calculator may use a fallback. For example, when no batch input rate is configured, batch cache writes use `cache_creation_input_token_cost` if set, otherwise the regular input rate, then apply the batch discount. A zero ordinary input rate does not make separately priced cache reads or writes free. Test `rate is None` before substituting a fallback, since `row.get("cache_creation_input_token_cost") or fallback` would discard a valid zero. Speech keeps character pricing ahead of token pricing even when the character rate is zero. If neither rate is configured, speech cost calculation raises an error. Its model lookup preserves unset rates instead of treating compatibility defaults as explicit zero prices. Perplexity keeps a configured legacy search rate ahead of the current search rate, including zero. When registering custom prices, `None` leaves an existing rate unchanged and `0.0` overrides it
+**Preserve zero prices.** In [`model_prices_and_context_window.json`](../model_prices_and_context_window.json), an explicit `0.0` means that the priced operation is free. A missing field or `null` means no rate is specified, so the relevant calculator may use a fallback. For example, when no batch input rate is configured, batch cache writes use `cache_creation_input_token_cost` if set, otherwise the regular input rate, then apply the batch discount. A zero ordinary input rate does not make separately priced cache reads or writes free. Test `rate is None` before substituting a fallback, since `row.get("cache_creation_input_token_cost") or fallback` would discard a valid zero. Speech keeps character pricing ahead of token pricing even when the character rate is zero. If neither rate is configured, speech cost calculation raises an error. Its model lookup preserves unset rates instead of treating compatibility defaults as explicit zero prices. Perplexity keeps a configured legacy search rate ahead of the current search rate, including zero. When registering custom prices, `None` leaves an existing rate unchanged and `0.0` overrides it
-**Find the existing machinery before you add plumbing.** LiteLLM already has generic paths that move data between the endpoints, the provider transformations, and cost tracking. A second, hand-rolled path for the same data is a common reason a provider PR gets rewritten before merge. [`ARCHITECTURE.md`](ARCHITECTURE.md) shows where the layers live, and the decision records in [`docs/adrs/`](docs/adrs/AGENTS.md) explain why they are shaped that way. For example, [the ADR on provider usage fields](docs/adrs/adr_001_provider_usage_fields_ride_as_usage_extras.md) covers how provider-specific usage fields reach cost tracking. If you cannot find a mechanism for what you need, say so in the PR or ask in [#pr-review on Slack](https://join.slack.com/t/litellmossslack/shared_invite/zt-3o7nkuyfr-p_kbNJj8taRfXGgQI1~YyA) before you build one
+**Find the existing machinery before you add plumbing.** LiteLLM already has generic paths that move data between the endpoints, the provider transformations, and cost tracking. A second, hand-rolled path for the same data is a common reason a provider PR gets rewritten before merge. [`ARCHITECTURE.md`](../docs/ARCHITECTURE.md) shows where the layers live, and the decision records in [`docs/adrs/`](../docs/adrs/AGENTS.md) explain why they are shaped that way. For example, [the ADR on provider usage fields](../docs/adrs/adr_001_provider_usage_fields_ride_as_usage_extras.md) covers how provider-specific usage fields reach cost tracking. If you cannot find a mechanism for what you need, say so in the PR or ask in [#pr-review on Slack](https://join.slack.com/t/litellmossslack/shared_invite/zt-3o7nkuyfr-p_kbNJj8taRfXGgQI1~YyA) before you build one
**Bill a new provider usage field from the usage object.** Leave the field on the usage object as an extra and read it in that provider's `cost_calculator.py`. A provider without one gets a new `cost_calculator.py` and an arm for it in `cost_per_token` (`litellm/cost_calculator.py`), which picks the calculator by provider. If the provider's transform builds `Usage` from named fields, as the Anthropic, Gemini, and Bedrock Converse ones do, pass the new field there too. Streamed requests rebuild usage in `ChunkProcessor.calculate_usage` (`litellm/litellm_core_utils/streaming_chunk_builder_utils.py`), which does not copy extras yet the way the Responses-to-chat bridge does (minus a short exclusion list), so until it does, bill the field on streams by naming it there, as Anthropic's `inference_geo` is. A provider that reports its own charge for the request, as OpenRouter does in `usage.cost`, is billed at that charge from `_hidden_params` before any calculator runs, unless the deployment sets its own prices. When the field counts a built-in tool such as web search, add the provider to `get_cost_for_web_search_request` in `litellm/llms/__init__.py`, and add a gate to `litellm/litellm_core_utils/llm_cost_calc/tool_call_cost_tracking.py` only when no existing gate fires. Then show the field's cost in your proof on every endpoint it reaches, streaming included
**Do not change what a caller sees to make internals easier.** Each endpoint keeps the schema of the API it emulates. A `/v1/responses` caller gets `input_tokens` and a `/v1/chat/completions` caller gets `prompt_tokens`, whatever the provider sent and whatever cost tracking needs. A provider usage field that shows up as an extra key is fine, since it only adds a key, but renaming, removing, or reshaping a key the endpoint defines is not. Reshaping a public response to feed an internal consumer breaks every user of that endpoint
-If your change makes an architecture decision, such as a new cross-cutting mechanism or a deliberate difference from a provider's API shape, add an ADR to [`docs/adrs/`](docs/adrs/AGENTS.md) in the same PR
+If your change makes an architecture decision, such as a new cross-cutting mechanism or a deliberate difference from a provider's API shape, add an ADR to [`docs/adrs/`](../docs/adrs/AGENTS.md) in the same PR
## Building and Running Locally
diff --git a/SECURITY.md b/.github/SECURITY.md
similarity index 100%
rename from SECURITY.md
rename to .github/SECURITY.md
diff --git a/.github/codecov.yml b/.github/codecov.yml
new file mode 100644
index 00000000000..6a70653be13
--- /dev/null
+++ b/.github/codecov.yml
@@ -0,0 +1,72 @@
+codecov:
+ require_ci_to_pass: false # post coverage status even if CI has unrelated failures
+ notify:
+ wait_for_ci: false # post as soon as expected uploads arrive, don't wait on CI
+
+ignore:
+ - "litellm-rust/**"
+
+# Flagged uploads avoid overlapping sessions on reruns.
+# Only the flags below reuse parent coverage, avoiding stale line maps elsewhere.
+flag_management:
+ default_rules:
+ carryforward: false
+ individual_flags:
+ - name: unit
+ carryforward: true
+ - name: core-packaging
+ carryforward: true
+ - name: mcp-integration
+ carryforward: true
+
+component_management:
+ individual_components:
+ - component_id: "Router"
+ paths:
+ - "router"
+ - component_id: "LLMs"
+ paths:
+ - "*/llms/*"
+ - component_id: "Caching"
+ paths:
+ - "*/caching/*"
+ - ".*redis.*"
+ - component_id: "litellm_logging"
+ paths:
+ - "*/integrations/*"
+ - ".*litellm_logging.*"
+ - component_id: "Proxy_Authentication"
+ paths:
+ - "*/proxy/auth/**"
+ - component_id: "Enterprise"
+ paths:
+ - "enterprise/**"
+ - component_id: "Batches"
+ paths:
+ - "*/proxy/batches_endpoints/**"
+ - "litellm/batches/**"
+ - "*/llms/*/batches/**"
+ - component_id: "Videos"
+ paths:
+ - "litellm/videos/**"
+ - "*/proxy/video_endpoints/**"
+ - "*/llms/*/videos/**"
+ - component_id: "Realtime"
+ paths:
+ - "litellm/realtime_api/**"
+ - "*/proxy/realtime_endpoints/**"
+ - "*/llms/*/realtime/**"
+ - "litellm/litellm_core_utils/realtime_streaming.py"
+comment:
+ layout: "header, diff, flags, components" # show component info in the PR comment
+
+coverage:
+ status:
+ project:
+ default:
+ target: auto
+ threshold: 0% # do not allow project coverage to drop
+ patch:
+ default:
+ target: auto
+ threshold: 0% # patch coverage should be 100%
diff --git a/.grype.yaml b/.github/grype.yaml
similarity index 100%
rename from .grype.yaml
rename to .github/grype.yaml
diff --git a/ruff-strict.toml b/.github/linters/ruff-strict.toml
similarity index 97%
rename from ruff-strict.toml
rename to .github/linters/ruff-strict.toml
index e9db4d95cc8..406306ae809 100644
--- a/ruff-strict.toml
+++ b/.github/linters/ruff-strict.toml
@@ -1,4 +1,4 @@
-extend = "ruff.toml"
+extend = "../../pyproject.toml"
[lint]
preview = true
@@ -6,7 +6,7 @@ select = ["ANN", "ASYNC230", "B004", "B006", "B008", "B009", "B010", "B018", "B0
extend-select = []
# Overrides the inherited list: rules this gate enforces itself must NOT be external here,
# so this config's RUF100 flags their stale `# noqa` directives. What remains external is
-# only what other tooling enforces: every base ruff.toml rule this select list doesn't
+# only what other tooling enforces: every base pyproject.toml rule this select list doesn't
# re-enable (all of the default E/F families plus T20/PGH004/RUF008/RUF009, minus the
# strict-selected F401 and RUF100; F4 is split out so stale F401 noqas stay detectable),
# plus upstream litellm's ruff config.
diff --git a/ruff-tests.toml b/.github/linters/ruff-tests.toml
similarity index 98%
rename from ruff-tests.toml
rename to .github/linters/ruff-tests.toml
index d75f10b9605..a3bf8744700 100644
--- a/ruff-tests.toml
+++ b/.github/linters/ruff-tests.toml
@@ -1,4 +1,4 @@
-# Lint config for the test tree, which ruff.toml excludes from `ruff check`.
+# Lint config for the test tree, which pyproject.toml excludes from `ruff check`.
#
# Every rule here catches a test that cannot fail. Rules land one at a time, each
# with its existing violations already fixed, so this list never needs a budget
diff --git a/osv-scanner.toml b/.github/osv-scanner.toml
similarity index 100%
rename from osv-scanner.toml
rename to .github/osv-scanner.toml
diff --git a/.semgrep/rules/README.md b/.github/semgrep/rules/README.md
similarity index 80%
rename from .semgrep/rules/README.md
rename to .github/semgrep/rules/README.md
index 0dbb77cdd48..315976f1eac 100644
--- a/.semgrep/rules/README.md
+++ b/.github/semgrep/rules/README.md
@@ -5,13 +5,13 @@ Add custom rule YAML files here. Semgrep loads all `.yml`/`.yaml` files under th
**Run only custom rules (CI / fail on findings):**
```bash
-semgrep scan --config .semgrep/rules . --error
+semgrep scan --config .github/semgrep/rules . --error
```
**Run with registry + custom rules:**
```bash
-semgrep scan --config auto --config .semgrep/rules .
+semgrep scan --config auto --config .github/semgrep/rules .
```
**Layout:**
diff --git a/.semgrep/rules/python/reliability/unbounded-memory.yml b/.github/semgrep/rules/python/reliability/unbounded-memory.yml
similarity index 100%
rename from .semgrep/rules/python/reliability/unbounded-memory.yml
rename to .github/semgrep/rules/python/reliability/unbounded-memory.yml
diff --git a/.semgrep/rules/python/unbounded-memory.yml b/.github/semgrep/rules/python/unbounded-memory.yml
similarity index 100%
rename from .semgrep/rules/python/unbounded-memory.yml
rename to .github/semgrep/rules/python/unbounded-memory.yml
diff --git a/.semgrep/rules/security/no-claude-directory.yml b/.github/semgrep/rules/security/no-claude-directory.yml
similarity index 100%
rename from .semgrep/rules/security/no-claude-directory.yml
rename to .github/semgrep/rules/security/no-claude-directory.yml
diff --git a/.github/workflows/conventional-commits.yml b/.github/workflows/conventional-commits.yml
index 4ae59ed581f..f8d416ca487 100644
--- a/.github/workflows/conventional-commits.yml
+++ b/.github/workflows/conventional-commits.yml
@@ -3,7 +3,7 @@ name: Conventional PR Title
# Squash-merge replaces the merge commit subject with the PR title, so
# enforcing Conventional Commits at the PR-title level is what actually gates
# the commits that land on the default branch. The local commit-msg hook
-# (.githooks/commit-msg) is a best-effort assist; this workflow is the gate.
+# (scripts/githooks/commit-msg) is a best-effort assist; this workflow is the gate.
#
# See https://www.conventionalcommits.org/en/v1.0.0/
@@ -28,7 +28,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
- # Must mirror the type list in .githooks/commit-msg.
+ # Must mirror the type list in scripts/githooks/commit-msg.
types: |
feat
fix
diff --git a/.github/workflows/cost-map-guard.yml b/.github/workflows/cost-map-guard.yml
index f26c649aad2..cfa0fc0fa87 100644
--- a/.github/workflows/cost-map-guard.yml
+++ b/.github/workflows/cost-map-guard.yml
@@ -49,7 +49,12 @@ jobs:
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
run: |
- uv run --no-project python ci_cd/cost_map_guard.py --base "$MERGE_BASE" --head "$HEAD_SHA" --head-ref "$HEAD_REF"
+ guard=scripts/ci_cd/cost_map_guard.py
+ # TODO: Drop the legacy path after this lands on main.
+ if [ ! -f "$guard" ]; then
+ guard=ci_cd/cost_map_guard.py
+ fi
+ uv run --no-project python "$guard" --base "$MERGE_BASE" --head "$HEAD_SHA" --head-ref "$HEAD_REF"
- name: Check whether the pull request changes the cost map
id: changed
env:
diff --git a/.github/workflows/image-scan.yml b/.github/workflows/image-scan.yml
index 54051104cb9..862e58aedf7 100644
--- a/.github/workflows/image-scan.yml
+++ b/.github/workflows/image-scan.yml
@@ -29,7 +29,7 @@ on:
- ui/litellm-dashboard/package-lock.json
- ui/nginx.conf
- .github/workflows/image-scan.yml
- - .grype.yaml
+ - .github/grype.yaml
schedule:
- cron: "41 6 * * *"
workflow_dispatch:
@@ -201,7 +201,7 @@ jobs:
GRYPE_MATCH_PYTHON_USING_CPES: "true"
run: |
"$RUNNER_TEMP/grype" "$IMAGE" \
- --config .grype.yaml \
+ --config .github/grype.yaml \
--only-fixed \
--fail-on high \
--output table
diff --git a/.github/workflows/osv-scan.yml b/.github/workflows/osv-scan.yml
index 5016957bd6f..90c0712b2ff 100644
--- a/.github/workflows/osv-scan.yml
+++ b/.github/workflows/osv-scan.yml
@@ -40,7 +40,7 @@ jobs:
- name: Scan lockfiles
run: |
"$RUNNER_TEMP/osv-scanner" scan source \
- --config osv-scanner.toml \
+ --config .github/osv-scanner.toml \
-L uv.lock \
-L ui/litellm-dashboard/package-lock.json \
-L vscode-extension/package-lock.json
diff --git a/.github/workflows/spend-logs-guard.yml b/.github/workflows/spend-logs-guard.yml
index bdba95f6a5d..26523450013 100644
--- a/.github/workflows/spend-logs-guard.yml
+++ b/.github/workflows/spend-logs-guard.yml
@@ -42,4 +42,9 @@ jobs:
MERGE_BASE: ${{ steps.revisions.outputs.merge_base }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
- uv run --no-project --with pydantic==2.14.1 python ci_cd/spend_logs_guard.py --base "$MERGE_BASE" --head "$HEAD_SHA" --approver kerry-berri --reviews "$RUNNER_TEMP/reviews.json"
+ guard=scripts/ci_cd/spend_logs_guard.py
+ # TODO: Drop the legacy path after this lands on main.
+ if [ ! -f "$guard" ]; then
+ guard=ci_cd/spend_logs_guard.py
+ fi
+ uv run --no-project --with pydantic==2.14.1 python "$guard" --base "$MERGE_BASE" --head "$HEAD_SHA" --approver kerry-berri --reviews "$RUNNER_TEMP/reviews.json"
diff --git a/.github/workflows/test-linting.yml b/.github/workflows/test-linting.yml
index fd1d2973951..7544aad8f60 100644
--- a/.github/workflows/test-linting.yml
+++ b/.github/workflows/test-linting.yml
@@ -145,7 +145,7 @@ jobs:
- name: Run Ruff linting (test tree)
if: steps.changes.outputs.decision != 'skip'
run: |
- uv run --no-sync ruff check --config ruff-tests.toml tests
+ uv run --no-sync ruff check --config .github/linters/ruff-tests.toml tests
- name: Check strict ruff rules (delta vs merge-base counts)
if: steps.changes.outputs.decision != 'skip'
@@ -191,7 +191,7 @@ jobs:
- name: Check tests/e2e basedpyright (zero errors)
if: steps.changes.outputs.decision != 'skip'
run: |
- if git diff --name-only --diff-filter=ACMRD "$GATE_BASE_SHA" HEAD -- ':(glob)tests/e2e/**/*.py' ':(glob)tests/e2e_harness/**/*.py' pyrightconfig.json | grep -q .; then
+ if git diff --name-only --diff-filter=ACMRD "$GATE_BASE_SHA" HEAD -- ':(glob)tests/e2e/**/*.py' ':(glob)tests/e2e_harness/**/*.py' pyproject.toml | grep -q .; then
uv run --no-sync basedpyright tests/e2e tests/e2e_harness
else
echo "No changed tests/e2e Python files; skipping."
@@ -703,7 +703,7 @@ jobs:
version: "0.10.9"
- name: Run Semgrep (custom rules)
- run: uv tool run --from 'semgrep==1.157.0' semgrep scan --config .semgrep/rules . --error
+ run: uv tool run --from 'semgrep==1.157.0' semgrep scan --config .github/semgrep/rules . --error
lint-passed:
name: lint passed
needs: [python, secret-scan, ui, ui-api-types, code-quality, ci-coverage, semgrep]
diff --git a/.npmrc b/.npmrc
deleted file mode 100644
index 7999681cc35..00000000000
--- a/.npmrc
+++ /dev/null
@@ -1,5 +0,0 @@
-# Supply-chain hardening
-# Packages needing lifecycle scripts: npm rebuild
Open Source AI Gateway for 100+ LLMs. Self-hosted. Enterprise-ready. Call any LLM in OpenAI format.
-
+
@@ -725,7 +725,7 @@ make test-unit # Run unit tests
make format-check # Check formatting only
```
-For detailed contributing guidelines, see [CONTRIBUTING.md](CONTRIBUTING.md).
+For detailed contributing guidelines, see [CONTRIBUTING.md](.github/CONTRIBUTING.md).
> **📖 Contributing to documentation?** The LiteLLM docs have moved to a separate repository: [BerriAI/litellm-docs](https://github.com/BerriAI/litellm-docs). Please open doc PRs there. Docs are served at [docs.litellm.ai](https://docs.litellm.ai).
diff --git a/codecov.yaml b/codecov.yaml
deleted file mode 100644
index 2fa8ea04f0e..00000000000
--- a/codecov.yaml
+++ /dev/null
@@ -1,164 +0,0 @@
-codecov:
- require_ci_to_pass: false # post coverage status even if CI has unrelated failures
- notify:
- wait_for_ci: false # post as soon as expected uploads arrive, don't wait on CI
-
-ignore:
- - "litellm-rust/**"
-
-# Uploads are flagged per workflow tier. carryforward makes
-# a re-upload of a flag replace its prior session instead of accumulating a
-# conflicting one, and lets a commit reuse a flag from its parent when that flag
-# was not re-uploaded. Required because the same commit can receive the
-# push-triggered workflows more than once (re-runs / branches cut at the same
-# SHA); flagless overlapping sessions made Codecov drop the largest files.
-flag_management:
- default_rules:
- carryforward: true
- # Dead flags no CI job uploads anymore, and flags whose job is path-gated on
- # PRs: their carried-forward sessions were measured against old revisions, and
- # the stale line maps mark comment lines of since-edited files as missed,
- # sinking patch coverage on unrelated PRs.
- individual_flags:
- - name: proxy-mgmt-behavior
- carryforward: false
- - name: security
- carryforward: false
- - name: proxy-db-schema-migration
- carryforward: false
- - name: circleci
- carryforward: false
- - name: core-utils
- carryforward: false
- - name: enterprise-routing
- carryforward: false
- - name: integrations
- carryforward: false
- - name: llm-vertex-ai
- carryforward: false
- - name: llm-other-providers
- carryforward: false
- - name: llm-openai-meta
- carryforward: false
- - name: misc
- carryforward: false
- - name: misc-dirs
- carryforward: false
- - name: proxy-auth
- carryforward: false
- - name: proxy-hooks-client
- carryforward: false
- - name: proxy-endpoints
- carryforward: false
- - name: proxy-feature-endpoints
- carryforward: false
- - name: proxy-server
- carryforward: false
- - name: mcp-elicitation
- carryforward: false
- - name: proxy-infra
- carryforward: false
- - name: proxy-infra-root
- carryforward: false
- - name: proxy-infra-root-a-p
- carryforward: false
- - name: proxy-infra-root-q-z
- carryforward: false
- - name: proxy-subprocess
- carryforward: false
- - name: caching-local
- carryforward: false
- - name: proxy-extras
- carryforward: false
- - name: enterprise-package
- carryforward: false
- - name: enterprise-managed-files
- carryforward: false
- - name: responses-caching-types
- carryforward: false
- - name: lens-python-310
- carryforward: false
- - name: lens-postgres
- carryforward: false
- - name: proxy-db-key-generation
- carryforward: false
- - name: proxy-db-auth-checks
- carryforward: false
- - name: proxy-db-jwt-and-keys
- carryforward: false
- - name: proxy-db-proxy-utils
- carryforward: false
- - name: proxy-db-proxy-server-core
- carryforward: false
- - name: proxy-db-proxy-runtime
- carryforward: false
- - name: proxy-db-mcp-oauth
- carryforward: false
- - name: proxy-db-custom-logging
- carryforward: false
- - name: proxy-db-logging-misc
- carryforward: false
- - name: proxy-db-db-and-spend
- carryforward: false
- - name: proxy-db-guardrails-hooks
- carryforward: false
- - name: proxy-db-budgets
- carryforward: false
- - name: proxy-db-endpoints-and-responses
- carryforward: false
- - name: roi-postgres
- carryforward: false
- - name: redis-compat
- carryforward: false
-
-component_management:
- individual_components:
- - component_id: "Router"
- paths:
- - "router"
- - component_id: "LLMs"
- paths:
- - "*/llms/*"
- - component_id: "Caching"
- paths:
- - "*/caching/*"
- - ".*redis.*"
- - component_id: "litellm_logging"
- paths:
- - "*/integrations/*"
- - ".*litellm_logging.*"
- - component_id: "Proxy_Authentication"
- paths:
- - "*/proxy/auth/**"
- - component_id: "Enterprise"
- paths:
- - "enterprise/**"
- - component_id: "Batches"
- paths:
- - "*/proxy/batches_endpoints/**"
- - "litellm/batches/**"
- - "*/llms/*/batches/**"
- - component_id: "Videos"
- paths:
- - "litellm/videos/**"
- - "*/proxy/video_endpoints/**"
- - "*/llms/*/videos/**"
- - component_id: "Realtime"
- paths:
- - "litellm/realtime_api/**"
- - "*/proxy/realtime_endpoints/**"
- - "*/llms/*/realtime/**"
- - "litellm/litellm_core_utils/realtime_streaming.py"
-comment:
- layout: "header, diff, flags, components" # show component info in the PR comment
-
-coverage:
- status:
- project:
- default:
- target: auto
- threshold: 0% # do not allow project coverage to drop
- patch:
- default:
- target: auto
- threshold: 0% # patch coverage should be 100%
diff --git a/ARCHITECTURE.md b/docs/ARCHITECTURE.md
similarity index 99%
rename from ARCHITECTURE.md
rename to docs/ARCHITECTURE.md
index f457720c58d..0e4f402d67a 100644
--- a/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -2,7 +2,7 @@
This document helps contributors understand where to make changes in LiteLLM.
-For why a layer is shaped the way it is, read the architecture decision records in [`docs/adrs/`](docs/adrs/AGENTS.md) (Python SDK and proxy) and [`litellm-rust/docs/adrs/`](litellm-rust/docs/adrs/AGENTS.md) (Rust core)
+For why a layer is shaped the way it is, read the architecture decision records in [`docs/adrs/`](adrs/AGENTS.md) (Python SDK and proxy) and [`litellm-rust/docs/adrs/`](../litellm-rust/docs/adrs/AGENTS.md) (Rust core)
---
diff --git a/litellm-proxy-extras/litellm_proxy_extras/request_log_indexes.py b/litellm-proxy-extras/litellm_proxy_extras/request_log_indexes.py
index 91eddf46782..ad74d67a2ff 100644
--- a/litellm-proxy-extras/litellm_proxy_extras/request_log_indexes.py
+++ b/litellm-proxy-extras/litellm_proxy_extras/request_log_indexes.py
@@ -4,7 +4,7 @@ background, once it serves).
A migration cannot build them: a plain `CREATE INDEX` blocks spend-log inserts for the
whole build, and `CREATE INDEX CONCURRENTLY` is refused on a partitioned parent
-(db_scripts/partition_spend_logs.sql). `REQUEST_LOG_INDEXES` is the one list to extend;
+(scripts/db/partition_spend_logs.sql). `REQUEST_LOG_INDEXES` is the one list to extend;
names match what Prisma derives from the `@@index` declarations in schema.prisma, so an
index a database already has is recognized and never rebuilt.
"""
diff --git a/litellm-proxy-extras/litellm_proxy_extras/utils.py b/litellm-proxy-extras/litellm_proxy_extras/utils.py
index 8b19576d86e..8d316e42ca5 100644
--- a/litellm-proxy-extras/litellm_proxy_extras/utils.py
+++ b/litellm-proxy-extras/litellm_proxy_extras/utils.py
@@ -147,7 +147,7 @@ _SPEND_LOGS_PK_CLAUSE_RE = re.compile(
)
PARTITIONED_SPEND_LOGS_PUSH_ERROR = (
- "LiteLLM_SpendLogs is a partitioned table (see db_scripts/partition_spend_logs.sql), "
+ "LiteLLM_SpendLogs is a partitioned table (see scripts/db/partition_spend_logs.sql), "
'so its primary key must include the partition key ("startTime"). `prisma db push` '
"reconciles the database against schema.prisma, which declares the unpartitioned "
'primary key ("request_id"), and Postgres rejects that rewrite with: unique '
@@ -179,7 +179,7 @@ def _without_spend_logs_pk_clauses(statement: str) -> str | None:
def filter_partitioned_spend_logs_diff(diff_sql: str) -> str:
"""Drop statements from a `prisma migrate diff` script that fight the
- SpendLogs partitioning runbook (db_scripts/partition_spend_logs.sql): the
+ SpendLogs partitioning runbook (scripts/db/partition_spend_logs.sql): the
primary-key rewrite on "LiteLLM_SpendLogs", which Postgres rejects on a
partitioned table, and drops of runbook artifacts such as
"LiteLLM_SpendLogs_legacy"."""
@@ -679,7 +679,7 @@ class ProxyExtrasDBManager:
"""True when the connected database's LiteLLM_SpendLogs is a
partitioned table in Prisma's target schema (the `schema` URL param,
falling back to Prisma's default target, public), i.e. the operator
- ran db_scripts/partition_spend_logs.sql. Returns False when psycopg is
+ ran scripts/db/partition_spend_logs.sql. Returns False when psycopg is
unavailable or the database cannot be reached, preserving the
pre-existing behavior in those cases."""
database_url = os.getenv("DATABASE_URL")
@@ -692,7 +692,7 @@ class ProxyExtrasDBManager:
logger.warning(
"psycopg is not installed; skipping the LiteLLM_SpendLogs "
"partition check. If this table is partitioned (see "
- "db_scripts/partition_spend_logs.sql), schema reconciliation "
+ "scripts/db/partition_spend_logs.sql), schema reconciliation "
"will try to rewrite its primary key and fail. Install the "
"litellm[extra_proxy] extra, which now includes psycopg."
)
diff --git a/litellm-proxy-extras/migration_runbook.md b/litellm-proxy-extras/migration_runbook.md
index b1e9236e520..e571422be8b 100644
--- a/litellm-proxy-extras/migration_runbook.md
+++ b/litellm-proxy-extras/migration_runbook.md
@@ -43,7 +43,7 @@ brew install postgresql@14 # macOS
export PATH="/opt/homebrew/opt/postgresql@14/bin:$PATH"
# Run migration
-uv run --with testing.postgresql python ci_cd/run_migration.py "your_migration_name"
+uv run --with testing.postgresql python scripts/ci_cd/run_migration.py "your_migration_name"
```
## What It Does
@@ -89,7 +89,7 @@ When the guard fires:
4. Only if the drops are genuinely intentional, re-run with the flag:
```bash
- uv run --with testing.postgresql python ci_cd/run_migration.py "your_migration_name" --allow-destructive
+ uv run --with testing.postgresql python scripts/ci_cd/run_migration.py "your_migration_name" --allow-destructive
```
> **AI AGENTS / ASSISTANTS:** Do **not** automatically re-run the command with `--allow-destructive`. If the guard fires while you are driving the runbook for a human, stop, show them the error, and wait for their explicit confirmation before passing the flag. Auto-passing `--allow-destructive` is the exact failure mode this guard exists to prevent.
@@ -98,7 +98,7 @@ When the guard fires:
**Missing testing module:**
```bash
-uv run --with testing.postgresql python ci_cd/run_migration.py "your_migration_name"
+uv run --with testing.postgresql python scripts/ci_cd/run_migration.py "your_migration_name"
```
**initdb not found:**
diff --git a/render.yaml b/litellm/deploy/render/render.yaml
similarity index 100%
rename from render.yaml
rename to litellm/deploy/render/render.yaml
diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py
index c29c5e9e01a..b7f4d7a70b9 100644
--- a/litellm/proxy/_types.py
+++ b/litellm/proxy/_types.py
@@ -3034,7 +3034,7 @@ class ConfigGeneralSettings(LiteLLMPydanticObjectBase):
)
use_spend_logs_partitioning: bool | None = Field(
None,
- description="If True and LiteLLM_SpendLogs has been converted to a range-partitioned table (db_scripts/partition_spend_logs.sql), retention cleanup drops expired partitions instead of deleting rows, and pre-creates upcoming partitions. Default is False.",
+ description="If True and LiteLLM_SpendLogs has been converted to a range-partitioned table (scripts/db/partition_spend_logs.sql), retention cleanup drops expired partitions instead of deleting rows, and pre-creates upcoming partitions. Default is False.",
)
maximum_spend_logs_cleanup_batch_size: int | None = Field(
None,
diff --git a/litellm/proxy/client/README.md b/litellm/proxy/client/README.md
index 9a7e067c792..d5c76428e3b 100644
--- a/litellm/proxy/client/README.md
+++ b/litellm/proxy/client/README.md
@@ -294,7 +294,7 @@ keys = client.keys.list(
## Contributing
-Contributions are welcome! Please check out our [contributing guidelines](../../CONTRIBUTING.md) for details.
+Contributions are welcome! Please check out our [contributing guidelines](../../../.github/CONTRIBUTING.md) for details.
## License
diff --git a/litellm/proxy/db/create_views.py b/litellm/proxy/db/create_views.py
index d51e39596c6..fd5c2356373 100644
--- a/litellm/proxy/db/create_views.py
+++ b/litellm/proxy/db/create_views.py
@@ -51,7 +51,7 @@ async def create_view_tolerating_race(db: SupportsExecuteRaw, view_name: str, dd
async def create_missing_views(db: SupportsRawQueries) -> None:
"""
--------------------------------------------------
- NOTE: Copy of `litellm/db_scripts/create_views.py`.
+ NOTE: Copy of `scripts/db/create_views.py`.
--------------------------------------------------
Checks if the LiteLLM_VerificationTokenView and MonthlyGlobalSpend exists in the user's db.
diff --git a/litellm/proxy/db/db_transaction_queue/spend_logs_partition_manager.py b/litellm/proxy/db/db_transaction_queue/spend_logs_partition_manager.py
index 049a6ecbeee..f916d53a63a 100644
--- a/litellm/proxy/db/db_transaction_queue/spend_logs_partition_manager.py
+++ b/litellm/proxy/db/db_transaction_queue/spend_logs_partition_manager.py
@@ -9,7 +9,7 @@ to the OS immediately, with no tombstones and no vacuum.
This manager only acts when use_spend_logs_partitioning is enabled in
general_settings AND the table is already partitioned (set up via the
-db_scripts/partition_spend_logs.sql runbook). Without both, the cleanup job
+scripts/db/partition_spend_logs.sql runbook). Without both, the cleanup job
keeps the batched-DELETE path, so existing deployments are untouched.
"""
diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py
index 874fcbc8d09..ee78db34d62 100644
--- a/litellm/proxy/utils.py
+++ b/litellm/proxy/utils.py
@@ -6006,7 +6006,7 @@ class PrismaClient:
missing_views: Final = expected_views_set - ret_view_names_set
verbose_proxy_logger.warning(
- "\n\n\x1b[93mNot all views exist in db, needed for UI 'Usage' tab. Missing=%s.\nRun 'create_views.py' from https://github.com/BerriAI/litellm/tree/main/db_scripts to create missing views.\x1b[0m\n",
+ "\n\n\x1b[93mNot all views exist in db, needed for UI 'Usage' tab. Missing=%s.\nRun 'create_views.py' from https://github.com/BerriAI/litellm/tree/main/scripts/db to create missing views.\x1b[0m\n",
missing_views,
)
diff --git a/litellm/utils.py b/litellm/utils.py
index 1d035215ca5..5b80c96ed89 100644
--- a/litellm/utils.py
+++ b/litellm/utils.py
@@ -1289,7 +1289,7 @@ def function_setup(
original_function: str,
rules_obj: Rules,
start_time: datetime.datetime,
- *args: Any, # positional passthrough to the wrapped LLM call (ANN401 ignored, see ruff-strict.toml)
+ *args: Any, # positional passthrough to the wrapped LLM call (ANN401 ignored, see .github/linters/ruff-strict.toml)
is_async_call: bool = True,
**kwargs: Any, # kwargs-ok: forwarded to Logging()/callbacks, varies per call_type
) -> tuple[LiteLLMLoggingObject, dict[str, Any]]:
diff --git a/pyproject.toml b/pyproject.toml
index bf4e9e52445..e3471cd45b3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -488,3 +488,79 @@ pytest_add_cli_args = [
[tool.coverage.run]
source = ["litellm"]
relative_files = true
+
+[tool.ruff]
+lint.ignore = ["F405", "E402", "F403"]
+# The second group is the strict gate's graduates: rules the codebase already has zero
+# violations of, so they hard-fail here instead of being counted by the strict gate.
+# That gives editors and `ruff check --fix` the diagnostic, which the gate script cannot.
+lint.extend-select = [
+ "T20", "PGH004", "RUF008", "RUF009", "RUF100",
+ "B004", "B018", "B021", "B033", "FURB136", "FURB168", "FURB188", "I001", "PERF402", "PIE790",
+ "PIE800", "PLC0208", "PLR0124", "PLR0402", "PLR0206", "PLR1704", "PLR1711", "PLR1730", "PLR2044",
+ "PLW0133", "PYI030", "PYI041", "PYI064", "RET501", "RUF010", "RUF022", "RUF023", "RUF051", "S113",
+ "SIM114", "SIM118", "SIM201", "SIM211", "SIM222", "TC005", "UP006", "UP007", "UP008",
+ "UP012", "UP018", "UP024", "UP032", "UP034", "UP035", "UP036", "UP037", "UP045",
+ "C404", "C419", "TID253",
+]
+# RUF100 (unused-noqa) only knows the rules enabled in THIS config, so it would strip
+# `# noqa` directives that protect rules enforced elsewhere. List those codes as external
+# so RUF100 leaves their directives alone: the strict gate (.github/linters/ruff-strict.toml) and upstream
+# litellm's own ruff config both rely on suppressions this config can't see.
+lint.external = [
+ # Enforced by the strict-rule gate (scripts/ruff_strict_gate.py + .github/linters/ruff-strict.toml).
+ # Family entries whose every strict rule graduated into extend-select above (FURB), and
+ # standalone graduated codes (I001, RUF010, RUF022, RUF023, RUF051), are dropped so this
+ # config's RUF100 polices their directives itself.
+ "ANN", "ASYNC230", "B", "C", "D202", "D403", "D417", "D419", "DTZ", "EXE", "LOG015", "N999", "PERF",
+ "PIE", "PL", "PYI", "RET", "RUF012", "RUF015", "RUF019",
+ "RUF046", "RUF059", "S110", "S112", "SIM", "TC", "TID251", "TRY", "UP",
+ # Enforced by upstream litellm's ruff config, but not run in this repo's CI
+ "PLC0415", "E402", "BLE001", "ARG002", "S102", "S324", "S606", "D401", "F403", "F405",
+]
+line-length = 120
+
+extend-exclude = ["litellm/proxy/discovery_endpoints/skill_scanner_adapter"]
+
+format.exclude = ["**/enterprise/**"]
+
+# Was the top-level `exclude`. Scoped to lint so `ruff format` still formats these paths
+# (Black did) while `ruff check` keeps skipping them.
+lint.exclude = ["litellm/__init__.py", "litellm/proxy/example_config_yaml/*", "tests/*"]
+
+
+[tool.ruff.lint.flake8-tidy-imports]
+banned-module-level-imports = ["prisma"]
+
+[tool.ruff.lint.per-file-ignores]
+"litellm/main.py" = ["F401"]
+"litellm/types/caching.py" = ["F401"]
+"litellm/types/integrations/slack_alerting.py" = ["F401"]
+"litellm/types/llms/custom_http.py" = ["F401"]
+"litellm/types/llms/openai.py" = ["F401"]
+"litellm/types/proxy/management_endpoints/scim_v2.py" = ["F401"]
+"litellm/types/responses/main.py" = ["F401"]
+"litellm/utils.py" = ["F401"]
+"litellm/proxy/proxy_server.py" = ["F401"]
+"litellm/caching/__init__.py" = ["F401"]
+"litellm/llms/anthropic/chat/__init__.py" = ["F401"]
+"litellm/llms/azure_ai/embed/__init__.py" = ["F401"]
+"litellm/llms/azure_ai/rerank/__init__.py" = ["F401"]
+"litellm/llms/bedrock/chat/__init__.py" = ["F401"]
+"litellm/proxy/utils.py" = ["F401"]
+
+[tool.pyright]
+include = ["litellm", "litellm_cli"]
+ignore = []
+exclude = ["**/node_modules", "**/__pycache__", "tests/e2e/claude_code", "tests/e2e_harness/claude_code", "tests/e2e/ui", "litellm/types/utils.py", "litellm/proxy/_types.py", "litellm/proxy/discovery_endpoints/skill_scanner_adapter"]
+pythonVersion = "3.12"
+typeCheckingMode = "strict"
+enableTypeIgnoreComments = false
+reportMissingImports = false
+reportPrivateImportUsage = false
+reportExplicitAny = "error"
+reportAny = "error"
+
+[[tool.pyright.executionEnvironments]]
+root = "tests/e2e_harness"
+extraPaths = ["tests/e2e", "tests/e2e/batches", "tests/e2e/guardrails", "tests/e2e/perf", "tests/e2e/logging"]
diff --git a/pyrightconfig.json b/pyrightconfig.json
deleted file mode 100644
index 820c4758b46..00000000000
--- a/pyrightconfig.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "include": ["litellm", "litellm_cli"],
- "ignore": [],
- "exclude": ["**/node_modules", "**/__pycache__", "tests/e2e/claude_code", "tests/e2e_harness/claude_code", "tests/e2e/ui", "litellm/types/utils.py", "litellm/proxy/_types.py", "litellm/proxy/discovery_endpoints/skill_scanner_adapter"],
- "executionEnvironments": [
- {
- "root": "tests/e2e_harness",
- "extraPaths": ["tests/e2e", "tests/e2e/batches", "tests/e2e/guardrails", "tests/e2e/perf", "tests/e2e/logging"]
- }
- ],
- "pythonVersion": "3.12",
- "typeCheckingMode": "strict",
- "enableTypeIgnoreComments": false,
- "reportMissingImports": false,
- "reportPrivateImportUsage": false,
- "reportExplicitAny": "error",
- "reportAny": "error"
-}
diff --git a/ruff.toml b/ruff.toml
deleted file mode 100644
index f7624f07f51..00000000000
--- a/ruff.toml
+++ /dev/null
@@ -1,58 +0,0 @@
-lint.ignore = ["F405", "E402", "F403"]
-# The second group is the strict gate's graduates: rules the codebase already has zero
-# violations of, so they hard-fail here instead of being counted by the strict gate.
-# That gives editors and `ruff check --fix` the diagnostic, which the gate script cannot.
-lint.extend-select = [
- "T20", "PGH004", "RUF008", "RUF009", "RUF100",
- "B004", "B018", "B021", "B033", "FURB136", "FURB168", "FURB188", "I001", "PERF402", "PIE790",
- "PIE800", "PLC0208", "PLR0124", "PLR0402", "PLR0206", "PLR1704", "PLR1711", "PLR1730", "PLR2044",
- "PLW0133", "PYI030", "PYI041", "PYI064", "RET501", "RUF010", "RUF022", "RUF023", "RUF051", "S113",
- "SIM114", "SIM118", "SIM201", "SIM211", "SIM222", "TC005", "UP006", "UP007", "UP008",
- "UP012", "UP018", "UP024", "UP032", "UP034", "UP035", "UP036", "UP037", "UP045",
- "C404", "C419", "TID253",
-]
-# RUF100 (unused-noqa) only knows the rules enabled in THIS config, so it would strip
-# `# noqa` directives that protect rules enforced elsewhere. List those codes as external
-# so RUF100 leaves their directives alone: the strict gate (ruff-strict.toml) and upstream
-# litellm's own ruff config both rely on suppressions this config can't see.
-lint.external = [
- # Enforced by the strict-rule gate (scripts/ruff_strict_gate.py + ruff-strict.toml).
- # Family entries whose every strict rule graduated into extend-select above (FURB), and
- # standalone graduated codes (I001, RUF010, RUF022, RUF023, RUF051), are dropped so this
- # config's RUF100 polices their directives itself.
- "ANN", "ASYNC230", "B", "C", "D202", "D403", "D417", "D419", "DTZ", "EXE", "LOG015", "N999", "PERF",
- "PIE", "PL", "PYI", "RET", "RUF012", "RUF015", "RUF019",
- "RUF046", "RUF059", "S110", "S112", "SIM", "TC", "TID251", "TRY", "UP",
- # Enforced by upstream litellm's ruff config, but not run in this repo's CI
- "PLC0415", "E402", "BLE001", "ARG002", "S102", "S324", "S606", "D401", "F403", "F405",
-]
-line-length = 120
-
-extend-exclude = ["litellm/proxy/discovery_endpoints/skill_scanner_adapter"]
-
-format.exclude = ["**/enterprise/**"]
-
-# Was the top-level `exclude`. Scoped to lint so `ruff format` still formats these paths
-# (Black did) while `ruff check` keeps skipping them.
-lint.exclude = ["litellm/__init__.py", "litellm/proxy/example_config_yaml/*", "tests/*"]
-
-
-[lint.flake8-tidy-imports]
-banned-module-level-imports = ["prisma"]
-
-[lint.per-file-ignores]
-"litellm/main.py" = ["F401"]
-"litellm/types/caching.py" = ["F401"]
-"litellm/types/integrations/slack_alerting.py" = ["F401"]
-"litellm/types/llms/custom_http.py" = ["F401"]
-"litellm/types/llms/openai.py" = ["F401"]
-"litellm/types/proxy/management_endpoints/scim_v2.py" = ["F401"]
-"litellm/types/responses/main.py" = ["F401"]
-"litellm/utils.py" = ["F401"]
-"litellm/proxy/proxy_server.py" = ["F401"]
-"litellm/caching/__init__.py" = ["F401"]
-"litellm/llms/anthropic/chat/__init__.py" = ["F401"]
-"litellm/llms/azure_ai/embed/__init__.py" = ["F401"]
-"litellm/llms/azure_ai/rerank/__init__.py" = ["F401"]
-"litellm/llms/bedrock/chat/__init__.py" = ["F401"]
-"litellm/proxy/utils.py" = ["F401"]
diff --git a/scripts/check_type_discipline.py b/scripts/check_type_discipline.py
index 52992281c99..81ab4c03615 100644
--- a/scripts/check_type_discipline.py
+++ b/scripts/check_type_discipline.py
@@ -32,7 +32,7 @@ LIT008 `**kwargs` parameter. The keyword contract is erased and everything it c
syntax. Declare explicit keyword params, or accept one frozen payload. `*args`,
by contrast, is fine when typed (it's just a tuple). Suppress: `# kwargs-ok: