Skip to content

Commit 55cf62e

Browse files
Fix abbreviation formatting in visualizer (#989)
Co-authored-by: openhands <[email protected]>
1 parent 18d04f2 commit 55cf62e

File tree

2 files changed

+45
-5
lines changed

2 files changed

+45
-5
lines changed

openhands-sdk/openhands/sdk/conversation/visualizer.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -294,14 +294,14 @@ def _format_metrics_subtitle(self) -> str | None:
294294
def abbr(n: int | float) -> str:
295295
n = int(n or 0)
296296
if n >= 1_000_000_000:
297-
s = f"{n / 1_000_000_000:.2f}B"
297+
val, suffix = n / 1_000_000_000, "B"
298298
elif n >= 1_000_000:
299-
s = f"{n / 1_000_000:.2f}M"
299+
val, suffix = n / 1_000_000, "M"
300300
elif n >= 1_000:
301-
s = f"{n / 1_000:.2f}K"
301+
val, suffix = n / 1_000, "K"
302302
else:
303303
return str(n)
304-
return s.replace(".0", "")
304+
return f"{val:.2f}".rstrip("0").rstrip(".") + suffix
305305

306306
input_tokens = abbr(usage.prompt_tokens or 0)
307307
output_tokens = abbr(usage.completion_tokens or 0)

tests/sdk/conversation/test_visualizer.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,13 +333,53 @@ def test_metrics_formatting():
333333
# Test the metrics subtitle formatting
334334
subtitle = visualizer._format_metrics_subtitle()
335335
assert subtitle is not None
336-
assert "1.50K" in subtitle # Input tokens abbreviated
336+
assert "1.5K" in subtitle # Input tokens abbreviated (trailing zeros removed)
337337
assert "500" in subtitle # Output tokens
338338
assert "20.00%" in subtitle # Cache hit rate
339339
assert "200" in subtitle # Reasoning tokens
340340
assert "0.0234" in subtitle # Cost
341341

342342

343+
def test_metrics_abbreviation_formatting():
344+
"""Test number abbreviation with various edge cases."""
345+
from openhands.sdk.conversation.conversation_stats import ConversationStats
346+
from openhands.sdk.llm.utils.metrics import Metrics
347+
348+
test_cases = [
349+
# (input_tokens, expected_abbr)
350+
(999, "999"), # Below threshold
351+
(1000, "1K"), # Exact K boundary, trailing zeros removed
352+
(1500, "1.5K"), # K with one decimal, trailing zero removed
353+
(89080, "89.08K"), # K with two decimals (regression test for bug)
354+
(89000, "89K"), # K with trailing zeros removed
355+
(1000000, "1M"), # Exact M boundary
356+
(1234567, "1.23M"), # M with decimals
357+
(1000000000, "1B"), # Exact B boundary
358+
]
359+
360+
for tokens, expected in test_cases:
361+
stats = ConversationStats()
362+
metrics = Metrics(model_name="test-model")
363+
metrics.add_token_usage(
364+
prompt_tokens=tokens,
365+
completion_tokens=100,
366+
cache_read_tokens=0,
367+
cache_write_tokens=0,
368+
reasoning_tokens=0,
369+
context_window=8000,
370+
response_id="test",
371+
)
372+
stats.usage_to_metrics["test"] = metrics
373+
374+
visualizer = ConversationVisualizer(conversation_stats=stats)
375+
subtitle = visualizer._format_metrics_subtitle()
376+
377+
assert subtitle is not None, f"Failed for {tokens}"
378+
assert expected in subtitle, (
379+
f"Expected '{expected}' in subtitle for {tokens}, got: {subtitle}"
380+
)
381+
382+
343383
def test_event_base_fallback_visualize():
344384
"""Test that Event provides fallback visualization."""
345385
from openhands.sdk.event.base import Event

0 commit comments

Comments
 (0)