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

0open issues
0open PRs
5items grouped
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

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #53574 fix(agent): restrict Ollama/GLM stop-to-length heuristic to explicit Ollama backends (#13971)

    merged to main

    files: run_agent.py, tests/run_agent/test_run_agent.py

    Show 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

  • #14789 fix(agent): restrict _is_ollama_glm_backend to explicit Ollama indicators · superseded by another PR → #53574, #13971
  • #15752 fix: GLM models via API gateways incorrectly treated as Ollama backends · superseded by another PR → #53574, #13971
Closed issues · 2
  • Closed#13971[Bug]: `_is_ollama_glm_backend()` false positive causes spurious truncation continuation with local API proxies
  • Closed#15751GLM models accessed via API gateways are incorrectly treated as Ollama backends
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.00 means 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 Lab

At a glance

Subsystem
agent
Merged PRs
#53574
Closed issues
2
Attempts
2 PRs closed without merging
Fix link checked
27 Jun 2026

For your agent

The same facts, typed.