Skip to content

fix(config): scope custom-provider context_length lookup by provider - #93024

Open
alexzsl wants to merge 1 commit into
NousResearch:mainfrom
alexzsl:fix/custom-provider-context-provider-scoping
Open

alexzsl wants to merge 1 commit into
NousResearch:mainfrom
alexzsl:fix/custom-provider-context-provider-scoping

Conversation

@alexzsl

@alexzsl alexzsl commented Aug 23, 2026

Copy link
Copy Markdown

Summary

get_custom_provider_context_length() matched custom_providers entries on base_url + model id alone, ignoring which provider the lookup was for. Multiple providers can legitimately share one base_url — an OpenAI-compatible aggregator such as new-api/one-api fronting several upstream accounts is a common setup — and serve identically named models with different context windows. In that configuration every context-length resolution path picked whichever matching entry appeared first in config order.

Observed symptom

Two configured providers (coding-plan-gpt and market-aigw) both point at http://127.0.0.1:3000/v1 and both define gpt-5.6-terra-2026-07-09:

providers:
  coding-plan-gpt:
    base_url: http://127.0.0.1:3000/v1
    models:
      gpt-5.6-terra-2026-07-09: {context_length: 500000}
  market-aigw:
    base_url: http://127.0.0.1:3000/v1
    models:
      gpt-5.6-terra-2026-07-09: {context_length: 1050000}

Selecting the model under market-aigw resolved its per-model override to 500,000 (the other provider's value). The wrong value propagated to every consumer of the resolution chain: status-bar display, /model switch confirmation, compression feasibility checks, gateway /info — and in our case surfaced as a hard failure when a large @ reference was rejected against the wrong 50% limit.

Fix

Add an optional provider parameter to get_custom_provider_context_length(). When supplied, only entries whose provider_key/name/slug equals it (case-insensitive) are eligible; entries without any provider identity remain reachable so hand-written legacy custom_providers: blocks keep working. Callers that pass no provider keep the historical first-match-in-config-order behaviour.

All six call sites now thread the active provider through:

  • agent/model_metadata.py (resolution step 0c)
  • agent/agent_init.py (startup)
  • agent/agent_runtime_helpers.py (mid-session /model switch)
  • gateway/run.py ×3 (gateway startup, message-path resolution, auxiliary compression)

Testing

  • New TestProviderScopedContextLength regression tests: per-provider resolution of the shared-base_url same-model collision, case-insensitive matching, unknown-provider miss, backward-compatible no-provider ordering, and legacy entries lacking a provider identity.
  • Full file: 14 passed (9 pre-existing + 5 new).
  • Manually verified against a live new-api aggregator: same model now resolves to 1,050,000 under market-aigw and 500,000 under coding-plan-gpt; no-provider calls unchanged.

@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery area/config Config system, migrations, profiles sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades P2 Medium — degraded but workaround exists labels Aug 23, 2026
@alt-glitch

Copy link
Copy Markdown
Contributor

This was generated by AI during triage.

Related to #63542, #34257, and #37716 in the custom-provider context-length family; this PR specifically scopes a shared base URL/model lookup by active provider identity.

@Enough1122

Copy link
Copy Markdown
Contributor

AI code review — automated review for reference, author can ignore or act on any point.

Correct diagnosis: base_url + model name is genuinely ambiguous when OpenAI-compatible aggregators (new-api/one-api style) serve identically named models under several providers, and provider-scoped matching with legacy fallback is the right shape. Test coverage for case-insensitivity, unknown providers, and backward compatibility is thorough.

Findings:

  1. hermes_cli/config.py:1846-1853 — the filter preserves config-order precedence, so an identity-less legacy entry placed before the correctly named one wins under a scoped lookup. With entries [unnamed → 128_000, {"provider_key": "market-aigw"} → 1_050_000] and provider="market-aigw", the unnamed entry matches first and returns 128_000 — precisely the misresolution this PR exists to prevent. Suggest a two-pass scan: first consider only entries whose identity equals provider_lower; fall back to identity-less entries only when no identified entry matched. Then add a regression test with that mixed ordering — it's the one arrangement not covered by tests/hermes_cli/test_custom_provider_context_length.py:126+.

  2. hermes_cli/config.py:1807provider: Optional[str] = "" pairs an Optional annotation with an empty-string default. Pick one convention (Optional[str] = None reads cleanest); the internal (provider or "") coercion already tolerates either.

  3. Call sites now read attributes that were not needed before: agent/agent_init.py:2579 and agent/agent_runtime_helpers.py:2884 use agent.provider directly. If any agent-construction path leaves the attribute unset, these raise AttributeError where the old code did not — getattr(agent, "provider", None) is the cheap defensive form unless every constructor guarantees the field. Same trust applies to _hyg_provider at gateway/run.py:19558, which depends on an earlier assignment deep in a long function.

Minor: document the identity precedence (provider_key > name > slug, hermes_cli/config.py:1848) in the docstring so config authors know which field is matched.

alexzsl pushed a commit to alexzsl/hermes-agent that referenced this pull request Aug 23, 2026
…t shadow

Addresses AI review on NousResearch#93024:

- get_custom_provider_context_length now scans identified entries first
  under a provider-scoped lookup; identity-less legacy entries are only a
  fallback when no identified entry matched. Previously an unnamed entry
  placed earlier in config order could win the scoped lookup, recreating
  the cross-provider misresolution this fix exists to prevent.
- provider parameter defaults to None (was Optional[str] = "").
- Call sites read agent.provider via getattr for defensive access.
- Document provider identity precedence (provider_key > name > slug) in
  the docstring.

Adds test_identityless_entry_does_not_shadow_named_entry for the mixed
ordering; renames the legacy fallback test to reflect its new semantics.
15 tests pass.
@alexzsl

alexzsl commented Aug 23, 2026

Copy link
Copy Markdown
Author

Thanks for the review — all points addressed in eb7dd79 (15 tests pass).

  1. Identity-less shadowing (config.py) — switched to a two-pass scan: under a provider-scoped lookup, identified entries are matched first; identity-less legacy entries are only consulted as a fallback when no identified entry matched. Added test_identityless_entry_does_not_shadow_named_entry covering the mixed [unnamed → 128K, market-aigw → 1.05M] ordering.

  2. Optional[str] conventionprovider now defaults to None; the internal (provider or "") coercion is unchanged.

  3. Defensive attribute accessagent_init.py and agent_runtime_helpers.py now read getattr(agent, "provider", None). _hyg_provider at gateway/run.py:19558 is initialized to None earlier in the same function (line 19431), so it cannot raise, but the value flows through unchanged.

  4. Docstring — documented provider identity precedence (provider_key > name > slug) and the identity-less fallback rule.

@alexzsl
alexzsl force-pushed the fix/custom-provider-context-provider-scoping branch from eb7dd79 to 863cb1a Compare September 22, 2026 23:59

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants