mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-15 14:22:43 +00:00
fix(model_metadata): widen localhost->IPv4 rewrite to all sibling probe sites
#37595 fixed the Windows dual-stack IPv6 timeout only inside detect_local_server_type. The same 2s-per-probe penalty existed at every other helper that builds a probe URL from base_url. Extract the rewrite into _localhost_to_ipv4() and apply it at: - query_ollama_num_ctx - query_ollama_supports_vision - _query_ollama_api_show (server_url derivation) - _query_local_context_length (server root + LM Studio native URL) Tests cover the helper's URL forms, non-localhost passthrough, and that the ollama probes actually POST to 127.0.0.1.
This commit is contained in:
parent
040e30aa72
commit
20cb385328
3 changed files with 60 additions and 10 deletions
|
|
@ -636,6 +636,24 @@ def is_local_endpoint(base_url: str) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def _localhost_to_ipv4(url: str) -> str:
|
||||
"""Rewrite a ``localhost`` host to ``127.0.0.1`` in a probe URL.
|
||||
|
||||
On Windows dual-stack machines, httpx resolves ``localhost`` to ``::1``
|
||||
first and pays a ~2s IPv6 connect timeout before falling back to IPv4
|
||||
when the local server only listens on IPv4 (LM Studio, Ollama defaults).
|
||||
Probing the IPv4 loopback directly skips that penalty. Non-localhost
|
||||
URLs pass through unchanged.
|
||||
"""
|
||||
if not url:
|
||||
return url
|
||||
url = url.replace("://localhost:", "://127.0.0.1:")
|
||||
url = url.replace("://localhost/", "://127.0.0.1/")
|
||||
if url.endswith("://localhost"):
|
||||
url = url[:-len("localhost")] + "127.0.0.1"
|
||||
return url
|
||||
|
||||
|
||||
def detect_local_server_type(base_url: str, api_key: str = "") -> Optional[str]:
|
||||
"""Detect which local server is running at base_url by probing known endpoints.
|
||||
|
||||
|
|
@ -652,10 +670,7 @@ def detect_local_server_type(base_url: str, api_key: str = "") -> Optional[str]:
|
|||
# Resolve localhost to IPv4 to avoid 2s IPv6 timeout on Windows dual-stack.
|
||||
# Applied to ``normalized`` before deriving server/LM Studio URLs AND
|
||||
# before the cache lookup, so localhost and 127.0.0.1 share a cache entry.
|
||||
normalized = normalized.replace("://localhost:", "://127.0.0.1:")
|
||||
normalized = normalized.replace("://localhost/", "://127.0.0.1/")
|
||||
if normalized.endswith("://localhost"):
|
||||
normalized = normalized[:-len("localhost")] + "127.0.0.1"
|
||||
normalized = _localhost_to_ipv4(normalized)
|
||||
|
||||
server_url = normalized
|
||||
if server_url.endswith("/v1"):
|
||||
|
|
@ -1393,7 +1408,7 @@ def query_ollama_num_ctx(model: str, base_url: str, api_key: str = "") -> Option
|
|||
import httpx
|
||||
|
||||
bare_model = _strip_provider_prefix(model)
|
||||
server_url = base_url.rstrip("/")
|
||||
server_url = _localhost_to_ipv4(base_url.rstrip("/"))
|
||||
if server_url.endswith("/v1"):
|
||||
server_url = server_url[:-3]
|
||||
|
||||
|
|
@ -1454,7 +1469,7 @@ def query_ollama_supports_vision(model: str, base_url: str, api_key: str = "") -
|
|||
except Exception:
|
||||
return None
|
||||
|
||||
server_url = base_url.rstrip("/")
|
||||
server_url = _localhost_to_ipv4(base_url.rstrip("/"))
|
||||
if server_url.endswith("/v1"):
|
||||
server_url = server_url[:-3]
|
||||
|
||||
|
|
@ -1531,7 +1546,7 @@ def _query_ollama_api_show_uncached(model: str, base_url: str, api_key: str = ""
|
|||
"""Uncached body of ``_query_ollama_api_show`` — one POST to ``/api/show``."""
|
||||
import httpx
|
||||
|
||||
server_url = base_url.rstrip("/")
|
||||
server_url = _localhost_to_ipv4(base_url.rstrip("/"))
|
||||
if server_url.endswith("/v1"):
|
||||
server_url = server_url[:-3]
|
||||
|
||||
|
|
@ -1650,10 +1665,10 @@ def _query_local_context_length_uncached(model: str, base_url: str, api_key: str
|
|||
model = _strip_provider_prefix(model)
|
||||
|
||||
# Strip /v1 suffix to get the server root
|
||||
server_url = base_url.rstrip("/")
|
||||
server_url = _localhost_to_ipv4(base_url.rstrip("/"))
|
||||
if server_url.endswith("/v1"):
|
||||
server_url = server_url[:-3]
|
||||
lmstudio_url = _lmstudio_server_root(base_url)
|
||||
lmstudio_url = _localhost_to_ipv4(_lmstudio_server_root(base_url))
|
||||
|
||||
headers = _auth_headers(api_key)
|
||||
|
||||
|
|
|
|||
|
|
@ -465,7 +465,7 @@ class TestQueryLocalContextLengthLmStudio:
|
|||
result = _query_local_context_length("publisher/model-a", "http://localhost:1234/api/v1")
|
||||
|
||||
assert result == 32768
|
||||
assert client_mock.get.call_args_list[0].args[0] == "http://localhost:1234/api/v1/models"
|
||||
assert client_mock.get.call_args_list[0].args[0] == "http://127.0.0.1:1234/api/v1/models"
|
||||
|
||||
|
||||
class TestDetectLocalServerTypeAuth:
|
||||
|
|
|
|||
|
|
@ -87,6 +87,41 @@ class TestOllamaApiShowCaching:
|
|||
assert client.post.call_count == 1
|
||||
|
||||
|
||||
class TestLocalhostIPv4SiblingSites:
|
||||
"""#37595 widened: every probe helper rewrites localhost→127.0.0.1,
|
||||
not just detect_local_server_type."""
|
||||
|
||||
def test_helper_rewrites_all_forms(self):
|
||||
from agent.model_metadata import _localhost_to_ipv4
|
||||
|
||||
assert _localhost_to_ipv4("http://localhost:1234/v1") == "http://127.0.0.1:1234/v1"
|
||||
assert _localhost_to_ipv4("http://localhost/v1") == "http://127.0.0.1/v1"
|
||||
assert _localhost_to_ipv4("http://localhost") == "http://127.0.0.1"
|
||||
# Non-localhost passes through untouched.
|
||||
assert _localhost_to_ipv4("http://192.168.1.10:8080") == "http://192.168.1.10:8080"
|
||||
assert _localhost_to_ipv4("https://api.openai.com/v1") == "https://api.openai.com/v1"
|
||||
assert _localhost_to_ipv4("") == ""
|
||||
|
||||
def test_ollama_api_show_probes_ipv4(self):
|
||||
from agent.model_metadata import _query_ollama_api_show
|
||||
|
||||
client = _client_mock(_mock_show_response(131072))
|
||||
with patch("httpx.Client", return_value=client):
|
||||
_query_ollama_api_show("llama3", "http://localhost:11434")
|
||||
|
||||
assert client.post.call_args[0][0].startswith("http://127.0.0.1:11434")
|
||||
|
||||
def test_query_ollama_num_ctx_probes_ipv4(self):
|
||||
from agent.model_metadata import query_ollama_num_ctx
|
||||
|
||||
client = _client_mock(_mock_show_response(131072))
|
||||
with patch("agent.model_metadata.detect_local_server_type", return_value="ollama"), \
|
||||
patch("httpx.Client", return_value=client):
|
||||
query_ollama_num_ctx("llama3", "http://localhost:11434")
|
||||
|
||||
assert client.post.call_args[0][0].startswith("http://127.0.0.1:11434")
|
||||
|
||||
|
||||
class TestContextCacheKeyNormalization:
|
||||
def test_trailing_slash_variants_share_one_entry(self, tmp_path, monkeypatch):
|
||||
from agent import model_metadata
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue