Skip to main content

ripvec_core/
searchable.rs

1//! Engine-agnostic searchable-index trait.
2//!
3//! Both [`HybridIndex`](crate::hybrid::HybridIndex) (transformer
4//! engines) and [`RipvecIndex`](crate::encoder::ripvec::index::RipvecIndex)
5//! (the cacheless ripvec engine) expose the same operational surface
6//! to downstream consumers: a slice of chunks, the embedding row for
7//! a chunk by index, and a search method that returns `(chunk_idx,
8//! score)` pairs ranked descending.
9//!
10//! Naming the surface as a trait lets LSP / MCP code (navigation,
11//! symbols, hover, references, calls) take `&dyn SearchableIndex`
12//! instead of `&HybridIndex` and work transparently across engines.
13//! Without it, every engine swap requires touching every LSP module.
14
15use std::any::Any;
16
17use crate::chunk::CodeChunk;
18use crate::hybrid::SearchMode;
19
20/// Engine-agnostic searchable index.
21///
22/// Implementations: [`HybridIndex`](crate::hybrid::HybridIndex) for
23/// transformer engines,
24/// [`RipvecIndex`](crate::encoder::ripvec::index::RipvecIndex) for
25/// the ripvec engine.
26pub trait SearchableIndex: Send + Sync {
27    /// Borrow the indexed chunks.
28    fn chunks(&self) -> &[CodeChunk];
29
30    /// Search by text query.
31    ///
32    /// Returns `(chunk_idx, score)` pairs ranked descending. Score is
33    /// normalized to `[0, 1]` regardless of mode so callers can apply
34    /// a single threshold consistently.
35    fn search(&self, query_text: &str, top_k: usize, mode: SearchMode) -> Vec<(usize, f32)>;
36
37    /// Search by similarity to an existing chunk's embedding.
38    ///
39    /// Caller passes the chunk index whose embedding should be used
40    /// as the query vector. The canonical `goto_definition` pattern:
41    /// the LSP layer identifies the chunk at the cursor, then asks
42    /// the index for structurally similar chunks elsewhere.
43    ///
44    /// If `chunk_idx` is out of range or the engine cannot provide
45    /// an embedding for it (keyword-only mode, embedding row not
46    /// stored), implementations fall back to text-only search via
47    /// [`Self::search`].
48    fn search_from_chunk(
49        &self,
50        chunk_idx: usize,
51        query_text: &str,
52        top_k: usize,
53        mode: SearchMode,
54    ) -> Vec<(usize, f32)>;
55
56    /// Downcast escape hatch for callers that legitimately need the
57    /// concrete engine-specific index type.
58    ///
59    /// Used by transformer-engine-only code paths (e.g., legacy
60    /// `run_search` with caller-supplied query embedding) that need
61    /// `&HybridIndex`'s richer signature. Returns `&dyn Any`; callers
62    /// use [`Any::downcast_ref`] to attempt the conversion. Returns
63    /// `None`-equivalent when the concrete type doesn't match.
64    ///
65    /// Engine-neutral code should never need this; the three
66    /// trait methods above are the supported surface.
67    fn as_any(&self) -> &dyn Any;
68}