ハイブリッド検索: 全文検索とkNN検索結果の組み合わせ

これまで、ドキュメントのコレクションを検索するための 2 つの異なるアプローチを見てきました。それぞれのアプローチには独自の利点があります。これらの方法のいずれかがニーズに合致する場合は、他の方法は必要ありませんが、多くの場合、各検索方法では、他の方法では得られない貴重な結果が返されるため、結合された結果セットを提供することが最善の選択肢となります。

このような場合に備えて、Elasticsearch は、2 つ以上のリストの結果を 1 つのリストに結合するアルゴリズムである Reciprocal Rank Fusionを提供します。

RRFの仕組み

Elasticsearch は RRF アルゴリズムを検索クエリに統合します。次の例を考えてみましょう。この例には、それぞれ全文検索とベクター検索を要求するqueryセクションとknnセクションがあり、それらを 1 つの結果リストに結合するrrfセクションがあります。

self.es.search(
    query={
        # full-text search query here
    },
    knn={
        # vector search query here
    },
    rank={
        "rrf": {}
    }
)

RRF は、構成なしでも結果の短いリストに対しては適切に機能しますが、最良の結果を提供するために調整できるパラメータがいくつかあります。詳細についてはドキュメントを参照してください。

RRFの実装

全文検索とベクター検索の両方の方法から結果を返す複合検索を有効にするには、 handle_search()関数で以前に使用した全文検索ロジックを戻す必要があります。ハイブリッド検索戦略を実装するには、 search()メソッドは、それぞれ別々のクエリを要求するquery引数とknn引数の両方を受け取る必要があります。上記のrankセクションも追加され、結果が 1 つのランク付けされたリストに結合されます。

ハイブリッド検索戦略を実装するhandle_search()のバージョンは次のとおりです。

@app.post('/')
def handle_search():
    query = request.form.get('query', '')
    filters, parsed_query = extract_filters(query)
    from_ = request.form.get('from_', type=int, default=0)

    if parsed_query:
        search_query = {
            'must': {
                'multi_match': {
                    'query': parsed_query,
                    'fields': ['name', 'summary', 'content'],
                }
            }
        }
    else:
        search_query = {
            'must': {
                'match_all': {}
            }
        }

    results = es.search(
        query={
            'bool': {
                **search_query,
                **filters
            }
        },
        knn={
            'field': 'embedding',
            'query_vector': es.get_embedding(parsed_query),
            'k': 10,
            'num_candidates': 50,
            **filters,
        },
        rank={
            'rrf': {}
        },
        aggs={
            'category-agg': {
                'terms': {
                    'field': 'category.keyword',
                }
            },
            'year-agg': {
                'date_histogram': {
                    'field': 'updated_at',
                    'calendar_interval': 'year',
                    'format': 'yyyy',
                },
            },
        },
        size=5,
        from_=from_,
    )
    aggs = {
        'Category': {
            bucket['key']: bucket['doc_count']
            for bucket in results['aggregations']['category-agg']['buckets']
        },
        'Year': {
            bucket['key_as_string']: bucket['doc_count']
            for bucket in results['aggregations']['year-agg']['buckets']
            if bucket['doc_count'] > 0
        },
    }
    return render_template('index.html', results=results['hits']['hits'],
                           query=query, from_=from_,
                           total=results['hits']['total']['value'], aggs=aggs)

このバージョンでは、各検索方法からの最良の結果が結合されます。変更を加えた完全な申請書を確認するには、ここをクリックしてください。

最先端の検索体験を構築する準備はできましたか?

十分に高度な検索は 1 人の努力だけでは実現できません。Elasticsearch は、データ サイエンティスト、ML オペレーター、エンジニアなど、あなたと同じように検索に情熱を傾ける多くの人々によって支えられています。ぜひつながり、協力して、希望する結果が得られる魔法の検索エクスペリエンスを構築しましょう。

はじめましょう