fix(model_metadata): never fuzzy-match an empty model name against endpoint catalogs

'' is a substring of every catalog key, so _resolve_endpoint_context_length
with an empty model name "matched" whatever the endpoint listed first —
on the Nous portal that is currently a 32K embedding model, which poisoned
the resolved context length and made AIAgent init fail the 64K minimum.
This is what turned tests/run_agent/test_primary_runtime_restore.py::
TestTryRecoverPrimaryTransport::test_allowed_for_nous_anthropic_messages
red on every PR (CI slice 7/12) after the portal catalog reordered.

Single-model endpoints still resolve with an empty name (unambiguous);
non-empty names keep the substring fuzzy match.
This commit is contained in:
Teknium 2026-08-13 09:54:08 -07:00
parent 996ae10ebd
commit 1a796a1247
2 changed files with 30 additions and 1 deletions

View File

@ -1399,7 +1399,12 @@ def _resolve_endpoint_context_length(
if not matched:
if len(endpoint_metadata) == 1:
matched = next(iter(endpoint_metadata.values()))
else:
elif model:
# Substring fuzzy match — only meaningful with a non-empty model
# name. An empty string is a substring of EVERY key, which would
# "match" whatever model the endpoint happens to list first (e.g.
# a 32K embedding model on the Nous portal) and poison the
# resolved context length for the whole agent.
for key, entry in endpoint_metadata.items():
if model in key or key in model:
matched = entry

View File

@ -591,6 +591,30 @@ class TestNousPortalContextResolution:
@patch("agent.model_metadata.fetch_endpoint_model_metadata")
def test_empty_model_never_fuzzy_matches_endpoint_catalog(self, mock_fetch):
"""An empty model name must not substring-match arbitrary catalog
entries '' is a substring of every key, so pre-fix it "matched"
whatever the endpoint listed first (e.g. a 32K embedding model on
the Nous portal) and poisoned the resolved context length."""
import agent.model_metadata as mm
mock_fetch.return_value = {
"voyageai/voyage-code-4": {"context_length": 32_000},
"x-ai/grok-4.6": {"context_length": 500_000},
}
assert mm._resolve_endpoint_context_length(
"", "https://inference-api.nousresearch.com/v1"
) is None
# Non-empty names still fuzzy-match.
assert mm._resolve_endpoint_context_length(
"grok-4.6", "https://inference-api.nousresearch.com/v1"
) == 500_000
# Single-model endpoints still resolve even with an empty name.
mock_fetch.return_value = {"only-model": {"context_length": 131_072}}
assert mm._resolve_endpoint_context_length(
"", "http://localhost:8080/v1"
) == 131_072
@patch("agent.model_metadata.fetch_endpoint_model_metadata")
@patch("agent.model_metadata.fetch_model_metadata")
def test_openrouter_fallback_is_not_persisted(