Repository navigation
Conversation
`novelty routing --model` now asks each query N times (--repeats) with the same card order, and reports each query's agreement across its repeats plus the spread of the selection rate between repeats (`noise`), so a change of a few points can be read against run-to-run noise. Every abstention or unparseable pick keeps the model's raw reply under per_query[].raw_reply. Repeats are only real samples if the response cache sees a new draw, so LLMClient.text takes a `draw` index and forwards it to llm_call_with_usage. Draw 0 is the default and keeps every existing cache entry. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 6a6631c. The design holds: one card order per query, draw forwarded so repeats are not cache replays, and draw 0 keeps existing cache keys. Two things are worth fixing before merge, and one nit.
What I ran, on a Linux review host at 6a6631c:
make ci-localdid not run:make devstops at the tools install on this base, which #179 fixed onmain. GitHub's CI passes at this head (tests on 3.11-3.14, static checks, mypy, the strict docs build).tests/test_novelty_routing.py: 60 passed.- Reproductions for the code findings, with the LLM transport mocked.
- A mutation run: 24 mutants, 19 killed, 5 survive the full suite (listed under Test gap).
Test gap. Five mutants pass all 60 tests: dropping the "(abstained)" label in _noise (routing.py:350), changing the len(known) > 1 guard to > 0 (routing.py:376, a single usable repeat would then report half_range 0.0 instead of None), keeping the tail instead of the head of raw_reply (routing.py:294), dividing agreement by the whole group instead of the non-errored rows (routing.py:355), and the abstention case in _noise generally. Add a _noise test with one abstention, one errored repeat and one usable repeat, and make the raw_reply test assert reply[:2000], not only the length.
Checked, no issues
- Draw forwarding: dropping
draw=repeator the pass-through inclient.pyfails the tests. - Card order: identical across repeats of a query.
- Spend cap:
SpendCapReachedis not swallowed by the per-repeatexcept Exception. - Docs and changelog: match the flag and keys in the code.
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #175 +/- ##
=======================================
Coverage 91.98% 91.99%
=======================================
Files 223 223
Lines 27743 27778 +35
Branches 4560 4568 +8
=======================================
+ Hits 25520 25553 +33
- Misses 1574 1575 +1
- Partials 649 650 +1
🚀 New features to boost your workflow:
|
antonwolfy
left a comment
There was a problem hiding this comment.
I pushed a commit per nit, up to 85765e2, which fix the nit from my review:
85765e2:dokimi/dokimi/novelty/routing.py:313: added ann_callskey (queries x repeats) and documented thatn_abstainedandn_failedcount calls; the CLI abstain line now reads "4 of 4 calls abstained or unparseable" whenrepeats > 1.
make ci-local at 85765e2: 19 passed, 9 failed (all host-caused), 7 skipped.
Not approving yet. Still open: the two risks from my review.
|
Test-gap items from the review, in 2b9a28f: abstained label in _noise (test_noise_counts_an_abstention_as_a_pick_and_an_error_as_no_answer, test_an_abstention_can_be_the_modal_pick); len(known) > 1 guard (test_a_single_usable_repeat_has_no_half_range); raw_reply head vs tail (test_a_long_declined_reply_keeps_its_head_not_its_tail, asserts reply[:2000]); agreement denominator (the abstention/error test, 1/2 not 1/3). |
…or the surviving mutants Each repeat's rate divided by its own non-errored rows, so a transient provider error read as half_range. Rates now cover the queries with no failed call in any repeat, reported as n_queries_compared. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
… call Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…commands/novelty.py for W8 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>
antonwolfy
left a comment
There was a problem hiding this comment.
The thread on half_range is fixed, and the earlier test gaps are closed. One test gap is left to fix before merge, and one nit.
What I ran, on a Linux review host at f21dcf6:
make ci-local: 28 passed, 1 failed, 7 skipped. The failure ispip-auditon the lock files, which this PR does not touch. Changed-line coverage passes.tests/test_novelty_routing.py: 65 passed.- A mutation run: 37 mutants, 30 killed. The five survivors from the last review are killed now. The ones that survive are in the inline comment.
- Reproductions for the code paths, with the LLM transport mocked: errors and abstentions mixed across repeats, every call failing, non-numeric picks,
SpendCapReachedmid-repeats. All behave as documented.
Checked, no issues
- Draw forwarding:
draw=repeatreachesllm_call_with_usage; draw 0 adds nothing to the cache key (caching.draw_extra), so existing entries still hit. - Guards:
--repeatswithout--modelexits before any work;--repeats 0is rejected. - Spend cap: each repeat is counted, and
SpendCapReachedis not swallowed by the per-repeatexcept Exception. - Repo rules: no versioned schema covers the routing output; the changelog fragment and growth baseline are in order.
| # denominators and report a transient provider error as sampling noise. | ||
| complete = [group for group in groups if not any(row.get("error") for row in group)] | ||
| for repeat in range(repeats): | ||
| rates.append(round(sum(1 for g in complete if g[repeat].get("correct")) / len(complete), 4) |
There was a problem hiding this comment.
Three new output fields can be changed without a test failing. Each mutation below passes all 65 tests in tests/test_novelty_routing.py:
"n_calls": len(queries)instead oflen(picks)(routing.py:315). The CLI printsn_callsas "of N calls" whenrepeats > 1./ len(groups)instead of/ len(complete)in the per-repeat rate (routing.py:375).test_a_failed_repeat_is_not_reported_as_sampling_noisehas only wrong answers among the complete queries, so the numerator is 0 and the denominator does not matter. A query that is right in a complete group plus one query with a failed call would tell the two apart.round(max(agreements), 4)instead of the mean (routing.py:380). No test assertsmean_agreementwith unequal agreements.
Suggestion: assert n_calls == 6 for 3 queries x 2 repeats; in the failed-repeat test make q1 right in both repeats and assert selection_rate_by_repeat == [1.0, 1.0] with n_queries_compared == 1; assert mean_agreement in a test with agreements 2/3 and 1.0 (0.8333).
There was a problem hiding this comment.
Fixed in bebf891 (tests only; the code was right). test_the_output_counts_calls_and_the_noise_means_each_agreement asserts n_calls == 6 for 2 queries x 3 repeats, and mean_agreement ~ 5/6 for agreements 2/3 and 1.0. test_a_failed_call_elsewhere_does_not_change_the_rate_of_the_complete_queries has q1 right in both repeats and q2 failing once, and asserts selection_rate_by_repeat == [1.0, 1.0] with n_queries_compared == 1. I ran your three mutants against the new tests; each now fails one. mean_agreement is compared with approx, since the mean of the rounded per-query values is 0.8334, not 0.8333.
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 881d5d2, which fix the nit from my review:
881d5d2:dokimi/docs/skill-novelty.md:619: added that with repeatsn_callsis queries x repeats,n_abstainedandn_failedcount calls, and eachper_queryrow carries itsrepeatindex.
make ci-local at 881d5d2: 28 passed, 1 failed (pip-audit, as at the reviewed head), 7 skipped.
Not approving yet. Still open: the risk from my review.
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
|
Merge with intel/main in ab6e6a7 (conflicts in |
…ement Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
CLI interface change detectedThis PR changes dokimi's user-facing CLI surface (commands and/or options): ~ `dokimi novelty routing` parameters changed:
+ added option `--repeats`Updated on every push; the |
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Part of #4.
Summary
novelty routing --modelgets two changes: a--repeats Nloop for a noise floor, and the model's raw reply kept whenever it declines to pick.Repeats keep one card order per query, so the spread between repeats is sampling noise and not a change of layout.
LLMClient.texttakes an optionaldraw: without it, underDOKIMI_RESPONSE_CACHEthe repeats would replay one reply and report a noise floor of zero. Draw 0 is the default and adds nothing to the cache key, so existing entries still hit.Evidence
intel/mainsources (12 of 60 intests/test_novelty_routing.py):TypeErrorforrepeats,KeyErroronraw_replyandnoise, and the--repeatsCLI is unknown. With the oldclient.pyalone,test_with_the_response_cache_on_each_repeat_is_asked_againfails (n_failed3, not 0), so the draw forwarding is load-bearing.tests/test_novelty_routing.py60 passed; fullpytest -n auto7080 passed, 22 skipped. Changed lines are at 100% coverage (diff-coveragainstintel/main).Checks:
ruff checkpassed,mypy dokimiclean (222 files),schema_checkandconfig_checkok.Merge Danger
Door: two-way. Every change is additive: new flag, new keys, and an optional keyword on
LLMClient.text. Removing the flag restores the old output shape.Blast Radius: routing command only.
--repeats) make the same calls with the same cache keys.per_queryrows gainrepeat: 0, and abstention rows gainraw_reply;routing.jsonhas no versioned schema, so readers of the old keys are unaffected.--repeats Nmultiplies model calls by N, so it counts against--max-usd. It is refused without--model.LLMClient.text(..., draw=)is a new optional keyword; injected transports do not receive it.Checklist
pytestpassesruff,mypy, and schema checks passdocs/skill-novelty.mdrouting section; changelog fragment added.github/.licenserc.yaml) — the changelog fragment is exempt🤖 Generated with Claude Code