Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion _query-dsl/compound/hybrid.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The following table lists all top-level parameters supported by `hybrid` queries
Parameter | Description
:--- | :---
`queries` | An array of one or more query clauses that are used to match documents. A document must match at least one query clause in order to be returned in the results. The documents' relevance scores from all query clauses are combined into one score by applying a [search pipeline]({{site.url}}{{site.baseurl}}/search-plugins/search-pipelines/index/). The maximum number of query clauses is 5. Required.
`filter` | A filter to apply to all the subqueries of the hybrid query.
`filter` | A filter to apply to all the subqueries of the hybrid query. The filter must be a single query object. To apply multiple filter conditions, combine them in a [Boolean query]({{site.url}}{{site.baseurl}}/query-dsl/compound/bool/). For more information, see [Hybrid search with pre-filtering]({{site.url}}{{site.baseurl}}/vector-search/ai-search/hybrid-search/pre-filtering/).

### Rescoring hybrid queries
Introduced 2.18
Expand Down
12 changes: 12 additions & 0 deletions _vector-search/ai-search/hybrid-search/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,18 @@ The response contains the matching documents:
```
{% include copy-curl.html %}

## Filtering data

Hybrid search supports two approaches to filtering: **pre-filtering** and **post-filtering**.

| | Pre-filtering | Post-filtering |
| :--- | :--- | :--- |
| **What it does** | Removes documents before they are scored | Removes documents after all scoring is complete |
| **How to use** | Add a top-level `filter` to the `hybrid` query | Add a `post_filter` to the search request |
| **When to use** | 95% of searches | Almost exclusively when you have faceted search with aggregations and want the facets to reflect the unfiltered query while narrowing only the displayed hits |

For details on each approach, see [Hybrid search with pre-filtering]({{site.url}}{{site.baseurl}}/vector-search/ai-search/hybrid-search/pre-filtering/) and [Hybrid search with post-filtering]({{site.url}}{{site.baseurl}}/vector-search/ai-search/hybrid-search/post-filtering/).

## Next steps

- Explore our [tutorials]({{site.url}}{{site.baseurl}}/vector-search/tutorials/) to learn how to build AI search applications.
164 changes: 119 additions & 45 deletions _vector-search/ai-search/hybrid-search/post-filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,71 +18,145 @@ The `post_filter` clause is applied after the search results have been retrieved
Post-filtering does not impact document relevance scores or aggregation results.
{: .note}

## Example
To filter all subqueries during query execution instead of filtering the final results, use a common filter. For more information, see [Hybrid search with pre-filtering]({{site.url}}{{site.baseurl}}/vector-search/ai-search/hybrid-search/pre-filtering/).

The following example request combines two query clauses---a `term` query and a `match` query---and contains a `post_filter`:
## Example: Faceted search with post-filtering

Post-filtering is commonly used in faceted search, where the UI displays aggregation counts (such as brand, color, and size filters) alongside search results. Using `post_filter` keeps the aggregation counts based on the full unfiltered query while narrowing only the displayed hits.

Consider an index containing product documents:

```json
GET /my-nlp-index/_search?search_pipeline=nlp-search-pipeline
{
"name": "Nike Air Max",
"brand": "Nike",
"color": "Red",
"size": 10,
"price": 120,
"category": "Running Shoes"
}
```

A user searches for "running shoes" and the application returns results with aggregations for brand, color, and size:

```json
POST /products/_search
{
"query": {
"hybrid":{
"queries":[
{
"match":{
"passage_text": "hello"
}
},
{
"term":{
"passage_text":{
"value":"planet"
}
}
}
]
"match": {
"category": "running shoes"
}
},
"aggs": {
"brands": {
"terms": { "field": "brand.keyword" }
},
"colors": {
"terms": { "field": "color.keyword" }
},
"sizes": {
"terms": { "field": "size" }
}
}
}
```
{% include copy-curl.html %}

The response returns hits and aggregations:

**Hits:**

```
Nike Air Max
Nike Pegasus
Adidas Adizero
Puma Velocity
...
```

**Aggregations:**

```
Brands: Nike (120), Adidas (80), Puma (45)
Colors: Black (90), White (70), Red (55)
Sizes: 8 (40), 9 (60), 10 (85)
```

The UI renders the aggregations as facet filters on the side. Now the user clicks "Nike" to narrow results.

### Why pre-filtering removes facets

If you apply the brand selection as a pre-filter, the aggregations are computed only on Nike documents:

```json
POST /products/_search
{
"query": {
"bool": {
"must": {
"match": { "category": "running shoes" }
},
"filter": {
"term": { "brand.keyword": "Nike" }
}
}
},
"post_filter":{
"match": { "passage_text": "world" }
"aggs": {
"brands": {
"terms": { "field": "brand.keyword" }
},
"colors": {
"terms": { "field": "color.keyword" }
}
}
}
```
{% include copy-curl.html %}

Compare the results to the results in the [example without post-filtering]({{site.url}}{{site.baseurl}}/vector-search/ai-search/hybrid-search/#example-combining-a-match-query-and-a-term-query). In the example without post-filtering, the response contains two documents. In this example, the response contains one document because the second document is filtered out:
The aggregations become:

```
Brands: Nike (120)
Colors: Black (50), White (40), Red (30)
```

Adidas and Puma disappear completely from the brand facet because those documents were excluded before aggregations were computed. The user loses the ability to see other brand options or switch to a different brand without first removing the filter.

### Using post_filter to preserve facets

With `post_filter`, the query and aggregations run on the full result set. The filter is applied only to the displayed hits:

```json
POST /products/_search
{
"took": 18,
"timed_out": false,
"_shards": {
"total": 2,
"successful": 2,
"skipped": 0,
"failed": 0
"query": {
"match": {
"category": "running shoes"
}
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
"aggs": {
"brands": {
"terms": { "field": "brand.keyword" }
},
"max_score": 0.3,
"hits": [
{
"_index": "my-nlp-index",
"_id": "1",
"_score": 0.3,
"_source": {
"id": "s1",
"passage_text": "Hello world"
}
}
]
"colors": {
"terms": { "field": "color.keyword" }
}
},
"post_filter": {
"term": { "brand.keyword": "Nike" }
}
}
```
{% include copy-curl.html %}

Now the hits contain only Nike shoes, but the aggregations still reflect the full unfiltered query:

```
Brands: ✓ Nike (120), Adidas (80), Puma (45)
Colors: Black (90), White (70), Red (55)
```

The user can switch from Nike to Adidas or see how many results each brand has without issuing a different query.

## How post-filtering affects search results and scoring

Expand Down Expand Up @@ -122,4 +196,4 @@ After applying a post-filter to the initial query results, the results are as fo

Observe that:
- Document `d2`'s score remains unchanged.
- Document `d4`'s score has changed.
- Document `d4`'s score has changed.
145 changes: 145 additions & 0 deletions _vector-search/ai-search/hybrid-search/pre-filtering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
---
layout: default
title: Hybrid search with pre-filtering
parent: Hybrid search
grand_parent: AI search
has_children: false
nav_order: 38
---

# Hybrid search with pre-filtering
**Introduced 3.0**
{: .label .label-purple }

You can perform pre-filtering on hybrid search results by providing a top-level `filter` parameter in the `hybrid` query.

The `filter` is applied during query execution and is pushed down to each subquery, so only documents that match the filter are scored. Pre-filtering is useful for applying the same filter to all subqueries without duplicating it in each one.

The `filter` must be a single query object.
{: .note}

To filter the final results after they have been retrieved instead of filtering each subquery during execution, use a post-filter. For more information, see [Hybrid search with post-filtering]({{site.url}}{{site.baseurl}}/vector-search/ai-search/hybrid-search/post-filtering/).

## Example

The following example request combines a `match` query and a `knn` query and applies a common `filter` that restricts both subqueries to the `shoes` category:

```json
POST /products/_search?search_pipeline=nlp-search-pipeline
{
"query": {
"hybrid": {
"filter": {
"term": { "category": "shoes" }
},
"queries": [
{
"match": { "description": "running shoes" }
},
{
"knn": {
"embedding": {
"vector": [1.23, 0.45, 0.67, ...],
"k": 10
}
}
}
]
}
}
}
```
{% include copy-curl.html %}

OpenSearch applies the `category: shoes` filter to both the `match` and `knn` subqueries, which is equivalent to the following query that applies the filter to each subquery individually:

```json
POST /products/_search?search_pipeline=nlp-search-pipeline
{
"query": {
"hybrid": {
"queries": [
{
"bool": {
"must": {
"match": { "description": "running shoes" }
},
"filter": {
"term": { "category": "shoes" }
}
}
},
{
"knn": {
"embedding": {
"vector": [1.23, 0.45, 0.67, ...],
"k": 10,
"filter": {
"term": { "category": "shoes" }
}
}
}
}
]
}
}
}
```
{% include copy-curl.html %}

## Filtering on multiple conditions

Because the `filter` must be a single query object, you combine multiple conditions in a [Boolean query]({{site.url}}{{site.baseurl}}/query-dsl/compound/bool/):

```json
"filter": {
"bool": {
"must": [
{ "term": { "category": "shoes" }},
{ "term": { "in_stock": true }}
]
}
}
```

## Combining a common filter with subquery filters

A subquery can define its own filter in addition to the common filter. In this case, OpenSearch combines the two using a logical `AND`, further narrowing that subquery's results. Subqueries without their own filter are constrained by the common filter only.

In the following example, the common filter restricts all subqueries to the `shoes` category, while the `match` subquery is additionally narrowed to the `nike` brand:

```json
POST /products/_search?search_pipeline=nlp-search-pipeline
{
"query": {
"hybrid": {
"filter": {
"term": { "category": "shoes" }
},
"queries": [
{
"bool": {
"must": {
"match": { "description": "running shoes" }
},
"filter": {
"term": { "brand": "nike" }
}
}
},
{
"knn": {
"embedding": {
"vector": [1.23, 0.45, 0.67, ...],
"k": 10
}
}
}
]
}
}
}
```
{% include copy-curl.html %}

As a result, the `match` subquery requires both `category: shoes` and `brand: nike`, while the `knn` subquery is constrained only to `category: shoes`.
Loading