Repository navigation
fix(kb): re-embed vectorless rows, report the real model, add --force - #14
Merged
Merged
Conversation
Three related faults in `kb build`, all of which made a lexical-only collection look embedded or made it impossible to fix one. 1. A surviving chunk was never re-embedded once it existed, even with no vector. The carry decision matched on presence in the carried map, not on whether a vector was actually there, so every row of a lexical-only build was stranded: the content hash does not change, so enabling an encoder later had no effect at all until the store was deleted by hand. Extracted as `reusable_vector`, which only reuses a row that has a vector. 2. `embedding_model` fell back to `LEIO_CODE_EMBED_MODEL` when no row carried a model, so a build that embedded nothing still reported an encoder that had never run -- `embedded: 0` alongside a confident model name. It now reports only a model that actually produced a stored vector, and the embed pass stamps rows from `config::embed_model` rather than reading the env var directly. 3. `lexical_only` was a property of the run, not the collection: it was left at its default when nothing needed embedding, so a collection with no vectors at all reported `lexical_only: false`. It is now derived from the finished rows. Also adds `--force`, which discards carried vectors and re-embeds everything. Needed when the encoder itself changes: content hashes are unchanged in that case, so nothing would otherwise be re-embedded. Verified end to end against a local encoder: `--force` with the encoder unreachable now reports `lexical_only: true` and `embedding_model: null` (previously `false` and a model name), and a plain rebuild of that lexical-only store with the encoder up re-embeds all 67 rows, leaving zero nulls in the Arrow `embedding` column. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #13. That PR made stored vectors readable; these three faults are
why a collection could still be stuck lexical-only with no way to tell and no
way out.
1. A vectorless row was never re-embedded
The carry decision matched on presence in the carried map rather than on
whether a vector was actually there:
A chunk from a lexical-only build is present with
None, so it took the firstarm and was carried forward empty. The content hash never changes, so turning
an encoder on later had no effect whatsoever — every subsequent
kb buildreported
embedded: 0and reused the empty slots. The only escape was deleting~/.leio-code/kb/<name>.{arrow,meta.json}by hand.Extracted as
reusable_vector, which reuses a row only when it really has avector.
2.
embedding_modelnamed an encoder that never ranWith no row carrying a model, this reported whatever the environment happened
to say. That produced the genuinely misleading combination
embedded: 0alongside a confident
"embedding_model": "google/embeddinggemma-2"— acollection with no vectors looking fully embedded.
Now only a model that actually produced a stored vector is reported, and the
embed pass stamps rows from
config::embed_modelinstead of reading the envvar directly, so the stamp follows the same resolution order as the request.
3.
lexical_onlydescribed the run, not the collectionIt was left at its default when nothing needed embedding, so a collection with
no vectors at all reported
lexical_only: false. It is now derived from thefinished rows.
4. New:
--forceDiscards carried vectors and re-embeds everything. Needed when the encoder
changes rather than the content — switching model or width leaves every content
hash identical, so nothing would otherwise be re-embedded.
Verification
cargo fmt --check,cargo clippy --all-targetsand the full suite are clean(556 lib tests + all integration targets). New regression test
kb::lexical_only_rows_are_requeued_for_embeddingcovers all three carry cases.End to end against a local encoder, same collection throughout:
embeddedlexical_onlyembedding_modelfalsegoogle/embeddinggemma-2--force, encoder unreachabletruenull--forcefalsegoogle/embeddinggemma-2Row two is what previously reported
lexical_only: falsewith a model name.Row three is the fix for #1 — on the old code this was a no-op; the Arrow
embeddingcolumn ends with 0 nulls of 67, andkb queryreturnscosine: 0.6711.Note for the private tree:
build_kb's signature becomes(name, repo_root, force). There is no other Rust caller — the CLI is the onlyone, and
cargo buildwould fail otherwise — but a vendored copy will need thesame change.
🤖 Generated with Claude Code