Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions next-plaid-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ client.add("docs",
# Semantic search
results = client.search("docs", ["vector database"])

# Hybrid search (semantic + keyword fused with RRF)
# Hybrid search (semantic + keyword, fused by relative-score normalization)
results = client.search("docs", ["vector database"],
text_query=["multi-vector"], alpha=0.75, fusion="rrf",
text_query=["multi-vector"], alpha=0.75, fusion="relative_score",
)

# Search with metadata filtering
Expand Down Expand Up @@ -318,7 +318,7 @@ POST /indices/my_index/search
"queries": [{"embeddings": [[0.1, 0.2, ...], [0.3, 0.4, ...]]}],
"text_query": ["capital France"],
"alpha": 0.75,
"fusion": "rrf",
"fusion": "relative_score",
"params": { "top_k": 10 }
}
```
Expand All @@ -329,7 +329,7 @@ Combines semantic (ColBERT) and keyword (FTS5 BM25) search using fusion:
| ------------ | -------- | ------------------------------------------------------------------ |
| `text_query` | `null` | List of FTS5 query strings (must match `queries` length in hybrid) |
| `alpha` | `0.75` | Balance: 0.0 = pure keyword, 1.0 = pure semantic |
| `fusion` | `"rrf"` | `"rrf"` (reciprocal rank fusion) or `"relative_score"` (min-max) |
| `fusion` | `"relative_score"` | `"relative_score"` (min-max normalize then alpha-weight) or `"rrf"` (reciprocal rank fusion) |

The `filter_condition` and `filter_parameters` fields can also be included directly in the `/search` body, replacing the need for the separate `/search/filtered` endpoint.

Expand Down
10 changes: 7 additions & 3 deletions next-plaid-api/src/handlers/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,10 @@ pub async fn search(
));
}

let fusion_mode = req.fusion.as_deref().unwrap_or("rrf");
// Default to relative-score fusion (min-max normalize each score list, then
// alpha-weight). It uses the actual ColBERT/BM25 score magnitudes rather than
// only rank positions, which generally fuses better given FTS5's high recall.
let fusion_mode = req.fusion.as_deref().unwrap_or("relative_score");
if fusion_mode != "rrf" && fusion_mode != "relative_score" {
return Err(ApiError::BadRequest(
"fusion must be 'rrf' or 'relative_score'".to_string(),
Expand Down Expand Up @@ -341,15 +344,16 @@ pub async fn search(
// Fuse
let (document_ids, scores) = match (semantic, keyword) {
(Some((sem_ids, sem_scores)), Some((kw_ids, kw_scores))) => match fusion_mode {
"relative_score" => text_search::fuse_relative_score(
"rrf" => text_search::fuse_rrf(&sem_ids, &kw_ids, alpha, top_k),
// Default (and explicit "relative_score").
_ => text_search::fuse_relative_score(
&sem_ids,
&sem_scores,
&kw_ids,
&kw_scores,
alpha,
top_k,
),
_ => text_search::fuse_rrf(&sem_ids, &kw_ids, alpha, top_k),
},
(Some((ids, scores)), None) => {
let mut r: Vec<(i64, f32)> = ids.into_iter().zip(scores).collect();
Expand Down
10 changes: 5 additions & 5 deletions next-plaid-api/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,10 +246,10 @@ pub struct SearchRequest {
#[serde(default)]
#[schema(example = 0.75)]
pub alpha: Option<f32>,
/// Fusion strategy for hybrid search: "rrf" (reciprocal rank fusion, default)
/// or "relative_score" (min-max normalize then alpha-weight).
/// Fusion strategy for hybrid search: "relative_score" (min-max normalize then
/// alpha-weight, default) or "rrf" (reciprocal rank fusion).
#[serde(default)]
#[schema(example = "rrf")]
#[schema(example = "relative_score")]
pub fusion: Option<String>,
/// SQL WHERE condition for metadata filtering.
#[serde(default)]
Expand Down Expand Up @@ -778,7 +778,7 @@ pub struct SearchWithEncodingRequest {
/// Balance between keyword and semantic (0.0 = pure keyword, 1.0 = pure semantic, default: 0.75)
#[serde(default)]
pub alpha: Option<f32>,
/// Fusion strategy: "rrf" (default) or "relative_score"
/// Fusion strategy: "relative_score" (default) or "rrf"
#[serde(default)]
pub fusion: Option<String>,
}
Expand All @@ -805,7 +805,7 @@ pub struct FilteredSearchWithEncodingRequest {
/// Balance between keyword and semantic (0.0 = pure keyword, 1.0 = pure semantic, default: 0.75)
#[serde(default)]
pub alpha: Option<f32>,
/// Fusion strategy: "rrf" (default) or "relative_score"
/// Fusion strategy: "relative_score" (default) or "rrf"
#[serde(default)]
pub fusion: Option<String>,
}
Expand Down
Loading