mirror of
https://github.com/qdrant/landing_page.git
synced 2026-10-09 12:58:31 +02:00
Apply adversarial-review fixes to hybrid-queries and snippets
Address findings from a code-bot adversarial review of the hybrid-search
materials:
- `hybrid-formula-decay/` (all 7 language sources + http.md +
`_description.md`): wrap the decay term in `MultExpression(mult=[0.1, ...])`
in every language. Previously the snippets summed `$score` with
`ExpDecayExpression` directly, modeling the failure mode the docs
explicitly warn against (un-weighted decay crowds out small RRF scores).
Also document the `defaults` requirement and the recommended datetime
payload index in `_description.md`. Build validated across all 6 SDKs.
- `hybrid-rrf/go.go`: add `Limit: qdrant.PtrOf(uint64(20))` to both
prefetches so the Go snippet matches the other language tabs.
- `hybrid-queries.md`:
- Reframe the weighted-RRF intro to drop "semantic search model
understands meaning better than a simple keyword matcher". On
SciFact (the corpus in the companion notebook) BM25 actually beats
dense, so the universal claim was contradicted by our own data.
- Clarify that the notebook provides a tuning helper to adapt to a
train/val split, not that it demonstrates the split itself.
- Add a one-line note that Qdrant uses zero-based rank positions so
readers can verify the RRF formula against actual scores.
- Apply brand-voice fixes: Title Case on "Multi-Stage Queries" and
"Re-Scoring Examples", replace "all the above techniques" with
"all of these techniques".
`generated/*.md` regenerated via `./docker.sh ./generate-md.py`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
fc299a0e9c
commit
c0128a53bc
+3
-1
@@ -1 +1,3 @@
|
||||
This code snippet shows the canonical pattern for combining hybrid search fusion with business-logic ranking. The inner prefetch fuses sparse and dense results with RRF, then the outer `FormulaQuery` applies exponential decay on a `published_at` payload field to boost recent documents. Use this pattern any time you want fusion plus recency, popularity, geo decay, or category-conditional multipliers, rather than trying to encode those signals as fusion weights.
|
||||
This code snippet shows the canonical pattern for combining hybrid search fusion with business-logic ranking. The inner prefetch fuses sparse and dense results with RRF, then the outer `FormulaQuery` applies exponential decay on a `published_at` payload field to boost recent documents. The decay term is wrapped in `mult` with a `0.1` coefficient so it nudges the ranking rather than crowding out the small RRF scores. Use this pattern any time you want fusion plus recency, popularity, geo decay, or category-conditional multipliers, rather than trying to encode those signals as fusion weights.
|
||||
|
||||
For production: `published_at` must exist on every point, or supply `defaults` in the `FormulaQuery` to fill missing values. A datetime payload index on `published_at` keeps query latency low.
|
||||
+14
-7
@@ -35,15 +35,22 @@ public class Snippet
|
||||
Sum =
|
||||
{
|
||||
"$score", // the fused score from the RRF prefetch
|
||||
Expression.FromExpDecay(
|
||||
new()
|
||||
new MultExpression
|
||||
{
|
||||
Mult =
|
||||
{
|
||||
X = Expression.FromDateTimeKey("published_at"),
|
||||
Target = Expression.FromDateTime("YYYY-MM-DDT00:00:00Z"),
|
||||
Scale = 86400 * 180, // 180 days in seconds
|
||||
Midpoint = 0.5f
|
||||
0.1f, // caps decay contribution
|
||||
Expression.FromExpDecay(
|
||||
new()
|
||||
{
|
||||
X = Expression.FromDateTimeKey("published_at"),
|
||||
Target = Expression.FromDateTime("YYYY-MM-DDT00:00:00Z"),
|
||||
Scale = 86400 * 180, // 180 days in seconds
|
||||
Midpoint = 0.5f
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+14
-7
@@ -32,15 +32,22 @@ await client.QueryAsync(
|
||||
Sum =
|
||||
{
|
||||
"$score", // the fused score from the RRF prefetch
|
||||
Expression.FromExpDecay(
|
||||
new()
|
||||
new MultExpression
|
||||
{
|
||||
Mult =
|
||||
{
|
||||
X = Expression.FromDateTimeKey("published_at"),
|
||||
Target = Expression.FromDateTime("YYYY-MM-DDT00:00:00Z"),
|
||||
Scale = 86400 * 180, // 180 days in seconds
|
||||
Midpoint = 0.5f
|
||||
0.1f, // caps decay contribution
|
||||
Expression.FromExpDecay(
|
||||
new()
|
||||
{
|
||||
X = Expression.FromDateTimeKey("published_at"),
|
||||
Target = Expression.FromDateTime("YYYY-MM-DDT00:00:00Z"),
|
||||
Scale = 86400 * 180, // 180 days in seconds
|
||||
Midpoint = 0.5f
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+10
-5
@@ -34,11 +34,16 @@ client.Query(context.Background(), &qdrant.QueryPoints{
|
||||
Expression: qdrant.NewExpressionSum(&qdrant.SumExpression{
|
||||
Sum: []*qdrant.Expression{
|
||||
qdrant.NewExpressionVariable("$score"), // the fused score from the RRF prefetch
|
||||
qdrant.NewExpressionExpDecay(&qdrant.DecayParamsExpression{
|
||||
X: qdrant.NewExpressionDatetimeKey("published_at"),
|
||||
Target: qdrant.NewExpressionDatetime("YYYY-MM-DDT00:00:00Z"),
|
||||
Scale: qdrant.PtrOf(float32(86400 * 180)), // 180 days in seconds
|
||||
Midpoint: qdrant.PtrOf(float32(0.5)),
|
||||
qdrant.NewExpressionMult(&qdrant.MultExpression{
|
||||
Mult: []*qdrant.Expression{
|
||||
qdrant.NewExpressionConstant(0.1), // caps decay contribution
|
||||
qdrant.NewExpressionExpDecay(&qdrant.DecayParamsExpression{
|
||||
X: qdrant.NewExpressionDatetimeKey("published_at"),
|
||||
Target: qdrant.NewExpressionDatetime("YYYY-MM-DDT00:00:00Z"),
|
||||
Scale: qdrant.PtrOf(float32(86400 * 180)), // 180 days in seconds
|
||||
Midpoint: qdrant.PtrOf(float32(0.5)),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
},
|
||||
}),
|
||||
|
||||
+15
-7
@@ -1,7 +1,9 @@
|
||||
```java
|
||||
import static io.qdrant.client.ExpressionFactory.constant;
|
||||
import static io.qdrant.client.ExpressionFactory.datetime;
|
||||
import static io.qdrant.client.ExpressionFactory.datetimeKey;
|
||||
import static io.qdrant.client.ExpressionFactory.expDecay;
|
||||
import static io.qdrant.client.ExpressionFactory.mult;
|
||||
import static io.qdrant.client.ExpressionFactory.sum;
|
||||
import static io.qdrant.client.ExpressionFactory.variable;
|
||||
import static io.qdrant.client.QueryFactory.formula;
|
||||
@@ -12,6 +14,7 @@ import io.qdrant.client.QdrantClient;
|
||||
import io.qdrant.client.QdrantGrpcClient;
|
||||
import io.qdrant.client.grpc.Points.DecayParamsExpression;
|
||||
import io.qdrant.client.grpc.Points.Formula;
|
||||
import io.qdrant.client.grpc.Points.MultExpression;
|
||||
import io.qdrant.client.grpc.Points.PrefetchQuery;
|
||||
import io.qdrant.client.grpc.Points.QueryPoints;
|
||||
import io.qdrant.client.grpc.Points.Rrf;
|
||||
@@ -49,13 +52,18 @@ client.queryAsync(
|
||||
SumExpression.newBuilder()
|
||||
.addSum(variable("$score"))
|
||||
.addSum(
|
||||
expDecay(
|
||||
DecayParamsExpression.newBuilder()
|
||||
.setX(datetimeKey("published_at"))
|
||||
.setTarget(
|
||||
datetime("YYYY-MM-DDT00:00:00Z"))
|
||||
.setScale(86400 * 180)
|
||||
.setMidpoint(0.5f)
|
||||
mult(
|
||||
MultExpression.newBuilder()
|
||||
.addMult(constant(0.1f))
|
||||
.addMult(
|
||||
expDecay(
|
||||
DecayParamsExpression.newBuilder()
|
||||
.setX(datetimeKey("published_at"))
|
||||
.setTarget(
|
||||
datetime("YYYY-MM-DDT00:00:00Z"))
|
||||
.setScale(86400 * 180)
|
||||
.setMidpoint(0.5f)
|
||||
.build()))
|
||||
.build()))
|
||||
.build()))
|
||||
.build()))
|
||||
|
||||
+11
-8
@@ -25,14 +25,17 @@ client.query_points(
|
||||
formula=models.SumExpression(
|
||||
sum=[
|
||||
"$score", # the fused score from the RRF prefetch
|
||||
models.ExpDecayExpression(
|
||||
exp_decay=models.DecayParamsExpression(
|
||||
x=models.DatetimeKeyExpression(datetime_key="published_at"),
|
||||
target=models.DatetimeExpression(datetime="YYYY-MM-DDT00:00:00Z"),
|
||||
scale=86400 * 180, # 180 days in seconds
|
||||
midpoint=0.5,
|
||||
)
|
||||
),
|
||||
models.MultExpression(mult=[
|
||||
0.1, # caps decay contribution; un-weighted decay [0, 1] would otherwise crowd out small RRF scores
|
||||
models.ExpDecayExpression(
|
||||
exp_decay=models.DecayParamsExpression(
|
||||
x=models.DatetimeKeyExpression(datetime_key="published_at"),
|
||||
target=models.DatetimeExpression(datetime="YYYY-MM-DDT00:00:00Z"),
|
||||
scale=86400 * 180, # 180 days in seconds
|
||||
midpoint=0.5,
|
||||
)
|
||||
),
|
||||
]),
|
||||
]
|
||||
)
|
||||
),
|
||||
|
||||
+9
-6
@@ -29,12 +29,15 @@ client.query(
|
||||
.query(
|
||||
FormulaBuilder::new(Expression::sum_with([
|
||||
Expression::score(),
|
||||
Expression::exp_decay(
|
||||
DecayParamsExpressionBuilder::new(Expression::datetime_key("published_at"))
|
||||
.target(Expression::datetime("YYYY-MM-DDT00:00:00Z"))
|
||||
.scale(86400.0 * 180.0)
|
||||
.midpoint(0.5),
|
||||
),
|
||||
Expression::mult_with([
|
||||
Expression::constant(0.1),
|
||||
Expression::exp_decay(
|
||||
DecayParamsExpressionBuilder::new(Expression::datetime_key("published_at"))
|
||||
.target(Expression::datetime("YYYY-MM-DDT00:00:00Z"))
|
||||
.scale(86400.0 * 180.0)
|
||||
.midpoint(0.5),
|
||||
),
|
||||
]),
|
||||
])),
|
||||
)
|
||||
.limit(10u64),
|
||||
|
||||
+11
-6
@@ -28,12 +28,17 @@ await client.query("{collection_name}", {
|
||||
sum: [
|
||||
"$score", // the fused score from the RRF prefetch
|
||||
{
|
||||
exp_decay: {
|
||||
x: { datetime_key: "published_at" },
|
||||
target: { datetime: "YYYY-MM-DDT00:00:00Z" },
|
||||
scale: 86400 * 180, // 180 days in seconds
|
||||
midpoint: 0.5,
|
||||
},
|
||||
mult: [
|
||||
0.1, // caps decay contribution; un-weighted decay [0, 1] would otherwise crowd out small RRF scores
|
||||
{
|
||||
exp_decay: {
|
||||
x: { datetime_key: "published_at" },
|
||||
target: { datetime: "YYYY-MM-DDT00:00:00Z" },
|
||||
scale: 86400 * 180, // 180 days in seconds
|
||||
midpoint: 0.5,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
+10
-5
@@ -38,11 +38,16 @@ func Main() {
|
||||
Expression: qdrant.NewExpressionSum(&qdrant.SumExpression{
|
||||
Sum: []*qdrant.Expression{
|
||||
qdrant.NewExpressionVariable("$score"), // the fused score from the RRF prefetch
|
||||
qdrant.NewExpressionExpDecay(&qdrant.DecayParamsExpression{
|
||||
X: qdrant.NewExpressionDatetimeKey("published_at"),
|
||||
Target: qdrant.NewExpressionDatetime("YYYY-MM-DDT00:00:00Z"),
|
||||
Scale: qdrant.PtrOf(float32(86400 * 180)), // 180 days in seconds
|
||||
Midpoint: qdrant.PtrOf(float32(0.5)),
|
||||
qdrant.NewExpressionMult(&qdrant.MultExpression{
|
||||
Mult: []*qdrant.Expression{
|
||||
qdrant.NewExpressionConstant(0.1), // caps decay contribution
|
||||
qdrant.NewExpressionExpDecay(&qdrant.DecayParamsExpression{
|
||||
X: qdrant.NewExpressionDatetimeKey("published_at"),
|
||||
Target: qdrant.NewExpressionDatetime("YYYY-MM-DDT00:00:00Z"),
|
||||
Scale: qdrant.PtrOf(float32(86400 * 180)), // 180 days in seconds
|
||||
Midpoint: qdrant.PtrOf(float32(0.5)),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
},
|
||||
}),
|
||||
|
||||
+15
-10
@@ -25,16 +25,21 @@ POST /collections/{collection_name}/points/query
|
||||
"sum": [
|
||||
"$score", // the fused score from the RRF prefetch
|
||||
{
|
||||
"exp_decay": {
|
||||
"x": {
|
||||
"datetime_key": "published_at"
|
||||
},
|
||||
"target": {
|
||||
"datetime": "YYYY-MM-DDT00:00:00Z"
|
||||
},
|
||||
"scale": 15552000, // 180 days in seconds
|
||||
"midpoint": 0.5
|
||||
}
|
||||
"mult": [
|
||||
0.1, // caps decay contribution
|
||||
{
|
||||
"exp_decay": {
|
||||
"x": {
|
||||
"datetime_key": "published_at"
|
||||
},
|
||||
"target": {
|
||||
"datetime": "YYYY-MM-DDT00:00:00Z"
|
||||
},
|
||||
"scale": 15552000, // 180 days in seconds
|
||||
"midpoint": 0.5
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+15
-7
@@ -1,8 +1,10 @@
|
||||
package com.example.snippets_amalgamation;
|
||||
|
||||
import static io.qdrant.client.ExpressionFactory.constant;
|
||||
import static io.qdrant.client.ExpressionFactory.datetime;
|
||||
import static io.qdrant.client.ExpressionFactory.datetimeKey;
|
||||
import static io.qdrant.client.ExpressionFactory.expDecay;
|
||||
import static io.qdrant.client.ExpressionFactory.mult;
|
||||
import static io.qdrant.client.ExpressionFactory.sum;
|
||||
import static io.qdrant.client.ExpressionFactory.variable;
|
||||
import static io.qdrant.client.QueryFactory.formula;
|
||||
@@ -13,6 +15,7 @@ import io.qdrant.client.QdrantClient;
|
||||
import io.qdrant.client.QdrantGrpcClient;
|
||||
import io.qdrant.client.grpc.Points.DecayParamsExpression;
|
||||
import io.qdrant.client.grpc.Points.Formula;
|
||||
import io.qdrant.client.grpc.Points.MultExpression;
|
||||
import io.qdrant.client.grpc.Points.PrefetchQuery;
|
||||
import io.qdrant.client.grpc.Points.QueryPoints;
|
||||
import io.qdrant.client.grpc.Points.Rrf;
|
||||
@@ -52,13 +55,18 @@ public class Snippet {
|
||||
SumExpression.newBuilder()
|
||||
.addSum(variable("$score"))
|
||||
.addSum(
|
||||
expDecay(
|
||||
DecayParamsExpression.newBuilder()
|
||||
.setX(datetimeKey("published_at"))
|
||||
.setTarget(
|
||||
datetime("YYYY-MM-DDT00:00:00Z"))
|
||||
.setScale(86400 * 180)
|
||||
.setMidpoint(0.5f)
|
||||
mult(
|
||||
MultExpression.newBuilder()
|
||||
.addMult(constant(0.1f))
|
||||
.addMult(
|
||||
expDecay(
|
||||
DecayParamsExpression.newBuilder()
|
||||
.setX(datetimeKey("published_at"))
|
||||
.setTarget(
|
||||
datetime("YYYY-MM-DDT00:00:00Z"))
|
||||
.setScale(86400 * 180)
|
||||
.setMidpoint(0.5f)
|
||||
.build()))
|
||||
.build()))
|
||||
.build()))
|
||||
.build()))
|
||||
|
||||
+11
-8
@@ -24,14 +24,17 @@ client.query_points(
|
||||
formula=models.SumExpression(
|
||||
sum=[
|
||||
"$score", # the fused score from the RRF prefetch
|
||||
models.ExpDecayExpression(
|
||||
exp_decay=models.DecayParamsExpression(
|
||||
x=models.DatetimeKeyExpression(datetime_key="published_at"),
|
||||
target=models.DatetimeExpression(datetime="YYYY-MM-DDT00:00:00Z"),
|
||||
scale=86400 * 180, # 180 days in seconds
|
||||
midpoint=0.5,
|
||||
)
|
||||
),
|
||||
models.MultExpression(mult=[
|
||||
0.1, # caps decay contribution; un-weighted decay [0, 1] would otherwise crowd out small RRF scores
|
||||
models.ExpDecayExpression(
|
||||
exp_decay=models.DecayParamsExpression(
|
||||
x=models.DatetimeKeyExpression(datetime_key="published_at"),
|
||||
target=models.DatetimeExpression(datetime="YYYY-MM-DDT00:00:00Z"),
|
||||
scale=86400 * 180, # 180 days in seconds
|
||||
midpoint=0.5,
|
||||
)
|
||||
),
|
||||
]),
|
||||
]
|
||||
)
|
||||
),
|
||||
|
||||
+9
-6
@@ -29,12 +29,15 @@ pub async fn main() -> anyhow::Result<()> {
|
||||
.query(
|
||||
FormulaBuilder::new(Expression::sum_with([
|
||||
Expression::score(),
|
||||
Expression::exp_decay(
|
||||
DecayParamsExpressionBuilder::new(Expression::datetime_key("published_at"))
|
||||
.target(Expression::datetime("YYYY-MM-DDT00:00:00Z"))
|
||||
.scale(86400.0 * 180.0)
|
||||
.midpoint(0.5),
|
||||
),
|
||||
Expression::mult_with([
|
||||
Expression::constant(0.1),
|
||||
Expression::exp_decay(
|
||||
DecayParamsExpressionBuilder::new(Expression::datetime_key("published_at"))
|
||||
.target(Expression::datetime("YYYY-MM-DDT00:00:00Z"))
|
||||
.scale(86400.0 * 180.0)
|
||||
.midpoint(0.5),
|
||||
),
|
||||
]),
|
||||
])),
|
||||
)
|
||||
.limit(10u64),
|
||||
|
||||
+11
-6
@@ -27,12 +27,17 @@ await client.query("{collection_name}", {
|
||||
sum: [
|
||||
"$score", // the fused score from the RRF prefetch
|
||||
{
|
||||
exp_decay: {
|
||||
x: { datetime_key: "published_at" },
|
||||
target: { datetime: "YYYY-MM-DDT00:00:00Z" },
|
||||
scale: 86400 * 180, // 180 days in seconds
|
||||
midpoint: 0.5,
|
||||
},
|
||||
mult: [
|
||||
0.1, // caps decay contribution; un-weighted decay [0, 1] would otherwise crowd out small RRF scores
|
||||
{
|
||||
exp_decay: {
|
||||
x: { datetime_key: "published_at" },
|
||||
target: { datetime: "YYYY-MM-DDT00:00:00Z" },
|
||||
scale: 86400 * 180, // 180 days in seconds
|
||||
midpoint: 0.5,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
+2
@@ -16,10 +16,12 @@ client.Query(context.Background(), &qdrant.QueryPoints{
|
||||
{
|
||||
Query: qdrant.NewQuerySparse([]uint32{1, 42}, []float32{0.22, 0.8}),
|
||||
Using: qdrant.PtrOf("sparse"),
|
||||
Limit: qdrant.PtrOf(uint64(20)),
|
||||
},
|
||||
{
|
||||
Query: qdrant.NewQueryDense([]float32{0.01, 0.45, 0.67}),
|
||||
Using: qdrant.PtrOf("dense"),
|
||||
Limit: qdrant.PtrOf(uint64(20)),
|
||||
},
|
||||
},
|
||||
Query: qdrant.NewQueryRRF(&qdrant.Rrf{}),
|
||||
|
||||
@@ -20,10 +20,12 @@ func Main() {
|
||||
{
|
||||
Query: qdrant.NewQuerySparse([]uint32{1, 42}, []float32{0.22, 0.8}),
|
||||
Using: qdrant.PtrOf("sparse"),
|
||||
Limit: qdrant.PtrOf(uint64(20)),
|
||||
},
|
||||
{
|
||||
Query: qdrant.NewQueryDense([]float32{0.01, 0.45, 0.67}),
|
||||
Using: qdrant.PtrOf("dense"),
|
||||
Limit: qdrant.PtrOf(uint64(20)),
|
||||
},
|
||||
},
|
||||
Query: qdrant.NewQueryRRF(&qdrant.Rrf{}),
|
||||
|
||||
Reference in New Issue
Block a user