Skip to content

Share term normalization between memory and SQLite indexes - #325

Merged
Bernhard Merkle (bmerkle) merged 5 commits into
microsoft:mainfrom
bmerkle:fix-term-normalization-parity
Oct 18, 2026
Merged

Bernhard Merkle (bmerkle) merged 5 commits into
microsoft:mainfrom
bmerkle:fix-term-normalization-parity

Conversation

@bmerkle

Copy link
Copy Markdown
Collaborator

Fixes #322.

The memory TermToSemanticRefIndex only lowercased terms, while the SQLite one also stripped, NFC-normalized and collapsed whitespace. Lookups, get_terms() and size() therefore disagreed between backends.

  • Add normalize_term() in knowpro/common.py (the SQLite behavior, promoted to the shared definition) and use it in the memory and SQLite term indexes and property indexes. Property name and value are normalized separately, so leading whitespace in a value is handled.
  • Memory deserialize now merges terms that collide after normalization instead of overwriting.
  • The fuzzy related-terms lists (add_messages, conversation_base) use the shared function too.
  • New tests/test_term_normalization.py runs parity checks against both backends.

Behavior change: memory-backed index keys are now normalized with the SQLite rules (as the issue suggested).

🤖 Generated with Claude Code

Copilot AI balanced review requested due to automatic review settings September 28, 2026 22:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Removal paths and SQLite deserialization do not consistently preserve normalized keys.

Review effort: Balanced
Findings: 4 Medium severity · 2 Low severity

Open (6)
What changed in this PR

Centralizes term normalization to align memory and SQLite index behavior.

Changes:

  • Adds shared Unicode, whitespace, and case normalization.
  • Applies normalization across term, property, and fuzzy indexes.
  • Adds backend parity tests and collision-aware deserialization.
File Description
src/​typeagent/​knowpro/​common.py Defines shared normalization.
src/​typeagent/​knowpro/​add_messages.py Normalizes fuzzy-index terms.
src/​typeagent/​knowpro/​conversation_base.py Normalizes incremental related terms.
src/​typeagent/​storage/​memory/​semrefindex.py Normalizes keys and merges collisions.
src/​typeagent/​storage/​memory/​propindex.py Normalizes property names and values separately.
src/​typeagent/​storage/​sqlite/​semrefindex.py Uses shared normalization.
src/​typeagent/​storage/​sqlite/​propindex.py Uses shared property normalization.
tests/​test_term_normalization.py Adds cross-backend parity tests.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/typeagent/knowpro/conversation_base.py
Comment thread src/typeagent/storage/memory/property_index.py
Comment thread src/typeagent/storage/sqlite/property_index.py
Comment thread src/typeagent/storage/sqlite/semantic_ref_index.py
Comment thread src/typeagent/knowpro/add_messages.py
Comment thread tests/test_term_normalization.py Outdated
@bmerkle

Copy link
Copy Markdown
Collaborator Author

robgruen can you rereview please ?

The memory term index only lowercased terms while the SQLite one also
stripped, NFC-normalized and collapsed whitespace, so lookups, get_terms()
and size() disagreed between backends. Add a shared normalize_term() and
use it in both term indexes and both property indexes (name and value are
normalized separately). Also merge, rather than overwrite, terms that
collide after normalization in the memory deserialize, and use the shared
function for the fuzzy related-terms lists.

Fixes microsoft#322

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Normalize prop_name in remove_property (memory and SQLite)
- Keep the empty normalized term through SQLite deserialize
- Don't embed empty normalized terms in the incremental fuzzy update
- Update the _collect_related_terms_for_fuzzy_index docstring
- Annotate the test fixtures; add tests for removal and empty-term round trip

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Normalize before the empty check in add_term/add_terms_batch so
  whitespace-only terms are not stored under an empty key.
- Skip missing, None and empty terms on deserialize in both backends
  (the memory backend previously crashed on a missing term).
- Dedupe semref ordinals when several raw terms normalize to the same
  key on deserialize, in memory and SQLite.
- Remove the unused PropertyIndex._prepare_term_text.
- Note in the CHANGELOG that SQLite property values from earlier
  versions may need re-ingesting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bmerkle

Copy link
Copy Markdown
Collaborator Author

robgruen can you rereview please ?

@bmerkle

Copy link
Copy Markdown
Collaborator Author

Guido van Rossum (@gvanrossum) could you take a look at this when you have a moment? robgruen seems to be busy, so I'm asking you as the other requested reviewer.

Summary:

  • In-memory and SQLite term indexes now use the same normalize_term helper. Before, the two backends could store the same term under different keys, so a query could match in one backend and miss in the other (Memory and SQLite term indexes normalize terms differently #322).
  • Empty terms are now skipped, and duplicate ordinals are removed when results are merged.
  • Copilot's review comments are addressed, the branch is up to date with main, and CI passes.

Applying the same normalization to search terms, query matchers and the alias index is left to #346 to keep this PR small.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rubberstamp

@bmerkle
Bernhard Merkle (bmerkle) merged commit afd0292 into microsoft:main Oct 18, 2026
21 checks passed
Kevin Turcios (KRRT7) added a commit to KRRT7/typeagent-py that referenced this pull request Oct 18, 2026
Lowercase before NFC in typeagent.knowpro.common.normalize_term, which both
backends now share (microsoft#325), instead of in the SQLite-only _prepare_term.
Move the regression tests to tests/test_term_normalization.py, cover memory
and SQLite, and note the stored-term compatibility impact in the CHANGELOG.
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.

Memory and SQLite term indexes normalize terms differently

3 participants