mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-16 23:41:43 +00:00
fix(cli): align wide and combining Unicode cost labels
This commit is contained in:
parent
bebc76316c
commit
4fca818f34
2 changed files with 45 additions and 2 deletions
|
|
@ -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
|
||||
|
|
@ -301,6 +302,14 @@ 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
|
||||
|
|
@ -315,13 +324,14 @@ def render(model: str, session: Session | None, config_dir: Path, use_color: boo
|
|||
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(session.router_name), len(reference))
|
||||
label_width: Final = max(_display_width(session.router_name), _display_width(reference))
|
||||
rows: Final = (
|
||||
(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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -265,6 +265,39 @@ class TestRender:
|
|||
"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),
|
||||
("ABC-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,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue