mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
## Description When the proxy runs against a custom OpenAI-compatible endpoint via `--openai-api-url` (OpenRouter, Groq, Together, Azure OpenAI, …), the dashboard always showed the provider as **OpenAI**, because the OpenAI handler records every request with `provider="openai"`. This detects well-known upstreams from the `--openai-api-url` host and adds a `--provider-name` override that takes precedence (the issue's option 3). The label is resolved only where the dashboard/stats payload is built — the internal provider key stays `openai`, so pricing and request formatting are unaffected. | Upstream URL | Provider shown | |--------------|----------------| | `https://api.openai.com/v1` | OpenAI | | `https://openrouter.ai/api/v1` | OpenRouter | | `https://api.groq.com/openai/v1` | Groq | | `https://api.together.xyz/v1` | Together AI | | `https://<resource>.openai.azure.com/` | Azure OpenAI | Unknown hosts keep the `openai` label unless `--provider-name` is set. Closes #1533 ## Type of Change - [x] New feature (non-breaking change that adds functionality) ## Changes Made - `helpers.py`: `classify_openai_upstream()` (host → display name) + `resolve_display_provider()` (precedence: `--provider-name` > host detection > raw provider; only relabels `openai`). - `models.py`: `ProxyConfig.provider_name`. - `cli/proxy.py`: `--provider-name` flag, threaded into `ProxyConfig`. - `server.py`: relabel at the four dashboard/stats display sites (recent requests, transformations feed, `requests.by_provider`, agent-usage breakdown) via the resolver / `_remap_provider_counts`. Stored logs and metrics keys are untouched. - `docs/content/docs/proxy.mdx`: document `--provider-name`. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check`) - [x] New tests added ### Test Output ```text $ pytest tests/test_provider_display_classification.py tests/test_dashboard_agent_usage.py -q 16 passed 13 passed $ ruff check headroom/proxy/helpers.py headroom/proxy/server.py headroom/proxy/models.py headroom/cli/proxy.py All checks passed! ``` ## Real Behavior Proof - Environment: repo branch `feat/1533-upstream-provider-classify` @ HEAD, local `.venv` (Python 3) - Exact command / steps: ran the helpers directly from the venv — `python -c "from headroom.proxy.helpers import classify_openai_upstream, resolve_display_provider; print(classify_openai_upstream('https://openrouter.ai/api/v1')); print(resolve_display_provider('openai', openai_api_url='https://openrouter.ai/api/v1')); print(resolve_display_provider('openai', openai_api_url='https://openrouter.ai/api/v1', provider_name='Groq')); print(resolve_display_provider('anthropic'))"` - Observed result: host detection relabels `openai` → `OpenRouter`, `--provider-name` overrides detection (`Groq`), and the `anthropic` label (plus the `openai` pricing key) is unchanged. Full output below: ```text classify openrouter -> OpenRouter resolve openai+openrouter url -> OpenRouter override provider-name -> Groq anthropic untouched -> anthropic ``` - Not tested: live dashboard render against a real OpenRouter key (the payload-builder logic is covered by the unit tests above). ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective Co-authored-by: JerrettDavis <mxjerrett@gmail.com>
85 lines
2.8 KiB
Python
85 lines
2.8 KiB
Python
"""Upstream-provider display classification for OpenAI-compatible endpoints.
|
|
|
|
Covers issue #1533: when ``--openai-api-url`` points at a non-OpenAI upstream
|
|
(OpenRouter, Groq, …), the dashboard should show that provider instead of
|
|
always "openai". The internal provider key stays ``openai`` so pricing and
|
|
request formatting are unaffected — only the display label changes.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from headroom.proxy.helpers import (
|
|
classify_openai_upstream,
|
|
resolve_display_provider,
|
|
)
|
|
from headroom.proxy.models import ProxyConfig
|
|
from headroom.proxy.server import _remap_provider_counts
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"url,expected",
|
|
[
|
|
("https://api.openai.com/v1", "OpenAI"),
|
|
("https://openrouter.ai/api/v1", "OpenRouter"),
|
|
("https://api.groq.com/openai/v1", "Groq"),
|
|
("https://api.together.xyz/v1", "Together AI"),
|
|
("https://my-resource.openai.azure.com/", "Azure OpenAI"),
|
|
("https://api.deepseek.com/v1", "DeepSeek"),
|
|
],
|
|
)
|
|
def test_classify_known_hosts(url: str, expected: str) -> None:
|
|
assert classify_openai_upstream(url) == expected
|
|
|
|
|
|
@pytest.mark.parametrize("url", [None, "", "not-a-url", "https://api.mycorp.internal/v1"])
|
|
def test_classify_unknown_or_missing_returns_none(url: str | None) -> None:
|
|
assert classify_openai_upstream(url) is None
|
|
|
|
|
|
def test_resolve_detects_from_url() -> None:
|
|
assert (
|
|
resolve_display_provider("openai", openai_api_url="https://openrouter.ai/api/v1")
|
|
== "OpenRouter"
|
|
)
|
|
|
|
|
|
def test_resolve_explicit_name_wins_over_detection() -> None:
|
|
assert (
|
|
resolve_display_provider(
|
|
"openai",
|
|
openai_api_url="https://openrouter.ai/api/v1",
|
|
provider_name="Internal Gateway",
|
|
)
|
|
== "Internal Gateway"
|
|
)
|
|
|
|
|
|
def test_resolve_non_openai_provider_untouched() -> None:
|
|
# Anthropic/Bedrock/etc. keep their own label regardless of openai_api_url.
|
|
assert (
|
|
resolve_display_provider("anthropic", openai_api_url="https://openrouter.ai/api/v1")
|
|
== "anthropic"
|
|
)
|
|
|
|
|
|
def test_resolve_plain_openai_unchanged() -> None:
|
|
assert resolve_display_provider("openai") == "openai"
|
|
# Unknown custom host with no override falls back to the raw label.
|
|
assert (
|
|
resolve_display_provider("openai", openai_api_url="https://api.mycorp.internal/v1")
|
|
== "openai"
|
|
)
|
|
|
|
|
|
def test_remap_provider_counts_relabels_only_openai() -> None:
|
|
config = ProxyConfig(openai_api_url="https://openrouter.ai/api/v1")
|
|
counts = {"openai": 7, "anthropic": 3}
|
|
assert _remap_provider_counts(counts, config) == {"OpenRouter": 7, "anthropic": 3}
|
|
|
|
|
|
def test_remap_provider_counts_noop_without_custom_upstream() -> None:
|
|
config = ProxyConfig()
|
|
counts = {"openai": 5}
|
|
assert _remap_provider_counts(counts, config) == {"openai": 5}
|