-
Notifications
You must be signed in to change notification settings - Fork 75
Persist relevance scores in the SQLite semantic ref index #324
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -59,6 +59,7 @@ | |
| CREATE TABLE IF NOT EXISTS SemanticRefIndex ( | ||
| term TEXT NOT NULL, -- lowercased, not-unique/normalized | ||
| semref_id INTEGER NOT NULL, | ||
| score REAL NOT NULL DEFAULT 1.0, | ||
|
|
||
| FOREIGN KEY (semref_id) REFERENCES SemanticRefs(semref_id) ON DELETE CASCADE | ||
| ); | ||
|
|
@@ -278,6 +279,15 @@ def _set_conversation_metadata( | |
| ) | ||
|
|
||
|
|
||
| def _migrate_semantic_ref_index_score(cursor: sqlite3.Cursor) -> None: | ||
| """Add the score column to SemanticRefIndex tables created before it existed.""" | ||
| columns = [row[1] for row in cursor.execute("PRAGMA table_info(SemanticRefIndex)")] | ||
| if "score" not in columns: | ||
| cursor.execute( | ||
| "ALTER TABLE SemanticRefIndex ADD COLUMN score REAL NOT NULL DEFAULT 1.0" | ||
| ) | ||
|
Comment on lines
+284
to
+288
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unlikely but seems like a simple enough change |
||
|
|
||
|
|
||
| def init_db_schema(db: sqlite3.Connection) -> None: | ||
| """Initialize the database schema with all required tables.""" | ||
| cursor = db.cursor() | ||
|
|
@@ -287,6 +297,7 @@ def init_db_schema(db: sqlite3.Connection) -> None: | |
| cursor.execute(MESSAGES_SCHEMA) | ||
| cursor.execute(SEMANTIC_REFS_SCHEMA) | ||
| cursor.execute(SEMANTIC_REF_INDEX_SCHEMA) | ||
| _migrate_semantic_ref_index_score(cursor) | ||
| cursor.execute(MESSAGE_TEXT_INDEX_SCHEMA) | ||
| cursor.execute(PROPERTY_INDEX_SCHEMA) | ||
| cursor.execute(RELATED_TERMS_ALIASES_SCHEMA) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,14 @@ | |
| ) | ||
|
|
||
|
|
||
| def _split_ordinal( | ||
| ordinal: SemanticRefOrdinal | ScoredSemanticRefOrdinal, | ||
| ) -> tuple[SemanticRefOrdinal, float]: | ||
| if isinstance(ordinal, ScoredSemanticRefOrdinal): | ||
| return ordinal.semantic_ref_ordinal, ordinal.score | ||
| return ordinal, 1.0 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe make this a constant and reuse this in/from schema.py when initializing the SemanticRefIndex table. |
||
|
|
||
|
|
||
| class SqliteTermToSemanticRefIndex(ITermToSemanticRefIndex): | ||
| """SQLite-backed implementation of term to semantic ref index.""" | ||
|
|
||
|
|
@@ -44,19 +52,15 @@ async def add_term( | |
|
|
||
| term = self._prepare_term(term) | ||
|
|
||
| # Extract semref_id from the ordinal | ||
| if isinstance(semantic_ref_ordinal, ScoredSemanticRefOrdinal): | ||
| semref_id = semantic_ref_ordinal.semantic_ref_ordinal | ||
| else: | ||
| semref_id = semantic_ref_ordinal | ||
| semref_id, score = _split_ordinal(semantic_ref_ordinal) | ||
|
|
||
| cursor = self.db.cursor() | ||
| cursor.execute( | ||
| """ | ||
| INSERT OR IGNORE INTO SemanticRefIndex (term, semref_id) | ||
| VALUES (?, ?) | ||
| INSERT OR IGNORE INTO SemanticRefIndex (term, semref_id, score) | ||
| VALUES (?, ?, ?) | ||
| """, | ||
| (term, semref_id), | ||
| (term, semref_id, score), | ||
| ) | ||
|
|
||
| return term | ||
|
|
@@ -72,15 +76,12 @@ async def add_terms_batch( | |
| if not term: | ||
| continue | ||
| term = self._prepare_term(term) | ||
| if isinstance(ordinal, ScoredSemanticRefOrdinal): | ||
| semref_id = ordinal.semantic_ref_ordinal | ||
| else: | ||
| semref_id = ordinal | ||
| rows.append((term, semref_id)) | ||
| semref_id, score = _split_ordinal(ordinal) | ||
| rows.append((term, semref_id, score)) | ||
| if rows: | ||
| cursor = self.db.cursor() | ||
| cursor.executemany( | ||
| "INSERT OR IGNORE INTO SemanticRefIndex (term, semref_id) VALUES (?, ?)", | ||
| "INSERT OR IGNORE INTO SemanticRefIndex (term, semref_id, score) VALUES (?, ?, ?)", | ||
| rows, | ||
| ) | ||
|
|
||
|
|
@@ -98,16 +99,13 @@ async def lookup_term(self, term: str) -> list[ScoredSemanticRefOrdinal] | None: | |
| term = self._prepare_term(term) | ||
| cursor = self.db.cursor() | ||
| cursor.execute( | ||
| "SELECT semref_id FROM SemanticRefIndex WHERE term = ?", | ||
| "SELECT semref_id, score FROM SemanticRefIndex WHERE term = ? ORDER BY rowid", | ||
| (term,), | ||
| ) | ||
|
|
||
| # Return as ScoredSemanticRefOrdinal with default score of 1.0 | ||
| results = [] | ||
| for row in cursor.fetchall(): | ||
| semref_id = row[0] | ||
| results.append(ScoredSemanticRefOrdinal(semref_id, 1.0)) | ||
| return results | ||
| return [ | ||
| ScoredSemanticRefOrdinal(semref_id, score) | ||
| for semref_id, score in cursor.fetchall() | ||
| ] | ||
|
|
||
| async def clear(self) -> None: | ||
| """Clear all terms from the semantic ref index.""" | ||
|
|
@@ -118,15 +116,16 @@ async def serialize(self) -> TermToSemanticRefIndexData: | |
| """Serialize the index data for compatibility with in-memory version.""" | ||
| cursor = self.db.cursor() | ||
| cursor.execute( | ||
| "SELECT term, semref_id FROM SemanticRefIndex ORDER BY term, semref_id" | ||
| "SELECT term, semref_id, score FROM SemanticRefIndex " | ||
| "ORDER BY term, semref_id, rowid" | ||
| ) | ||
|
|
||
| # Group by term | ||
| term_to_semrefs: dict[str, list[ScoredSemanticRefOrdinalData]] = {} | ||
| for term, semref_id in cursor.fetchall(): | ||
| for term, semref_id, score in cursor.fetchall(): | ||
| if term not in term_to_semrefs: | ||
| term_to_semrefs[term] = [] | ||
| scored_ref = ScoredSemanticRefOrdinal(semref_id, 1.0) | ||
| scored_ref = ScoredSemanticRefOrdinal(semref_id, score) | ||
| term_to_semrefs[term].append(scored_ref.serialize()) | ||
|
|
||
| # Convert to the expected format | ||
|
|
@@ -155,15 +154,17 @@ async def deserialize(self, data: TermToSemanticRefIndexData) -> None: | |
| for semref_ordinal_data in item["semanticRefOrdinals"]: | ||
| if isinstance(semref_ordinal_data, dict): | ||
| semref_id = semref_ordinal_data["semanticRefOrdinal"] | ||
| score = semref_ordinal_data.get("score", 1.0) | ||
| else: | ||
| # Fallback for direct integer | ||
| semref_id = semref_ordinal_data | ||
| insertion_data.append((term, semref_id)) | ||
| score = 1.0 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same const reuse here and on line #157 |
||
| insertion_data.append((term, semref_id, score)) | ||
|
|
||
| # Bulk insert all the data | ||
| if insertion_data: | ||
| cursor.executemany( | ||
| "INSERT OR IGNORE INTO SemanticRefIndex (term, semref_id) VALUES (?, ?)", | ||
| "INSERT OR IGNORE INTO SemanticRefIndex (term, semref_id, score) VALUES (?, ?, ?)", | ||
| insertion_data, | ||
| ) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't defaulting the score to 1.0 might hide more valid results? Should this be 0 or some really small value?