mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
fix(zai): pass thinking and reasoning_effort via extra_body
ZAIChatConfig inherits OpenAIGPTConfig._map_openai_params, which copies every
supported param to the top level of optional_params. The OpenAI SDK has no
`thinking` kwarg, so a request to a GLM reasoning model with
`thinking={"type": "disabled"}` failed inside the SDK with "unexpected keyword
argument 'thinking'" and surfaced as a 500. `reasoning_effort` was not in the
supported list at all, so it was rejected as unsupported or silently dropped
Both are Z.AI request body fields, so the zai config now lists them as supported
on reasoning models and moves them into extra_body, which the SDK flattens into
the JSON body. The merged extra_body is built as a new dict so a caller-owned
extra_body is never mutated
Co-authored-by: songkuan-zheng <252822057+songkuan-zheng@users.noreply.github.com>
Co-authored-by: songkuan-zheng <songkuan-zheng@users.noreply.github.com>
This commit is contained in:
parent
9a715df212
commit
9660f22e85
2 changed files with 130 additions and 7 deletions
|
|
@ -1,3 +1,4 @@
|
|||
from types import MappingProxyType
|
||||
from typing import Final
|
||||
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
|
|
@ -6,6 +7,7 @@ from litellm.types.llms.openai import AllMessageValues, ChatCompletionToolParam
|
|||
from ...openai.chat.gpt_transformation import OpenAIGPTConfig
|
||||
|
||||
ZAI_API_BASE: Final = "https://api.z.ai/api/paas/v4"
|
||||
ZAI_REASONING_PARAMS: Final = frozenset(("thinking", "reasoning_effort"))
|
||||
|
||||
|
||||
class ZAIChatConfig(OpenAIGPTConfig):
|
||||
|
|
@ -49,8 +51,31 @@ class ZAIChatConfig(OpenAIGPTConfig):
|
|||
|
||||
try:
|
||||
if litellm.supports_reasoning(model=model, custom_llm_provider=self.custom_llm_provider):
|
||||
base_params.append("thinking")
|
||||
return [*base_params, *sorted(ZAI_REASONING_PARAMS)] # mutable-ok: base class returns a list
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return base_params
|
||||
|
||||
def _map_openai_params(
|
||||
self,
|
||||
non_default_params: dict,
|
||||
optional_params: dict,
|
||||
model: str,
|
||||
drop_params: bool,
|
||||
) -> dict:
|
||||
supported_openai_params: Final = frozenset(self.get_supported_openai_params(model))
|
||||
reasoning_params: Final = MappingProxyType(
|
||||
{k: v for k, v in non_default_params.items() if k in ZAI_REASONING_PARAMS and k in supported_openai_params}
|
||||
)
|
||||
optional_params.update(
|
||||
(k, v)
|
||||
for k, v in non_default_params.items()
|
||||
if k in supported_openai_params and k not in ZAI_REASONING_PARAMS
|
||||
)
|
||||
if reasoning_params:
|
||||
optional_params["extra_body"] = { # mutable-ok: the OpenAI SDK json-encodes extra_body from a plain dict
|
||||
**(optional_params.get("extra_body") or MappingProxyType({})),
|
||||
**reasoning_params,
|
||||
}
|
||||
return optional_params
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
Tests for Z.AI (Zhipu AI) provider - GLM models
|
||||
"""
|
||||
|
||||
import json
|
||||
import math
|
||||
|
||||
import pytest
|
||||
|
|
@ -89,9 +90,7 @@ async def test_zai_completion_call(respx_mock, zai_response, monkeypatch):
|
|||
monkeypatch.setenv("ZAI_API_KEY", "test-api-key")
|
||||
monkeypatch.setattr(litellm, "disable_aiohttp_transport", True)
|
||||
|
||||
respx_mock.post("https://api.z.ai/api/paas/v4/chat/completions").respond(
|
||||
json=zai_response
|
||||
)
|
||||
respx_mock.post("https://api.z.ai/api/paas/v4/chat/completions").respond(json=zai_response)
|
||||
|
||||
response = await litellm.acompletion(
|
||||
model="zai/glm-4.6",
|
||||
|
|
@ -115,9 +114,7 @@ def test_zai_sync_completion(respx_mock, zai_response, monkeypatch):
|
|||
monkeypatch.setenv("ZAI_API_KEY", "test-api-key")
|
||||
monkeypatch.setattr(litellm, "disable_aiohttp_transport", True)
|
||||
|
||||
respx_mock.post("https://api.z.ai/api/paas/v4/chat/completions").respond(
|
||||
json=zai_response
|
||||
)
|
||||
respx_mock.post("https://api.z.ai/api/paas/v4/chat/completions").respond(json=zai_response)
|
||||
|
||||
response = completion(
|
||||
model="zai/glm-4.6",
|
||||
|
|
@ -127,3 +124,104 @@ def test_zai_sync_completion(respx_mock, zai_response, monkeypatch):
|
|||
|
||||
assert response.choices[0].message.content == "Hello! How can I help you today?"
|
||||
assert response.usage.total_tokens == 25
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def zai_thinking_response():
|
||||
return {
|
||||
"id": "chatcmpl-zai-thinking",
|
||||
"object": "chat.completion",
|
||||
"created": 1700000000,
|
||||
"model": "glm-4.7",
|
||||
"choices": [{"index": 0, "message": {"role": "assistant", "content": "hi"}, "finish_reason": "stop"}],
|
||||
"usage": {"prompt_tokens": 5, "completion_tokens": 2, "total_tokens": 7},
|
||||
}
|
||||
|
||||
|
||||
def _captured_body(respx_mock) -> dict:
|
||||
assert len(respx_mock.calls) == 1
|
||||
return json.loads(respx_mock.calls[0].request.content.decode("utf-8"))
|
||||
|
||||
|
||||
def test_reasoning_params_supported_on_reasoning_models(local_model_cost_map):
|
||||
from litellm.llms.zai.chat.transformation import ZAIChatConfig
|
||||
|
||||
params = ZAIChatConfig().get_supported_openai_params(model="glm-4.7")
|
||||
assert "thinking" in params
|
||||
assert "reasoning_effort" in params
|
||||
|
||||
|
||||
def test_reasoning_params_not_supported_without_reasoning_flag(monkeypatch):
|
||||
from litellm.llms.zai.chat.transformation import ZAIChatConfig
|
||||
|
||||
monkeypatch.setattr(litellm, "supports_reasoning", lambda **kwargs: False)
|
||||
params = ZAIChatConfig().get_supported_openai_params(model="glm-4-32b-0414-128k")
|
||||
assert "thinking" not in params
|
||||
assert "reasoning_effort" not in params
|
||||
|
||||
|
||||
def test_thinking_and_reasoning_effort_move_into_extra_body(local_model_cost_map):
|
||||
from litellm.llms.zai.chat.transformation import ZAIChatConfig
|
||||
|
||||
result = ZAIChatConfig()._map_openai_params(
|
||||
non_default_params={"max_tokens": 100, "thinking": {"type": "disabled"}, "reasoning_effort": "low"},
|
||||
optional_params={},
|
||||
model="glm-4.7",
|
||||
drop_params=False,
|
||||
)
|
||||
assert result["max_tokens"] == 100
|
||||
assert "thinking" not in result
|
||||
assert "reasoning_effort" not in result
|
||||
assert result["extra_body"] == {"thinking": {"type": "disabled"}, "reasoning_effort": "low"}
|
||||
|
||||
|
||||
def test_existing_extra_body_is_kept_and_not_mutated(local_model_cost_map):
|
||||
from litellm.llms.zai.chat.transformation import ZAIChatConfig
|
||||
|
||||
caller_extra_body = {"already_here": True}
|
||||
result = ZAIChatConfig()._map_openai_params(
|
||||
non_default_params={"thinking": {"type": "disabled"}},
|
||||
optional_params={"extra_body": caller_extra_body},
|
||||
model="glm-4.7",
|
||||
drop_params=False,
|
||||
)
|
||||
assert result["extra_body"] == {"already_here": True, "thinking": {"type": "disabled"}}
|
||||
assert caller_extra_body == {"already_here": True}
|
||||
|
||||
|
||||
def test_reasoning_params_dropped_for_non_reasoning_model(monkeypatch):
|
||||
from litellm.utils import get_optional_params
|
||||
|
||||
monkeypatch.setattr(litellm, "supports_reasoning", lambda **kwargs: False)
|
||||
result = get_optional_params(
|
||||
model="glm-4-32b-0414-128k",
|
||||
custom_llm_provider="zai",
|
||||
thinking={"type": "disabled"},
|
||||
reasoning_effort="low",
|
||||
drop_params=True,
|
||||
)
|
||||
assert "thinking" not in result
|
||||
assert "reasoning_effort" not in result
|
||||
assert "thinking" not in result.get("extra_body", {})
|
||||
assert "reasoning_effort" not in result.get("extra_body", {})
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_thinking_and_reasoning_effort_reach_http_body(
|
||||
respx_mock, zai_thinking_response, monkeypatch, local_model_cost_map
|
||||
):
|
||||
monkeypatch.setenv("ZAI_API_KEY", "test-api-key")
|
||||
monkeypatch.setattr(litellm, "disable_aiohttp_transport", True)
|
||||
respx_mock.post("https://api.z.ai/api/paas/v4/chat/completions").respond(json=zai_thinking_response)
|
||||
|
||||
await litellm.acompletion(
|
||||
model="zai/glm-4.7",
|
||||
messages=[{"role": "user", "content": "hi"}],
|
||||
thinking={"type": "disabled"},
|
||||
reasoning_effort="low",
|
||||
)
|
||||
|
||||
body = _captured_body(respx_mock)
|
||||
assert body["thinking"] == {"type": "disabled"}
|
||||
assert body["reasoning_effort"] == "low"
|
||||
assert "extra_body" not in body
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue