Skip to content

Commit 3de7a4c

Browse files
simorenohannatisch
andauthored
[Cosmos] Hybrid Search query pipeline (Azure#38275)
* Create hybrid_search_aggregator.py * others * Update execution_dispatcher.py * Update execution_dispatcher.py * sync changes, need to look at vector + FTS/ skip + take * async pipeline * account for skip/take and simplify logics * small hack for now * fixing top/limit logic * return only payload * fix hack * pylint * simplifying further * small changes * adds readme, buffer limit, simplifies * simplify async, CI green * Update hybrid_search_aggregator.py * Update sdk/cosmos/azure-cosmos/README.md Co-authored-by: Anna Tisch <[email protected]> * update variable name * add sync and async tests * Update README.md * simplifications, test fixes * add wrong query tests * pylint/cspell * Update CHANGELOG.md * small changes * test updates * Update hybrid_search_data.py * cspell, samples * change tops * address comments * Update hybrid_search_aggregator.py * update pipeline description * Update CHANGELOG.md * Update CHANGELOG.md --------- Co-authored-by: Anna Tisch <[email protected]>
1 parent 67aa17f commit 3de7a4c

21 files changed

+1179
-39
lines changed

sdk/cosmos/azure-cosmos/CHANGELOG.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
## Release History
22

3-
### 4.9.0 (Unreleased)
3+
### 4.9.0 (2024-11-18)
44

55
#### Features Added
66
* Added full text policy and full text indexing policy. See [PR 37891](https://github.com/Azure/azure-sdk-for-python/pull/37891).
7-
8-
#### Breaking Changes
9-
10-
#### Bugs Fixed
11-
12-
#### Other Changes
7+
* Added support for full text search and hybrid search queries. See [PR 38275](https://github.com/Azure/azure-sdk-for-python/pull/38275).
138

149
### 4.8.0 (2024-11-12)
1510
This version and all future versions will support Python 3.13.

sdk/cosmos/azure-cosmos/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,40 @@ indexing_policy = {
818818
Modifying the index in a container is an asynchronous operation that can take a long time to finish. See [here][cosmos_index_policy_change] for more information.
819819
For more information on using full text policies and full text indexes, see [here][cosmos_fts].
820820

821+
### Public Preview - Full Text Search and Hybrid Search
822+
823+
With the addition of the full text indexing and full text policies, the SDK can now perform full text search and hybrid search queries.
824+
These queries can utilize the new query functions `FullTextContains()`, `FullTextContainsAll`, and `FullTextContainsAny` to efficiently
825+
search for the given terms within your item fields.
826+
827+
Beyond these, you can also utilize the new `Order By RANK` and `Order By RANK RRF` along with `FullTextScore` to execute the [BM25][BM25] scoring algorithm
828+
or [Reciprocal Rank Fusion][RRF] (RRF) on your query, finding the items with the highest relevance to the terms you are looking for.
829+
All of these mentioned queries would look something like this:
830+
831+
- `SELECT TOP 10 c.id, c.text FROM c WHERE FullTextContains(c.text, 'quantum')`
832+
833+
834+
- `SELECT TOP 10 c.id, c.text FROM c WHERE FullTextContainsAll(c.text, 'quantum', 'theory')`
835+
836+
837+
- `SELECT TOP 10 c.id, c.text FROM c WHERE FullTextContainsAny(c.text, 'quantum', 'theory')`
838+
839+
840+
- `SELECT TOP 10 c.id, c.text FROM c ORDER BY RANK FullTextScore(c.text, ['quantum', 'theory'])`
841+
842+
843+
- `SELECT TOP 10 c.id, c.text FROM c ORDER BY RANK RRF(FullTextScore(c.text, ['quantum', 'theory']), FullTextScore(c.text, ['model']))`
844+
845+
846+
- `SELECT TOP 10 c.id, c.text FROM c ORDER BY RANK RRF(FullTextScore(c.text, ['quantum', 'theory']), FullTextScore(c.text, ['model']), VectorDistance(c.embedding, {item_embedding}))"`
847+
848+
These queries must always use a TOP or LIMIT clause within the query since hybrid search queries have to look through a lot of data otherwise and may become too expensive or long-running.
849+
Since these queries are relatively expensive, the SDK sets a default limit of 1000 max items per query - if you'd like to raise that further, you
850+
can use the `AZURE_COSMOS_HYBRID_SEARCH_MAX_ITEMS` environment variable to do so. However, be advised that queries with too many vector results
851+
may have additional latencies associated with searching in the service.
852+
853+
You can find our sync samples [here][cosmos_index_sample] and our async samples [here][cosmos_index_sample_async] as well for additional guidance.
854+
821855
## Troubleshooting
822856

823857
### General
@@ -954,6 +988,8 @@ For more extensive documentation on the Cosmos DB service, see the [Azure Cosmos
954988
[cosmos_concurrency_sample]: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/cosmos/azure-cosmos/samples/concurrency_sample.py
955989
[cosmos_index_sample]: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/cosmos/azure-cosmos/samples/index_management.py
956990
[cosmos_index_sample_async]: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/cosmos/azure-cosmos/samples/index_management_async.py
991+
[RRF]: https://learn.microsoft.com/azure/search/hybrid-search-ranking
992+
[BM25]: https://learn.microsoft.com/azure/search/index-similarity-and-scoring
957993
[cosmos_fts]: https://aka.ms/cosmosfulltextsearch
958994
[cosmos_index_policy_change]: https://learn.microsoft.com/azure/cosmos-db/index-policy#modifying-the-indexing-policy
959995

sdk/cosmos/azure-cosmos/azure/cosmos/_cosmos_client_connection.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3141,7 +3141,9 @@ def _GetQueryPlanThroughGateway(self, query: str, resource_link: str, **kwargs:
31413141
documents._QueryFeature.OffsetAndLimit + "," +
31423142
documents._QueryFeature.OrderBy + "," +
31433143
documents._QueryFeature.Top + "," +
3144-
documents._QueryFeature.NonStreamingOrderBy)
3144+
documents._QueryFeature.NonStreamingOrderBy + "," +
3145+
documents._QueryFeature.HybridSearch + "," +
3146+
documents._QueryFeature.CountIf)
31453147
if os.environ.get('AZURE_COSMOS_DISABLE_NON_STREAMING_ORDER_BY', False):
31463148
supported_query_features = (documents._QueryFeature.Aggregate + "," +
31473149
documents._QueryFeature.CompositeAggregate + "," +

sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/aio/document_producer.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def __init__(self, partition_key_target_range, client, collection_link, query, d
5353
self._is_finished = False
5454
self._has_started = False
5555
self._cur_item = None
56+
self._query = query
5657
# initiate execution context
5758

5859
path = _base.GetPathFromLink(collection_link, "docs")

sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/aio/endpoint_component.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def __init__(self, execution_context, aggregate_operators):
173173
for operator in aggregate_operators:
174174
if operator == "Average":
175175
self._local_aggregators.append(_AverageAggregator())
176-
elif operator == "Count":
176+
elif operator in ("Count", "CountIf"):
177177
self._local_aggregators.append(_CountAggregator())
178178
elif operator == "Max":
179179
self._local_aggregators.append(_MaxAggregator())

sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/aio/execution_dispatcher.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@
2525

2626
import os
2727
from azure.cosmos._execution_context.aio import endpoint_component, multi_execution_aggregator
28-
from azure.cosmos._execution_context.aio import non_streaming_order_by_aggregator
28+
from azure.cosmos._execution_context.aio import non_streaming_order_by_aggregator, hybrid_search_aggregator
2929
from azure.cosmos._execution_context.aio.base_execution_context import _QueryExecutionContextBase
3030
from azure.cosmos._execution_context.aio.base_execution_context import _DefaultQueryExecutionContext
31-
from azure.cosmos._execution_context.execution_dispatcher import _is_partitioned_execution_info
31+
from azure.cosmos._execution_context.execution_dispatcher import _is_partitioned_execution_info,\
32+
_is_hybrid_search_query, _verify_valid_hybrid_search_query
3233
from azure.cosmos._execution_context.query_execution_info import _PartitionedQueryExecutionInfo
3334
from azure.cosmos.documents import _DistinctType
3435
from azure.cosmos.exceptions import CosmosHttpResponseError
@@ -89,7 +90,7 @@ async def fetch_next_block(self):
8990
try:
9091
return await self._execution_context.fetch_next_block()
9192
except CosmosHttpResponseError as e:
92-
if _is_partitioned_execution_info(e): #cross partition query not servable
93+
if _is_partitioned_execution_info(e) or _is_hybrid_search_query(self._query, e):
9394
query_to_use = self._query if self._query is not None else "Select * from root r"
9495
query_execution_info = _PartitionedQueryExecutionInfo(await self._client._GetQueryPlanThroughGateway
9596
(query_to_use, self._resource_link))
@@ -126,6 +127,16 @@ async def _create_pipelined_execution_context(self, query_execution_info):
126127
self._options,
127128
query_execution_info)
128129
await execution_context_aggregator._configure_partition_ranges()
130+
elif query_execution_info.has_hybrid_search_query_info():
131+
hybrid_search_query_info = query_execution_info._query_execution_info['hybridSearchQueryInfo']
132+
_verify_valid_hybrid_search_query(hybrid_search_query_info)
133+
execution_context_aggregator = \
134+
hybrid_search_aggregator._HybridSearchContextAggregator(self._client,
135+
self._resource_link,
136+
self._options,
137+
query_execution_info,
138+
hybrid_search_query_info)
139+
await execution_context_aggregator._run_hybrid_search()
129140
else:
130141
execution_context_aggregator = multi_execution_aggregator._MultiExecutionContextAggregator(self._client,
131142
self._resource_link,

0 commit comments

Comments
 (0)