A thinking block that says how long and how much
Each block reports its own round now. `reasoning_ms` was the reply's first burst, written once, so on the fifteen-block reply GPT-OSS actually produces only the first could claim a duration and the other fourteen said "Thought" and nothing at all. `Generation.thinking_ms` accumulates per round and `close_step` stamps it cumulatively, so steps.py diffs it exactly as it already diffs the three lengths beside it. The interval between a round's first and last reasoning delta, deliberately, not a sum of gaps between deltas -- that would count the network's latency as the model's thinking. While it runs: "Thinking" with an ellipsis that types itself, and the seconds and tokens climbing beside it. The ellipsis is a `content` keyframe, so there is no timer to start, stop or clean up when the block is swapped away -- it stops existing when the element does. The numbers come from a `think` frame, and `round_thinking_ms` is written by the producer rather than computed by the follower from a start time: a model that has stopped thinking and moved on to a tool should show a settled number, not a clock that keeps running. Tokens read exactly up to 200 and as `0.4k` above it, from one helper shared by the live label and the stored one, so the two cannot drift into two conventions. The live duration is terser than the finished one -- `6s` against `6 seconds` -- because it sits beside an animating word and changes every second, where "less than a second" flickering into "1 second" reads as a glitch. Checked against the real endpoint: fourteen marks carrying 919ms through 14223ms, per-block labels from "less than a second · 111" to "4 seconds · 0.5k", and the live frames resetting each round rather than accumulating. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -346,3 +346,64 @@ def _live_steps(generation):
|
||||
events=list(generation.tool_events),
|
||||
marks=list(generation.steps),
|
||||
)
|
||||
|
||||
|
||||
# --- What a thinking block says about itself ----------------------------------
|
||||
def test_a_token_count_stays_exact_until_the_digits_stop_meaning_anything():
|
||||
assert steps.format_tokens(0) == ""
|
||||
assert steps.format_tokens(86) == "86"
|
||||
assert steps.format_tokens(200) == "200"
|
||||
assert steps.format_tokens(201) == "0.2k"
|
||||
assert steps.format_tokens(1500) == "1.5k"
|
||||
|
||||
|
||||
def test_the_live_label_is_terser_than_the_finished_one():
|
||||
"""It sits beside an animating word and changes every second. "less than a
|
||||
second" flickering into "1 second" reads as a glitch, not a measurement."""
|
||||
assert steps.thinking_label(ms=6000, tokens=400, live=True) == "6s · 0.4k"
|
||||
assert steps.thinking_label(ms=6000, tokens=400, live=False) == "6 seconds · 0.4k"
|
||||
assert steps.thinking_label(ms=64000, tokens=0, live=True) == "1m 04s"
|
||||
|
||||
|
||||
def test_a_block_with_nothing_to_report_says_nothing():
|
||||
"""Rather than "Thought for 0 seconds", which is worse than the bare word."""
|
||||
assert steps.thinking_label(ms=0, tokens=0, live=False) == ""
|
||||
assert steps.thinking_label(ms=0, tokens=0, live=True) == ""
|
||||
|
||||
|
||||
def test_each_block_reports_its_own_round_not_the_reply():
|
||||
"""The whole point of the per-round timing. It used to read the reply's
|
||||
total, so only the first block could honestly claim it and every other one
|
||||
said "Thought" and nothing else."""
|
||||
built = steps.for_message(
|
||||
_message(
|
||||
reasoning="a" * 400 + "b" * 800,
|
||||
events=[{"name": "x"}],
|
||||
marks=[
|
||||
{"round": 1, "thinking_to": 400, "text_to": 0, "tools_to": 1, "thinking_ms": 4000}
|
||||
],
|
||||
ms=9000,
|
||||
)
|
||||
)
|
||||
|
||||
labels = [s.label for s in built if s.kind == "thinking"]
|
||||
assert labels[0].startswith("4 seconds")
|
||||
# The last round closes no mark -- it is the one that stopped calling
|
||||
# tools -- so its duration is whatever the reply spent beyond the last mark.
|
||||
assert labels[1].startswith("5 seconds")
|
||||
|
||||
|
||||
def test_a_row_with_no_per_round_timing_falls_back_to_the_reply():
|
||||
"""Every reply written before the marks carried a duration. One block, and
|
||||
the reply's own figure is exactly right for it."""
|
||||
built = steps.for_message(_message(reasoning="a" * 400, ms=3000))
|
||||
|
||||
assert built[0].label.startswith("3 seconds")
|
||||
|
||||
|
||||
def test_a_running_block_carries_no_label_of_its_own():
|
||||
"""The live one is fed by the `think` frame, which knows the clock. Baking a
|
||||
stale number into the markup would be a number that never moved."""
|
||||
generation = _generation(reasoning=["still thinking"])
|
||||
|
||||
assert [s.label for s in _live_steps(generation)] == [""]
|
||||
|
||||
Reference in New Issue
Block a user