irregular6612 commited on
Commit
6de0428
·
1 Parent(s): 78a6863

fix(llm): local Ollama provider falls back to no-think when model lacks thinking support

Browse files
proteus/providers/ollama_local.py CHANGED
@@ -19,6 +19,7 @@ import os
19
 
20
  import httpx
21
 
 
22
  from proteus.providers.ollama_cloud import OllamaCloudProvider
23
 
24
  logger = logging.getLogger(__name__)
@@ -87,3 +88,31 @@ class OllamaLocalProvider(OllamaCloudProvider):
87
  "OllamaLocalProvider targeting %s at %s (thinking=%s, effort=%s)",
88
  model, self._base_url, enable_thinking, reasoning_effort,
89
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
  import httpx
21
 
22
+ from proteus.providers.base import CompletionResult
23
  from proteus.providers.ollama_cloud import OllamaCloudProvider
24
 
25
  logger = logging.getLogger(__name__)
 
88
  "OllamaLocalProvider targeting %s at %s (thinking=%s, effort=%s)",
89
  model, self._base_url, enable_thinking, reasoning_effort,
90
  )
91
+
92
+ def complete(
93
+ self,
94
+ messages: list[dict[str, str]],
95
+ temperature: float = 0.7,
96
+ max_tokens: int = 4096,
97
+ ) -> CompletionResult:
98
+ """Same native ``/api/chat`` call as the cloud parent, but tolerant of
99
+ models that don't support the ``think`` field.
100
+
101
+ ``reasoning_effort`` defaults to ``"low"`` for gpt-oss; a non-reasoning
102
+ local model (e.g. plain qwen/llama) answers Ollama's ``think`` with a
103
+ ``400 ... does not support thinking``. On that specific error we disable
104
+ ``think`` for the rest of this provider's life and retry once, so the
105
+ same toggle works across reasoning and non-reasoning local models.
106
+ """
107
+ try:
108
+ return super().complete(messages, temperature, max_tokens)
109
+ except httpx.HTTPStatusError as exc:
110
+ resp = exc.response
111
+ sent_think = self._reasoning_effort is not None or self._enable_thinking is not None
112
+ if (resp is not None and resp.status_code == 400 and sent_think
113
+ and "thinking" in (resp.text or "").lower()):
114
+ logger.info("model %r does not support thinking; disabling and retrying", self._model)
115
+ self._reasoning_effort = None
116
+ self._enable_thinking = None
117
+ return super().complete(messages, temperature, max_tokens)
118
+ raise
tests/providers/test_ollama_local.py CHANGED
@@ -50,6 +50,31 @@ def test_complete_sends_native_payload_no_auth_and_parses_thinking():
50
  assert res.input_tokens == 11 and res.output_tokens == 7
51
 
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  def test_vanilla_agent_plays_a_move_via_local_provider():
54
  # End-to-end: the LLM-play agent parses a valid action from a gpt-oss-style reply.
55
  from proteus.game.agents.vanilla import VanillaAgent
 
50
  assert res.input_tokens == 11 and res.output_tokens == 7
51
 
52
 
53
+ def test_think_fallback_when_model_does_not_support_thinking():
54
+ # gpt-oss accepts think="low"; a plain (non-reasoning) local model returns a
55
+ # 400 "does not support thinking". The provider must drop think and retry.
56
+ calls = []
57
+
58
+ def handler(request: httpx.Request) -> httpx.Response:
59
+ import json
60
+ body = json.loads(request.content)
61
+ calls.append(body)
62
+ if "think" in body:
63
+ return httpx.Response(400, json={"error": '"qwen" does not support thinking'})
64
+ return httpx.Response(200, json={
65
+ "message": {"content": "ACTION: stay"}, "prompt_eval_count": 3, "eval_count": 2,
66
+ })
67
+
68
+ p = _provider_with_mock(handler, model="qwen2.5:0.5b") # reasoning_effort defaults to "low"
69
+ res = p.complete([{"role": "user", "content": "move"}])
70
+ assert "ACTION: stay" in res.text
71
+ assert len(calls) == 2 # first with think (400), retry without
72
+ assert "think" in calls[0] and "think" not in calls[1]
73
+ # think stays disabled for subsequent calls (no repeated 400 round-trip)
74
+ p.complete([{"role": "user", "content": "again"}])
75
+ assert "think" not in calls[2]
76
+
77
+
78
  def test_vanilla_agent_plays_a_move_via_local_provider():
79
  # End-to-end: the LLM-play agent parses a valid action from a gpt-oss-style reply.
80
  from proteus.game.agents.vanilla import VanillaAgent