Skip to content

Commit 9c20446

Browse files
Merge pull request #15612 from BerriAI/litellm_replace_deepcopy
perf(router): replace deepcopy with shallow copy for default deployment
2 parents c432b5a + 4d543b9 commit 9c20446

File tree

2 files changed

+90
-3
lines changed

2 files changed

+90
-3
lines changed

litellm/router.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6914,9 +6914,11 @@ def _common_checks_available_deployment(
69146914

69156915
# check if default deployment is set
69166916
if self.default_deployment is not None:
6917-
updated_deployment = copy.deepcopy(
6918-
self.default_deployment
6919-
) # self.default_deployment
6917+
# Shallow copy with nested litellm_params copy (100x+ faster than deepcopy)
6918+
updated_deployment = self.default_deployment.copy()
6919+
updated_deployment["litellm_params"] = self.default_deployment[
6920+
"litellm_params"
6921+
].copy()
69206922
updated_deployment["litellm_params"]["model"] = model
69216923
return model, updated_deployment
69226924

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"""
2+
Regression test for default_deployment shallow copy optimization.
3+
4+
Tests the critical side effect: ensure modifying returned deployment
5+
doesn't corrupt the original default_deployment instance.
6+
"""
7+
import sys
8+
import os
9+
10+
sys.path.insert(0, os.path.abspath("../.."))
11+
12+
from litellm import Router
13+
14+
15+
def test_default_deployment_isolation():
16+
"""
17+
Regression test for shallow copy optimization in _common_checks_available_deployment.
18+
19+
When a model is not in model_names and default_deployment is set, the router
20+
returns a copy of default_deployment with the model name updated. This test
21+
ensures the optimization (shallow copy instead of deepcopy) properly isolates
22+
each returned deployment from the original and from each other.
23+
24+
The shallow copy optimization copies two levels:
25+
1. Top-level deployment dict
26+
2. litellm_params dict
27+
28+
Deeper nested objects are intentionally shared for performance (safe because
29+
the router only modifies the 'model' field at litellm_params level).
30+
31+
Critical behavior verified:
32+
1. Each deployment gets independent model value
33+
2. Original default_deployment unchanged for litellm_params fields
34+
3. Shared fields (api_key) accessible in all copies
35+
4. Adding new litellm_params fields is isolated per deployment
36+
5. Deep nested objects ARE shared (acceptable trade-off)
37+
"""
38+
# Setup: Router with a default deployment (used for unknown models)
39+
router = Router(model_list=[])
40+
41+
router.default_deployment = { # type: ignore
42+
"model_name": "default-model",
43+
"litellm_params": {
44+
"model": "gpt-3.5-turbo", # This will be overwritten per request
45+
"api_key": "test-key", # This should be shared
46+
"custom_config": { # Deep nested - will be SHARED
47+
"nested_setting": "original",
48+
},
49+
},
50+
}
51+
52+
# Act: Request two different unknown models (triggers default deployment path)
53+
_, deployment1 = router._common_checks_available_deployment(
54+
model="custom-model-1", # Unknown model
55+
messages=[{"role": "user", "content": "test"}],
56+
)
57+
58+
_, deployment2 = router._common_checks_available_deployment(
59+
model="custom-model-2", # Different unknown model
60+
messages=[{"role": "user", "content": "test"}],
61+
)
62+
63+
# Assert: Each deployment should have its own independent model value
64+
assert deployment1["litellm_params"]["model"] == "custom-model-1" # type: ignore
65+
assert deployment2["litellm_params"]["model"] == "custom-model-2" # type: ignore
66+
67+
# Assert: Original default_deployment must remain unchanged (not mutated by requests)
68+
assert router.default_deployment["litellm_params"]["model"] == "gpt-3.5-turbo" # type: ignore
69+
70+
# Assert: Shared fields should still be accessible in all copies
71+
assert deployment1["litellm_params"]["api_key"] == "test-key" # type: ignore
72+
assert deployment2["litellm_params"]["api_key"] == "test-key" # type: ignore
73+
74+
# Assert: Modifying litellm_params in one deployment doesn't affect others
75+
# This tests the shallow copy properly isolated the litellm_params dict level
76+
deployment1["litellm_params"]["temperature"] = 0.9 # type: ignore
77+
assert "temperature" not in deployment2["litellm_params"] # type: ignore
78+
assert "temperature" not in router.default_deployment["litellm_params"] # type: ignore
79+
80+
# Assert: Deep nested objects ARE shared (intentional trade-off for 100x perf gain)
81+
# Safe because router only modifies top-level litellm_params fields
82+
deployment1["litellm_params"]["custom_config"]["nested_setting"] = "modified" # type: ignore
83+
assert deployment2["litellm_params"]["custom_config"]["nested_setting"] == "modified" # type: ignore
84+
assert router.default_deployment["litellm_params"]["custom_config"]["nested_setting"] == "modified" # type: ignore
85+

0 commit comments

Comments
 (0)