# NousResearch/hermes-agent · Ollama/GLM backend detection false positive fix

## Open now

- 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.

## TL;DR

- 5 member(s): 0 open issue(s), 2 closed issue(s), 0 open PR(s), 1 merged PR(s), 2 PR(s) closed without merging.
- A fix was merged to main in PR #53574. Verify it addresses your case.
- **Recommended:** read PR #53574 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-53574`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.44`: the share of member 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.

## Closed / duplicate issues

- #13971: \[Bug\]: \`_is_ollama_glm_backend()\` false positive causes spurious truncation continuation with local API proxies
- #15751: GLM models accessed via API gateways are incorrectly treated as Ollama backends

## Prior & competing PRs

- **#53574: fix(agent): restrict Ollama/GLM stop-to-length heuristic to explicit Ollama backends (#13971)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `run_agent.py`, `tests/run_agent/test_run_agent.py`
  - fix hunks:
  - `run_agent.py`

    ```diff
    @@ -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`

    ```diff
    @@ -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)
    ```
- **#14789: fix(agent): restrict _is_ollama_glm_backend to explicit Ollama indicators**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#53574`, `#13971`
- **#15752: fix: GLM models via API gateways incorrectly treated as Ollama backends**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#53574`, `#13971`

## Fix status

- Merged to main in PR #53574 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-06-27`.
- Subsystem: `agent`
