Skip to content

feat(nlp): entity resolution with Locy, and word and sentence analysis (kniv v7 phases 3 and 2b) - #331

Merged
milliondreams merged 5 commits into
rustic-ai:mainfrom
milliondreams:main
Nov 9, 2026
Merged

milliondreams merged 5 commits into
rustic-ai:mainfrom
milliondreams:main

Conversation

@milliondreams

Copy link
Copy Markdown
Contributor

Summary

Phases 3 and 2b of the kniv v7 NLP proposal (docs/proposals/kniv_v7_nlp_extraction_2026_11_07.md, rev. 7), on top of phases 1–2 (#327).

Phase 3: entity resolution with Locy

  • A tested Locy program on the NLP feature page links each extracted :Mention to an existing :Entity. It combines weighted evidence (exact name, surname, embedding similarity) with MNOR, keeps the best match per mention, and creates one entity per new name, so variants of one name in a batch share an entity.
  • Claims are stored against Mentions, so claims reach their entities through resolution. Speakers, forks (review with diff, then promote) and 300 × 300 scale are covered by tests.
  • Testing that path exposed three engine bugs, each fixed here:
    • Fork writes to an auto-embedded label failed with "runtime not configured": a fork's writer now gets the Xervo runtime.
    • Promote duplicated auto-embedded vertices and skipped edges to them: the writer now stores auto-embedded values as Value::Vector, so the content UID matches the stored row.
    • DERIVE timed out on large rule sets: it re-joined its clause against the full cartesian product. It now narrows the MATCH to the derived pairs and indexes facts by entity (from over 30 s down to 54 ms at 300 × 300).

Phase 2b: word and sentence analysis

  • uni.nlp.words: one row per word, with Universal Dependencies part of speech, morphology, lemma, and dependency head and relation. Indices count across the whole document, so head joins to another row's word.
  • uni.nlp.sentences: one row per sentence, with sentiment, dialogue acts above the threshold, and every act's probability.
  • Input is the same as for extraction, including conversations. options takes only alias, and a v1 model is refused.
  • Rust and Python get analyze_words / analyze_sentences on db.xervo().

Known limits (filed)

Tests

  • New integration tests: nlp_resolve (10 pass, 1 ignored) and nlp_analyze (8 pass against a mock model). The real-model tests are ignored and run in the nightly sweep. I ran them locally against kniv-v7-xlarge-u-en, including the feature page's query examples, and they pass.
  • Locy TCK 558/558; uni-query lib 545; Python NLP tests 62.
  • Clippy (as CI runs it), cargo fmt, ruff, stubtest, the strict docs build and the docs snippets all pass.
  • Full uni-db integration suite under cargo test: 3,948 passed, 10 failed. The failures are scratch-directory, bulk-load and deadline tests. Each passed when rerun on its own: plain cargo test runs every test in one process, while CI's nextest gives each test its own.

🤖 Generated with Claude Code

milliondreams and others added 5 commits November 8, 2026 21:24
Phase 3 of the kniv v7 proposal, as a documented and tested Locy program
rather than engine code. Evidence clauses (exact name, surname, embedding
similarity) feed an MNOR fold under a merge threshold; BEST BY keeps each
mention's best match; DERIVE writes REFERS_TO; a Cypher command MERGEs an
entity for each mention left unmatched.

Running the proposal's sketch corrected it: MNOR is a fold over clauses,
BEST BY cannot share a rule with MNOR, DERIVE creates an edge on every run
(so evidence skips resolved mentions, which makes the program idempotent),
and CALIBRATE fits only CREATE MODEL predicates. The docs said to
CALIBRATE extraction confidence; they now point to VALIDATE, which scores
any PROB rule against labels.

Tests (crates/uni/tests/common/nlp_resolve.rs) run the program over
mentions from the mock kniv model with a trigram mock embedder, and cover
idempotency, best-match selection, type agreement, new-entity creation,
EXPLAIN RULE evidence and VALIDATE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The resolution program gave every unmatched mention its own entity in a
single create-and-link step, so "Grace Hopper", "grace hopper" and
"Hopper" arriving in one batch became three entities. Across batches the
same mentions resolve to one, and since evidence skips linked mentions, a
re-run never reconciled them: the result depended on batch boundaries.

Unmatched mentions now seed entities first (one per name ignoring case,
none for a surname of a longer unmatched name), a second DERIVE links the
rest through the same rules, and a last command gives anything left its
own entity. Adds variants_of_a_new_name_in_one_batch_make_one_entity and
updates the docs, Black Book, proposal and release notes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and DERIVE scale

Complete phase 3 of the kniv v7 proposal:

- Claims now point at the Mentions they were extracted from, so the
  resolution program links claim endpoints to entities. Speakers resolve
  like any name, kept apart from people by their type.
- Tests cover claims from prose and conversations meeting at resolved
  entities, resolution reviewed in a fork and promoted, 300 mentions
  against 300 entities, a real kniv v7 run (ignored), and a Python run of
  the program read from the docs page.

Engine fixes found by those tests:

- A fork's writer now gets the Xervo runtime, so forked sessions can write
  to auto-embedded labels.
- Auto-embedding stores vectors as Value::Vector (and multi-vectors as
  lists of them). Float lists made the flushed content UID differ from the
  stored row, so promote duplicated auto-embedded vertices and skipped
  edges to them.
- A DERIVE re-joining its clause materialised the MATCH's cartesian
  product and compared every row with every fact, cloning nodes each time:
  300 x 300 timed out at 30 s. The MATCH is now narrowed to the nodes the
  IS reference's facts hold, and the in-memory joins index facts by entity
  identity. Results are unchanged.

The program's own cost is still mentions x entities; 1,000 x 1,000
exceeds the default Locy timeout. That needs a nearest-neighbour generator
and is documented.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The proposal still said Phase 3 needed no engine change; record the
three engine fixes, the claim shape against Mentions, and the scale
limit. The skill reference gains claims via Mentions, cost and fork
review. The forks page notes the auto-embedded promote fix and that
vertices flushed before it keep their old UID until rewritten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 2b of the kniv v7 proposal, as two procedures rather than one
uni.nlp.analyze, since words and sentences are different row grains:

- uni.nlp.words: one row per word with UD part of speech, morphology,
  lemma, and dependency head and relation. Word, sentence and head
  indices count across the document, so head joins to another row's word.
- uni.nlp.sentences: one row per sentence with sentiment, dialogue acts
  above threshold, and every act's probability from the model's label map.

Input is as for extraction (texts or a conversation, with turn and
speaker); options take only alias. Each requests only its own heads, and
a v1 model is refused. Rust and Python gain analyze_words /
analyze_sentences returning AnalyzedWord / AnalyzedSentence.

Tested against a mock model and, ignored, the real kniv-v7-xlarge-u-en,
including the feature page's query examples.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@milliondreams
milliondreams merged commit 4246efb into rustic-ai:main Nov 9, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant