Skip to content

feat(pricing): infer upstream billing provider from model name for proxied endpoints - #53305

Open
vanhoof wants to merge 1 commit into
NousResearch:mainfrom
vanhoof:feat/proxy-model-pricing-inference
Open

feat(pricing): infer upstream billing provider from model name for proxied endpoints#53305
vanhoof wants to merge 1 commit into
NousResearch:mainfrom
vanhoof:feat/proxy-model-pricing-inference

Conversation

@vanhoof

@vanhoof vanhoof commented Jun 26, 2026

Copy link
Copy Markdown

Problem

When models are served through a local proxy (vertex-proxy, LiteLLM, custom gateway), the Hermes provider name is a user-defined config alias (e.g. vertex-opus46, litellm) that does not match any known billing route. resolve_billing_route() hits the localhost catch-all and returns billing_mode="unknown", so the dashboard and session cost tracker show "unknown" cost for every proxied session.

Observed behavior: every API call through a proxy produces amount_usd=None from estimate_usage_cost(), regardless of the upstream model.

Affected setups: any user routing through a local proxy with a custom provider name in providers: config. This includes vertex-proxy (GCP Vertex AI), LiteLLM, and similar OpenAI-compatible gateways on localhost.

Root Cause

resolve_billing_route() checks provider names against a fixed set of known providers (anthropic, openai, openrouter, etc.). User-defined provider names like vertex-opus46 fall through every named check and land in the custom/local/localhost branch, which returns billing_mode="unknown". The official-docs pricing table has the correct entries keyed by (anthropic, claude-opus-4-6), but the billing route never maps the custom provider name to anthropic.

Secondary issue: the localhost detection only checked for the string "localhost" in the base URL, missing 127.0.0.1 which is the more common form in practice.

Fix

Adds _infer_upstream_provider(), a ~15-line helper that maps well-known model name prefixes to their upstream billing provider:

Prefix Provider
claude- anthropic
gpt- openai
o1-, o3-, o4- openai
gemini- google
deepseek- deepseek

The inference runs as a fallback in two places within resolve_billing_route():

  1. Inside the custom/local/localhost branch (before returning unknown)
  2. In the final catch-all (for unrecognized provider names that are not on localhost)

When a model name matches, the route resolves to official_docs_snapshot pricing. When it does not match, behavior is unchanged (billing_mode="unknown").

Also adds "127.0.0.1" to the localhost detection alongside "localhost".

Per-file summary

  • agent/usage_pricing.py: adds _MODEL_PREFIX_TO_PROVIDER table, _infer_upstream_provider() function, and two inference call sites in resolve_billing_route(). Adds "127.0.0.1" to localhost detection.
  • tests/agent/test_usage_pricing.py: 12 new tests covering prefix inference, billing route resolution for proxied models, end-to-end pricing lookup, and end-to-end cost estimation.

Design Notes

This is the simplest approach that solves the problem for the vast majority of proxy users without requiring any config changes. A future billing_provider config field on custom providers could override the inference for edge cases, but was deliberately not included to keep the diff minimal and avoid a config schema change.

The pattern is consistent with existing inference in agent_init.py (which infers api_mode from URL patterns) and _normalize_anthropic_model_name() / _normalize_bedrock_model_name() (which normalize model name variants for pricing lookup).

Related: #18886 attempted to solve this for LiteLLM specifically by probing /model/info. That approach does not generalize to non-LiteLLM proxies (vertex-proxy, custom gateways) and was closed.

Testing

$ python3 -m pytest tests/agent/test_usage_pricing.py -v
27 passed in 0.57s

$ python3 -m pytest tests/agent/test_usage_pricing.py tests/agent/test_insights.py \
    tests/hermes_cli/test_model_cost_guard.py tests/gateway/test_usage_command.py -q
95 passed in 1.46s

Manually verified against a live vertex-proxy (podman, localhost:8788):

Model Provider Before After
claude-opus-4.6 vertex-opus46 None (unknown) $5.00/$25.00 per M (estimated)
claude-sonnet-4.6 vertex-sonnet46 None (unknown) $3.00/$15.00 per M (estimated)
gemini-pro vertex-gemini-pro None (unknown) routes to google (official_docs_snapshot)

Signed-off-by: Chris van Hoof vanhoof@ouwish.com

…oxied endpoints

When models are served through a local proxy (vertex-proxy, LiteLLM,
custom gateway), the Hermes provider name is a user-defined alias
(e.g. "vertex-opus46") that does not match any known billing route.
resolve_billing_route() falls through to billing_mode="unknown" and
the dashboard shows "unknown" cost for every proxied session.

This patch adds _infer_upstream_provider(), which maps well-known model
name prefixes (claude-*, gpt-*, gemini-*, deepseek-*, o1-/o3-/o4-*) to
their upstream billing provider. The inference runs as a fallback in
resolve_billing_route() when no named provider matches, so proxied
models resolve to official-docs pricing without any config changes.

Also fixes 127.0.0.1 not being recognized as localhost in the
custom/local provider detection branch.

Signed-off-by: Chris van Hoof <vanhoof@ouwish.com>

Assisted-by: hermes-agent/v0.17.0-fd2a35b16 · claude-opus-4.6 · director profile
@alt-glitch alt-glitch added type/feature New feature or request comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint area/billing Account usage, credit usage, billing (cross-cutting) P2 Medium — degraded but workaround exists labels Jun 26, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for targeting a verified pricing gap: current main still returns an unknown route for custom/local and unrecognized provider aliases at agent/usage_pricing.py:675-677.

Problems

  • The added inference checks only raw model prefixes. With a configured proxy alias, current routing strips vendor/ only when no provider was supplied (agent/usage_pricing.py:648-652), so anthropic/claude-* and google/gemini-* do not match the proposed claude-/gemini- prefixes. Vendor-prefixed IDs are supported; the bundled Vertex profile uses google/gemini-3-flash-preview at plugins/model-providers/vertex/__init__.py:72.

Suggested changes

  • Normalize a recognized vendor prefix before inference (while retaining the original model for pricing lookup), and add proxy-alias end-to-end tests for vendor-prefixed Anthropic and Gemini IDs.
  • Salvage will need conflict-aware integration: resolve_billing_route() changed after this PR's base in db117af4785f79d0adfafcea4d75ee556f4006dd.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform area/usage-cost Token accounting, usage reporting, billing, cost tracking labels Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/billing Account usage, credit usage, billing (cross-cutting) area/usage-cost Token accounting, usage reporting, billing, cost tracking comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants