Repository navigation
feat(novelty): usage and cost block for routing --model - #164
Conversation
A model-backed `novelty routing` run wrote no record of what it spent, so a routing rate could not be read against its cost. The run now writes a `cost` block into routing.json (calls, input/output tokens, cache read/write tokens, and usd, null when any call is unpriced) and prints the same totals on stdout. Cache token totals are added to the per-role cost summary, so novelty analyze reports them too. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Declare cache_read_tokens and cache_write_tokens (integer, minimum 0) in the novelty.v1 cost block, which routing and analyze both write. Say in skill-novelty.md that cache tokens are reported separately and may or may not be inside input_tokens, depending on the provider. The routing test stub now sets cache_write_tokens too, and asserts its total. The stdout cost line drops the trailing model tag, which the model line above already carries. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #164 +/- ##
==========================================
+ Coverage 92.74% 92.75% +0.01%
==========================================
Files 254 256 +2
Lines 32087 32425 +338
Branches 5344 5422 +78
==========================================
+ Hits 29759 30077 +318
- Misses 1560 1562 +2
- Partials 768 786 +18
🚀 New features to boost your workflow:
|
antonwolfy
left a comment
There was a problem hiding this comment.
The cost block is right when every call returns a priced usage record, and the JSON totals are correct. Three things are worth fixing before merge: the stdout line when calls are missing or failed, the replayed-call accounting, and the stdout assertions.
What I ran, on a Linux review host at 14a7e93:
make ci-local: 20 passed, 8 failed, 7 skipped. 6883 tests passed, 21 skipped; changed-line coverage, schema-compat vs the base, wheel build and smoke all pass. The 8 failures are ruff, mypy, mkdocs, pip-audit and the other tool steps, missing from my venv becausemake devstops at the tools install on this base, which #179 fixed onmain. Static checks, mypy and the strict docs build pass on GitHub's CI at this head.- Reproductions for the code findings, with the provider call stubbed.
- Mutation run on the new code and tests: mutants of
client.py, the JSON totals and thecostblock are all caught. Four mutants of the printed line pass the suite (inline comment).
Checked, no issues
- Schema: the two new fields are additive optional integers matching their neighbours, and the
costobject has noadditionalProperties: false, so no version bump is needed.routing.jsonitself is not validated by any schema. - Other callers:
roll_up_costhas one other caller (novelty/analyze.py); the markdown report prints only calls, tokens and usd, so it does not change. - Lexical runs: no
costblock is written and the print sits under the sameif args.modelguard.
… cache replays Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…commands/novelty.py for W1 Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…baseline after the extraction Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
roll_up_cost sums the new cache_read/cache_write/replayed_calls fields over every role, but only single-client tests covered them, so a total taken from the first role alone (what novelty analyze would then report) passed. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
antonwolfy
left a comment
There was a problem hiding this comment.
Review at d2fd404. The three earlier threads are fixed: the zero-call line, the replayed-call accounting and the full-line assertion all hold. Two things are worth fixing before merge: a change to novelty analyze's existing cost keys that the PR calls additive, and two test gaps found by mutation.
What I ran, on a Linux review host at d2fd404:
make ci-local: 28 passed, 1 failed, 7 skipped. 7213 tests passed, 1 skipped; changed-line coverage, schema-compat vs the base, mypy, ruff, wheel build and smoke all pass. The one failure is pip-audit, which reports a gitpython 3.2.0 advisory (fix 3.2.1) from the lock files; the PR does not touch them.- Reproductions with the LLM transport injected, comparing
roll_up_costat the base and at the head. - Mutation run on the new code against
test_novelty_routing.pyandtest_novelty_measure.py: 23 mutants, 4 of which pass the suite (two inline comments).
This PR changes existing novelty analyze cost values, not only adds keys. analyze.py:386 calls roll_up_cost, and cost_summary (client.py:171-178) now skips replayed rows and sets usd to null when a call returned no usage. Two calls, each 100 in / 10 out / $0.01, replayed=True:
- base:
{'llm_calls': 2, 'input_tokens': 200, 'output_tokens': 20, 'usd': 0.02} - head:
{'llm_calls': 2, 'input_tokens': 0, 'output_tokens': 0, ..., 'replayed_calls': 2, 'usd': 0}
One live call and one call withusage=None: baseusd0.01, headusdNone. So a resumedanalyzerun withDOKIMI_RESPONSE_CACHEnow reportsllm_callsthat no longer matches its token andusdtotals, and the Markdown report (novelty/report.py:468-474, outside this diff) prints "2 LLM calls, 0 in / 0 out, usd 0" without mentioning replays. The new rules are the right ones, and they match the spend cap. But the PR description says "no existing key changes meaning", the changelog fragment files this under Added and namesanalyzeonly for the cache keys, and the docs paragraph sits in the routing section. Suggestion: say in the changelog fragment (as Changed) and in thecostschema descriptions ofllm_calls,input_tokens,output_tokensandusdthat replayed calls are excluded from the totals, printreplayed_callsin thereport.pycost line when it is non-zero, and correct the PR description. Related: with an unpriced replayed row and a priced live row,usdis 0.01, while the docs and schema sayusdisnullwhen any call had no known price. Say "any call this run paid for".
|
antonwolfy
left a comment
There was a problem hiding this comment.
Review at a5b007e. The only change since my last review is a test that pins the cross-role roll_up_cost sums, and it holds. The analyze behaviour change and both test gaps from d2fd404 are still open, plus one more wording point on usd.
What I ran, on a Linux review host at a5b007e:
make ci-local: 28 passed, 1 failed, 7 skipped. 7214 tests passed, 1 skipped; ruff, mypy, schema-compat vs the base, docs build, wheel build and smoke all pass. The one failure ispip-audit, which reports an advisory from the lock files; this PR does not touch them.- Mutation run on
cost_summary,roll_up_costand the printed routing summary againsttest_novelty_routing.pyandtest_novelty_measure.py: 33 mutants, 8 of which pass both files. Three of them are the ones in my open threads; the rest are print-only lines of_print_routing_summarythat predate the PR's logic. - Reproductions with the LLM transport injected.
Still open from my earlier review:
dokimi/tests/test_novelty_routing.py:545: nothing pinsusd is Nonefor a client with no calls; droppingself.calls > 0atclient.py:178still passes.dokimi/tests/test_novelty_routing.py:569: the replay test gives the replayed rows no cache tokens; summing cache tokens overself.usageinstead ofbilled(client.py:174-175) still passes, and so does printing the replayed suffix unconditionally.
-
This PR still changes existing
novelty analyzecost values, not only adds keys.analyze.pycallsroll_up_cost, which now takescost_summary's rules: replayed rows are left out of tokens andusd, andusdisnullwhen a call returned no usage. Two replayed calls, each 100 in / 10 out / $0.01, giveinput_tokens: 0, output_tokens: 0, usd: 0, replayed_calls: 2at the head, against 200 / 20 / 0.02 at the base.llm_callsstill counts them (2), so calls and tokens describe different sets. Nothing else moved with it:novelty/report.py:468-473(outside this diff) prints only calls, tokens andusd, so a cachedanalyzererun reads "2 LLM calls, 0 in / 0 out, usd 0" with no mention of replays, andusd Nonefor a call with no usage.docs/benchmarking-and-comparison.md:691-693(outside this diff) still says a replayed entry reports the stored usage "so cost stays comparable across runs".- The changelog fragment files it all under Added; the
usd,llm_calls,input_tokensandoutput_tokensdescriptions inschemas/novelty.v1.jsondo not mention the exclusion.
Suggestion: move theanalyzepart of the fragment to Changed, add the exclusion to those schema descriptions, printreplayed_callsin thereport.pycost line when it is non-zero, fix the benchmarking doc, and correct the PR description ("no existing key changes meaning").
-
usdis notnull"when any call had no known price" if the unpriced call was replayed. The check runs over non-replayed rows only (client.py:178). One live call (5 in, $0.1) plus one replayed call with no price givesllm_calls: 2, input_tokens: 5, replayed_calls: 1, usd: 0.1. A single unpriced replayed call givesusd: 0and the routing line prints$0.0000. The changelog,docs/skill-novelty.md:611and thecost_summarydocstring all say "any call". Excluding replayed rows is the right rule; the wording is not. Suggestion: say "any call this run paid for" in the fragment, the doc paragraph, the docstring and the schemausddescription.
@xaleryb's question on whether analyze keeps stored totals or this-run spend, and whether zero calls give usd None or 0.0, waits for the author.
Checked, no issues
- Schema: the new
costfields are optional integers added in place, so no version bump orSCHEMA_VERSIONSchange is needed. roll_up_cost: zeroing any one of the calls, input, output, cache read, cache write or replayed sums fails the suite, and the new cross-role test covers the first-role-only total.- Routing line: the zero-calls branch, the unknown-USD text, the
.4fformat and swaps of read/write or input/output are all caught.
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
antonwolfy
left a comment
There was a problem hiding this comment.
I pushed a commit per nit, up to 54f4ca8, which fix the nit from my review:
54f4ca8:dokimi/tests/test_novelty_measure.py:247: added the second blank line beforetest_a_failed_call_is_a_dropped_pair_not_a_graded_answer.
make ci-local at 54f4ca8: 28 passed, 1 failed (pip-audit, as at the reviewed head), 7 skipped.
Not approving yet. Still open: the two risks from my review; the two earlier threads of mine above nit level.
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com> # Conflicts: # dokimi/dokimi/commands/novelty.py # dokimi/scripts/growth_baseline.json # dokimi/tests/test_novelty_routing.py
…ay 'this run paid for' Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
|
Review-body items at 2594a80 (after merging main in 21a1e0d, which had conflicts in novelty.py, growth_baseline.json and test_novelty_routing.py):
|
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com> # Conflicts: # dokimi/scripts/growth_baseline.json
antonwolfy
left a comment
There was a problem hiding this comment.
Review at f4df39a. Both threads from d2fd404 are fixed, and the printed line, the zero-call case, the usd wording and the replayed-row accounting in cost_summary all hold. Three things are worth fixing before merge: the novelty analyze cost values that this PR still changes, and two test gaps found by mutation.
What I ran, on a Linux review host at f4df39a:
make ci-local: 28 passed, 1 failed, 7 skipped. 7769 tests passed, 1 skipped; ruff, mypy, bandit, schema-compat vs the base, changed-line coverage, docs build, wheel build and smoke all pass. The one failure ispip-audit, which reports advisories onfilelockandsetuptoolsfrom the lock files; this PR does not touch them.- Mutation run on
cost_summary,roll_up_costand the printed routing summary againsttest_novelty_routing.pyandtest_novelty_measure.py: 46 mutants, 8 of which pass both files (126 passed). I re-ran five of them by hand; they are in the inline comments. - Offline reproductions of
cost_summaryandroll_up_costwith synthetic usage rows.
Still open from my earlier review: none of my threads. The risk from my last review body is still open, as item 1.
-
This PR still changes existing
novelty analyzecost values, and the all-replayed case now looks like a free run.analyze.py:386callsroll_up_cost, which now leaves replayed rows out of tokens andusd. Two replayed calls, each 100 in / 10 out / $0.01, givellm_calls: 2, input_tokens: 0, output_tokens: 0, replayed_calls: 2, usd: 0(an int0, not0.0). Theusddescription inschemas/novelty.v1.json:453still saysnullis "distinct from a genuinely free run", yet a cached rerun and a free run now differ only inreplayed_calls.novelty/report.py:468-473(outside this diff) prints calls, tokens andusdonly, so a cached rerun reads "2 LLM calls, 0 in / 0 out, usd 0".docs/benchmarking-and-comparison.md:693(outside this diff) still says a replayed entry keeps the stored usage "so cost stays comparable across runs".- The changelog fragment files the
analyzechange under Added, and the PR description says no existing key changes meaning.
Suggestion: move theanalyzepart of the fragment to Changed, printreplayed_callsin thereport.pycost line when it is non-zero, fix the benchmarking doc, and correct the PR description. @xaleryb's question on whetheranalyzekeeps stored totals or this-run spend waits for the author.
-
Nit:
llm_callsincludes replayed calls, and the docs do not say so. The stdout line reads "2 calls, 0 input / 0 output tokens, ..., 2 replayed from the cache and not counted", where the 2 calls are the 2 replayed ones.docs/skill-novelty.md:612-618and the changelog fragment say replayed calls are left out of the token andusdtotals, but not thatllm_callsstill counts them. Suggestion: add "llm_callsstill counts them" to the docs paragraph and the fragment.
Checked, no issues
- Schema: the new
costfields are optional integers added in place, andcosthas noadditionalProperties: false, so no version bump orSCHEMA_VERSIONSchange is needed.routing.jsonitself is not schema-validated. - Other callers:
roll_up_costis called only byanalyze.py:386and the new routing code; both copy the dict, so the extra keys break nothing. - Lexical runs: no
costkey is written, and the print sits under the sameif args.modelguard, so there is noKeyError. - Mutants killed: dropping
self.calls > 0, dropping thelen(self.usage) == self.callscheck, summing cache tokens overself.usageinstead of the billed rows, zeroing anyroll_up_costsum exceptoutput_tokens, and swapping read/write or input/output in the printed line all fail the suite. - Growth baseline:
scripts/growth_baseline.jsonsays 879 lines forcommands/novelty.pyand the file has 879.
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
antonwolfy
left a comment
There was a problem hiding this comment.
I pushed a commit per nit, up to 415c5cd, which fix the two nits from my review:
6ca352c:dokimi/tests/test_novelty_routing.py:547: the cost line is now compared as a whole line inout.splitlines(), so extra digits fail.415c5cd:dokimi/docs/skill-novelty.md:618: the docs and the changelog fragment now sayllm_callsstill counts replayed calls.
make ci-local at 415c5cd: 28 passed, 1 failed (pip-audit, same as the reviewed head), 7 skipped.
GitHub CI has not run on these commits: the branch conflicts with main, and Actions runs no workflow on a conflicting PR. CI will check them once main is merged in.
Not approving yet. Still open: the three risks from my review.
Conflicts in tests/test_novelty_routing.py (both sides added tests; the PR's cost tests move to tests/test_novelty_routing_cost.py so the file stays under the 1000-line cap) and scripts/growth_baseline.json (main's baseline, novelty.py entry raised to 999 for this PR's lines). Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…lyze change under Changed The analyze cost totals now leave replayed calls out (cost_summary is shared), so the Markdown cost line says how many were replayed, the benchmarking doc says the run totals are this run's spend, and the changelog fragment files the analyze part under Changed. Tests pin the whole replayed routing line, a live + replayed + usage-less mix (3 calls, 1 replayed, usd None), and the cross-role output_tokens sum. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
|
Updated at 9993768 (merged Review at f4df39a (@antonwolfy) and @xaleryb's question:
|
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 9993768. Both earlier threads are fixed, and the novelty analyze decision (this-run spend, replayed calls named in the report line, benchmarking doc and changelog updated) closes my earlier risk. One nit inline, a test gap in the new report line.
What I ran, on Linux at 9993768:
make ci-local: 28 passed, 0 failed, 7 skipped. Changed-line coverage, schema-compat vs the base, ruff, mypy, bandit, docs build, wheel build and smoke all pass.- Mutation run on
cost_summary,roll_up_cost, the printed routing line and the report line againsttest_novelty_routing_cost.py,test_novelty_measure.pyandtest_novelty_report.py: 37 mutants, 5 of which pass. One is the inline comment; I re-ran it by hand.
Checked, no issues
- Schema: the three new
costfields are optional integers added in place, andcosthas noadditionalProperties: false, so no version bump is needed.schema_compatreports only warnings for new properties. - Accounting: replayed rows are left out of tokens, cache tokens and
usd;llm_callscounts them; zero calls and a call with no usage giveusdnull. The docs, changelog and schema descriptions match this. - Growth baseline:
commands/novelty.pyis 999 lines against a baseline of 999.
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
A two-query run on a small model costs about $0.00004, which the cost line printed as $0.0000, the same figure as an all-replayed run. Use the "<$0.0001" form task_runs_report already uses for the same case. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
|
Part of #4.
Summary
A
novelty routing --modelrun now records what it spent.The totals are what this run spent: calls replayed from the response cache are counted in
replayed_callsand left out of the token andusdsums (llm_callsstill counts them), as the spend cap already does. A run with no counted calls printscost 0 calls.The per-role summary is shared with
novelty analyze, so itscostblock changes too: it gains the cache token counts andreplayed_calls, leaves replayed calls out ofinput_tokens,output_tokensandusd(they were summed from the stored records before), and givesusdnull when a call returned no usage. The Markdown report's cost line names the replayed calls when there are any. A run without--modelmakes no calls and writes nocostblock.Evidence
Routing cost tests in
tests/test_novelty_routing_cost.py(split out sotest_novelty_routing.pystays under the 1000-line cap), roll-up tests intests/test_novelty_measure.py, and the report line intests/test_novelty_report.py. The first ones listed below were the initial red/green check: The two model-run tests fail with theintel/mainversion ofcommands/novelty.pyandnovelty/client.py(nocostblock, no cache fields) and pass on this branch:Local checks on the rebased head:
Merge Danger
Door: two-way. The routing JSON gains keys. In the
novelty analyzecost block existing keys change meaning:input_tokens,output_tokensandusdnow exclude replayed calls, andusdis null when a call returned no usage (filed under Changed in the changelog). Reverting restores the old output.Blast Radius: low, limited to
--modelrouting runs and the analyze cost summary and report line. A cachedanalyzererun now reports 0 tokens andusd 0plusreplayed_calls, where it reported the stored totals.schemas/novelty.v1.jsongainscache_read_tokens,cache_write_tokensandreplayed_callsin the cost summary. Readers that validate strictly will see three new optional integer fields.usdisnull, not0, when any call had no price, so a missing price is not read as a free run.input_tokensdepends on the provider. The docs say so; the two counts are reported separately.Checklist
pytestpassesruff,mypy, and schema checks passdocs/skill-novelty.md(routing cost block),docs/benchmarking-and-comparison.md(run totals leave replayed calls out)🤖 Generated with Claude Code