Repository navigation
Collect the same fuzzy-index terms on both ingestion paths (#346) - #360
Conversation
…#346) add_messages_with_indexing (ConversationBase._update_related_terms_incremental) only embedded entity names, topic text and individual verbs, while add_messages_streaming used collect_entity_terms / collect_action_terms and also embedded types, facets, subjects, objects and params. So related-term expansion depended on the ingestion path. The incremental path also embedded each verb on its own, while the term index key is the joined verbs, and the streaming path embedded types and facets of nameless entities, which the term index skips. The streaming path also never embedded terms from message metadata (e.g. speakers and recipients), which are indexed at commit time. Add semantic_ref_index.collect_knowledge_terms and collect_related_terms_for_fuzzy_index, which return exactly the normalized, deduplicated terms a knowledge item adds to the semantic ref index, and use them on both paths. The streaming commit now also embeds the terms of the new semantic refs that have no precomputed embedding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Kevin Turcios (@KRRT7) could you take a look? This is the other half of #346: both ingestion paths now add the same terms to the fuzzy related-terms index, including metadata terms on the streaming path. Independent of #358. |
There was a problem hiding this comment.
🟡 Changes recommended
Commit-time embedding failures can leave retries with missing persisted terms, and nameless metadata entities still produce inconsistent index contents.
2 open issues
What changed in this PR
Addresses #346’s ingestion-path inconsistency by sharing fuzzy-index term collection between incremental and streaming ingestion.
Changes:
- Centralizes normalized, deduplicated knowledge-term collection.
- Adds streaming metadata terms at commit time.
- Adds term-parity regression tests and documents the behavior change.
| File | Description |
|---|---|
| tests/test_add_messages_with_indexing.py | Tests incremental ingestion term parity. |
| tests/test_add_messages_streaming.py | Tests streaming term parity, including metadata. |
| tests/test_add_messages_pipeline.py | Tests shared collectors against semantic-index terms. |
| src/typeagent/storage/memory/semantic_ref_index.py | Adds shared knowledge-term collectors. |
| src/typeagent/knowpro/conversation_base.py | Uses shared collection and embeds remaining metadata terms. |
| src/typeagent/knowpro/add_messages.py | Delegates pipeline collection to the shared helper. |
| CHANGELOG.md | Documents ingestion behavior and embedding changes. |
Review effort: Balanced
…tities - Embed commit-time (metadata) terms before inserting the precomputed terms. If that embedding fails, the rollback restores only DB rows, so previously inserted terms stayed in SqliteRelatedTermsFuzzy's in-memory _added_terms and were skipped on retry. - Filter nameless entities only for extracted knowledge (streaming path). Metadata entities are indexed without validate_entity, so their terms (e.g. types of a nameless entity) must reach the fuzzy index too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
taking a look |
|
Both Copilot issues are fixed in 22b1d93, and the regression tests cover them. The new collectors return exactly the terms the term index adds. A few things, none blocking: 1. SQLite fuzzy index: in-memory duplicates within a streaming commit (made worse by this PR). 2. In-memory 3. The reorder narrows the rollback window but doesn't close it. |
SqliteRelatedTermsFuzzy skipped only terms from earlier calls, so terms repeated within one streaming commit went into its VectorBase and terms list several times (the DB collapsed them via INSERT OR REPLACE). The in-memory TermEmbeddingIndex didn't skip anything, so every batch re-added common terms such as "person". Both now skip held terms and in-call repeats, keeping the first embedding. Also note that a failure after the fuzzy index is updated still leaves its in-memory state ahead of a rolled-back DB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Kevin Turcios (@KRRT7) thanks for the review. All three points are addressed in 6f62c51:
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Kevin Turcios (@KRRT7) opened #362 to track the rollback point (3); the TODO in |
…xtraction - Test that TermEmbeddingIndex still skips held terms after a serialize/deserialize round trip, and accepts them again after clear(). - Run the streaming fuzzy/term-index parity test with and without knowledge extraction; without it, metadata terms reach the fuzzy index only through the commit-time embedding. - CHANGELOG: only extracted nameless entities are skipped; nameless metadata entities are still indexed and embedded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- TermEmbeddingIndex.deserialize and SqliteRelatedTermsFuzzy.deserialize keep the first of each repeated term, so data saved by older versions no longer brings duplicates back. The SQLite version also no longer loads embeddings that have no matching term. - The streaming commit only scans the metadata semrefs for terms still to embed, instead of re-collecting every extracted semref's terms just to exclude them. _update_related_terms_incremental takes an explicit end ordinal instead of a sentinel. - Name SqliteRelatedTermsFuzzy in the rollback comment. - Tests: commit-time embedding skips precomputed terms; in-memory parity across two overlapping batches on both ingestion paths; dedup on load for both backends. Move the shared knowledge extractor to conftest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Guido van Rossum (@gvanrossum) can you please approve ? |
Guido van Rossum (gvanrossum)
left a comment
There was a problem hiding this comment.
Happy to!

Part of #346 (item 3). Items 1 and 2 are in #358; the two PRs are independent and can merge in either order. Together they fix #346.
The fuzzy related-terms index got different terms depending on how messages were ingested, so related-term expansion depended on the ingestion path:
add_messages_with_indexing(before)add_messages_streaming(before)"looks","up")"looks up"), like the term indexChange
semantic_ref_index.collect_knowledge_termsandcollect_related_terms_for_fuzzy_indexreturn exactly the normalized, deduplicated terms a knowledge item adds to the semantic ref index (callers filter out nameless extracted entities, as the term index does).ConversationBase._update_related_terms_incrementalandadd_messages._collect_related_terms_for_fuzzy_indexboth use them._update_related_terms_incremental(..., exclude=<precomputed terms>)before adding the precomputed ones, which embeds only the remaining (metadata) terms; if that embedding fails, the fuzzy index is still unchanged.After this, on both paths the fuzzy index holds the same terms as the semantic ref index, which is also what
build_related_terms_indexproduces when rebuilding from scratch.Cost:
add_messages_with_indexingembeds more terms per message, andadd_messages_streamingembeds metadata terms at commit time. Terms already in the fuzzy index are skipped before embedding (memory and SQLite).Tests (each fails on
main):test_fuzzy_terms_match_semantic_ref_index_terms: both collectors return exactly the term-index terms for knowledge with multi-word verbs, a nameless entity, an inverse action, params and facets.test_add_messages_with_indexing_fuzzy_terms_match_term_index(with and without knowledge extraction) andtest_streaming_fuzzy_terms_match_term_index(also with and without knowledge extraction): after ingesting on SQLite, fuzzy-index terms equal semantic-ref-index terms.test_memory_fuzzy_terms_match_term_index_across_batches(both ingestion paths): two overlapping batches on in-memory storage leave exactly one fuzzy entry per term-index term.test_streaming_commit_embeds_only_metadata_terms: commit-time embedding covers only metadata terms that have no precomputed embedding.test_fuzzy_index_skips_duplicate_terms,test_term_embedding_index_skips_held_terms_after_reload_and_clear,test_fuzzy_index_deserialize_drops_repeated_terms: each term is stored once on both backends, including after reload and when loading older data with repeated terms.Fuzzy-index duplicates (review follow-up, 6f62c51): both the in-memory
TermEmbeddingIndexandSqliteRelatedTermsFuzzynow skip terms they already hold and terms repeated within one call, so common terms such aspersonare stored once.Not addressed (pre-existing): a failure after the fuzzy index is updated in a streaming commit rolls back the DB but not
SqliteRelatedTermsFuzzy's in-memory state, so a retry on the same storage skips those terms (tracked in #362; TODO inconversation_base.py).Testing:
makelocally: format, ruff and pyright (3.12 and 3.15) clean, build OK; 912 passed. 7 tests fail locally because of a brokenjiternative module on my machine (plus an MCP sampling test that depends on it); they fail identically onmain, so CI is the reference for those.🤖 Generated with Claude Code