クイックスタート: Azure SDK を使用したフル テキスト検索

Azure SDK で Azure.Search.Documents クライアント ライブラリを使用して、フル テキスト検索用のサンプル データを使用して検索インデックスの作成、読み込み、クエリの実行を行う方法について説明します。 フル テキスト検索では、インデックス作成とクエリには Apache Lucene を、結果のスコアリングには BM25 優先度付けアルゴリズムを使用します。

このクイックスタートには、次の SDK 用の手順が含まれています。

前提条件

  • アクティブなサブスクリプションが含まれる Azure アカウント。 無料でアカウントを作成できます

  • Azure AI Search Service。 まだ作成していない場合、サービスを作成します。 このクイックスタート用には、Free レベルを使用できます。

  • API キーとサービス エンドポイント。 Azure portal にサインインし、ご利用の検索サービスを探します

    [概要] で、URL をコピーし、後の手順のためにメモ帳に保存します。 たとえば、エンドポイントは https://mydemo.search.windows.net のようになります。

    [キー] で、オブジェクトを作成および削除するための完全な権限を持つ管理キーをコピーして保存します。 キーには、プライマリとセカンダリの 2 つがあり、どちらでも同じように機能します どちらかを選択します。

    Get an HTTP endpoint and access key

インデックスの作成、読み込み、クエリの実行

次の手順のためのプログラミング言語を選択します。 Azure.Search.Documents クライアント ライブラリは、.NET、Python、Java、JavaScript 用の Azure SDK で使用できます。

Azure.Search.Documents クライアント ライブラリを使用してコンソール アプリケーションを構築し、検索インデックスの作成、読み込み、クエリの実行を行います。 代わりに、ソース コードをダウンロードして完成したプロジェクトから開始することも、次の手順に従って独自のアプリケーションを作成することもできます。

環境を設定する

  1. Visual Studio を起動し、コンソール アプリ用の新しいプロジェクトを作成します。

  2. [ツール]>[NuGet パッケージ マネージャー] で、 [ソリューションの NuGet パッケージの管理] を選択します。

  3. [参照] を選択します。

  4. Azure.Search.Documents パッケージを検索し、バージョン 11.0 以降を選択します。

  5. 右側にある [インストール] を選択して、プロジェクトとソリューションにアセンブリを追加します。

検索クライアントを作成する

  1. Program.cs で名前空間を AzureSearch.SDK.Quickstart.v11 に変更し、次の using ディレクティブを追加します。

    using Azure;
    using Azure.Search.Documents;
    using Azure.Search.Documents.Indexes;
    using Azure.Search.Documents.Indexes.Models;
    using Azure.Search.Documents.Models;
    
  2. 2 つのクライアントを作成します。SearchIndexClient はインデックスを作成するクライアントで、SearchClient は既存のインデックスを読み込んで照会するクライアントです。 どちらも、作成と削除の権限に関する認証のためのサービス エンドポイントと管理 API キーが必要です。

    コードによって URI が構築されるため、"serviceName" プロパティに検索サービス名のみを指定します。

     static void Main(string[] args)
     {
         string serviceName = "<your-search-service-name>";
         string apiKey = "<your-search-service-admin-api-key>";
         string indexName = "hotels-quickstart";
    
         // Create a SearchIndexClient to send create/delete index commands
         Uri serviceEndpoint = new Uri($"https://{serviceName}.search.windows.net/");
         AzureKeyCredential credential = new AzureKeyCredential(apiKey);
         SearchIndexClient adminClient = new SearchIndexClient(serviceEndpoint, credential);
    
         // Create a SearchClient to load and query documents
         SearchClient srchclient = new SearchClient(serviceEndpoint, indexName, credential);
         . . . 
     }
    

インデックスを作成する

このクイックスタートでは、Hotels インデックスを作成します。そこにホテル データを読み込んでクエリを実行することになります。 この手順では、インデックス内のフィールドを定義します。 それぞれのフィールドの定義には、名前とデータ型、属性が存在し、それらによってフィールドの使い方が決まります。

簡潔で読みやすくするために、この例では、Azure.Search.Documents ライブラリの同期メソッドを使用しています。 ただし運用環境のシナリオでは、アプリのスケーラビリティと応答性を確保するために非同期メソッドを使用する必要があります。 たとえば、CreateIndex ではなく CreateIndexAsync を使用します。

  1. 空のクラス定義を Hotel.cs プロジェクトに追加します。

  2. Hotel.cs に次のコードをコピーして、ホテル ドキュメントの構造を定義します。 フィールドは、その属性によって、アプリケーション内でどのように使用できるかが決まります。 たとえばフィルター式をサポートするフィールドには、それぞれ IsFilterable 属性が割り当てられている必要があります。

    using System;
    using System.Text.Json.Serialization;
    using Azure.Search.Documents.Indexes;
    using Azure.Search.Documents.Indexes.Models;
    
    namespace AzureSearch.Quickstart
    {
        public partial class Hotel
        {
            [SimpleField(IsKey = true, IsFilterable = true)]
            public string HotelId { get; set; }
    
            [SearchableField(IsSortable = true)]
            public string HotelName { get; set; }
    
            [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.EnLucene)]
            public string Description { get; set; }
    
            [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.FrLucene)]
            [JsonPropertyName("Description_fr")]
            public string DescriptionFr { get; set; }
    
            [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public string Category { get; set; }
    
            [SearchableField(IsFilterable = true, IsFacetable = true)]
            public string[] Tags { get; set; }
    
            [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public bool? ParkingIncluded { get; set; }
    
            [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public DateTimeOffset? LastRenovationDate { get; set; }
    
            [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public double? Rating { get; set; }
    
            [SearchableField]
            public Address Address { get; set; }
        }
    }
    

    Azure.Search.Documents クライアント ライブラリでは、SearchableFieldSimpleField を使用してフィールドの定義を効率化できます。 どちらも SearchField から派生したもので、コードの簡素化に役立つ可能性があります。

    • SimpleField は任意のデータ型にすることができます。また、常に検索不可能 (フルテキスト検索クエリでは無視される) で、取得可能 (非表示ではない) となります。 その他の属性は、既定ではオフですが、有効にすることができます。 SimpleField は、フィルター、ファセット、スコアリング プロファイルでのみ使用されるフィールドやドキュメント ID での使用が考えられます。 その場合は必ず、シナリオに必要な属性を適用してください (ドキュメント ID の IsKey = true など)。 詳細については、ソース コードの SimpleFieldAttribute.cs を参照してください。

    • SearchableField は文字列であることが必要です。常に検索可能で、取得可能となります。 その他の属性は、既定ではオフですが、有効にすることができます。 検索可能なタイプのフィールドであるため、同意語がサポートされるほか、アナライザーのプロパティがすべてサポートされます。 詳細については、ソース コードの SearchableFieldAttribute.cs を参照してください。

    基本 SearchField API を使用する場合も、そのいずれかのヘルパー モデルを使用する場合も、フィルター、ファセット、並べ替えの属性は明示的に有効にする必要があります。 たとえば、IsFilterableIsSortableIsFacetable の各属性は、上のサンプルのように明示的に指定する必要があります。

  3. 2 つ目の空のクラス定義をプロジェクトに追加します (Address.cs)。 このクラスに次のコードをコピーします。

    using Azure.Search.Documents.Indexes;
    
     namespace AzureSearch.Quickstart
     {
         public partial class Address
         {
             [SearchableField(IsFilterable = true)]
             public string StreetAddress { get; set; }
    
             [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
             public string City { get; set; }
    
             [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
             public string StateProvince { get; set; }
    
             [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
             public string PostalCode { get; set; }
    
             [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
             public string Country { get; set; }
         }
     }
    
  4. さらに 2 つのクラスを作成します。ToString() のオーバーライドの Hotel.Methods.csAddress.Methods.cs です。 これらのクラスは、コンソール出力に検索結果をレンダリングするために使用されます。 これらのクラスの内容はこの記事では提供されていませんが、GitHub のファイルからコードをコピーできます。

  5. Program.csSearchIndex オブジェクトを作成し、CreateIndex メソッドを呼び出して、検索サービスのインデックスを表現します。 インデックスには、指定されたフィールドでオートコンプリートを有効にするための SearchSuggester も含まれています。

     // Create hotels-quickstart index
     private static void CreateIndex(string indexName, SearchIndexClient adminClient)
     {
         FieldBuilder fieldBuilder = new FieldBuilder();
         var searchFields = fieldBuilder.Build(typeof(Hotel));
    
         var definition = new SearchIndex(indexName, searchFields);
    
         var suggester = new SearchSuggester("sg", new[] { "HotelName", "Category", "Address/City", "Address/StateProvince" });
         definition.Suggesters.Add(suggester);
    
         adminClient.CreateOrUpdateIndex(definition);
     }
    

ドキュメントを読み込む

Azure AI Search は、サービスに保存されているコンテンツを検索します。 この手順では、先ほど作成したホテル インデックスに準拠した JSON ドキュメントを読み込みます。

Azure AI Search では、検索ドキュメントは、インデックス作成への入力とクエリからの出力の両方であるデータ構造です。 外部データ ソースから取得するドキュメント入力には、データベース内の行、Blob storage 内の BLOB、ディスク上の JSON ドキュメントがあります。 この例では、手短な方法として、4 つのホテルの JSON ドキュメントをコード自体に埋め込みます。

ドキュメントをアップロードするときは、IndexDocumentsBatch オブジェクトを使用する必要があります。 IndexDocumentsBatch オブジェクトには、 Actionsのコレクションが含まれています。各オブジェクトには、Azure AICognitive Searchに実行するアクション (アップロード、マージ、削除、および mergeOrUpload) を示すドキュメントとプロパティが含まれています。

  1. Program.cs で、ドキュメントとインデックス アクションの配列を作成し、その配列を IndexDocumentsBatch に渡します。 以下のドキュメントは、Hotel クラスで定義されている hotels-quickstart インデックスに準拠しています。

    // Upload documents in a single Upload request.
    private static void UploadDocuments(SearchClient searchClient)
    {
        IndexDocumentsBatch<Hotel> batch = IndexDocumentsBatch.Create(
            IndexDocumentsAction.Upload(
                new Hotel()
                {
                    HotelId = "1",
                    HotelName = "Secret Point Motel",
                    Description = "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.",
                    DescriptionFr = "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de New York. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de New York l'une des villes les plus attractives et cosmopolites de l'Amérique.",
                    Category = "Boutique",
                    Tags = new[] { "pool", "air conditioning", "concierge" },
                    ParkingIncluded = false,
                    LastRenovationDate = new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero),
                    Rating = 3.6,
                    Address = new Address()
                    {
                        StreetAddress = "677 5th Ave",
                        City = "New York",
                        StateProvince = "NY",
                        PostalCode = "10022",
                        Country = "USA"
                    }
                }),
            IndexDocumentsAction.Upload(
                new Hotel()
                {
                    HotelId = "2",
                    HotelName = "Twin Dome Motel",
                    Description = "The hotel is situated in a  nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts.",
                    DescriptionFr = "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.",
                    Category = "Boutique",
                    Tags = new[] { "pool", "free wifi", "concierge" },
                    ParkingIncluded = false,
                    LastRenovationDate = new DateTimeOffset(1979, 2, 18, 0, 0, 0, TimeSpan.Zero),
                    Rating = 3.60,
                    Address = new Address()
                    {
                        StreetAddress = "140 University Town Center Dr",
                        City = "Sarasota",
                        StateProvince = "FL",
                        PostalCode = "34243",
                        Country = "USA"
                    }
                }),
            IndexDocumentsAction.Upload(
                new Hotel()
                {
                    HotelId = "3",
                    HotelName = "Triple Landscape Hotel",
                    Description = "The Hotel stands out for its gastronomic excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services.",
                    DescriptionFr = "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.",
                    Category = "Resort and Spa",
                    Tags = new[] { "air conditioning", "bar", "continental breakfast" },
                    ParkingIncluded = true,
                    LastRenovationDate = new DateTimeOffset(2015, 9, 20, 0, 0, 0, TimeSpan.Zero),
                    Rating = 4.80,
                    Address = new Address()
                    {
                        StreetAddress = "3393 Peachtree Rd",
                        City = "Atlanta",
                        StateProvince = "GA",
                        PostalCode = "30326",
                        Country = "USA"
                    }
                }),
            IndexDocumentsAction.Upload(
                new Hotel()
                {
                    HotelId = "4",
                    HotelName = "Sublime Cliff Hotel",
                    Description = "Sublime Cliff Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 1800 palace.",
                    DescriptionFr = "Le sublime Cliff Hotel est situé au coeur du centre historique de sublime dans un quartier extrêmement animé et vivant, à courte distance de marche des sites et monuments de la ville et est entouré par l'extraordinaire beauté des églises, des bâtiments, des commerces et Monuments. Sublime Cliff fait partie d'un Palace 1800 restauré avec amour.",
                    Category = "Boutique",
                    Tags = new[] { "concierge", "view", "24-hour front desk service" },
                    ParkingIncluded = true,
                    LastRenovationDate = new DateTimeOffset(1960, 2, 06, 0, 0, 0, TimeSpan.Zero),
                    Rating = 4.60,
                    Address = new Address()
                    {
                        StreetAddress = "7400 San Pedro Ave",
                        City = "San Antonio",
                        StateProvince = "TX",
                        PostalCode = "78216",
                        Country = "USA"
                    }
                })
            );
    
        try
        {
            IndexDocumentsResult result = searchClient.IndexDocuments(batch);
        }
        catch (Exception)
        {
            // If for some reason any documents are dropped during indexing, you can compensate by delaying and
            // retrying. This simple demo just logs the failed document keys and continues.
            Console.WriteLine("Failed to index some of the documents: {0}");
        }
    }
    

    IndexDocumentsBatch オブジェクトを初期化したら、SearchClient オブジェクトの IndexDocuments を呼び出すことによって、それをインデックスに送信することができます。

  2. Main() に次の行を追加します。 ドキュメントの読み込みは SearchClient を使用して実行されますが、この操作にはサービスに対する管理者権限も必要であり、通常、SearchIndexClient に関連付けられています。 この操作を設定する 1 つの方法として、SearchIndexClient (この例では adminClient) を介して SearchClient を取得します。

     SearchClient ingesterClient = adminClient.GetSearchClient(indexName);
    
     // Load documents
     Console.WriteLine("{0}", "Uploading documents...\n");
     UploadDocuments(ingesterClient);
    
  3. これはすべてのコマンドを連続して実行するコンソール アプリであるため、インデックス作成とクエリの間に 2 秒の待ち時間を追加します。

    // Wait 2 seconds for indexing to complete before starting queries (for demo and console-app purposes only)
    Console.WriteLine("Waiting for indexing...\n");
    System.Threading.Thread.Sleep(2000);
    

    2 秒の遅延により、非同期のインデックス作成を待ち、クエリの実行前にすべてのドキュメントのインデックスを作成できるようにしています。 通常、遅延のコーディングは、デモ、テスト、およびサンプル アプリケーションでのみ必要です。

インデックスを検索する

最初のドキュメントのインデックスが作成されるとすぐにクエリの結果を取得できますが、インデックスの実際のテストではすべてのドキュメントのインデックスが作成されるまで待つ必要があります。

このセクションでは、クエリ ロジックと結果の 2 つの機能を追加します。 クエリには、Search メソッドを使用します。 このメソッドは、検索テキスト (クエリ文字列) と他のオプションを受け取ります。

その結果は、SearchResults クラスによって表されます。

  1. Program.cs で、検索結果をコンソールに出力する WriteDocuments メソッドを作成します。

    // Write search results to console
    private static void WriteDocuments(SearchResults<Hotel> searchResults)
    {
        foreach (SearchResult<Hotel> result in searchResults.GetResults())
        {
            Console.WriteLine(result.Document);
        }
    
        Console.WriteLine();
    }
    
    private static void WriteDocuments(AutocompleteResults autoResults)
    {
        foreach (AutocompleteItem result in autoResults.Results)
        {
            Console.WriteLine(result.Text);
        }
    
        Console.WriteLine();
    }
    
  2. クエリを実行し、結果を返す RunQueries メソッドを作成します。 結果は、Hotel オブジェクトです。 このサンプルは、メソッド シグネチャと最初のクエリを示しています。 このクエリは、ドキュメントから選択されたフィールドを使用して結果を作成できる Select パラメーターを示しています。

    // Run queries, use WriteDocuments to print output
    private static void RunQueries(SearchClient srchclient)
    {
        SearchOptions options;
        SearchResults<Hotel> response;
    
        // Query 1
        Console.WriteLine("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n");
    
        options = new SearchOptions()
        {
            IncludeTotalCount = true,
            Filter = "",
            OrderBy = { "" }
        };
    
        options.Select.Add("HotelId");
        options.Select.Add("HotelName");
        options.Select.Add("Address/City");
    
        response = srchclient.Search<Hotel>("*", options);
        WriteDocuments(response);
    
  3. 2 つ目のクエリでは、語句を検索し、Rating が 4 を超えるドキュメントを選択するフィルターを追加したうえで、Rating の降順で並べ替えます。 フィルターは、インデックス内の IsFilterable フィールドに対して評価されるブール式です。 フィルター クエリでは、値は包含されるか除外されるかのどちらかです。 そのため、フィルター クエリに関連付けられている関連性スコアはありません。

    // Query 2
    Console.WriteLine("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n");
    
    options = new SearchOptions()
    {
        Filter = "Rating gt 4",
        OrderBy = { "Rating desc" }
    };
    
    options.Select.Add("HotelId");
    options.Select.Add("HotelName");
    options.Select.Add("Rating");
    
    response = srchclient.Search<Hotel>("hotels", options);
    WriteDocuments(response);
    
  4. 3 番目のクエリは、フルテキスト検索操作の範囲を特定のフィールドに設定するために使用する searchFields を示しています。

    // Query 3
    Console.WriteLine("Query #3: Limit search to specific fields (pool in Tags field)...\n");
    
    options = new SearchOptions()
    {
        SearchFields = { "Tags" }
    };
    
    options.Select.Add("HotelId");
    options.Select.Add("HotelName");
    options.Select.Add("Tags");
    
    response = srchclient.Search<Hotel>("pool", options);
    WriteDocuments(response);
    
  5. 4 番目のクエリは、ファセット ナビゲーション構造を構築するために使用できるファセットを示しています。

     // Query 4
     Console.WriteLine("Query #4: Facet on 'Category'...\n");
    
     options = new SearchOptions()
     {
         Filter = ""
     };
    
     options.Facets.Add("Category");
    
     options.Select.Add("HotelId");
     options.Select.Add("HotelName");
     options.Select.Add("Category");
    
     response = srchclient.Search<Hotel>("*", options);
     WriteDocuments(response);
    
  6. 5 番目のクエリでは、特定のドキュメントを返します。 ドキュメント検索は、結果セット内の OnClick イベントに対する一般的な応答です。

     // Query 5
     Console.WriteLine("Query #5: Look up a specific document...\n");
    
     Response<Hotel> lookupResponse;
     lookupResponse = srchclient.GetDocument<Hotel>("3");
    
     Console.WriteLine(lookupResponse.Value.HotelId);
    
  7. 最後のクエリは、オートコンプリートの構文を示しています。これは、インデックスで定義した suggester に関連付けられている sourceFields の 2 つの一致候補に解決される、"sa" という部分的なユーザー入力をシミュレートしています。

     // Query 6
     Console.WriteLine("Query #6: Call Autocomplete on HotelName that starts with 'sa'...\n");
    
     var autoresponse = srchclient.Autocomplete("sa", "sg");
     WriteDocuments(autoresponse);
    
  8. Main() に RunQueries を追加します。

    // Call the RunQueries method to invoke a series of queries
    Console.WriteLine("Starting queries...\n");
    RunQueries(srchclient);
    
    // End the program
    Console.WriteLine("{0}", "Complete. Press any key to end this program...\n");
    Console.ReadKey();
    

上記のクエリは、クエリで語句を照合する複数の方法 (フルテキスト検索、フィルター、オートコンプリート) を示しています。

フルテキスト検索とフィルターは、SearchClient.Search メソッドを使用して実行されます。 検索クエリは searchText 文字列で渡すことができます。一方、フィルター式は SearchOptions クラスの Filter プロパティで渡すことができます。 検索せずにフィルター処理を実行するには、Search メソッドの searchText パラメーターに "*" を渡します。 フィルター処理を行わずに検索するには、Filter プロパティを未設定のままにするか、SearchOptions インスタンスを 1 つも渡さないようにします。

プログラムを実行する

F5 キーを押して、アプリをリビルドし、プログラム全体を実行します。

出力には、Console.WriteLine からのメッセージに加え、クエリの情報と結果が表示されます。

リソースをクリーンアップする

独自のサブスクリプションを使用している場合は、プロジェクトの最後に、作成したリソースがまだ必要かどうかを確認してください。 リソースを実行したままにすると、お金がかかる場合があります。 リソースは個別に削除することも、リソース グループを削除してリソースのセット全体を削除することもできます。

ポータルの左側のナビゲーション ウィンドウにある [All resources](すべてのリソース) または [Resource groups](リソース グループ) リンクを使って、リソースを検索および管理できます。

無料サービスを使っている場合は、3 つのインデックス、インデクサー、およびデータソースに制限されることに注意してください。 ポータルで個別の項目を削除して、制限を超えないようにすることができます。

次のステップ

このクイックスタートでは、インデックスの作成、それとドキュメントの読み込み、およびクエリの実行を行うという一連のタスクに取り組みました。 さまざまな段階で、読みやすく、理解しやすいように、手短な方法として、コードを簡略化しました。 基本的な概念を理解したら、Web アプリで Azure AI Search API を呼び出すチュートリアルをお試しください。