Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions agent/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -730,8 +730,15 @@ def _forward_delta(_event_type: str, data: dict[str, Any]) -> None:
except ModelError as exc:
text = str(exc).lower()
unsupported_reasoning = effort and (
"reasoning_effort" in text
and ("unsupported_parameter" in text or "unknown" in text)
(
"reasoning_effort" in text
and ("unsupported_parameter" in text or "unknown" in text)
)
# Ollama's OpenAI-compat shim rejects reasoning on models that
# don't support it (e.g. llama3.2) with a message like
# `"llama3.2" does not support thinking`, which never
# mentions `reasoning_effort` at all.
or ("thinking" in text and "does not support" in text)
)
if not unsupported_reasoning:
raise
Expand Down
35 changes: 35 additions & 0 deletions tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,41 @@ def fake_http_json(url, method, headers, payload=None, timeout_sec=90): # type:
self.assertIn("reasoning_effort", calls[0])
self.assertNotIn("reasoning_effort", calls[1])

def test_openai_retries_without_reasoning_for_ollama_thinking_error(self) -> None:
"""Ollama's OpenAI-compat shim rejects reasoning with a message that
never mentions `reasoning_effort`, e.g. for llama3.2 (issue #20)."""
calls: list[dict] = []

def fake_http_json(url, method, headers, payload=None, timeout_sec=90): # type: ignore[no-untyped-def]
calls.append(dict(payload or {}))
if len(calls) == 1:
raise ModelError(
"HTTP 400 calling http://localhost:11434/v1/chat/completions: "
"{\"error\":{\"message\":\"\\\"llama3.2\\\" does not support "
"thinking\",\"type\":\"api_error\",\"param\":null,\"code\":null}}"
)
return {
"choices": [
{
"message": {"content": "ok", "tool_calls": None},
"finish_reason": "stop",
}
]
}

with patch("agent.model._http_stream_sse", mock_openai_stream(fake_http_json)):
model = OpenAICompatibleModel(
model="llama3.2",
api_key="k",
base_url="http://localhost:11434/v1",
reasoning_effort="high",
)
conv = model.create_conversation("system", "user msg")
turn = model.complete(conv)
self.assertEqual(turn.text, "ok")
self.assertIn("reasoning_effort", calls[0])
self.assertNotIn("reasoning_effort", calls[1])

def test_anthropic_retries_without_thinking_when_unsupported(self) -> None:
calls: list[dict] = []

Expand Down