Merge pull request #41186 from BerriAI/litellm_statusline_router_cost_label

fix(cli): label savings cost bars with the auto-router name
This commit is contained in:
tin-berri 2026-09-15 00:32:08 -07:00 committed by GitHub
commit feab83aae1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 66 additions and 18 deletions

View file

@ -580,8 +580,8 @@ What the command changed is recorded in `~/.litellm/claude_configure_state.json`
`lite configure claude`, `lite login --config-claude`, `lite up` and `lite autoroute up` also install a status line (`~/.litellm/statusline.py`, registered as `statusLine` in `~/.claude/settings.json` unless you already run one) that shows which model the auto-router actually served the last turn and, once the proxy has recorded the session, what the session cost against the router's savings baseline:
```
claude-auto · Routed to: claude-haiku-4-5 -63% vs Claude Opus 5
LiteLLM ████████░░░░░░░░░░░░░░░░ $0.14
Routed to: claude-haiku-4-5 -63% vs Claude Opus 5
claude-auto ████████░░░░░░░░░░░░░░░░ $0.14
Claude Opus 5 ████████████████████████ $0.38
```

View file

@ -28,6 +28,7 @@ import os
import sys
import tempfile
import time
import unicodedata
import urllib.error
import urllib.request
from collections.abc import Callable, Mapping
@ -42,7 +43,6 @@ FETCH_TIMEOUT_SECONDS: Final = 3
BAR_WIDTH: Final = 24
BAR_FULL: Final = "\u2588"
BAR_EMPTY: Final = "\u2591"
SEPARATOR: Final = " \u00b7 "
TRANSCRIPT_SCAN_LIMIT_BYTES: Final = 4 * 1024 * 1024
CLAUDE_BASE_URL_ENV_KEYS: Final = ("ANTHROPIC_BASE_URL",)
CLAUDE_API_KEY_ENV_KEYS: Final = ("ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_API_KEY")
@ -50,7 +50,6 @@ CODEX_BASE_URL_ENV_KEYS: Final = ("OPENAI_BASE_URL",)
CODEX_API_KEY_ENV_KEYS: Final = ("OPENAI_API_KEY",)
CODEX_STOP_EVENT: Final = "Stop"
SYNTHETIC_MODEL: Final = "<synthetic>"
LITELLM_LABEL: Final = "LiteLLM"
RESET: Final = "\033[0m"
BOLD: Final = "\033[1m"
DIM: Final = "\033[90m"
@ -302,31 +301,37 @@ def _bar(fraction: float, color: str, width: int, use_color: bool) -> str:
return f"{color}{BAR_FULL * filled}{DIM}{BAR_EMPTY * (width - filled)}{RESET}"
def _display_width(label: str) -> int:
return sum(
2 if unicodedata.east_asian_width(character) in ("W", "F") else 1
for character in label
if unicodedata.category(character) not in ("Mn", "Me")
)
def render(model: str, session: Session | None, config_dir: Path, use_color: bool, bar_width: int = BAR_WIDTH) -> str:
def paint(code: str, text: str) -> str:
return f"{code}{text}{RESET}" if use_color else text
routed: Final = paint(BOLD, f"Routed to: {model}")
if session is None:
if session is None or session.baseline_model is None or session.baseline_spend <= 0:
return routed
header: Final = f"{session.router_name}{SEPARATOR}{routed}"
if session.baseline_model is None or session.baseline_spend <= 0:
return header
reference: Final = baseline_label(session.baseline_model, config_dir)
pct: Final = (session.baseline_spend - session.spend) / session.baseline_spend * 100
delta: Final = paint(LITELLM_COLOR, f"{'-' if pct >= 0 else '+'}{abs(round(pct))}% vs {reference}")
peak: Final = max(session.spend, session.baseline_spend)
label_width: Final = max(len(LITELLM_LABEL), len(reference))
label_width: Final = max(_display_width(session.router_name), _display_width(reference))
rows: Final = (
(LITELLM_LABEL, session.spend, LITELLM_COLOR),
(session.router_name, session.spend, LITELLM_COLOR),
(reference, session.baseline_spend, BASELINE_COLOR),
)
lines: Final = (
f"{paint(DIM, label.ljust(label_width))} {_bar(amount / peak, color, bar_width, use_color)} "
f"{paint(DIM, label + ' ' * (label_width - _display_width(label)))} "
f"{_bar(amount / peak, color, bar_width, use_color)} "
f"{paint(DIM, f'${amount:.2f}')}"
for label, amount, color in rows
)
return "\n".join((f"{header} {delta}", *lines))
return "\n".join((f"{routed} {delta}", *lines))
def color_enabled(env: Mapping[str, str]) -> bool:

View file

@ -252,11 +252,52 @@ class TestRender:
def test_savings_header_and_bars_against_the_routers_baseline(self, config_dir):
text = render("claude-sonnet-5", RECORDED, config_dir, use_color=False, bar_width=10)
assert text.splitlines() == [
"claude-auto · Routed to: claude-sonnet-5 -63% vs Claude Opus 5",
"LiteLLM ████░░░░░░ $0.14",
"Routed to: claude-sonnet-5 -63% vs Claude Opus 5",
"claude-auto ████░░░░░░ $0.14",
"Claude Opus 5 ██████████ $0.38",
]
def test_a_long_router_name_keeps_both_cost_bars_aligned(self, config_dir: Path) -> None:
session: Final = RECORDED._replace(router_name="engineering-smart-router")
text: Final = render("claude-sonnet-5", session, config_dir, use_color=False, bar_width=10)
assert text.splitlines()[1:] == [
"engineering-smart-router ████░░░░░░ $0.14",
"Claude Opus 5 ██████████ $0.38",
]
@pytest.mark.parametrize(
("router_name", "baseline_name", "router_padding", "baseline_padding"),
(
("路由-router", "Claude Opus 5", 3, 1),
("智能模型路由器", "Claude Opus 5", 1, 2),
("-router", "Claude Opus 5", 1, 1),
("cafe\u0301-router", "Claude Opus 5", 3, 1),
("a\u20dd-router", "Claude Opus 5", 6, 1),
("\u3099-router", "Claude Opus 5", 5, 1),
("auto", "基準モデル", 7, 1),
("auto", "cafe\u0301", 1, 1),
),
)
@pytest.mark.parametrize("use_color", (False, True))
def test_unicode_labels_align_cost_bars_by_terminal_columns(
self,
config_dir: Path,
router_name: str,
baseline_name: str,
router_padding: int,
baseline_padding: int,
use_color: bool,
) -> None:
(config_dir / "cache" / "gateway-models.json").write_text(
json.dumps({"models": [{"id": "claude-opus-5", "display_name": baseline_name}]})
)
session: Final = RECORDED._replace(router_name=router_name)
text: Final = ANSI.sub("", render("claude-sonnet-5", session, config_dir, use_color, bar_width=10))
assert text.splitlines()[1:] == [
f"{router_name}{' ' * router_padding}████░░░░░░ $0.14",
f"{baseline_name}{' ' * baseline_padding}██████████ $0.38",
]
def test_control_characters_in_any_externally_sourced_label_never_reach_the_terminal(self, tmp_path, config_dir):
# The transcript, the proxy payload and Claude Code's model cache all feed labels straight into a
# terminal, and none is under this script's control. Only the control bytes are dropped (ESC, BEL,
@ -289,7 +330,7 @@ class TestRender:
assert "+25% vs Claude Opus 5" in render("m", dearer, config_dir, use_color=False)
def test_without_a_baseline_only_the_routed_line_shows(self, config_dir):
assert render("m", RECORDED._replace(baseline_model=None), config_dir, False) == "claude-auto · Routed to: m"
assert render("m", RECORDED._replace(baseline_model=None), config_dir, False) == "Routed to: m"
assert render("m", None, config_dir, False) == "Routed to: m"
def test_color_wraps_the_same_text(self, config_dir):
@ -311,7 +352,8 @@ class TestClaudeCodeMode:
return Fetched(RECORDED, definitive=True)
text: Final = _run(_payload(transcript), _env(tmp_path, config_dir), fetch)
assert text.startswith("claude-auto · Routed to: claude-sonnet-5 -63% vs Claude Opus 5\n")
assert text.startswith("Routed to: claude-sonnet-5 -63% vs Claude Opus 5\n")
assert text.splitlines()[1].startswith("claude-auto ")
def test_a_discovered_display_name_labels_the_sessions_model(
self, tmp_path: Path, transcript: Path, config_dir: Path
@ -322,7 +364,7 @@ class TestClaudeCodeMode:
return Fetched(session, definitive=True)
text: Final = _run(_payload(transcript), _env(tmp_path, config_dir), fetch)
assert text.startswith("claude-auto · Routed to: Claude Opus 5 -63% vs Claude Opus 5\n")
assert text.startswith("Routed to: Claude Opus 5 -63% vs Claude Opus 5\n")
def test_an_unrecorded_session_degrades_to_the_routed_line(self, tmp_path, transcript, config_dir):
assert _run(_payload(transcript), _env(tmp_path, config_dir), lambda c, s: Fetched(None, True)) == (
@ -378,7 +420,8 @@ class TestCodexMode:
out = _run({"hook_event_name": "Stop", "session_id": SESSION_ID, "transcript_path": "/nope"}, env, fetch)
message = json.loads(out)["systemMessage"]
assert message.splitlines()[1] == "claude-auto · Routed to: claude-sonnet-5 -63% vs Claude Opus 5"
assert message.splitlines()[1] == "Routed to: claude-sonnet-5 -63% vs Claude Opus 5"
assert message.splitlines()[2].startswith("claude-auto ")
assert message.startswith("\n")
assert seen == [Credentials("http://127.0.0.1:4000", "sk-codex")]