refactor(nous): trim comments and drop an unused field
This commit is contained in:
@@ -885,10 +885,9 @@ def _fast_model_from_catalog(provider_id: str) -> str:
|
||||
logger.debug("No credentials for %s catalog", provider_id, exc_info=True)
|
||||
|
||||
if not api_key and provider_id.strip().lower() == "nous":
|
||||
# Nous is OAuth, so the api-key resolver above raises for it. An
|
||||
# anonymous read returns the full catalog rather than the one the
|
||||
# org may reach, and a model picked from it is refused at request
|
||||
# time with model_blocked_by_org_policy.
|
||||
# Nous is OAuth, so the resolver above raises for it. An anonymous
|
||||
# read returns the full catalog, and a model picked from it is
|
||||
# refused at request time by the org's policy.
|
||||
try:
|
||||
from hermes_cli.models import _resolve_nous_pricing_credentials
|
||||
|
||||
@@ -913,8 +912,8 @@ def _fast_model_from_catalog(provider_id: str) -> str:
|
||||
|
||||
ids = sorted((str(m) for m in catalog), key=_model_recency_key, reverse=True)
|
||||
if provider_id.strip().lower() == "nous":
|
||||
# The catalog's keys are a source of ids here, so the policy has to
|
||||
# narrow them the same way it narrows the pickers' lists.
|
||||
# The catalog's keys are a source of ids here, so the policy narrows
|
||||
# them as it does the pickers' lists.
|
||||
try:
|
||||
from hermes_cli.models import (
|
||||
nous_policy_allowed_ids,
|
||||
|
||||
@@ -254,10 +254,7 @@ def is_free_tier_model(model: str, base_url: str = "") -> bool:
|
||||
try:
|
||||
from hermes_cli.models import _is_model_free, peek_cached_pricing
|
||||
|
||||
# The agent's Nous base_url is /v1-suffixed
|
||||
# (https://inference-api.nousresearch.com/v1) but the catalog fetchers
|
||||
# key on the pre-/v1 root, and on auth state besides; peek_cached_pricing
|
||||
# owns both details.
|
||||
# peek_cached_pricing owns the /v1-suffix and auth-state key details.
|
||||
pricing = peek_cached_pricing(base_url)
|
||||
if not pricing:
|
||||
return False
|
||||
|
||||
+2
-3
@@ -9428,9 +9428,8 @@ def _login_nous(args, pconfig: ProviderConfig) -> None:
|
||||
model_ids, pricing = union_with_portal_paid_recommendations(
|
||||
model_ids, pricing, _portal_for_recs,
|
||||
)
|
||||
# The curated list and the Portal's recommendations are both
|
||||
# unauthenticated, so neither knows what the org may reach.
|
||||
# Narrow both lists to the policy before they are shown.
|
||||
# Neither the curated list nor the Portal's recommendations
|
||||
# know what the org may reach.
|
||||
_policy_allowed = nous_policy_allowed_ids()
|
||||
model_ids = restrict_to_nous_policy(
|
||||
model_ids, _policy_allowed, rescue_empty=True,
|
||||
|
||||
@@ -559,9 +559,8 @@ def _model_flow_nous(config, current_model="", args=None):
|
||||
model_ids, pricing, _nous_portal_url,
|
||||
)
|
||||
|
||||
# The curated list and the Portal's recommendations are both
|
||||
# unauthenticated, so neither knows what the org may reach. Narrow both
|
||||
# lists to the policy before they are shown.
|
||||
# Neither the curated list nor the Portal's recommendations know what the
|
||||
# org may reach.
|
||||
from hermes_cli.models import nous_policy_allowed_ids, restrict_to_nous_policy
|
||||
|
||||
_policy_allowed = nous_policy_allowed_ids()
|
||||
|
||||
@@ -2565,11 +2565,9 @@ def _collect_authed_provider_slugs(
|
||||
slugs.append(_cp.slug)
|
||||
seen.add(_cp.slug.lower())
|
||||
|
||||
# Nous is deliberately excluded. Its picker branch builds from the curated
|
||||
# list rather than cached_provider_model_ids, and nous cannot reach the
|
||||
# api_key-only unified pathway, so a prefetched entry is written and never
|
||||
# read — a live authenticated /v1/models round trip per picker open for
|
||||
# nothing.
|
||||
# Nous excluded: its picker branch builds from the curated list and it
|
||||
# cannot reach the api_key-only pathway, so a prefetched entry is written
|
||||
# and never read.
|
||||
return [s for s in slugs if s != "nous"]
|
||||
|
||||
|
||||
@@ -3101,10 +3099,8 @@ def list_authenticated_providers(
|
||||
# curated list alone (still correct, just may lag newly
|
||||
# launched models, exactly like an offline CLI run).
|
||||
pass
|
||||
# Both the curated list and the Portal's recommendations are
|
||||
# unauthenticated, so neither knows what the org may reach. Narrow
|
||||
# to the policy outside the try, so a failed recommendation fetch
|
||||
# still yields a filtered curated list.
|
||||
# Outside the try above, so a failed recommendation fetch still
|
||||
# yields a policy-filtered curated list.
|
||||
try:
|
||||
from hermes_cli.models import (
|
||||
nous_policy_allowed_ids as _nous_policy,
|
||||
|
||||
+26
-52
@@ -2255,18 +2255,16 @@ def _cache_catalog(
|
||||
return result
|
||||
|
||||
|
||||
# A governed endpoint answers an authenticated read with a policy-filtered
|
||||
# catalog and an anonymous read with the full one, so auth state is part of the
|
||||
# cache identity. NUL cannot appear in a URL, so the suffix cannot collide with
|
||||
# a base URL that happens to end this way.
|
||||
# NUL cannot appear in a URL, so this cannot collide with a real base URL.
|
||||
_PRICING_AUTH_KEY_SUFFIX = "\x00auth"
|
||||
|
||||
|
||||
def _pricing_cache_key(url_root: str, api_key: str | None) -> str:
|
||||
"""The ``_pricing_cache`` key for a read of *url_root*.
|
||||
"""Cache key for a read of *url_root*.
|
||||
|
||||
Only *whether* a key was supplied participates — never its value, so no
|
||||
secret reaches the cache key.
|
||||
A governed endpoint answers an authenticated read with a policy-filtered
|
||||
catalog and an anonymous one with the full catalog, so the two cannot share
|
||||
an entry. Only whether a key was supplied participates, never its value.
|
||||
"""
|
||||
return url_root + _PRICING_AUTH_KEY_SUFFIX if api_key else url_root
|
||||
|
||||
@@ -2274,9 +2272,8 @@ def _pricing_cache_key(url_root: str, api_key: str | None) -> str:
|
||||
def peek_cached_pricing(base_url: str) -> dict[str, dict[str, Any]]:
|
||||
"""Pricing already cached for *base_url*, or ``{}``. Never fetches.
|
||||
|
||||
Accepts a ``/v1``-suffixed URL as well as the pre-``/v1`` root the
|
||||
catalog fetchers key on. Prefers the authenticated catalog, which is the
|
||||
one scoped to the caller's org.
|
||||
Accepts a ``/v1``-suffixed URL as well as the pre-``/v1`` root the fetchers
|
||||
key on, and prefers the authenticated catalog.
|
||||
"""
|
||||
root = (base_url or "").rstrip("/")
|
||||
if root.endswith("/v1"):
|
||||
@@ -2607,23 +2604,13 @@ def _resolve_nous_pricing_credentials() -> tuple[str, str]:
|
||||
def nous_policy_allowed_ids(*, force_refresh: bool = False) -> Optional[set[str]]:
|
||||
"""The Nous model ids the caller's org may reach, or ``None`` to not filter.
|
||||
|
||||
The gateway filters ``GET /v1/models`` by the org's model policy for an
|
||||
authenticated read, omitting blocked rows with no marker field, so the keys
|
||||
of the authenticated pricing response are the reachable set. This reuses
|
||||
that response rather than issuing a second round trip.
|
||||
The gateway omits policy-blocked rows from an authenticated
|
||||
``GET /v1/models``, so that response's keys are the reachable set.
|
||||
|
||||
Returns ``None`` — meaning "leave the caller's list alone" — in three cases,
|
||||
each of which would otherwise narrow a list on evidence that cannot support
|
||||
it:
|
||||
|
||||
* the org carries no policy, or the token is too old to say (see
|
||||
:func:`~hermes_cli.nous_account.nous_policy_present`). Filtering an
|
||||
unrestricted org's list buys nothing and risks dropping a model the
|
||||
Portal recommends before the gateway catalog lists it.
|
||||
* credential resolution failed, so the read is anonymous and therefore
|
||||
unfiltered. A full catalog must not be mistaken for a policy-filtered one.
|
||||
* the read came back empty, which is a fetch failure rather than an org
|
||||
that may reach nothing.
|
||||
``None`` means "leave the caller's list alone", for the three states that
|
||||
cannot support narrowing one: no policy (or a token too old to say), an
|
||||
anonymous read whose catalog is unfiltered, and an empty read, which is a
|
||||
fetch failure rather than an org that may reach nothing.
|
||||
"""
|
||||
try:
|
||||
from hermes_cli.nous_account import nous_policy_present
|
||||
@@ -2638,8 +2625,8 @@ def nous_policy_allowed_ids(*, force_refresh: bool = False) -> Optional[set[str]
|
||||
return None
|
||||
|
||||
# Same arguments as get_pricing_for_provider's nous branch, so a caller
|
||||
# that also asks for pricing shares this cache entry instead of paying for
|
||||
# a second request.
|
||||
# asking for pricing too shares this entry instead of paying for a second
|
||||
# request.
|
||||
pricing = fetch_models_with_pricing(
|
||||
api_key=api_key,
|
||||
base_url=base_url,
|
||||
@@ -2649,10 +2636,8 @@ def nous_policy_allowed_ids(*, force_refresh: bool = False) -> Optional[set[str]
|
||||
return set(pricing) or None
|
||||
|
||||
|
||||
# Above this many reachable models, an allowed set is treated as catalog-wide
|
||||
# rather than as an allowlist worth showing in place of an empty picker. NAS
|
||||
# caps an allowlist at 512, but a set this large is indistinguishable from the
|
||||
# full catalog for display purposes.
|
||||
# Past this size an allowed set reads as a whole catalog rather than an
|
||||
# allowlist, and is not worth showing in place of an empty picker.
|
||||
_NOUS_POLICY_APPEND_MAX = 64
|
||||
|
||||
|
||||
@@ -2664,14 +2649,12 @@ def restrict_to_nous_policy(
|
||||
) -> list[str]:
|
||||
"""*model_ids* narrowed to *allowed*, preserving the caller's order.
|
||||
|
||||
A ``None`` or empty *allowed* leaves the list untouched — see
|
||||
:func:`nous_policy_allowed_ids` for when that happens.
|
||||
A ``None`` or empty *allowed* leaves the list untouched.
|
||||
|
||||
A ``:free`` sibling is kept when its base model is reachable. The gateway
|
||||
admits a row when any of its requestable ids passes, and treats anything
|
||||
unknown as a keep on the grounds that over-listing costs a 403 from the
|
||||
authoritative gate while hiding a row the gate would serve is unrecoverable
|
||||
from the client. This mirrors that.
|
||||
A ``:free`` sibling is kept when its base model is reachable, mirroring the
|
||||
gateway, which admits a row when any of its requestable ids passes. Prefer
|
||||
over-listing: that costs a 403 from the authoritative gate, while hiding a
|
||||
row the gate would serve is unrecoverable from the client.
|
||||
"""
|
||||
if not allowed:
|
||||
return list(model_ids)
|
||||
@@ -2681,19 +2664,10 @@ def restrict_to_nous_policy(
|
||||
if mid in allowed or mid.split(":", 1)[0] in allowed
|
||||
]
|
||||
|
||||
# An allowlist can admit only models the curated manifest has never heard
|
||||
# of, leaving nothing to intersect and an empty picker — strictly worse than
|
||||
# the unfiltered list, because the models the org may actually use are the
|
||||
# ones dropped. *rescue_empty* falls back to the reachable set in exactly
|
||||
# that case, and callers opt in per list: it is meaningful for the list a
|
||||
# user picks from, and wrong for any list whose emptiness carries meaning.
|
||||
# An unavailable/gated list is legitimately empty, and rescuing it would
|
||||
# read that as "nothing survived" and fill it with the whole reachable set.
|
||||
#
|
||||
# Bounded, because a jurisdiction or provider policy narrows the catalog
|
||||
# without shrinking it to an allowlist, and a large alphabetical dump buries
|
||||
# the curated order the pickers show on purpose. Anything omitted stays
|
||||
# reachable through the picker's custom-model entry.
|
||||
# An allowlist can name only models the curated manifest lacks, leaving an
|
||||
# empty picker — worse than no filter, since the models the org may use are
|
||||
# the ones dropped. Opt-in per list: an already-empty list (a paid tier's
|
||||
# gated models) means "nothing to gate", not "nothing survived".
|
||||
if rescue_empty and not kept and len(allowed) <= _NOUS_POLICY_APPEND_MAX:
|
||||
return sorted(allowed)
|
||||
return kept
|
||||
|
||||
@@ -99,7 +99,6 @@ class NousPortalAccountInfo:
|
||||
subscription: Optional[NousPortalSubscriptionInfo] = None
|
||||
paid_service_access: Optional[bool] = None
|
||||
paid_service_access_info: Optional[NousPaidServiceAccessInfo] = None
|
||||
policy_present: Optional[bool] = None
|
||||
tool_access: Optional[NousToolAccessInfo] = None
|
||||
raw_claims: Optional[dict[str, Any]] = None
|
||||
raw_account: Optional[dict[str, Any]] = None
|
||||
@@ -400,17 +399,12 @@ def get_nous_portal_account_info(
|
||||
def nous_policy_present() -> Optional[bool]:
|
||||
"""Whether the caller's org carries a restrictive model/provider policy.
|
||||
|
||||
Read from the ``policy_present`` claim on the Nous OAuth access token, so
|
||||
this costs no request. ``/api/oauth/account`` does not carry the claim,
|
||||
which is why this reads the token directly rather than going through
|
||||
:func:`get_nous_portal_account_info`.
|
||||
Reads the ``policy_present`` claim off the access token, so it costs no
|
||||
request; ``/api/oauth/account`` does not carry it. Stamped at mint time, so
|
||||
it goes stale until the next token refresh.
|
||||
|
||||
``None`` means unknown — an older mint, an unreadable token, or a
|
||||
non-boolean claim. Unknown is NOT "no policy": callers must not report the
|
||||
absence of the claim as the absence of a restriction.
|
||||
|
||||
The claim is stamped at mint time, so it goes stale until the next token
|
||||
refresh.
|
||||
``None`` is unknown — an older mint or an unreadable claim — and must not be
|
||||
reported as the absence of a policy.
|
||||
"""
|
||||
try:
|
||||
from hermes_cli.auth import get_provider_auth_state, _decode_jwt_claims
|
||||
@@ -430,15 +424,9 @@ def nous_policy_present() -> Optional[bool]:
|
||||
def nous_policy_notice() -> str:
|
||||
"""A one-line notice for an org that restricts model choice, else ``""``.
|
||||
|
||||
Under the gateway's policy filter a blocked model is omitted rather than
|
||||
marked, which reads as "Hermes does not support this" instead of "your org
|
||||
disallows it". This says which it is without enumerating anything: model
|
||||
policy is an allowlist, so an org that admits a handful of models blocks
|
||||
the whole rest of the catalog, and listing those would be a worse UI than
|
||||
omitting them.
|
||||
|
||||
Silent unless the claim is explicitly true — absent means an older mint,
|
||||
not an unrestricted org.
|
||||
A blocked model is omitted rather than marked, which reads as "Hermes does
|
||||
not support this". This says which it is without enumerating the blocked
|
||||
set, which under an allowlist is most of the catalog.
|
||||
"""
|
||||
if nous_policy_present() is not True:
|
||||
return ""
|
||||
@@ -694,7 +682,6 @@ def _info_from_valid_jwt(
|
||||
expires_at=datetime.fromtimestamp(exp, tz=timezone.utc),
|
||||
paid_service_access=paid_access,
|
||||
paid_service_access_info=access_info,
|
||||
policy_present=_coerce_bool(claims.get("policy_present")),
|
||||
tool_access=_tool_access_from_value(claims.get("tool_access")),
|
||||
raw_claims=dict(claims),
|
||||
)
|
||||
|
||||
@@ -7517,9 +7517,8 @@ def get_recommended_default_model(provider: str = ""):
|
||||
model_ids, pricing, portal_url
|
||||
)
|
||||
|
||||
# Neither the curated list nor the Portal's recommendations know
|
||||
# what the org may reach, and this endpoint picks the model a user
|
||||
# lands on without choosing it.
|
||||
# This endpoint picks the model a user lands on without choosing
|
||||
# it, so an unreachable one here is worse than in a picker.
|
||||
model_ids = restrict_to_nous_policy(
|
||||
model_ids, nous_policy_allowed_ids(), rescue_empty=True,
|
||||
)
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
"""Narrowing the Nous model lists to an org's policy.
|
||||
|
||||
The inference gateway omits policy-blocked rows from an authenticated
|
||||
``GET /v1/models`` with no marker field, so the keys of the authenticated
|
||||
catalog read are the reachable set. These helpers turn that into a filter the
|
||||
pickers can apply without a second round trip, and — just as importantly —
|
||||
decline to filter when the evidence cannot support it.
|
||||
The gateway omits policy-blocked rows from an authenticated ``GET /v1/models``,
|
||||
so that response's keys are the reachable set.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -44,15 +41,12 @@ class TestRestrictToNousPolicy:
|
||||
) == ["a/one", "c/three"]
|
||||
|
||||
def test_preserves_curated_order(self):
|
||||
"""The pickers show a curated order deliberately; filtering must not
|
||||
reorder it into the catalog's alphabetical order."""
|
||||
curated = ["z/last", "a/first", "m/middle"]
|
||||
allowed = {"a/first", "m/middle", "z/last"}
|
||||
assert restrict_to_nous_policy(curated, allowed) == curated
|
||||
|
||||
def test_keeps_a_free_sibling_when_its_base_is_reachable(self):
|
||||
"""Portal free recommendations are ``:free`` ids; the gateway admits a
|
||||
row when any of its requestable ids passes."""
|
||||
"""Portal free recommendations are ``:free`` ids."""
|
||||
assert restrict_to_nous_policy(["vendor/model:free"], {"vendor/model"}) == [
|
||||
"vendor/model:free"
|
||||
]
|
||||
@@ -115,8 +109,7 @@ class TestNousPolicyAllowedIds:
|
||||
assert calls == []
|
||||
|
||||
def test_declines_to_filter_on_an_anonymous_read(self, monkeypatch):
|
||||
"""An anonymous read returns the full catalog; treating it as the
|
||||
policy-filtered set would silently widen the list to everything."""
|
||||
"""An anonymous read returns the full, unfiltered catalog."""
|
||||
self._patch(monkeypatch, policy_present=True, api_key="", pricing={"a/one": {}})
|
||||
assert nous_policy_allowed_ids() is None
|
||||
|
||||
@@ -146,7 +139,6 @@ class TestNousPolicyPresent:
|
||||
assert nous_policy_present() is None
|
||||
|
||||
def test_non_boolean_claim_is_unknown(self, monkeypatch):
|
||||
"""The gateway refuses to read a corrupt claim as "no policy"."""
|
||||
self._patch_token(monkeypatch, _jwt({"policy_present": "yes"}))
|
||||
assert nous_policy_present() is None
|
||||
|
||||
@@ -160,8 +152,6 @@ class TestNousPolicyPresent:
|
||||
|
||||
|
||||
class TestNousPolicyNotice:
|
||||
"""A governed org is told its choice is restricted, rather than left to
|
||||
read an omitted model as one Hermes does not support."""
|
||||
|
||||
def _patch(self, monkeypatch, present):
|
||||
monkeypatch.setattr(account_mod, "nous_policy_present", lambda: present)
|
||||
@@ -172,14 +162,12 @@ class TestNousPolicyNotice:
|
||||
|
||||
@pytest.mark.parametrize("present", [False, None])
|
||||
def test_silent_otherwise(self, monkeypatch, present):
|
||||
"""Absent is an older mint, not an unrestricted org — either way there
|
||||
is nothing truthful to say."""
|
||||
"""Absent is an older mint, not an unrestricted org."""
|
||||
self._patch(monkeypatch, present)
|
||||
assert account_mod.nous_policy_notice() == ""
|
||||
|
||||
def test_names_no_models(self, monkeypatch):
|
||||
"""Policy is an allowlist, so the blocked set is most of the catalog;
|
||||
the notice must not try to enumerate it."""
|
||||
"""The blocked set is most of the catalog under an allowlist."""
|
||||
self._patch(monkeypatch, True)
|
||||
notice = account_mod.nous_policy_notice()
|
||||
assert "/" not in notice, f"looks like it names a model: {notice}"
|
||||
@@ -187,12 +175,8 @@ class TestNousPolicyNotice:
|
||||
|
||||
|
||||
class TestAllowlistOutsideTheCuratedList:
|
||||
"""An allowlist can name a model the curated manifest has never heard of.
|
||||
|
||||
Intersecting alone leaves the picker empty in that case — strictly worse
|
||||
than showing an unfiltered list, because the one model the org may use is
|
||||
the one that got dropped.
|
||||
"""
|
||||
"""An allowlist can name only models the curated manifest lacks, which
|
||||
intersecting alone turns into an empty picker."""
|
||||
|
||||
def test_surfaces_an_allowed_model_the_curated_list_lacks(self):
|
||||
assert restrict_to_nous_policy(
|
||||
@@ -200,9 +184,6 @@ class TestAllowlistOutsideTheCuratedList:
|
||||
) == ["amazon/nova-2-lite-v1"]
|
||||
|
||||
def test_does_not_append_when_the_curated_overlap_is_non_empty(self):
|
||||
"""A jurisdiction or provider policy narrows the catalog without
|
||||
emptying the curated overlap. Appending its remainder would push
|
||||
non-curated alphabetical ids into a deliberately curated order."""
|
||||
kept = restrict_to_nous_policy(
|
||||
["z/curated", "a/curated"],
|
||||
{"z/curated", "a/curated", "new/model"},
|
||||
@@ -211,8 +192,8 @@ class TestAllowlistOutsideTheCuratedList:
|
||||
assert kept == ["z/curated", "a/curated"]
|
||||
|
||||
def test_jurisdiction_policy_never_grows_the_list(self):
|
||||
"""Regression: a region filter leaves few enough models to slip under
|
||||
the size cap, so a size-only guard let it append."""
|
||||
"""A region filter can slip under the size cap, so the cap alone is not
|
||||
enough of a guard."""
|
||||
curated = ["vendor/one", "vendor/two", "vendor/three"]
|
||||
reachable = {"vendor/one", "vendor/two"} | {f"cn/model-{i}" for i in range(20)}
|
||||
assert restrict_to_nous_policy(curated, reachable, rescue_empty=True) == [
|
||||
@@ -226,16 +207,13 @@ class TestAllowlistOutsideTheCuratedList:
|
||||
) == ["vendor/m:free"]
|
||||
|
||||
def test_a_provider_only_policy_does_not_bury_the_curated_order(self):
|
||||
"""Such a policy leaves the whole catalog reachable; appending it would
|
||||
drop hundreds of alphabetical ids into the picker."""
|
||||
curated = ["vendor/one", "vendor/two"]
|
||||
catalog = {f"vendor/model-{i}" for i in range(300)} | set(curated)
|
||||
assert restrict_to_nous_policy(curated, catalog, rescue_empty=True) == curated
|
||||
|
||||
|
||||
class TestRescueIsOptIn:
|
||||
"""The empty-intersection rescue is meaningful only for the list a user
|
||||
picks from. Any list whose emptiness carries meaning must not get it."""
|
||||
"""The rescue is meaningful only for the list a user picks from."""
|
||||
|
||||
def test_no_rescue_by_default(self):
|
||||
assert restrict_to_nous_policy([], {"a/one", "b/two"}) == []
|
||||
@@ -246,15 +224,10 @@ class TestRescueIsOptIn:
|
||||
) == ["a/one"]
|
||||
|
||||
def test_an_already_empty_unavailable_list_is_never_filled(self):
|
||||
"""Regression: a paid-tier user has no gated models, so the
|
||||
unavailable list is legitimately empty. Rescuing it read that as
|
||||
"nothing survived" and pushed the whole reachable set into the picker's
|
||||
unavailable block."""
|
||||
"""A paid tier has no gated models, so this list is legitimately
|
||||
empty — not a filter result to rescue."""
|
||||
reachable = {f"cn/model-{i}" for i in range(42)}
|
||||
assert restrict_to_nous_policy([], reachable) == []
|
||||
|
||||
def test_rescue_does_not_resurrect_a_fully_blocked_list(self):
|
||||
"""A list whose every entry was blocked is a real filter result, not a
|
||||
signal to show something else — unless the caller asked for the
|
||||
rescue, which only the selectable list does."""
|
||||
assert restrict_to_nous_policy(["x/blocked"], {"y/allowed"}) == []
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
"""Every Nous model list is narrowed to the org's policy before it is shown.
|
||||
|
||||
Four surfaces build a Nous list from the curated manifest unioned with the
|
||||
Portal's ``recommended-models`` endpoint. Neither source is authenticated, so
|
||||
without this filter an org's hidden model is offered to the user and then
|
||||
refused at request time with ``model_blocked_by_org_policy``.
|
||||
Four surfaces build their list from the curated manifest unioned with the
|
||||
Portal's ``recommended-models`` endpoint; neither source is authenticated.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -32,7 +30,6 @@ def no_policy(monkeypatch):
|
||||
|
||||
|
||||
class TestLoginNous:
|
||||
"""``_login_nous`` — the model picked at login is the model then used."""
|
||||
|
||||
def _run(self, monkeypatch, tmp_path):
|
||||
import hermes_cli.auth as auth_mod
|
||||
@@ -119,8 +116,7 @@ class TestModelSwitchPicker:
|
||||
assert set(CURATED) <= set(row["models"])
|
||||
|
||||
def test_filter_survives_a_failed_recommendation_fetch(self, monkeypatch, policy):
|
||||
"""The filter sits outside the try that wraps the Portal union, so a
|
||||
Portal outage still yields a policy-filtered curated list."""
|
||||
"""The filter sits outside the try wrapping the Portal union."""
|
||||
|
||||
def _boom(_p):
|
||||
raise RuntimeError("portal down")
|
||||
@@ -132,8 +128,7 @@ class TestModelSwitchPicker:
|
||||
|
||||
|
||||
class TestRecommendedDefaultEndpoint:
|
||||
"""``GET /api/model/recommended-default`` picks a model the user never sees
|
||||
chosen, so an unreachable one there is worse than in a picker."""
|
||||
"""This endpoint picks a model the user never sees chosen."""
|
||||
|
||||
def _call(self, monkeypatch):
|
||||
import hermes_cli.auth as auth_mod
|
||||
@@ -163,8 +158,7 @@ class TestRecommendedDefaultEndpoint:
|
||||
|
||||
|
||||
class TestAuxiliaryFastModel:
|
||||
"""``_fast_model_from_catalog`` treats the catalog's keys as a source of
|
||||
ids, so an anonymous read there can select a model the gateway refuses."""
|
||||
"""``_fast_model_from_catalog`` uses the catalog's keys as a source of ids."""
|
||||
|
||||
def _pick(self, monkeypatch, *, catalog):
|
||||
import agent.auxiliary_client as aux
|
||||
@@ -184,8 +178,7 @@ class TestAuxiliaryFastModel:
|
||||
return picked, seen
|
||||
|
||||
def test_reads_the_catalog_with_nous_oauth_credentials(self, monkeypatch, no_policy):
|
||||
"""The api-key resolver raises for OAuth providers; without a fallback
|
||||
the read goes out anonymous and returns the unfiltered catalog."""
|
||||
"""The api-key resolver raises for OAuth providers."""
|
||||
_, seen = self._pick(monkeypatch, catalog=["vendor/haiku-fast"])
|
||||
assert seen["api_key"] == "sk-nous"
|
||||
|
||||
@@ -204,8 +197,8 @@ class TestAuxiliaryFastModel:
|
||||
|
||||
|
||||
class TestNousPrefetch:
|
||||
"""The nous disk-cache entry is write-only: its picker branch builds from
|
||||
the curated list, so prefetching it is a round trip for nothing."""
|
||||
"""The nous disk-cache entry is write-only, so prefetching it is a round
|
||||
trip for nothing."""
|
||||
|
||||
def test_nous_is_not_collected_for_prefetch(self, monkeypatch):
|
||||
import hermes_cli.auth as auth_mod
|
||||
@@ -220,7 +213,6 @@ class TestNousPrefetch:
|
||||
|
||||
|
||||
class TestPolicyNoticeIsShown:
|
||||
"""The notice reaches the two flows where a user picks a model."""
|
||||
|
||||
def test_login_prints_it(self, monkeypatch, tmp_path, policy, capsys):
|
||||
import hermes_cli.nous_account as account_mod
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
"""``_pricing_cache`` keys on auth state, not just the base URL.
|
||||
|
||||
A governed endpoint (Nous ``/v1/models`` filtered by an org's model policy)
|
||||
answers an authenticated read with a narrower catalog than an anonymous one.
|
||||
Keyed on the base URL alone, whichever read landed first in a process answered
|
||||
every later one — so an authenticated caller could be handed the full,
|
||||
unfiltered catalog without a request going out.
|
||||
Nous ``/v1/models`` answers an authenticated read with a policy-filtered
|
||||
catalog and an anonymous one with the full catalog, so the two must not share
|
||||
a cache entry.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -60,8 +58,6 @@ def catalog(monkeypatch):
|
||||
|
||||
|
||||
def test_authenticated_read_is_not_answered_by_an_anonymous_one(catalog):
|
||||
"""The bug: an anonymous read landing first must not answer the next
|
||||
authenticated read out of cache."""
|
||||
anon = fetch_models_with_pricing(api_key="", base_url=BASE)
|
||||
authed = fetch_models_with_pricing(api_key="sk-test", base_url=BASE)
|
||||
|
||||
@@ -72,7 +68,6 @@ def test_authenticated_read_is_not_answered_by_an_anonymous_one(catalog):
|
||||
|
||||
|
||||
def test_anonymous_read_is_not_answered_by_an_authenticated_one(catalog):
|
||||
"""And the reverse direction, so neither entry can shadow the other."""
|
||||
authed = fetch_models_with_pricing(api_key="sk-test", base_url=BASE)
|
||||
anon = fetch_models_with_pricing(api_key="", base_url=BASE)
|
||||
|
||||
@@ -108,12 +103,11 @@ class TestPeekCachedPricing:
|
||||
assert peek_cached_pricing(BASE) == {}
|
||||
|
||||
def test_accepts_a_v1_suffixed_url(self, catalog):
|
||||
"""The agent holds a /v1-suffixed base URL; the fetchers key on the root."""
|
||||
"""The agent holds a /v1-suffixed base URL; fetchers key on the root."""
|
||||
fetch_models_with_pricing(api_key="sk-test", base_url=BASE)
|
||||
assert sorted(peek_cached_pricing(BASE + "/v1")) == sorted(_FILTERED)
|
||||
|
||||
def test_prefers_the_authenticated_catalog(self, catalog):
|
||||
"""It is the one scoped to the caller's org."""
|
||||
fetch_models_with_pricing(api_key="", base_url=BASE)
|
||||
fetch_models_with_pricing(api_key="sk-test", base_url=BASE)
|
||||
assert sorted(peek_cached_pricing(BASE)) == sorted(_FILTERED)
|
||||
|
||||
Reference in New Issue
Block a user