Repository navigation
usage: the chat replys usage object is a pure function, so engine#2111 is testable without an NPU - #2188
Conversation
…2111 is testable without an NPU
engine#2111 is about one field: `completion_tokens_details.reasoning_tokens` is what distinguishes "the budget ran
out inside a thinking block we strip" from "the model said nothing". The field was emitted correctly, but its rule
lived in a lambda inside the reply builder - and linking app/forward_serve.cpp drags in `npu/rt.h`, which refuses a
build with no NPU runtime. So the only way to check the rule was to run a model on the device, which is why the row
still had a device window as its last step.
app/forward_serve.h UsageCounters + usage_json_text() declared (no nlohmann in the header)
app/serve_usage.cpp the definition: the JSON stays beside the reply it belongs to
app/forward_serve.cpp the lambda now fills the struct and parses the function's text (one implementation)
tests/serve_usage_test.cpp + cmake/tests-serve-usage.cmake 8 arms, links ONLY app/serve_usage.cpp
The three cases that must stay three answers:
reasoning_tokens > 0 -> the field is PRESENT with that number
reasoning_tokens == 0 -> PRESENT with 0 (counted, none)
reasoning_tokens == -1 -> ABSENT, never a zero (the route could not count it, e.g. /v1/completions)
plus the prefix-cache fields (cached_tokens, slot, reason, pinned) unchanged by the extraction, and a
default-constructed UsageCounters omitting the field so a caller that never set it is safe.
Verified: the 8 arms pass standalone (`g++ tests/serve_usage_test.cpp app/serve_usage.cpp`, rc 0) and through ctest in
a real configure (`Test #230 serve_usage`, 100% passed). The test can fail: flipping the rule to `>= -1` in a scratch
copy makes exactly the two "-1 must be absent" arms go red. Ratchets unmoved: ci_targets 9 (baseline 9, none new),
copyright rc 0, tmp_path 44 sites none new/stale.
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at c4555aa: READY
The usage object as a pure function (serve_usage.cpp, UsageCounters filled from st.prompt_tokens/st.completion_tokens) is the extraction the #2111/#2131 thread needed. Reasoning-token accounting and an empty-content refusal both depend on the same counters, and while they were computed inline in the server every assertion about them was really an assertion about a request.
Pulling them into a function that takes the counts and returns the object means a test can state the rule — prompt tokens, completion tokens, and the reasoning split — without a model, a socket or a template. Same move as #2062, #2054, #2055: pull the decision out where an arm can reach it, then let the runtime be a thin caller.
target_sources(onebit PRIVATE app/serve_usage.cpp) keeps it compiled into the real binary rather than into a test-only copy, so the pure function under test is the one that ships.
This is the last step of engine#2111 that needed a device, made CPU-only — I lead that row, and its ORCH line said it needed a ~10-minute NPU window to confirm the wiring. It does not, now.
Why the window existed
#2111is about one field:completion_tokens_details.reasoning_tokensis what distinguishes "the budget ran out inside a thinking block we strip" from "the model said nothing". The field is emitted correctly (verified earlier:think_split_test12 arms,npu_answer_parser_test59 arms, rc 0). But the rule lived in a lambda inside the reply builder, and linkingapp/forward_serve.cppdrags innpu/rt.h, which refuses a build with no NPU runtime:So the field's presence rule could only be observed by running a model on the device.
What this does
app/forward_serve.hUsageCounters+usage_json_text()declared — no nlohmann in the headerapp/serve_usage.cppapp/forward_serve.cpptests/serve_usage_test.cpp+cmake/tests-serve-usage.cmakeapp/serve_usage.cppThe three cases that must stay three answers:
plus the prefix-cache fields (
cached_tokens, slot, reason, pinned) unchanged by the extraction, and a default-constructedUsageCountersomitting the field so a caller that never set it is safe.Verified three ways
Ratchets unmoved:
ci_targets_check9 (baseline 9, none new) ·copyrightrc 0 ·tmp_path_check44 sites, none new/stale.Scope
+1TU,+1test,+1family file, and a lambda replaced by three lines that delegate.app/forward_serve.cppis a hot file (#1913touches it), so the change there is deliberately mechanical: the JSON text is produced by one function that the reply parses, and nothing about the reply's shape changes.