Ollama/GLM backend detection false positive fix
NousResearch/hermes-agent · cluster 13971 · agent
Data as of
Nothing open on GitHub. Fix merged in #53574. Check it covers your case.
The short answer
- Known?
- Yes. 5 related items grouped here, 2 issues already closed.
- Fix event
- A fix was merged to main in PR #53574. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
partially resolved, deciding PR #53574. That is separate from the fix event. - Still open
- Nothing open on GitHub.
A heuristic for identifying Ollama/GLM backends was triggering incorrectly for non-Ollama API proxies and gateways, causing spurious truncation continuation behavior. Fixes narrow the detection logic to explicit Ollama indicators only.
Open issues0
Open PRs0
Fixes on record
-
#53574 fix(agent): restrict Ollama/GLM stop-to-length heuristic to explicit Ollama backends (#13971)
files:
run_agent.py,tests/run_agent/test_run_agent.pyShow the diff
-
run_agent.py@@ -1367,14 +1367,26 @@ def _has_natural_response_ending(content: str) -> bool: return False def _is_ollama_glm_backend(self) -> bool: - """Detect the narrow backend family affected by Ollama/GLM stop misreports.""" + """Detect Ollama-hosted GLM models affected by stop misreports. + + Ollama can misreport truncated output as finish_reason='stop'. + Detection relies on explicit Ollama signatures: + - Port 11434 (Ollama default) + - "ollama" in the base URL (e.g. ollama.local, /ollama/ path) + - provider explicitly set to "ollama" + + Crucially it does NOT match arbitrary local/private endpoints + (LiteLLM/sglang/vLLM/LM Studio proxies, Tailscale boxes), which + report finish_reason correctly and were the source of #13971's + false-positive truncation continuations. + """ model_lower = (self.model or "").lower() provider_lower = (self.provider or "").lower() if "glm" not in model_lower and provider_lower != "zai": return False if "ollama" in self._base_url_lower or ":11434" in self._base_url_lower: return True - return bool(self.base_url and is_local_endpoint(self.base_url)) + return provider_lower == "ollama" def _should_treat_stop_as_truncated( self, -
tests/run_agent/test_run_agent.py@@ -4526,6 +4526,159 @@ def test_non_ollama_stop_without_terminal_boundary_does_not_continue(self, agent assert result["api_calls"] == 2 assert result["final_response"] == "Based on the search results, the best next" + def test_sglang_glm_stop_without_terminal_boundary_does_not_continue(self, agent): + """sglang/vLLM-hosted GLM models report finish_reason correctly. + + The stop->length workaround must NOT apply to non-Ollama local + servers that expose OpenAI-compatible /v1 endpoints (sglang, vLLM, + LM Studio, etc.). A Chinese-text response ending without ASCII + punctuation should not be reclassified as truncated. + """ + self._setup_agent(agent) + agent.base_url = "http://127.0.0.1:60000/v1" + agent._base_url_lower = agent.base_url.lower() + agent.model = "glm-5-fp8" + + tool_turn = _mock_response( + content="", + finish_reason="tool_calls", + tool_calls=[_mock_tool_call(name="web_search", arguments="{}", call_id="c1")], + ) + # Response ends with Chinese character (no ASCII punctuation) — NOT truncated + normal_stop = _mock_response( + content="根据搜索结果,建议修改配置", + finish_reason="stop", + ) + agent.client.chat.completions.create.side_effect = [tool_turn, normal_stop] + + with ( + patch("run_agent.handle_function_call", return_value="search result"), + patch.object(agent, "_persist_session"), + patch.object(agent, "_save_trajectory"), + patch.object(agent, "_cleanup_task_resources"), + ): + result = agent.run_conversation("hello") + + assert result["completed"] is True + assert result["api_calls"] == 2 + assert result["final_response"] == "根据搜索结果,建议修改配置" + + def test_ollama_glm_on_port_11434_still_triggers_heuristic(self, agent): + """Ollama on port 11434 should still trigger the stop->length heuristic.""" + self._setup_agent(agent) + agent.base_url = "http://localhost:11434/v1" + agent._base_url_lower = agent.base_url.lower() + agent.model = "glm-5.1:cloud" + + tool_turn = _mock_response( + content="", + finish_reason="tool_calls", + tool_calls=[_mock_tool_call(name="web_search", arguments="{}", call_id="c1")], + ) + misreported_stop = _mock_response( + content="Based on the search results, the best next", + finish_reason="stop", + ) + continued = _mock_response( + content=" step is to update the config.", + finish_reason="stop", + ) + agent.client.chat.completions.create.side_effect = [ + tool_turn, + misreported_stop, + continued, + ] + + with ( + patch("run_agent.handle_function_call", return_value="search result"), + patch.object(agent, "_persist_session"), + patch.object(agent, "_save_trajectory"), + patch.object(agent, "_cleanup_task_resources"), + ): + result = agent.run_conversation("hello") + + assert result["completed"] is True + assert result["api_calls"] == 3 + third_call_messages = agent.client.chat.completions.create.call_args_list[2].kwargs["messages"] + assert "truncated by the output length limit" in third_call_messages[-1]["content"] + + def test_ollama_provider_without_url_signature_still_triggers_heuristic(self, agent): + """provider='ollama' triggers the heuristic even when the base URL + carries no ``ollama``/``:11434`` signature (e.g. a reverse proxy).""" + self._setup_agent(agent) + agent.base_url = "http://my-proxy.internal:9000/v1" + agent._base_url_lower = agent.base_url.lower() + agent.provider = "ollama" + agent.model = "glm-5.1:cloud" + + tool_turn = _mock_response( + content="", + finish_reason="tool_calls", + tool_calls=[_mock_tool_call(name="web_search", arguments="{}", call_id="c1")], + ) + misreported_stop = _mock_response( + content="Based on the search results, the best next", + finish_reason="stop", + ) + continued = _mock_response( + content=" step is to update the config.", + finish_reason="stop", + ) + agent.client.chat.completions.create.side_effect = [ + tool_turn, + misreported_stop, + continued, + ] + + with ( + patch("run_agent.handle_function_call", return_value="search result"), + patch.object(agent, "_persist_session"), + patch.object(agent, "_save_trajectory"), + patch.object(agent, "_cleanup_task_resources"), + ): + result = agent.run_conversation("hello") + + assert result["completed"] is True + assert result["api_calls"] == 3 + third_call_messages = agent.client.chat.completions.create.call_args_list[2].kwargs["messages"] + assert "truncated by the output length limit" in third_call_messages[-1]["content"] + + def test_zai_via_local_proxy_does_not_trigger_heuristic(self, agent): + """Issue #13971: a local LiteLLM proxy forwarding to remote Z.AI + must NOT be treated as an Ollama backend. provider='zai' on + localhost:8000 with no ollama/:11434 signature reports stop + correctly and the response should be delivered as-is.""" + self._setup_agent(agent) + agent.base_url = "http://localhost:8000/v1" + agent._base_url_lower = agent.base_url.lower() + agent.provider = "zai" + agent.model = "glm-5-turbo" + + tool_turn = _mock_response( + content="", + finish_reason="tool_calls", + tool_calls=[_mock_tool_call(name="web_search", arguments="{}", call_id="c1")], + ) + # Complete response ending without ASCII punctuation — must NOT be + # reclassified as truncated. + normal_stop = _mock_response( + content="Done — the config has been updated", + finish_reason="stop", + ) + agent.client.chat.completions.create.side_effect = [tool_turn, normal_stop] + + with ( + patch("run_agent.handle_function_call", return_value="search result"), + patch.object(agent, "_persist_session"), + patch.object(agent, "_save_trajectory"), + patch.object(agent, "_cleanup_task_resources"), + ): + result = agent.run_conversation("hello") + + assert result["completed"] is True + assert result["api_calls"] == 2 + assert result["final_response"] == "Done — the config has been updated" + def test_length_thinking_exhausted_skips_continuation(self, agent): """When finish_reason='length' but content is only thinking, skip retries.""" self._setup_agent(agent)
-
2 PRs closed without merging
From the close comments: superseded by another PR 2
Closed issues · 2
How this cluster was grouped
-
Link evidence (
structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference. -
Pairwise link density
0.44: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the LabAt a glance
- Subsystem
- agent
- Merged PRs
- #53574
- Closed issues
- 2
- Attempts
- 2 PRs closed without merging
- Fix link checked
- 27 Jun 2026