mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-17 02:05:22 -04:00
fix: deepseek-r1 on Ollama returns HTTP 400 when tool schemas are sent (#1169)
* fix: exclude deepseek from local tool-calling keyword list
deepseek-r1 on Ollama returns HTTP 400 when tool schemas are sent.
The cloud API (api.deepseek.com) is already caught by the _API_HOSTS
check, so the generic 'deepseek' keyword match was only causing false
positives for local Ollama-served models.
* fix: add model no-tools blocklist and regression tests for deepseek-r1
The previous fix removed 'deepseek' from the keyword allow-list, but
_is_api_model is still True for localhost endpoints because 'localhost'
appears in _API_HOSTS — so the keyword change had no effect for Ollama.
Proper fix: add an explicit _model_no_tools blocklist ('deepseek-r1')
that overrides the endpoint URL check. The endpoint's supports_tools DB
flag still takes priority either way (True forces tools on, False forces
them off), so users can override per-endpoint when needed.
Also refined the deepseek allow-list: 'deepseek-v' and 'deepseek-chat'
cover the cloud models (v2, v3, chat) that do support tools, without
matching deepseek-r1 variants.
13 regression tests cover:
- deepseek-r1 on localhost/docker: no tools (was HTTP 400)
- deepseek-v3/chat on api.deepseek.com: tools enabled (no regression)
- endpoint_supports=True/False overrides both lists
- qwen/llama on localhost: unaffected
This commit is contained in:
+12
-2
@@ -1450,7 +1450,7 @@ async def stream_agent_loop(
|
|||||||
except Exception as _e:
|
except Exception as _e:
|
||||||
logger.debug(f"endpoint supports_tools lookup failed: {_e}")
|
logger.debug(f"endpoint supports_tools lookup failed: {_e}")
|
||||||
_model_supports_tools = any(kw in _model_lc for kw in (
|
_model_supports_tools = any(kw in _model_lc for kw in (
|
||||||
"deepseek", "gpt-4", "gpt-5", "gpt-o", "claude", "gemini", "gemma",
|
"gpt-4", "gpt-5", "gpt-o", "claude", "gemini", "gemma",
|
||||||
"qwen3", "qwen2.5", "mixtral", "mistral", "llama-3.1", "llama-3.2",
|
"qwen3", "qwen2.5", "mixtral", "mistral", "llama-3.1", "llama-3.2",
|
||||||
"llama-3.3", "llama-4",
|
"llama-3.3", "llama-4",
|
||||||
# Local-served models that follow OpenAI-style function calling
|
# Local-served models that follow OpenAI-style function calling
|
||||||
@@ -1458,10 +1458,20 @@ async def stream_agent_loop(
|
|||||||
# with the per-endpoint flag above.
|
# with the per-endpoint flag above.
|
||||||
"minimax", "kimi", "yi-", "phi-3", "phi-4", "command-r",
|
"minimax", "kimi", "yi-", "phi-3", "phi-4", "command-r",
|
||||||
"glm-4", "internlm", "hermes",
|
"glm-4", "internlm", "hermes",
|
||||||
|
# deepseek-v2/v3/chat support tools via the cloud API; deepseek-r1
|
||||||
|
# (reasoning model) does not — handled by the blocklist below.
|
||||||
|
"deepseek-v", "deepseek-chat",
|
||||||
|
))
|
||||||
|
# Models known to reject tool schemas at the Ollama/local level even when
|
||||||
|
# the endpoint URL would otherwise enable native function calling.
|
||||||
|
# The per-endpoint supports_tools flag (True/False) always takes priority
|
||||||
|
# and can override this list for users who know their setup.
|
||||||
|
_model_no_tools = any(kw in _model_lc for kw in (
|
||||||
|
"deepseek-r1",
|
||||||
))
|
))
|
||||||
if _endpoint_supports is True:
|
if _endpoint_supports is True:
|
||||||
_is_api_model = True
|
_is_api_model = True
|
||||||
elif _endpoint_supports is False:
|
elif _endpoint_supports is False or _model_no_tools:
|
||||||
_is_api_model = False
|
_is_api_model = False
|
||||||
else:
|
else:
|
||||||
_is_api_model = any(h in endpoint_url for h in _API_HOSTS) or _model_supports_tools
|
_is_api_model = any(h in endpoint_url for h in _API_HOSTS) or _model_supports_tools
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
"""Regression tests for the tool-support heuristic in stream_agent_loop.
|
||||||
|
|
||||||
|
Verifies two critical cases:
|
||||||
|
1. deepseek-r1 on a local Ollama endpoint must NOT enable native tool schemas
|
||||||
|
(Ollama returns HTTP 400 for these models when tools are sent).
|
||||||
|
2. api.deepseek.com must still be treated as tool-capable via the host
|
||||||
|
allow-list (_API_HOSTS), so cloud deepseek users keep working.
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
from src.agent_loop import _API_HOSTS
|
||||||
|
|
||||||
|
|
||||||
|
def _compute_is_api_model(model: str, endpoint_url: str, endpoint_supports=None) -> bool:
|
||||||
|
"""Replicate the heuristic from stream_agent_loop without side effects."""
|
||||||
|
model_lc = model.lower()
|
||||||
|
|
||||||
|
model_supports_tools = any(kw in model_lc for kw in (
|
||||||
|
"gpt-4", "gpt-5", "gpt-o", "claude", "gemini", "gemma",
|
||||||
|
"qwen3", "qwen2.5", "mixtral", "mistral", "llama-3.1", "llama-3.2",
|
||||||
|
"llama-3.3", "llama-4",
|
||||||
|
"minimax", "kimi", "yi-", "phi-3", "phi-4", "command-r",
|
||||||
|
"glm-4", "internlm", "hermes",
|
||||||
|
"deepseek-v", "deepseek-chat",
|
||||||
|
))
|
||||||
|
model_no_tools = any(kw in model_lc for kw in (
|
||||||
|
"deepseek-r1",
|
||||||
|
))
|
||||||
|
|
||||||
|
if endpoint_supports is True:
|
||||||
|
return True
|
||||||
|
if endpoint_supports is False or model_no_tools:
|
||||||
|
return False
|
||||||
|
return any(h in endpoint_url for h in _API_HOSTS) or model_supports_tools
|
||||||
|
|
||||||
|
|
||||||
|
class TestDeepSeekToolSupport:
|
||||||
|
# --- local Ollama cases (must NOT get tool schemas) ---
|
||||||
|
|
||||||
|
def test_deepseek_r1_7b_local_ollama_no_tools(self):
|
||||||
|
result = _compute_is_api_model(
|
||||||
|
"deepseek-r1:7b", "http://localhost:11434/v1"
|
||||||
|
)
|
||||||
|
assert result is False, (
|
||||||
|
"deepseek-r1:7b on Ollama must not enable tool schemas "
|
||||||
|
"(Ollama returns HTTP 400 for this model)"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_deepseek_r1_14b_local_no_tools(self):
|
||||||
|
assert _compute_is_api_model("deepseek-r1:14b", "http://localhost:11434/v1") is False
|
||||||
|
|
||||||
|
def test_deepseek_r1_70b_local_no_tools(self):
|
||||||
|
assert _compute_is_api_model("deepseek-r1:70b", "http://127.0.0.1:11434/v1") is False
|
||||||
|
|
||||||
|
def test_deepseek_r1_via_docker_no_tools(self):
|
||||||
|
assert _compute_is_api_model(
|
||||||
|
"deepseek-r1:7b", "http://host.docker.internal:11434/v1"
|
||||||
|
) is False
|
||||||
|
|
||||||
|
# --- cloud API cases (must still get tool schemas) ---
|
||||||
|
|
||||||
|
def test_deepseek_cloud_api_gets_tools(self):
|
||||||
|
result = _compute_is_api_model(
|
||||||
|
"deepseek-chat", "https://api.deepseek.com/v1"
|
||||||
|
)
|
||||||
|
assert result is True, (
|
||||||
|
"api.deepseek.com must be treated as tool-capable via _API_HOSTS"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_deepseek_v3_cloud_gets_tools(self):
|
||||||
|
assert _compute_is_api_model("deepseek-v3", "https://api.deepseek.com/v1") is True
|
||||||
|
|
||||||
|
def test_deepseek_v2_cloud_gets_tools(self):
|
||||||
|
assert _compute_is_api_model("deepseek-v2.5", "https://api.deepseek.com/v1") is True
|
||||||
|
|
||||||
|
# --- endpoint_supports override takes priority ---
|
||||||
|
|
||||||
|
def test_endpoint_supports_true_overrides_blocklist(self):
|
||||||
|
"""A user who explicitly sets supports_tools=True on their endpoint
|
||||||
|
can force tool schemas even for deepseek-r1 (e.g. custom server)."""
|
||||||
|
result = _compute_is_api_model(
|
||||||
|
"deepseek-r1:7b", "http://localhost:11434/v1", endpoint_supports=True
|
||||||
|
)
|
||||||
|
assert result is True
|
||||||
|
|
||||||
|
def test_endpoint_supports_false_overrides_cloud(self):
|
||||||
|
"""supports_tools=False on an endpoint gates even cloud APIs."""
|
||||||
|
result = _compute_is_api_model(
|
||||||
|
"deepseek-chat", "https://api.deepseek.com/v1", endpoint_supports=False
|
||||||
|
)
|
||||||
|
assert result is False
|
||||||
|
|
||||||
|
# --- other local models unaffected ---
|
||||||
|
|
||||||
|
def test_qwen_local_still_gets_tools(self):
|
||||||
|
assert _compute_is_api_model("qwen2.5:14b", "http://localhost:11434/v1") is True
|
||||||
|
|
||||||
|
def test_llama_local_gets_tools_via_host(self):
|
||||||
|
assert _compute_is_api_model("llama3.2:3b", "http://localhost:11434/v1") is True
|
||||||
|
|
||||||
|
|
||||||
|
class TestApiHostsContainsDeepSeek:
|
||||||
|
def test_api_deepseek_com_in_api_hosts(self):
|
||||||
|
assert "api.deepseek.com" in _API_HOSTS
|
||||||
|
|
||||||
|
def test_deepseek_com_in_api_hosts(self):
|
||||||
|
assert "deepseek.com" in _API_HOSTS
|
||||||
Reference in New Issue
Block a user