Skip to content

Commit 7d23d41

Browse files
committed
Merge branch 'litellm_internal_staging' of https://github.com/BerriAI/litellm into litellm_lit5879_semantic_cache_embedding_timeout
2 parents 7003525 + 0c2e404 commit 7d23d41

282 files changed

Lines changed: 20368 additions & 3885 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/ci-coverage-allowlist.yml

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,15 @@ test_paths:
4949
paths:
5050
- tests/code_coverage_tests/test_aio_http_image_conversion.py
5151
- reason: >-
52-
What is left of a second mirror that sat beside tests/test_litellm and ran nowhere. Its
53-
other 30 files moved into the real mirror on 2026-08-20 and now run; these four cannot,
54-
because each shares a filename with a live test whose contents are disjoint from it, so
55-
landing them means merging test bodies rather than moving a file. Measured on the same
56-
date: test_common_utils.py holds 15 tests the live file does not, test_oci_chat_transformation
57-
13, test_deepseek_chat_transformation 12, and test_discoverable_endpoints 5. Revisit by
58-
merging each into its twin, which is a content review, not a move
52+
The last file of a second mirror that sat beside tests/test_litellm and ran nowhere. Its
53+
other 33 files landed in the real mirror during August 2026, 30 as moves and 3 by merging
54+
their bodies into the live file of the same name. This one cannot follow either route yet:
55+
its live twin was rewritten from 1268 lines to 9434, and of the 19 tests here 5 have no
56+
counterpart while 25 assertions fail against today's code, so what survives that rewrite
57+
is a judgement about the endpoints, not a merge. Revisit by deciding which of the five
58+
behaviours still hold
5959
paths:
60-
- tests/litellm/llms/deepseek/chat/test_deepseek_chat_transformation.py
61-
- tests/litellm/llms/oci/chat/test_oci_chat_transformation.py
6260
- tests/litellm/proxy/_experimental/mcp_server/test_discoverable_endpoints.py
63-
- tests/litellm/proxy/management_endpoints/test_common_utils.py
6461
- reason: >-
6562
No job invokes this suite and its files mix pure transformation tests with ones driving live
6663
vendor vector stores, so assigning them needs a per-file decision

.github/scripts/assert_ci_coverage.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from __future__ import annotations
22

33
import ast
4+
import operator
45
import pathlib
56
import re
67
import sys
78
import warnings
8-
from collections.abc import Iterable, Mapping, Sequence
9+
from collections.abc import Callable, Iterable, Mapping, Sequence
910
from dataclasses import dataclass
1011
from typing import Final
1112

@@ -56,6 +57,14 @@ def covers_dockerfile(self, relative_path: str) -> bool:
5657
return any(relative_path == path for entry in self.dockerfiles for path in entry.paths)
5758

5859

60+
@dataclass(frozen=True, slots=True)
61+
class Section:
62+
name: str
63+
entries: tuple[AllowEntry, ...]
64+
candidates: tuple[str, ...]
65+
matches: Callable[[str, str], bool]
66+
67+
5968
@dataclass(frozen=True, slots=True)
6069
class Scalar:
6170
key: str
@@ -368,6 +377,25 @@ def _uncovered_dockerfiles(allowlist: Allowlist, tokens: frozenset[str]) -> tupl
368377
)
369378

370379

380+
def _stale_allowlist_paths(
381+
allowlist: Allowlist,
382+
*,
383+
test_files: tuple[str, ...],
384+
dockerfiles: tuple[str, ...],
385+
) -> tuple[Finding, ...]:
386+
sections: Final[tuple[Section, ...]] = (
387+
Section("test_paths", allowlist.test_paths, test_files, _token_covers),
388+
Section("dockerfiles", allowlist.dockerfiles, dockerfiles, operator.eq),
389+
)
390+
return tuple(
391+
Finding(subject=path, detail=f"listed under '{section.name}' but matches no file the census looks at")
392+
for section in sections
393+
for entry in section.entries
394+
for path in entry.paths
395+
if not any(section.matches(path, candidate) for candidate in section.candidates)
396+
)
397+
398+
371399
def _parse_entry(item: object, section: str) -> AllowEntry:
372400
if not isinstance(item, dict):
373401
raise SystemExit(f"{ALLOWLIST_FILE.name}: '{section}' entries must be mappings")
@@ -465,7 +493,14 @@ def main() -> int:
465493

466494
test_findings = _uncovered_tests(allowlist, _invoked_test_tokens(scalars))
467495
dockerfile_findings = _uncovered_dockerfiles(allowlist, _built_dockerfile_tokens(scalars))
496+
stale_findings = _stale_allowlist_paths(allowlist, test_files=_test_files(), dockerfiles=_dockerfiles())
468497

498+
if stale_findings:
499+
_report(
500+
"allowlist entries that exempt nothing",
501+
stale_findings,
502+
"Delete each from .github/ci-coverage-allowlist.yml; the file it named is gone or was renamed.",
503+
)
469504
if test_findings:
470505
_report(
471506
"test files that no CI job invokes",
@@ -478,7 +513,7 @@ def main() -> int:
478513
dockerfile_findings,
479514
"Build each in a workflow, or list it in .github/ci-coverage-allowlist.yml with a reason.",
480515
)
481-
if test_findings or dockerfile_findings:
516+
if stale_findings or test_findings or dockerfile_findings:
482517
return 1
483518

484519
_write(
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#!/usr/bin/env python3
2+
"""Three invariants about what lives in .github/workflows/ and what its names mean.
3+
4+
`.github/workflows/` is a directory GitHub reads, not a place to keep things. Every
5+
file at its top level is parsed as a workflow, so a script or a data file parked there
6+
is either an invalid workflow or an orphan nobody can find. A subdirectory is not read
7+
at all, so helper files may live in one. GitHub accepts both `.yml` and `.yaml`, and
8+
this repo spells them `.yml`, which is a naming rule rather than a validity one and is
9+
reported separately. And the `_` prefix is the repo's only signal that a workflow is a
10+
reusable building block rather than something that runs on its own, which is worth
11+
nothing unless it is true both ways.
12+
13+
WF001 a top-level file in .github/workflows/ that is not a workflow at all
14+
WF002 a workflow whose only trigger is `workflow_call` but is not `_`-prefixed
15+
WF003 a `_`-prefixed workflow that no other workflow can call
16+
WF004 a real workflow spelled `.yaml` where this directory spells them `.yml`
17+
18+
A workflow with `workflow_call` alongside a human trigger is deliberately dual-mode
19+
and belongs under its plain name, so only the call-only ones are held to WF002.
20+
21+
Usage
22+
-----
23+
python assert_workflow_dir_hygiene.py
24+
25+
Exit code 1 if any violation is found.
26+
"""
27+
28+
from __future__ import annotations
29+
30+
import pathlib
31+
import sys
32+
from dataclasses import dataclass
33+
from typing import Final
34+
35+
import yaml
36+
37+
REPO_ROOT: Final = pathlib.Path(__file__).resolve().parents[2]
38+
WORKFLOW_DIR: Final = REPO_ROOT / ".github" / "workflows"
39+
SCRIPT_HOME: Final = ".github/scripts/"
40+
REUSABLE_PREFIX: Final = "_"
41+
CALL_TRIGGER: Final = "workflow_call"
42+
CANONICAL_SUFFIX: Final = ".yml"
43+
WORKFLOW_SUFFIXES: Final = frozenset((CANONICAL_SUFFIX, ".yaml"))
44+
45+
46+
@dataclass(frozen=True, slots=True)
47+
class Finding:
48+
subject: str
49+
code: str
50+
detail: str
51+
52+
def render(self) -> str:
53+
return f" - {self.subject}: {self.code} {self.detail}"
54+
55+
56+
def _triggers(document: object) -> frozenset[str]:
57+
if not isinstance(document, dict):
58+
return frozenset()
59+
raw: Final = document.get("on", document.get(True))
60+
if isinstance(raw, str):
61+
return frozenset({raw})
62+
if isinstance(raw, dict):
63+
return frozenset(str(key) for key in raw)
64+
if isinstance(raw, list):
65+
return frozenset(str(item) for item in raw)
66+
return frozenset()
67+
68+
69+
def _workflows(directory: pathlib.Path) -> tuple[pathlib.Path, ...]:
70+
return tuple(
71+
path
72+
for path in sorted(directory.iterdir())
73+
if path.is_file() and path.suffix in WORKFLOW_SUFFIXES
74+
)
75+
76+
77+
def _strays(directory: pathlib.Path) -> tuple[Finding, ...]:
78+
return tuple(
79+
Finding(
80+
path.name,
81+
"WF001",
82+
f"is not a workflow, and GitHub parses every top-level file here as one; "
83+
f"move it to {SCRIPT_HOME} or into a subdirectory, which GitHub does not read",
84+
)
85+
for path in sorted(directory.iterdir())
86+
if path.is_file() and path.suffix not in WORKFLOW_SUFFIXES
87+
)
88+
89+
90+
def _misspelled(directory: pathlib.Path) -> tuple[Finding, ...]:
91+
return tuple(
92+
Finding(
93+
path.name,
94+
"WF004",
95+
f"is a real workflow and GitHub reads it, but this directory spells them "
96+
f"{CANONICAL_SUFFIX}; rename it to {path.stem}{CANONICAL_SUFFIX}",
97+
)
98+
for path in _workflows(directory)
99+
if path.suffix != CANONICAL_SUFFIX
100+
)
101+
102+
103+
def _misnamed(directory: pathlib.Path) -> tuple[Finding, ...]:
104+
return tuple(
105+
finding
106+
for path in _workflows(directory)
107+
for finding in _naming_findings(path, _triggers(yaml.safe_load(path.read_text(encoding="utf-8"))))
108+
)
109+
110+
111+
def _naming_findings(path: pathlib.Path, triggers: frozenset[str]) -> tuple[Finding, ...]:
112+
underscored: Final = path.name.startswith(REUSABLE_PREFIX)
113+
if triggers == frozenset({CALL_TRIGGER}) and not underscored:
114+
return (
115+
Finding(
116+
path.name,
117+
"WF002",
118+
f"is only callable by another workflow, so name it {REUSABLE_PREFIX}{path.name}",
119+
),
120+
)
121+
if underscored and CALL_TRIGGER not in triggers:
122+
return (
123+
Finding(
124+
path.name,
125+
"WF003",
126+
f"is named as a reusable workflow but has no {CALL_TRIGGER} trigger; "
127+
"add one or drop the prefix",
128+
),
129+
)
130+
return ()
131+
132+
133+
def main() -> int:
134+
findings: Final = _strays(WORKFLOW_DIR) + _misspelled(WORKFLOW_DIR) + _misnamed(WORKFLOW_DIR)
135+
if not findings:
136+
total: Final = len(_workflows(WORKFLOW_DIR))
137+
sys.stdout.write(
138+
f"OK: {total} workflows, every file in .github/workflows/ is one, and the "
139+
f"{REUSABLE_PREFIX} prefix means callable in both directions.\n"
140+
)
141+
return 0
142+
sys.stdout.write("ERROR: .github/workflows/ holds files that break its own conventions\n")
143+
for finding in findings:
144+
sys.stdout.write(f"{finding.render()}\n")
145+
return 1
146+
147+
148+
if __name__ == "__main__":
149+
sys.exit(main())

.github/workflows/_test-unit-base.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,13 @@ jobs:
129129
WORKERS: ${{ inputs.workers }}
130130
RERUNS: ${{ inputs.reruns }}
131131
DIST: ${{ inputs.dist }}
132+
# coverage.py's sys.monitoring backend (PEP 669), the cheapest core it has.
133+
# It is only the default from Python 3.14, and these shards run 3.12, so it
134+
# has to be asked for. Coverage refuses it when branch measurement is on
135+
# (`branch_right_left` needs > 3.14.0a5) and falls back to the slow core with
136+
# a `no-sysmon` warning, so turning on `branch = true` here means giving this
137+
# back until the runners move to 3.14.
138+
COVERAGE_CORE: sysmon
132139
run: |
133140
if [ "${WORKERS}" = "0" ]; then
134141
uv run --no-sync pytest ${TEST_PATH:?} \

.github/workflows/ci-coverage.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,6 @@ jobs:
4646
# nowhere while counting as covered, which is how the caching suite went unrun.
4747
- name: Assert no -k expression deselects a file from every job that globs it
4848
run: python .github/scripts/assert_ci_coverage.py --slices
49+
50+
- name: Assert .github/workflows/ holds only workflows, correctly named
51+
run: python .github/scripts/assert_workflow_dir_hygiene.py

.github/workflows/test-linting.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ jobs:
122122
uv run --no-sync ruff check .
123123
cd ..
124124
125+
- name: Run Ruff linting (test tree)
126+
if: steps.changes.outputs.decision != 'skip'
127+
run: |
128+
uv run --no-sync ruff check --config ruff-tests.toml tests
129+
125130
- name: Check strict-rule budget (delta vs base)
126131
if: steps.changes.outputs.decision != 'skip'
127132
run: |
@@ -132,7 +137,7 @@ jobs:
132137
run: |
133138
uv run --no-sync python scripts/type_discipline_gate.py --base "$GATE_BASE_SHA"
134139
135-
- name: Check test-quality budget (zero-assert / mock-echo tests, sys.path.insert, raw env writes, litellm global mutation, credential-gated skips, delta vs base)
140+
- name: Check test-quality budget (zero-assert / mock-echo tests, sys.path.insert, raw env writes, litellm global mutation, credential-gated skips, conftest snapshot inventory, delta vs base)
136141
if: steps.changes.outputs.decision != 'skip'
137142
run: |
138143
uv run --no-sync python scripts/test_quality_gate.py --base "$GATE_BASE_SHA"

.github/workflows/test-unit.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ jobs:
113113
tests/test_litellm/rag
114114
tests/test_litellm/realtime_api
115115
tests/test_litellm/rerank_api
116+
tests/test_litellm/rust_bridge
116117
tests/test_litellm/sandbox
117118
tests/test_litellm/test_router
118119
tests/test_litellm/vector_stores

Makefile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ lint-format-check-changed: $(LINT_DEP_INSTALL) $(LINT_DEP_BASE)
160160
# Linting targets
161161
lint-ruff: $(LINT_DEP_INSTALL)
162162
cd litellm && $(UV_RUN) ruff check . && cd ..
163+
$(UV_RUN) ruff check --config ruff-tests.toml tests
163164

164165
# faster linter for developing ...
165166
# inspiration from:
@@ -205,8 +206,8 @@ lint-type-discipline: $(LINT_DEP_INSTALL) $(LINT_DEP_BASE)
205206
$(UV_RUN) python scripts/type_discipline_gate.py --base origin/litellm_internal_staging
206207

207208
# Test-quality budget (zero-assert / mock-echo tests, sys.path.insert, raw env writes,
208-
# litellm module-global mutation, credential-gated skips), counted across tests/ the
209-
# same delta-vs-base way.
209+
# litellm module-global mutation, credential-gated skips, conftest snapshot
210+
# inventory), counted across tests/ the same delta-vs-base way.
210211
lint-test-quality: $(LINT_DEP_INSTALL) $(LINT_DEP_BASE)
211212
$(UV_RUN) python scripts/test_quality_gate.py --base origin/litellm_internal_staging
212213

basedpyright-code-budget.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,13 @@
105105
"limit": 109
106106
},
107107
"reportUnknownMemberType": {
108-
"limit": 39017
108+
"limit": 39011
109109
},
110110
"reportUnknownParameterType": {
111111
"limit": 19885
112112
},
113113
"reportUnknownVariableType": {
114-
"limit": 30572
114+
"limit": 30569
115115
},
116116
"reportUnnecessaryCast": {
117117
"limit": 117

0 commit comments

Comments
 (0)