6BenchmarkPresenter — the measurement tap over the presenter contract.
10A standalone :class:`~oc_ui.presenter_base.Presenter` (not a delegating
11wrapper) that records, per turn, exactly what the measurement surface
12exposes — and nothing else. It is the single lowest-blast-radius way to
13observe a turn with zero production changes (plan 136 ADR-136-1):
15- **TTFT** = monotonic at ``show_waiting`` (fired immediately before
16 ``client.stream_chat``) → the first ``show_reasoning`` / ``show_content``
17 chunk of that iteration. Iterations that emit only tool calls have no
18 first-chunk token, so their TTFT is ``None`` — never fabricated.
19- **Raw usage** — ``show_usage`` receives the raw provider dict (the
20 only place ``reasoning_tokens`` and the DeepSeek cache split survive).
21- **Main-loop tool names** — ``show_tool_call`` fires for main-loop
22 calls only; sub-loop internal tools never reach the presenter, so the
23 count is derived and the *names* are marked unavailable (ADR-136-5).
24- **Turn summary** — ``iterations`` and ``tool_calls`` drive the
25 sub-loop-count derivation (``tool_calls - main_loop_names``).
27Every other presenter hook is a no-op (the benchmark has no UI, and
28the real startup composition path calls ``show_info`` etc.).
32- :class:`BenchmarkPresenter` — implement the full ``Presenter`` ABC;
33 capture per-turn buffers; :meth:`reset_turn`; :meth:`build_turn`.
45- A *fresh* presenter (or one used only for a discarded warmup) leaks
46 nothing into a measured run; the runner uses a separate throwaway
47 presenter for warmup (plan §3.2 step 6).
48- :meth:`build_turn` is pure given the captured buffers; it does not
53- oc_diagnostics (structured debug logging via _dbg)
54- oc_chat.benchmarks.metrics (CallMetrics, TurnMetrics, extract_call_metrics)
55- oc_ui.presenter_base (Presenter ABC)
56- oc_chat.core.session (lazy; ChatSession - type hint for the no-op show_welcome)
58from __future__
import annotations
61from contextlib
import contextmanager
62from typing
import TYPE_CHECKING, Iterator
64from oc_diagnostics
import _dbg
73_dbg(
"OC_CHAT.MAIN",
"module",
"presenter loaded", 4)
77 """Standalone presenter that records per-turn measurements only."""
79 def __init__(self, clock=time.monotonic) -> None:
86 """Clear all per-turn buffers. Called between measured turns."""
126 if isinstance(usage, dict):
127 self.
usages.append(dict(usage))
136 prompt_cached: int |
None =
None,
138 cache_hit_pct: float |
None =
None,
139 cache_saved_tokens: int |
None =
None,
140 performance_stats: object =
None,
141 model: str |
None =
None,
142 context_used_pct: float |
None =
None,
143 duration_ms: int |
None =
None,
144 turn_cost: object =
None,
154 def build_turn(self, index: int, cold_label: str) -> TurnMetrics:
155 """Materialise the captured buffers into a :class:`TurnMetrics`.
157 Sub-loop tool *names* are not observable via the presenter, so
158 ``sub_loop_count`` is derived as ``summary.tool_calls −
159 len(main_loop_tool_names)`` (acc.tool_calls counts main-loop
160 dispatches + sub-loop offloads — react_loop.py:545/721) and the
161 names are marked unavailable.
163 calls = [extract_call_metrics(u)
for u
in self.
usages]
167 cold_label=cold_label,
172 sub_loop_count=sub_loop_count,
173 sub_loop_tool_names_available=
False,
179 def turn(self) -> Iterator[None]:
190 def show_help(self, command_help: dict, tools: list[str] |
None =
None) ->
None: ...
196__all__ = [
"BenchmarkPresenter"]
TurnMetrics build_turn(self, int index, str cold_label)
None show_error(self, str msg)
Iterator[None] turn(self)
None __init__(self, clock=time.monotonic)
None show_info(self, str msg)
None show_usage(self, dict usage)
None show_content(self, str text)
None show_turn_summary(self, int prompt, int completion, int total, int iterations, int tool_calls, int|None prompt_cached=None, *, float|None cache_hit_pct=None, int|None cache_saved_tokens=None, object performance_stats=None, str|None model=None, float|None context_used_pct=None, int|None duration_ms=None, object turn_cost=None)
None show_tool_call(self, str name, str arguments)
None show_compressed(self)
None show_welcome(self, "ChatSession" session)
None show_tool_result(self, str name, object result)
None show_help(self, dict command_help, list[str]|None tools=None)
None show_tool_progress(self, str name)
None show_session_list(self, list sessions, str|None current_name=None)
None show_reasoning(self, str text)
None show_iteration_stat(self, "IterationStat" stat)
None show_available_models(self, dict models)