feat(models): add vLLM provider support (#1860)

support for vLLM 0.19.0 OpenAI-compatible chat endpoints and fixes the Qwen reasoning toggle so flash mode can actually disable thinking.

Co-authored-by: NmanQAQ <normangyao@qq.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
NmanQAQ
2026-04-06 15:18:34 +08:00
committed by GitHub
parent 5fd2c581f6
commit dd30e609f7
8 changed files with 534 additions and 5 deletions
+57
View File
@@ -604,6 +604,63 @@ def test_codex_provider_strips_unsupported_max_tokens(monkeypatch):
assert "max_tokens" not in FakeChatModel.captured_kwargs
def test_thinking_disabled_vllm_chat_template_format(monkeypatch):
wte = {"extra_body": {"chat_template_kwargs": {"thinking": True}}}
model = _make_model(
"vllm-qwen",
use="deerflow.models.vllm_provider:VllmChatModel",
supports_thinking=True,
when_thinking_enabled=wte,
)
model.extra_body = {"top_k": 20}
cfg = _make_app_config([model])
_patch_factory(monkeypatch, cfg)
captured: dict = {}
class CapturingModel(FakeChatModel):
def __init__(self, **kwargs):
captured.update(kwargs)
BaseChatModel.__init__(self, **kwargs)
monkeypatch.setattr(factory_module, "resolve_class", lambda path, base: CapturingModel)
factory_module.create_chat_model(name="vllm-qwen", thinking_enabled=False)
assert captured.get("extra_body") == {"top_k": 20, "chat_template_kwargs": {"thinking": False}}
assert captured.get("reasoning_effort") is None
def test_thinking_disabled_vllm_enable_thinking_format(monkeypatch):
wte = {"extra_body": {"chat_template_kwargs": {"enable_thinking": True}}}
model = _make_model(
"vllm-qwen-enable",
use="deerflow.models.vllm_provider:VllmChatModel",
supports_thinking=True,
when_thinking_enabled=wte,
)
model.extra_body = {"top_k": 20}
cfg = _make_app_config([model])
_patch_factory(monkeypatch, cfg)
captured: dict = {}
class CapturingModel(FakeChatModel):
def __init__(self, **kwargs):
captured.update(kwargs)
BaseChatModel.__init__(self, **kwargs)
monkeypatch.setattr(factory_module, "resolve_class", lambda path, base: CapturingModel)
factory_module.create_chat_model(name="vllm-qwen-enable", thinking_enabled=False)
assert captured.get("extra_body") == {
"top_k": 20,
"chat_template_kwargs": {"enable_thinking": False},
}
assert captured.get("reasoning_effort") is None
def test_openai_responses_api_settings_are_passed_to_chatopenai(monkeypatch):
model = ModelConfig(
name="gpt-5-responses",
+138
View File
@@ -0,0 +1,138 @@
from __future__ import annotations
from langchain_core.messages import AIMessage, AIMessageChunk, HumanMessage
from deerflow.models.vllm_provider import VllmChatModel
def _make_model() -> VllmChatModel:
return VllmChatModel(
model="Qwen/QwQ-32B",
api_key="dummy",
base_url="http://localhost:8000/v1",
)
def test_vllm_provider_restores_reasoning_in_request_payload():
model = _make_model()
payload = model._get_request_payload(
[
AIMessage(
content="",
tool_calls=[{"name": "bash", "args": {"cmd": "pwd"}, "id": "tool-1", "type": "tool_call"}],
additional_kwargs={"reasoning": "Need to inspect the workspace first."},
),
HumanMessage(content="Continue"),
]
)
assistant_message = payload["messages"][0]
assert assistant_message["role"] == "assistant"
assert assistant_message["reasoning"] == "Need to inspect the workspace first."
assert assistant_message["tool_calls"][0]["function"]["name"] == "bash"
def test_vllm_provider_normalizes_legacy_thinking_kwarg_to_enable_thinking():
model = VllmChatModel(
model="qwen3",
api_key="dummy",
base_url="http://localhost:8000/v1",
extra_body={"chat_template_kwargs": {"thinking": True}},
)
payload = model._get_request_payload([HumanMessage(content="Hello")])
assert payload["extra_body"]["chat_template_kwargs"] == {"enable_thinking": True}
def test_vllm_provider_preserves_explicit_enable_thinking_kwarg():
model = VllmChatModel(
model="qwen3",
api_key="dummy",
base_url="http://localhost:8000/v1",
extra_body={"chat_template_kwargs": {"enable_thinking": False, "foo": "bar"}},
)
payload = model._get_request_payload([HumanMessage(content="Hello")])
assert payload["extra_body"]["chat_template_kwargs"] == {
"enable_thinking": False,
"foo": "bar",
}
def test_vllm_provider_preserves_reasoning_in_chat_result():
model = _make_model()
result = model._create_chat_result(
{
"model": "Qwen/QwQ-32B",
"choices": [
{
"message": {
"role": "assistant",
"content": "42",
"reasoning": "I compared the two numbers directly.",
},
"finish_reason": "stop",
}
],
"usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
}
)
message = result.generations[0].message
assert message.additional_kwargs["reasoning"] == "I compared the two numbers directly."
assert message.additional_kwargs["reasoning_content"] == "I compared the two numbers directly."
def test_vllm_provider_preserves_reasoning_in_streaming_chunks():
model = _make_model()
chunk = model._convert_chunk_to_generation_chunk(
{
"model": "Qwen/QwQ-32B",
"choices": [
{
"delta": {
"role": "assistant",
"reasoning": "First, call the weather tool.",
"content": "Calling tool...",
},
"finish_reason": None,
}
],
},
AIMessageChunk,
{},
)
assert chunk is not None
assert chunk.message.additional_kwargs["reasoning"] == "First, call the weather tool."
assert chunk.message.additional_kwargs["reasoning_content"] == "First, call the weather tool."
assert chunk.message.content == "Calling tool..."
def test_vllm_provider_preserves_empty_reasoning_values_in_streaming_chunks():
model = _make_model()
chunk = model._convert_chunk_to_generation_chunk(
{
"model": "Qwen/QwQ-32B",
"choices": [
{
"delta": {
"role": "assistant",
"reasoning": "",
"content": "Still replying...",
},
"finish_reason": None,
}
],
},
AIMessageChunk,
{},
)
assert chunk is not None
assert "reasoning" in chunk.message.additional_kwargs
assert chunk.message.additional_kwargs["reasoning"] == ""
assert "reasoning_content" not in chunk.message.additional_kwargs
assert chunk.message.content == "Still replying..."