Elasticsearch Goクライアントを使用してElasticsearchでベクトル検索を実行する
実際の例を通して、Elasticsearch Go クライアントを使用して Elasticsearch でベクトル検索を実行する方法を学びます。
Go を含むあらゆるプログラミング言語でソフトウェアを構築することは、生涯にわたる学習に取り組むことです。Carly は大学時代や仕事を通じて、ベクトル検索の最新かつ最高の実装を含む、数多くのプログラミング言語やテクノロジーに携わってきました。しかし、それだけでは十分ではありませんでした!それで最近カーリーも囲碁を始めました。
動物、プログラミング言語、そして親しみやすい著者と同じように、検索もさまざまな方法の進化を遂げており、独自の検索ユースケースに応じてどれを選択するかを決めるのは難しい場合があります。このブログでは、ベクトル検索の概要と、 Elasticsearch および Elasticsearch Go クライアント を使用した各アプローチの 例を 紹介します。これらの例では、Elasticsearch と Go のベクトル検索を使用して、ホリネズミを見つけて、その食べ物を特定する方法を説明します。
要件
この例を実行するには、次の前提条件が満たされていることを確認してください。
Goバージョン1.21以降のインストール
独自のGoリポジトリを作成する
独自の Elasticsearch クラスターを作成し、Wikipedia のフレンドリーなGopherを含む一連のげっ歯類ベースのページを設定します。

Elasticsearchへの接続
この例では、Go クライアントが提供するTyped APIを利用します。クエリに対して安全な接続を確立するには、次のいずれかを使用してクライアントを構成する必要があります。
Elastic Cloud を利用する場合のクラウド ID と API キー。
クラスター URL、ユーザー名、パスワード、証明書。
Elastic Cloud にあるクラスターに接続すると、次のようになります。
func GetElasticsearchClient() (*elasticsearch.TypedClient, error) {
var cloudID = os.Getenv("ELASTIC_CLOUD_ID")
var apiKey = os.Getenv("ELASTIC_API_KEY")
var es, err = elasticsearch.NewTypedClient(elasticsearch.Config{
CloudID: cloudID,
APIKey: apiKey,
Logger: &elastictransport.ColorLogger{os.Stdout, true, true},
})
if err != nil {
return nil, fmt.Errorf("unable to connect: %w", err)
}
return es, nil
}後続のセクションに示すように、 client接続はベクトル検索に使用できます。
ベクトル検索
ベクトル検索は、検索問題をベクトルを使用した数学的な比較に変換することでこの問題を解決しようとします。ドキュメント埋め込みプロセスには、モデルを使用してドキュメントを高密度ベクトル表現、つまり単純な数値のストリームに変換する追加の段階があります。このアプローチの利点は、画像や音声などのテキスト以外のドキュメントをクエリと一緒にベクトルに変換して検索できることです。

簡単に言えば、ベクトル検索はベクトル距離の計算のセットです。下の図では、クエリGo Gopherのベクトル表現がベクトル空間内のドキュメントと比較され、最も近い結果 (定数kで示される) が返されます。

ドキュメントの埋め込みを生成するために使用するアプローチに応じて、ホリネズミが何を食べるかを調べる方法が 2 つあります。
アプローチ1: 独自のモデルを持ち込む
Platinum ライセンスでは、モデルをアップロードし、推論 API を使用して Elasticsearch 内で埋め込みを生成できます。モデルの設定には 6 つのステップがあります。
モデル リポジトリからアップロードする PyTorch モデルを選択します。この例では、Hugging Face のsentence-transformers/msmarco-MiniLM-L-12-v3を使用して埋め込みを生成します。
Elasticsearch クラスターとタスク タイプ
text_embeddingsの資格情報を使用して、Python 用の Eland Machine Learning クライアントでモデルを Elastic にロードします。Eland がインストールされていない場合は、以下に示すようにDocker を使用してインポート手順を実行できます。
docker run -it --rm --network host \
docker.elastic.co/eland/eland \
eland_import_hub_model \
--cloud-id $ELASTIC_CLOUD_ID \
--es-api-key $ELASTIC_API_KEY \
--hub-model-id sentence-transformers/msmarco-MiniLM-L-12-v3 \
--task-type text_embeddingアップロードしたら、サンプル ドキュメントを使用してモデル
sentence-transformers__msmarco-minilm-l-12-v3をすぐにテストし、埋め込みが期待どおりに生成されることを確認します。

推論プロセッサを含む取り込みパイプラインを作成します。これにより、アップロードされたモデルを使用してベクター表現を生成できるようになります。
PUT _ingest/pipeline/search-rodents-vector-embedding-pipeline
{
"processors": [
{
"inference": {
"model_id": "sentence-transformers__msmarco-minilm-l-12-v3",
"target_field": "text_embedding",
"field_map": {
"body_content": "text_field"
}
}
}
]
}各ドキュメントに対して生成されたベクトル埋め込みを格納するための、タイプ
dense_vectorのフィールドtext_embedding.predicted_valueを含む新しいインデックスを作成します。
PUT vector-search-rodents
{
"mappings": {
"properties": {
"text_embedding.predicted_value": {
"type": "dense_vector",
"dims": 384,
"index": true,
"similarity": "cosine"
},
"text": {
"type": "text"
}
}
}
}新しく作成された取り込みパイプラインを使用してドキュメントのインデックスを再作成し、各ドキュメントの追加フィールド
text_embedding.predicted_valueとしてテキスト埋め込みを生成します。
POST _reindex
{
"source": {
"index": "search-rodents"
},
"dest": {
"index": "vector-search-rodents",
"pipeline": "search-rodents-vector-embedding-pipeline"
}
}次の例に示すように、新しいインデックスvector-search-rodentsを使用して、同じ検索 API でKnnオプションを使用できるようになりました。
func VectorSearch(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
var k = 10
var numCandidates = 10
res, err := client.Search().
Index("vector-search-rodents").
Knn(types.KnnSearch{
# Field in document containing vector
Field: "text_embedding.predicted_value",
# Number of neighbors to return
K: &k,
# Number of candidates to evaluate in comparison
NumCandidates: &numCandidates,
# Generate query vector using the same model used in the inference processor
QueryVectorBuilder: &types.QueryVectorBuilder{
TextEmbedding: &types.TextEmbedding{
ModelId: "sentence-transformers__msmarco-minilm-l-12-v3",
ModelText: term,
},
}}).Do(context.Background())
if err != nil {
return nil, fmt.Errorf("error in rodents vector search: %w", err)
}
return getRodents(res.Hits.Hits)
}アンマーシャリングによる JSON 結果オブジェクトの変換は、キーワード検索の例とまったく同じ方法で実行されます。定数KとNumCandidates使用すると、返される隣接ドキュメントの数と、シャードごとに考慮する候補の数を設定できます。候補の数を増やすと結果の精度は上がりますが、比較が多く実行されるためクエリの実行時間が長くなることに注意してください。
クエリWhat do Gophers eat?を使用してコードを実行すると、返される結果は以下と同様になり、以前のキーワード検索とは異なり、Gopher の記事に要求された情報が含まれていることが強調表示されます。
[
{ID:64f74ecd4acb3df024d91112 Title:Gopher - Wikipedia Url:https://en.wikipedia.org/wiki/Gopher}
{ID:64f74ed34acb3d71aed91fcd Title:Squirrel - Wikipedia Url:https://en.wikipedia.org/wiki/Squirrel}
//Other results omitted
]アプローチ2:ハグフェイス推論API
もう 1 つのオプションは、Elasticsearch の外部で同じ埋め込みを生成し、ドキュメントの一部として取り込むことです。このオプションは Elasticsearch 機械学習ノードを使用しないため、無料利用枠で実行できます。
Hugging Face は、無料で使用できるレート制限付きの推論 API を公開しています。アカウントと API トークンを使用すると、実験やプロトタイピングを開始するために同じ埋め込みを手動で生成できます。実稼働環境での使用は推奨されません。同様のアプローチを使用して、ローカルで独自のモデルを呼び出して埋め込みを生成したり、有料の API を使用したりすることもできます。
以下の関数GetTextEmbeddingForQueryでは、クエリ文字列に対して推論 API を使用して、エンドポイントへのPOSTリクエストから返されるベクトルを生成します。
// HuggingFace text embedding helper
func GetTextEmbeddingForQuery(term string) []float32 {
// HTTP endpoint
model := "sentence-transformers/msmarco-minilm-l-12-v3"
posturl := fmt.Sprintf("https://api-inference.huggingface.co/pipeline/feature-extraction/%s", model)
// JSON body
body := []byte(fmt.Sprintf(`{
"inputs": "%s",
"options": {"wait_for_model":True}
}`, term))
// Create a HTTP post request
r, err := http.NewRequest("POST", posturl, bytes.NewBuffer(body))
if err != nil {
log.Fatal(err)
return nil
}
token := os.Getenv("HUGGING_FACE_TOKEN")
r.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
client := &http.Client{}
res, err := client.Do(r)
if err != nil {
panic(err)
}
defer res.Body.Close()
var post []float32
derr := json.NewDecoder(res.Body).Decode(&post)
if derr != nil {
log.Fatal(derr)
return nil
}
return post
}結果の[]float32型のベクトルは、 QueryVectorBuilderオプションを使用する代わりにQueryVectorとして渡され、以前に Elastic にアップロードされたモデルが活用されます。
func VectorSearchWithGeneratedQueryVector(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
vector, err := GetTextEmbeddingForQuery(term)
if err != nil {
return nil, err
}
if vector == nil {
return nil, fmt.Errorf("unable to generate vector: %w", err)
}
var k = 10
var numCandidates = 10
res, err := client.Search().
Index("vector-search-rodents").
Knn(types.KnnSearch{
# Field in document containing vector
Field: "text_embedding.predicted_value",
# Number of neighbors to return
K: &k,
# Number of candidates to evaluate in comparison
NumCandidates: &numCandidates,
# Query vector returned from Hugging Face inference API
QueryVector: vector,
}).
Do(context.Background())
if err != nil {
return nil, err
}
return getRodents(res.Hits.Hits)
}KとNumCandidatesオプションは2つのオプションに関係なく同じままであり、埋め込みを生成するために同じモデルを使用しているため、同じ結果が生成されることに注意してください。
まとめ
ここでは、 Elasticsearch Go クライアントを使用して Elasticsearch でベクトル検索を実行する方法について説明しました。このシリーズのすべてのコードについては、 GitHub リポジトリをご覧ください。パート 3に進み、パート 1で説明した Go のキーワード検索機能とベクトル検索を組み合わせる方法の概要を確認します。
それまでは、楽しいホリネズミ狩りを!




