feat(search): semantic / related-message search (LC-549) #519
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/LC-549-semantic-search"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
LC-549: Semantic / related-message search (Round 4, Vein B - search, reach item)
Embeddings-backed search that surfaces conceptually-similar messages keyword FTS misses. Two features light up when an operator points
LETS_CHAT_EMBEDDINGS_URLat an OpenAI-compatible/v1/embeddingsendpoint; both are hidden and search stays on FTS when it is absent.1. "Find related" (per message)
A "Find related" item in the message overflow menu ranks other messages in the same room by cosine similarity to that one and shows the nearest few in the shared
#thread-panelslot as jump links. The source vector is the stored embedding, or embedded on demand if the background populator has not reached the message yet; hits below a similarity floor are dropped so an empty result reads honestly.2. Opt-in semantic search mode
A "Semantic" toggle on the room-header search box ranks the query against the room's stored embeddings instead of FTS keywords. On any miss (embeddings not configured, the query cannot be embedded, a DB error) it falls through to the existing FTS path, so search always returns something. The DM/room label mapping is extracted into a shared
render_resultshelper used by both search paths.Infrastructure
embeddings.rs: same operator-config posture as the LLM client (LC-396).EmbeddingClienttrait, a reqwest/v1/embeddingsimpl via the trusted-outbound helper (a localhost embedder works), a deterministic hashed mock for tests, pluscosine_similarityand little-endian f32 vec<->bytes helpers.message_embeddingssidecar table (one f32-BLOB per message,ON DELETE CASCADE) rather than a column onmessages, keeping the hot message SELECT surface andRawMessagemapping untouched (the column-drift trap from LC-547). A hard-deleted or self-destructed (LC-547) message drops its embedding automatically.Trust / privacy posture
Reached through
http_client::outbound_trusted_post(not public-IP SSRF-filtered, so a local engine works). Message text leaves the device only when an operator has configured an endpoint. Ranking is a Rust cosine scan of one room's vectors at a time, fine at self-host scale.Tests
routes_related_search.rs: related surfaces the near message and drops the unrelated one below the floor; semantic mode ranks by meaning; no-embeddings related endpoint is refused.Scope notes / follow-ups
Gates:
just check, fulljust test,just test-saasall green.🤖 Generated with Claude Code
https://claude.ai/code/session_01S5obszLoUgUi8qaF7Hi5qz
Add GET /messages/{id}/related: rank other messages in the same room by cosine similarity to this one and render the nearest few into the shared #thread-panel slot (jump links, like search). The source vector is the stored embedding, or embedded on demand if the background populator has not reached it; hits below a similarity floor are dropped so an empty result reads honestly. New messages are embedded best-effort in a background task off the send path (related::embed_message), so a failure never fails a send. The menu item and the page-root data-lc-embeddings gate (CSS mirrors data-lc-llm) hide the feature when no embeddings endpoint is configured. Regenerate the LC-77 webhook/email fixtures for the new (embeddings-gated, CSS-hidden) menu button. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S5obszLoUgUi8qaF7Hi5qzDrive the feature over HTTP with the deterministic mock embedder: /messages/{id}/related surfaces the shared-word message and drops the unrelated one below the similarity floor; semantic search mode ranks by meaning; and with no embeddings client the related endpoint is refused. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S5obszLoUgUi8qaF7Hi5qz