From 7c60e5339c8c491543a29f4960c423495574bb40 Mon Sep 17 00:00:00 2001 From: Skyler Forge Date: Sat, 18 Apr 2026 23:10:57 -0700 Subject: [PATCH] fix(agent): also send chat_template_kwargs.enable_thinking=false for llama.cpp/vLLM The custom-provider thinking opt-out at run_agent.py only sets extra_body.think=false, which is Ollama's field. llama.cpp and vLLM ignore `think` and look for `chat_template_kwargs.enable_thinking`. As a result, setting `agent.reasoning_effort: none` on a custom provider pointed at llama.cpp or vLLM has no effect: thinking-capable models (Qwen3, etc.) still emit blocks, and any subsequent assistant- prefill turn (e.g. Hermes' thinking-only continuation path) hits llama.cpp's prefill-incompatibility 400. Send both fields. Backends ignore unknown body fields by convention, so this is additive for Ollama. Tests added alongside the existing custom-provider think tests. --- run_agent.py | 17 +++++++++----- tests/run_agent/test_run_agent.py | 38 +++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/run_agent.py b/run_agent.py index 010648809823..64276bba417b 100644 --- a/run_agent.py +++ b/run_agent.py @@ -7134,17 +7134,22 @@ def _build_api_kwargs(self, api_messages: list) -> dict: options["num_ctx"] = self._ollama_num_ctx extra_body["options"] = options - # Ollama / custom provider: pass think=false when reasoning is disabled. - # Ollama does not recognise the OpenRouter-style `reasoning` extra_body - # field, so we use its native `think` parameter instead. - # This prevents thinking-capable models (Qwen3, etc.) from generating - # blocks and producing empty-response errors when the user has - # set reasoning_effort: none. + # Custom provider: opt out of thinking when reasoning is disabled. + # Different OpenAI-compat backends use different field names and + # ignore unknown fields, so send all the common ones: + # * Ollama: think: false + # * llama.cpp / vLLM (Qwen): chat_template_kwargs.enable_thinking: false + # Without this, thinking-capable models (Qwen3, etc.) emit + # blocks and trigger llama.cpp's prefill-incompatibility 400 on the + # next turn (see llama.cpp tools/server/server-common.cpp). if self.provider == "custom" and self.reasoning_config and isinstance(self.reasoning_config, dict): _effort = (self.reasoning_config.get("effort") or "").strip().lower() _enabled = self.reasoning_config.get("enabled", True) if _effort == "none" or _enabled is False: extra_body["think"] = False + _ctk = extra_body.setdefault("chat_template_kwargs", {}) + if isinstance(_ctk, dict): + _ctk["enable_thinking"] = False if self._is_qwen_portal(): extra_body["vl_high_resolution_images"] = True diff --git a/tests/run_agent/test_run_agent.py b/tests/run_agent/test_run_agent.py index d30445cf459b..3bae47578ff1 100644 --- a/tests/run_agent/test_run_agent.py +++ b/tests/run_agent/test_run_agent.py @@ -1088,6 +1088,44 @@ def test_non_custom_provider_unaffected(self, agent): kwargs = agent._build_api_kwargs(messages) assert kwargs.get("extra_body", {}).get("think") is None + def test_custom_chat_template_kwargs_on_effort_none(self, agent): + """Custom provider with effort=none should also inject + chat_template_kwargs.enable_thinking=false (llama.cpp / vLLM).""" + agent.provider = "custom" + agent.base_url = "http://localhost:11434/v1" + agent._base_url_lower = agent.base_url.lower() + agent.reasoning_config = {"effort": "none"} + messages = [{"role": "user", "content": "hi"}] + kwargs = agent._build_api_kwargs(messages) + extra = kwargs.get("extra_body", {}) + assert extra.get("think") is False + assert extra.get("chat_template_kwargs", {}).get("enable_thinking") is False + + def test_custom_chat_template_kwargs_on_enabled_false(self, agent): + """Custom provider with enabled=false should also inject + chat_template_kwargs.enable_thinking=false.""" + agent.provider = "custom" + agent.base_url = "http://localhost:11434/v1" + agent._base_url_lower = agent.base_url.lower() + agent.reasoning_config = {"enabled": False} + messages = [{"role": "user", "content": "hi"}] + kwargs = agent._build_api_kwargs(messages) + extra = kwargs.get("extra_body", {}) + assert extra.get("think") is False + assert extra.get("chat_template_kwargs", {}).get("enable_thinking") is False + + def test_custom_no_chat_template_kwargs_when_reasoning_enabled(self, agent): + """Custom provider with reasoning enabled should NOT inject + chat_template_kwargs.enable_thinking=false.""" + agent.provider = "custom" + agent.base_url = "http://localhost:11434/v1" + agent._base_url_lower = agent.base_url.lower() + agent.reasoning_config = {"enabled": True, "effort": "medium"} + messages = [{"role": "user", "content": "hi"}] + kwargs = agent._build_api_kwargs(messages) + extra = kwargs.get("extra_body", {}) + assert extra.get("chat_template_kwargs") is None + class TestBuildAssistantMessage: