feat(search): rank search results by semantic similarity#67
Draft
bilipp wants to merge 1 commit into
Draft
Conversation
Search now consumes the NLContextualEmbedding vectors the background ContentIndexer already stores in Movie/Series.embeddingData, which were generated but never read. - SemanticSearchService: actor that embeds the query and cosine-ranks the stored vectors off the main thread (Accelerate/vDSP), on a background ModelContext. Falls back silently to lexical-only when the embedding model can't load on the device. - SearchView: hybrid results — the existing localizedStandardContains pass always leads (covers exact titles, live channels, and not-yet-indexed items), then semantic-only matches are appended, deduped and ranked by score. Staleness-guarded after the await. - LumeApp: configure the service with the shared container at launch.
e69725a to
f921d5d
Compare
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.
What
Search now actually uses the on-device
NLContextualEmbeddingvectors that the backgroundContentIndexeralready stores inMovie.embeddingData/Series.embeddingData. Until now those vectors were generated and persisted but never read —TextEmbedder.decodehad no callers and search was pure substring matching.How
SemanticSearchService(new actor, singleton likeContentIndexingService)prepare()s aTextEmbedder, reused across searches. If the model can't load on the device, it caches that fact and returnsnilso search degrades gracefully — no repeated OTA download attempts.ModelContextfetches every indexed title (embeddingData != nil,propertiesToFetchto avoid faulting whole rows), decodes each vector, and cosine-ranks with Accelerate (vDSP).(id, score)lists above a0.35similarity threshold, capped per content type.SearchView— now hybrid:localizedStandardContains(lexical) pass still runs and always leads the results, so exact-title hits, live channels (never embedded), and not-yet-indexed titles always appear.awaitso debounced/cancelled queries don't publish stale results.LumeApp— configures the service with the shared container at launch, alongside the indexer.Behavior
Testing
0.35threshold may want tuning against a real catalog.🤖 Generated with Claude Code