Solidify LiteLLM global kwargs handling
Limit LiteLLM module mutation to documented global switches such as drop_params, while preserving configured LiteLLM kwargs as per-call options. Avoid freezing global kwargs into provider defaults so runtime settings remain current, and extend focused tests for drop_params, additional_drop_params, and provider default behavior.
Alessandro committed
Jun 12, 2026 at 03:27 UTC
3d4c3021856022562cbf1ca64f3ddf25f685173f
3 files changed
+63
-9
AGENTS.md
+1
-1
@@ -100,7 +100,7 @@ Key Files:
100
- helpers/plugins.py: Plugin discovery and configuration logic.
101
- webui/js/AlpineStore.js: Store factory for reactive frontend state.
102
- helpers/api.py: Base class for all API endpoints.
103
-- models.py: LLM provider configuration and LiteLLM wrappers; framework LiteLLM defaults such as `drop_params=True` are merged with `litellm_global_kwargs`, configured values override framework defaults, and the merged kwargs are applied at LiteLLM module and per-call boundaries.
103
+- models.py: LLM provider configuration and LiteLLM wrappers; framework LiteLLM defaults such as `drop_params=True` are merged with `litellm_global_kwargs`, configured values override framework defaults, documented module-level switches such as `drop_params` are applied to LiteLLM, and merged kwargs are passed per call.
104
- scripts/openrouter_release_notes_system_prompt.md: Editable system prompt used to generate GitHub release notes during Docker publishing.
105
- knowledge/main/about/: Agent self-knowledge files, indexed into the vector DB for runtime recall. Not user-facing docs - written for the agent's internal reference.
106
- webui/components/AGENTS.md: DOX contract for Alpine component architecture.
models.py
+7
-6
@@ -49,6 +49,11 @@ DEFAULT_LITELLM_GLOBAL_KWARGS: dict[str, Any] = {
49
"drop_params": True,
50
}
51
52
+# LiteLLM documents drop_params as both a module-level switch and per-call kwarg.
53
+# Other entries in litellm_global_kwargs, such as timeout or additional_drop_params,
54
+# are kept as per-call kwargs instead of becoming arbitrary module attributes.
55
+LITELLM_MODULE_GLOBAL_KEYS = frozenset({"drop_params"})
56
+
57
58
def _normalize_litellm_kwargs(values: dict[str, Any]) -> dict[str, Any]:
59
# Normalize .env/UI-style scalar strings into native types for LiteLLM.
@@ -100,6 +105,8 @@ def turn_off_logging():
105
def set_litellm_params():
106
global_kwargs = get_litellm_global_kwargs()
107
for key, value in global_kwargs.items():
108
+ if key not in LITELLM_MODULE_GLOBAL_KEYS:
109
+ continue
110
setattr(litellm, key, value)
111
return global_kwargs
112
@@ -959,12 +966,6 @@ def _merge_provider_defaults(
966
if key and key not in ("None", "NA"):
967
kwargs["api_key"] = key
968
962
- # Merge LiteLLM global kwargs. Framework defaults are merged first, then
963
- # configured global kwargs override those defaults; explicit provider/model
964
- # kwargs still keep priority via setdefault.
965
- for k, v in get_litellm_global_kwargs().items():
966
- kwargs.setdefault(k, v)
967
-
969
return provider_name, kwargs
970
971
tests/test_stream_tool_early_stop.py
+55
-2
@@ -94,30 +94,83 @@ def test_litellm_global_kwargs_merge_defaults_and_config(monkeypatch):
94
monkeypatch.setattr(
95
models.settings,
96
"get_settings",
97
- lambda: {"litellm_global_kwargs": {"drop_params": "false", "timeout": "30"}},
97
+ lambda: {
98
+ "litellm_global_kwargs": {
99
+ "drop_params": "false",
100
+ "timeout": "30",
101
+ "additional_drop_params": ["response_format"],
102
+ }
103
+ },
104
)
105
106
assert models._merge_litellm_call_kwargs({}) == {
107
"drop_params": False,
108
"timeout": 30,
109
+ "additional_drop_params": ["response_format"],
110
}
111
112
original_drop_params = getattr(models.litellm, "drop_params", None)
113
had_timeout = hasattr(models.litellm, "timeout")
114
original_timeout = getattr(models.litellm, "timeout", None)
115
+ had_additional_drop_params = hasattr(models.litellm, "additional_drop_params")
116
+ original_additional_drop_params = getattr(
117
+ models.litellm, "additional_drop_params", None
118
+ )
119
try:
120
assert models.set_litellm_params() == {
121
"drop_params": False,
122
"timeout": 30,
123
+ "additional_drop_params": ["response_format"],
124
}
125
assert models.litellm.drop_params is False
114
- assert models.litellm.timeout == 30
126
+ if had_timeout:
127
+ assert models.litellm.timeout == original_timeout
128
+ else:
129
+ assert not hasattr(models.litellm, "timeout")
130
+ if had_additional_drop_params:
131
+ assert (
132
+ models.litellm.additional_drop_params
133
+ == original_additional_drop_params
134
+ )
135
+ else:
136
+ assert not hasattr(models.litellm, "additional_drop_params")
137
finally:
138
setattr(models.litellm, "drop_params", original_drop_params)
139
if had_timeout:
140
setattr(models.litellm, "timeout", original_timeout)
141
elif hasattr(models.litellm, "timeout"):
142
delattr(models.litellm, "timeout")
143
+ if had_additional_drop_params:
144
+ setattr(
145
+ models.litellm,
146
+ "additional_drop_params",
147
+ original_additional_drop_params,
148
+ )
149
+ elif hasattr(models.litellm, "additional_drop_params"):
150
+ delattr(models.litellm, "additional_drop_params")
151
+
152
+
153
+def test_provider_defaults_do_not_freeze_litellm_global_kwargs(monkeypatch):
154
+ monkeypatch.setattr(models, "get_provider_config", lambda *args, **kwargs: None)
155
+ monkeypatch.setattr(models, "get_api_key", lambda *_args, **_kwargs: None)
156
+ monkeypatch.setattr(
157
+ models.settings,
158
+ "get_settings",
159
+ lambda: {"litellm_global_kwargs": {"drop_params": "true"}},
160
+ )
161
+
162
+ _, provider_kwargs = models._merge_provider_defaults("chat", "openai", {})
163
+
164
+ assert "drop_params" not in provider_kwargs
165
+ assert models._merge_litellm_call_kwargs(provider_kwargs)["drop_params"] is True
166
+
167
+ monkeypatch.setattr(
168
+ models.settings,
169
+ "get_settings",
170
+ lambda: {"litellm_global_kwargs": {"drop_params": "false"}},
171
+ )
172
+
173
+ assert models._merge_litellm_call_kwargs(provider_kwargs)["drop_params"] is False
174
175
176
@pytest.mark.asyncio