Partager via


Démarrage rapide : Recherche en texte intégral avec les SDK Azure

Découvrez comment utiliser la bibliothèque cliente Azure.Search.Documents afin de créer, charger et interroger un index de recherche à l’aide d’exemples de données pour la recherche en texte intégral. La recherche en texte intégral utilise Apache Lucene pour l’indexation et les requêtes, ainsi qu’un algorithme de classement BM25 pour le scoring des résultats.

Ce démarrage rapide crée et interroge un petit index de démarrage rapide pour hôtels qui contient des données sur quatre hôtels.

Conseil

Vous pouvez télécharger le code source pour commencer avec un projet terminé ou suivre les étapes décrites dans cet article pour créer votre propre projet.

Prérequis

  • Un abonnement Azure actif - En créer un gratuitement
  • Un service de recherche Azure AI. Créez un service si vous n’en avez pas. Vous pouvez utiliser le niveau gratuit dans le cadre de ce démarrage rapide.

Prérequis pour Microsoft Entra ID

Pour l’authentification sans clé recommandée avec Microsoft Entra ID, vous devez effectuer les tâches suivantes :

  • Installez l’interface Azure CLI utilisée pour l’authentification sans clé avec Microsoft Entra ID.
  • Attribuez Search Service Contributor et Search Index Data Contributor des rôles à votre compte d’utilisateur. Vous pouvez attribuer des rôles dans le Portail Azure sous Contrôle d’accès (IAM)>Ajouter une attribution de rôle. Pour plus d’informations, consultez Se connecter à la Recherche Azure AI à l’aide des rôles.

Récupérer des informations sur les ressources

Vous devez récupérer les informations suivantes pour authentifier votre application auprès de votre ressource Recherche Azure AI :

Nom de la variable Valeur
SEARCH_API_ENDPOINT Cette valeur se trouve dans le portail Azure. Sélectionnez votre service de recherche, puis dans le menu de gauche, sélectionnez Vue d’ensemble. La valeur d’URL sous Essentials est le point de terminaison dont vous avez besoin. Voici un exemple de point de terminaison : https://mydemo.search.windows.net.

En savoir plus sur l’authentification sans clé et la définition de variables d’environnement.

Configurer

  1. Créez un dossier full-text-quickstart pour contenir l’application et ouvrez Visual Studio Code dans ce dossier avec la commande suivante :

    mkdir full-text-quickstart && cd full-text-quickstart
    
  2. Créez une application console avec la commande suivante :

    dotnet new console
    
  3. Installez la bibliothèque de client Recherche Azure AI (Azure.Search.Documents) pour .NET avec :

    dotnet add package Azure.Search.Documents
    
  4. Pour l’authentification sans clé recommandée avec Microsoft Entra ID, installez le package Azure.Identity avec :

    dotnet add package Azure.Identity
    
  5. Pour l’authentification sans clé recommandée avec Microsoft Entra ID, connectez-vous à Azure avec la commande suivante :

    az login
    

Créer, charger et interroger un index de recherche

Dans la section de configuration précédente, vous avez créé une application console et installé la bibliothèque de client Recherche Azure AI.

Dans cette section, vous ajoutez du code pour créer un index de recherche, chargez-le avec des documents et exécutez des requêtes. Vous exécutez le programme pour afficher les résultats dans la console. Pour obtenir une explication détaillée du code, consultez la section expliquant le code.

L’exemple de code de ce guide de démarrage rapide utilise Microsoft Entra pour l’authentification sans clé recommandée. Si vous préférez utiliser une clé API, vous pouvez remplacer l’objet DefaultAzureCredential par un objet AzureKeyCredential.

Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");
DefaultAzureCredential credential = new();
  1. Dans Program.cs, collez le code suivant. Modifiez les variables serviceName et apiKey avec votre nom de votre service de recherche et votre clé API d’administration.

    using System;
    using Azure;
    using Azure.Identity;
    using Azure.Search.Documents;
    using Azure.Search.Documents.Indexes;
    using Azure.Search.Documents.Indexes.Models;
    using Azure.Search.Documents.Models;
    
    namespace AzureSearch.Quickstart
    
    {
        class Program
        {
            static void Main(string[] args)
            {    
                // Your search service endpoint
                Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");
    
                // Use the recommended keyless credential instead of the AzureKeyCredential credential.
                DefaultAzureCredential credential = new();
                //AzureKeyCredential credential = new AzureKeyCredential("Your search service admin key");
    
                // Create a SearchIndexClient to send create/delete index commands
                SearchIndexClient searchIndexClient = new SearchIndexClient(serviceEndpoint, credential);
    
                // Create a SearchClient to load and query documents
                string indexName = "hotels-quickstart";
                SearchClient searchClient = new SearchClient(serviceEndpoint, indexName, credential);
    
                // Delete index if it exists
                Console.WriteLine("{0}", "Deleting index...\n");
                DeleteIndexIfExists(indexName, searchIndexClient);
    
                // Create index
                Console.WriteLine("{0}", "Creating index...\n");
                CreateIndex(indexName, searchIndexClient);
    
                SearchClient ingesterClient = searchIndexClient.GetSearchClient(indexName);
    
                // Load documents
                Console.WriteLine("{0}", "Uploading documents...\n");
                UploadDocuments(ingesterClient);
    
                // Wait 2 secondsfor indexing to complete before starting queries (for demo and console-app purposes only)
                Console.WriteLine("Waiting for indexing...\n");
                System.Threading.Thread.Sleep(2000);
    
                // Call the RunQueries method to invoke a series of queries
                Console.WriteLine("Starting queries...\n");
                RunQueries(searchClient);
    
                // End the program
                Console.WriteLine("{0}", "Complete. Press any key to end this program...\n");
                Console.ReadKey();
            }
    
            // Delete the hotels-quickstart index to reuse its name
            private static void DeleteIndexIfExists(string indexName, SearchIndexClient searchIndexClient)
            {
                searchIndexClient.GetIndexNames();
                {
                    searchIndexClient.DeleteIndex(indexName);
                }
            }
            // Create hotels-quickstart index
            private static void CreateIndex(string indexName, SearchIndexClient searchIndexClient)
            {
                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);
    
                searchIndexClient.CreateOrUpdateIndex(definition);
            }
    
            // 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}");
                }
            }
    
            // Run queries, use WriteDocuments to print output
            private static void RunQueries(SearchClient searchClient)
            {
                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("Rating");
    
                response = searchClient.Search<Hotel>("*", options);
                WriteDocuments(response);
    
                // 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 = searchClient.Search<Hotel>("hotels", options);
                WriteDocuments(response);
    
                // 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 = searchClient.Search<Hotel>("pool", options);
                WriteDocuments(response);
    
                // Query 4 - Use Facets to return a faceted navigation structure for a given query
                // Filters are typically used with facets to narrow results on OnClick events
                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 = searchClient.Search<Hotel>("*", options);
                WriteDocuments(response);
    
                // Query 5
                Console.WriteLine("Query #5: Look up a specific document...\n");
    
                Response<Hotel> lookupResponse;
                lookupResponse = searchClient.GetDocument<Hotel>("3");
    
                Console.WriteLine(lookupResponse.Value.HotelId);
    
    
                // Query 6
                Console.WriteLine("Query #6: Call Autocomplete on HotelName...\n");
    
                var autoresponse = searchClient.Autocomplete("sa", "sg");
                WriteDocuments(autoresponse);
    
            }
    
            // 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. Dans le même dossier, créez un fichier nommé Hotel.cs et collez le code suivant. Ce code définit la structure d’un document d’hôtel.

    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; }
        }
    }
    
  3. Créez un fichier nommé Hotel.cs et collez le code suivant pour définir la structure d’un document hôtel. Les attributs du champ déterminent la façon dont il est utilisé dans une application. Par exemple, l’attribut IsFilterable doit être assigné à chaque champ qui prend en charge une expression de filtre.

    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; }
        }
    }
    
  4. Créez un fichier nommé Address.cs et collez le code suivant pour définir la structure d’un document d’adresse.

    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; }
        }
    }
    
  5. Créez un fichier nommé Hotel.Methods.cs et collez le code suivant pour définir un remplacement ToString() pour la classe Hotel.

    using System;
    using System.Text;
    
    namespace AzureSearch.Quickstart
    {
        public partial class Hotel
        {
            public override string ToString()
            {
                var builder = new StringBuilder();
    
                if (!String.IsNullOrEmpty(HotelId))
                {
                    builder.AppendFormat("HotelId: {0}\n", HotelId);
                }
    
                if (!String.IsNullOrEmpty(HotelName))
                {
                    builder.AppendFormat("Name: {0}\n", HotelName);
                }
    
                if (!String.IsNullOrEmpty(Description))
                {
                    builder.AppendFormat("Description: {0}\n", Description);
                }
    
                if (!String.IsNullOrEmpty(DescriptionFr))
                {
                    builder.AppendFormat("Description (French): {0}\n", DescriptionFr);
                }
    
                if (!String.IsNullOrEmpty(Category))
                {
                    builder.AppendFormat("Category: {0}\n", Category);
                }
    
                if (Tags != null && Tags.Length > 0)
                {
                    builder.AppendFormat("Tags: [ {0} ]\n", String.Join(", ", Tags));
                }
    
                if (ParkingIncluded.HasValue)
                {
                    builder.AppendFormat("Parking included: {0}\n", ParkingIncluded.Value ? "yes" : "no");
                }
    
                if (LastRenovationDate.HasValue)
                {
                    builder.AppendFormat("Last renovated on: {0}\n", LastRenovationDate);
                }
    
                if (Rating.HasValue)
                {
                    builder.AppendFormat("Rating: {0}\n", Rating);
                }
    
                if (Address != null && !Address.IsEmpty)
                {
                    builder.AppendFormat("Address: \n{0}\n", Address.ToString());
                }
    
                return builder.ToString();
            }
        }
    }
    
  6. Créez un fichier nommé Address.Methods.cs et collez le code suivant pour définir un remplacement ToString() pour la classe Address.

    using System;
    using System.Text;
    using System.Text.Json.Serialization;
    
    namespace AzureSearch.Quickstart
    {
        public partial class Address
        {
            public override string ToString()
            {
                var builder = new StringBuilder();
    
                if (!IsEmpty)
                {
                    builder.AppendFormat("{0}\n{1}, {2} {3}\n{4}", StreetAddress, City, StateProvince, PostalCode, Country);
                }
    
                return builder.ToString();
            }
    
            [JsonIgnore]
            public bool IsEmpty => String.IsNullOrEmpty(StreetAddress) &&
                                   String.IsNullOrEmpty(City) &&
                                   String.IsNullOrEmpty(StateProvince) &&
                                   String.IsNullOrEmpty(PostalCode) &&
                                   String.IsNullOrEmpty(Country);
        }
    }
    
  7. Compilez et exécutez l’application avec la commande suivante :

    dotnet run
    

La sortie comprend des messages de la méthode Console.WriteLine, avec en plus des informations et des résultats de requête.

Explication du code

Dans les sections précédentes, vous avez créé une nouvelle application console et installé la bibliothèque de client Recherche Azure AI. Vous avez ajouté du code pour créer un index de recherche, le charger avec des documents et exécuter des requêtes. Vous avez exécuté le programme pour afficher les résultats dans la console.

Dans cette section, nous expliquons le code que vous avez ajouté à l’application console.

Créer un client de recherche

Dans Program.cs, vous avez créé deux clients :

Les deux clients ont besoin du point de terminaison de service de recherche et des informations d’identification décrits précédemment dans la section informations sur la ressource.

L’exemple de code de ce guide de démarrage rapide utilise Microsoft Entra pour l’authentification sans clé recommandée. Si vous préférez utiliser une clé API, vous pouvez remplacer l’objet DefaultAzureCredential par un objet AzureKeyCredential.

Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");
DefaultAzureCredential credential = new();
static void Main(string[] args)
{
    // Your search service endpoint
    Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");

    // Use the recommended keyless credential instead of the AzureKeyCredential credential.
    DefaultAzureCredential credential = new();
    //AzureKeyCredential credential = new AzureKeyCredential("Your search service admin key");

    // Create a SearchIndexClient to send create/delete index commands
    SearchIndexClient searchIndexClient = new SearchIndexClient(serviceEndpoint, credential);

    // Create a SearchClient to load and query documents
    string indexName = "hotels-quickstart";
    SearchClient searchClient = new SearchClient(serviceEndpoint, indexName, credential);
    
    // REDACTED FOR BREVITY . . . 
}

Création d'un index

Ce guide de démarrage rapide crée un index Hotels que vous allez charger avec des données sur des hôtels et sur lequel vous allez effectuer des requêtes. Dans cette étape, définissez les champs de l’index. Chaque définition de champ comprend un nom, un type de données et des attributs qui déterminent la façon dont le champ est utilisé.

Dans cet exemple, des méthodes synchrones de la bibliothèque Azure.Search.Documents sont utilisées par souci de simplicité et de lisibilité. En revanche, dans des scénarios de production, vous devez utiliser des méthodes asynchrones pour maintenir la scalabilité et la réactivité de votre application. Par exemple, vous pouvez utiliser CreateIndexAsync au lieu de CreateIndex.

Définir les structures

Vous avez créé deux classes d’assistance, Hotel.cs et Address.cs, pour définir la structure d’un document d’hôtel et son adresse. La classe Hotel comprend des champs pour un ID d’hôtel, nom, description, catégorie, étiquettes, parking, date de rénovation, évaluation et adresse. La classe Address inclut des champs pour l’adresse postale, la ville, l’état/la province, le code postal et le pays/région.

Dans la bibliothèque de client Azure.Search.Documents, vous pouvez utiliser SearchableField et SimpleField pour simplifier les définitions des champs. Les deux sont des dérivés d’un SearchField et peuvent potentiellement simplifier votre code :

  • SimpleField peut être n’importe quel type de données, ne peut jamais faire l’objet d’une recherche (il est ignoré pour les requêtes de recherche de texte intégral) et peut être récupéré (il n’est pas masqué). Les autres attributs sont désactivés par défaut, mais peuvent être activés. Vous pouvez utiliser un SimpleField pour les ID de document ou les champs utilisés seulement dans des filtres, des facettes ou des profils de scoring. Dans ce cas, veillez à appliquer tous les attributs nécessaires pour le scénario, comme IsKey = true pour un ID de document. Pour plus d’informations, consultez SimpleFieldAttribute.cs dans le code source.

  • SearchableField doit être une chaîne, et peut toujours faire l’objet d’une recherche et d’une récupération. Les autres attributs sont désactivés par défaut, mais peuvent être activés. Comme ce type de champ peut faire l’objet d’une recherche, il prend en charge les synonymes et l’ensemble complet des propriétés de l’analyseur. Pour plus d’informations, consultez SearchableFieldAttribute.cs dans le code source.

Que vous utilisiez l’API SearchField de base ou un des modèles d’assistance, vous devez activer explicitement les attributs de filtre, de facette et de tri. Par exemple, IsFilterable, IsSortable et IsFacetable doivent être explicitement attribués, comme illustré dans l’exemple précédent.

Créer l’index de recherche

Dans Program.cs, créez un objet SearchIndex, puis appelez la méthode CreateIndex pour exprimer l’index dans votre service de recherche. L’index comprend également un SearchSuggester pour activer l’autocomplétion sur les champs spécifiés.

// Create hotels-quickstart index
private static void CreateIndex(string indexName, SearchIndexClient searchIndexClient)
{
    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);

    searchIndexClient.CreateOrUpdateIndex(definition);
}

Chargement de documents

La recherche Azure AI effectue des recherches parmi les contenus stockés dans le service. Au cours de cette étape, vous allez charger des documents JSON conformes à l’index de l’hôtel que vous avez créé.

Dans la recherche Azure AI, les documents de recherche sont des structures de données qui sont à la fois des entrées pour l’indexation et des sorties de requêtes. Selon une source de données externe, les entrées de documents peuvent être des lignes dans une base de données, des objets blob dans le Stockage Blob ou des documents JSON sur le disque. Dans cet exemple, nous prenons un raccourci et incorporons des documents JSON pour quatre hôtels dans le code lui-même.

Lors du chargement de documents, vous devez utiliser un objet IndexDocumentsBatch. Un objet IndexDocumentsBatch contient une collection d’Actions, chacune contenant un document et une propriété qui indiquent à la recherche Azure AI l’action à effectuer (upload, merge, delete et mergeOrUpload).

Dans Program.cs, créez un tableau des documents et des actions d’index, puis passez le tableau à IndexDocumentsBatch. Les documents suivants sont conformes à l’index hotels-quickstart, tel que défini par la classe hotel.

// 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 = "Stay-Kay City Hotel",
                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"
                }
            }),
        // REDACTED FOR BREVITY
}

Une fois que vous avez initialisé l’objet IndexDocumentsBatch, vous pouvez l’envoyer à l’index en appelant IndexDocuments sur votre objet SearchClient.

Vous allez charger des documents en utilisant SearchClient dans Main(), mais l’opération nécessite également des droits d’administrateur sur le service, généralement associé à SearchIndexClient. Une façon de configurer cette opération consiste à obtenir SearchClient via SearchIndexClient (searchIndexClient dans cet exemple).

SearchClient ingesterClient = searchIndexClient.GetSearchClient(indexName);

// Load documents
Console.WriteLine("{0}", "Uploading documents...\n");
UploadDocuments(ingesterClient);

Étant donné qu’il s’agit d’une application console qui exécute toutes les commandes de manière séquentielle, nous allons ajouter un délai d’attente de 2 secondes entre l’indexation et les requêtes.

// 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);

Le retard de 2 secondes compense l’indexation, qui est asynchrone, afin que tous les documents puissent être indexés avant l’exécution des requêtes. Le codage dans un retard n’est nécessaire que dans les démonstrations, les tests et les exemples d’applications.

Rechercher dans un index

Vous pouvez obtenir les résultats de la requête dès que le premier document est indexé, mais les tests réels de votre index doivent attendre que tous les documents soient indexés.

Cette section ajoute deux éléments de fonctionnalité : logique de requête et résultats. Pour les requêtes, utilisez la méthode Search. Cette méthode prend le texte de recherche (la chaîne de requête) ainsi que d’autres options.

La classe SearchResults représente les résultats.

Dans Program.cs, la méthode WriteDocuments imprime les résultats de recherche dans la console.

// 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();
}

Exemple de requête 1

La méthode RunQueries exécute des requêtes et retourne des résultats. Les résultats sont des objets Hotel. Cet exemple montre la signature de la méthode et la première requête. Cette requête montre le paramètre Select qui vous permet de composer le résultat à l’aide de champs sélectionnés dans le document.

// Run queries, use WriteDocuments to print output
private static void RunQueries(SearchClient searchClient)
{
    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 = searchClient.Search<Hotel>("*", options);
    WriteDocuments(response);
    // REDACTED FOR BREVITY
}

Exemple de requête 2

Dans la deuxième requête, recherchez un terme, ajoutez un filtre qui sélectionne les documents dont l’évaluation (Rating) est supérieure à 4, puis triez par Rating dans l’ordre décroissant. Un filtre est une expression booléenne évaluée sur des champs IsFilterable dans un index. Les requêtes de filtre incluent ou excluent des valeurs. Par conséquent, aucun score de pertinence n’est associé à une requête de filtre.

// 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 = searchClient.Search<Hotel>("hotels", options);
WriteDocuments(response);

Exemple de requête 3

La troisième requête illustre l’utilisation de searchFields, qui sert à limiter l’étendue d’une opération de recherche en texte intégral à des champs spécifiques.

// 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 = searchClient.Search<Hotel>("pool", options);
WriteDocuments(response);

Exemple de requête 4

La quatrième requête illustre les facets, que vous pouvez utiliser pour créer une structure de navigation par facettes.

// 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 = searchClient.Search<Hotel>("*", options);
WriteDocuments(response);

Exemple de requête 5

La cinquième requête retourne un document spécifique. Une recherche de document est une réponse classique à l’événement OnClick dans un jeu de résultats.

// Query 5
Console.WriteLine("Query #5: Look up a specific document...\n");

Response<Hotel> lookupResponse;
lookupResponse = searchClient.GetDocument<Hotel>("3");

Console.WriteLine(lookupResponse.Value.HotelId);

Exemple de requête 6

La dernière requête affiche la syntaxe pour l’autocomplétion ; elle simule une entrée utilisateur partielle, sa, qui est résolue en deux correspondances possibles dans les sourceFields associés au suggesteur que vous avez défini dans l’index.

// Query 6
Console.WriteLine("Query #6: Call Autocomplete on HotelName that starts with 'sa'...\n");

var autoresponse = searchClient.Autocomplete("sa", "sg");
WriteDocuments(autoresponse);

Résumé des requêtes

Les requêtes précédentes illustrent plusieurs manières d’établir des correspondances entre des termes dans une requête : recherche en texte intégral, filtres et autocomplétion.

La recherche en texte intégral et les filtres sont exécutés à l’aide de la méthode SearchClient.Search. Une requête de recherche peut être transmise dans la chaîne searchText, tandis qu’une expression de filtre peut être transmise dans la propriété Filter de la classe SearchOptions. Pour filtrer sans effectuer de recherche, transmettez simplement "*" pour le paramètre searchText de la méthode Search. Pour effectuer une recherche sans filtrage, ne définissez pas la propriété Filter et ne transmettez aucune instance SearchOptions.

Découvrez comment utiliser la bibliothèque cliente Azure.Search.Documents afin de créer, charger et interroger un index de recherche à l’aide d’exemples de données pour la recherche en texte intégral. La recherche en texte intégral utilise Apache Lucene pour l’indexation et les requêtes, ainsi qu’un algorithme de classement BM25 pour le scoring des résultats.

Ce démarrage rapide crée et interroge un petit index de démarrage rapide pour hôtels qui contient des données sur quatre hôtels.

Conseil

Vous pouvez télécharger le code source pour commencer avec un projet terminé ou suivre les étapes décrites dans cet article pour créer votre propre projet.

Prérequis

  • Un abonnement Azure actif - En créer un gratuitement
  • Un service de recherche Azure AI. Créez un service si vous n’en avez pas. Vous pouvez utiliser le niveau gratuit dans le cadre de ce démarrage rapide.

Prérequis pour Microsoft Entra ID

Pour l’authentification sans clé recommandée avec Microsoft Entra ID, vous devez effectuer les tâches suivantes :

  • Installez l’interface Azure CLI utilisée pour l’authentification sans clé avec Microsoft Entra ID.
  • Attribuez Search Service Contributor et Search Index Data Contributor des rôles à votre compte d’utilisateur. Vous pouvez attribuer des rôles dans le Portail Azure sous Contrôle d’accès (IAM)>Ajouter une attribution de rôle. Pour plus d’informations, consultez Se connecter à la Recherche Azure AI à l’aide des rôles.

Récupérer des informations sur les ressources

Vous devez récupérer les informations suivantes pour authentifier votre application auprès de votre ressource Recherche Azure AI :

Nom de la variable Valeur
SEARCH_API_ENDPOINT Cette valeur se trouve dans le portail Azure. Sélectionnez votre service de recherche, puis dans le menu de gauche, sélectionnez Vue d’ensemble. La valeur d’URL sous Essentials est le point de terminaison dont vous avez besoin. Voici un exemple de point de terminaison : https://mydemo.search.windows.net.

En savoir plus sur l’authentification sans clé et la définition de variables d’environnement.

Configurer

L’exemple de ce guide de démarrage rapide fonctionne avec le runtime Java. Installez un kit de développement Java tel que Azul Zulu OpenJDK. La build Microsoft d’OpenJDK ou le JDK de votre choix doivent également fonctionner.

  1. Installez Apache Maven. Exécutez ensuite mvn -v pour confirmer la réussite de l’installation.

  2. Créez un fichier pom.xml à la racine de votre projet, puis copiez-y le code suivant :

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>azure.search.sample</groupId>
        <artifactId>azuresearchquickstart</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <build>
            <sourceDirectory>src</sourceDirectory>
            <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                <source>1.8</source>
                <target>1.8</target>
                </configuration>
            </plugin>
            </plugins>
        </build>
        <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.11</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>com.azure</groupId>
                <artifactId>azure-search-documents</artifactId>
                <version>11.7.3</version>
            </dependency>
            <dependency>
                <groupId>com.azure</groupId>
                <artifactId>azure-core</artifactId>
                <version>1.53.0</version>
            </dependency>
            <dependency>
                <groupId>com.azure</groupId>
                <artifactId>azure-identity</artifactId>
                <version>1.15.1</version>
            </dependency>
        </dependencies>
    </project>
    
  3. Installez les dépendances, notamment la bibliothèque de client Recherche Azure AI (Azure.Search.Documents) pour Java et la bibliothèque de client Azure Identity pour Java avec :

    mvn clean dependency:copy-dependencies
    
  4. Pour l’authentification sans clé recommandée avec Microsoft Entra ID, connectez-vous à Azure avec la commande suivante :

    az login
    

Créer, charger et interroger un index de recherche

Dans la section de configuration précédente, vous avez installé la bibliothèque de client Recherche Azure AI et d’autres dépendances.

Dans cette section, vous ajoutez du code pour créer un index de recherche, chargez-le avec des documents et exécutez des requêtes. Vous exécutez le programme pour afficher les résultats dans la console. Pour obtenir une explication détaillée du code, consultez la section expliquant le code.

L’exemple de code de ce guide de démarrage rapide utilise Microsoft Entra pour l’authentification sans clé recommandée. Si vous préférez utiliser une clé API, vous pouvez remplacer l’objet DefaultAzureCredential par un objet AzureKeyCredential.

String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
  1. Créez un fichier nommé App.java et collez le code suivant dans App.java :

    import java.util.Arrays;
    import java.util.ArrayList;
    import java.time.OffsetDateTime;
    import java.time.ZoneOffset;
    import java.time.LocalDateTime;
    import java.time.LocalDate;
    import java.time.LocalTime;
    import com.azure.core.util.Configuration;
    import com.azure.core.util.Context;
    import com.azure.identity.DefaultAzureCredential;
    import com.azure.identity.DefaultAzureCredentialBuilder;
    import com.azure.search.documents.SearchClient;
    import com.azure.search.documents.SearchClientBuilder;
    import com.azure.search.documents.indexes.SearchIndexClient;
    import com.azure.search.documents.indexes.SearchIndexClientBuilder;
    import com.azure.search.documents.indexes.models.IndexDocumentsBatch;
    import com.azure.search.documents.models.SearchOptions;
    import com.azure.search.documents.indexes.models.SearchIndex;
    import com.azure.search.documents.indexes.models.SearchSuggester;
    import com.azure.search.documents.util.AutocompletePagedIterable;
    import com.azure.search.documents.util.SearchPagedIterable;
    
    public class App {
    
        public static void main(String[] args) {
            // Your search service endpoint
            "https://<Put your search service NAME here>.search.windows.net/";
    
            // Use the recommended keyless credential instead of the AzureKeyCredential credential.
            DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
            //AzureKeyCredential credential = new AzureKeyCredential("<Your search service admin key>");
    
            // Create a SearchIndexClient to send create/delete index commands
            SearchIndexClient searchIndexClient = new SearchIndexClientBuilder()
                .endpoint(searchServiceEndpoint)
                .credential(credential)
                .buildClient();
    
            // Create a SearchClient to load and query documents
            String indexName = "hotels-quickstart-java";
            SearchClient searchClient = new SearchClientBuilder()
                .endpoint(searchServiceEndpoint)
                .credential(credential)
                .indexName(indexName)
                .buildClient();
    
            // Create Search Index for Hotel model
            searchIndexClient.createOrUpdateIndex(
                new SearchIndex(indexName, SearchIndexClient.buildSearchFields(Hotel.class, null))
                .setSuggesters(new SearchSuggester("sg", Arrays.asList("HotelName"))));
    
            // Upload sample hotel documents to the Search Index
            uploadDocuments(searchClient);
    
            // Wait 2 seconds for indexing to complete before starting queries (for demo and console-app purposes only)
            System.out.println("Waiting for indexing...\n");
            try
            {
                Thread.sleep(2000);
            }
            catch (InterruptedException e)
            {
            }
    
            // Call the RunQueries method to invoke a series of queries
            System.out.println("Starting queries...\n");
            RunQueries(searchClient);
    
            // End the program
            System.out.println("Complete.\n");
        }
    
        // Upload documents in a single Upload request.
        private static void uploadDocuments(SearchClient searchClient)
        {
            var hotelList = new ArrayList<Hotel>();
    
            var hotel = new Hotel();
            hotel.hotelId = "1";
            hotel.hotelName = "Stay-Kay City Hotel";
            hotel.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.";
            hotel.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.";
            hotel.category = "Boutique";
            hotel.tags = new String[] { "pool", "air conditioning", "concierge" };
            hotel.parkingIncluded = false;
            hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(1970, 1, 18), LocalTime.of(0, 0)), ZoneOffset.UTC);
            hotel.rating = 3.6;
            hotel.address = new Address();
            hotel.address.streetAddress = "677 5th Ave";
            hotel.address.city = "New York";
            hotel.address.stateProvince = "NY";
            hotel.address.postalCode = "10022";
            hotel.address.country = "USA";
            hotelList.add(hotel);
    
            hotel = new Hotel();
            hotel.hotelId = "2";
            hotel.hotelName = "Old Century Hotel";
            hotel.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.";
            hotel.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.";
            hotel.category = "Boutique";
            hotel.tags = new String[] { "pool", "free wifi", "concierge" };
            hotel.parkingIncluded = false;
            hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(1979, 2, 18), LocalTime.of(0, 0)), ZoneOffset.UTC);
            hotel.rating = 3.60;
            hotel.address = new Address();
            hotel.address.streetAddress = "140 University Town Center Dr";
            hotel.address.city = "Sarasota";
            hotel.address.stateProvince = "FL";
            hotel.address.postalCode = "34243";
            hotel.address.country = "USA";
            hotelList.add(hotel);
    
            hotel = new Hotel();
            hotel.hotelId = "3";
            hotel.hotelName = "Gastronomic Landscape Hotel";
            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.";
            hotel.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.";
            hotel.category = "Resort and Spa";
            hotel.tags = new String[] { "air conditioning", "bar", "continental breakfast" };
            hotel.parkingIncluded = true;
            hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(2015, 9, 20), LocalTime.of(0, 0)), ZoneOffset.UTC);
            hotel.rating = 4.80;
            hotel.address = new Address();
            hotel.address.streetAddress = "3393 Peachtree Rd";
            hotel.address.city = "Atlanta";
            hotel.address.stateProvince = "GA";
            hotel.address.postalCode = "30326";
            hotel.address.country = "USA";
            hotelList.add(hotel);
    
            hotel = new Hotel();
            hotel.hotelId = "4";
            hotel.hotelName = "Sublime Palace Hotel";
            hotel.description = "Sublime Palace  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 Palace is part of a lovingly restored 1800 palace.";
            hotel.descriptionFr = "Le Sublime Palace 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 Palace fait partie d'un Palace 1800 restauré avec amour.";
            hotel.category = "Boutique";
            hotel.tags = new String[] { "concierge", "view", "24-hour front desk service" };
            hotel.parkingIncluded = true;
            hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(1960, 2, 06), LocalTime.of(0, 0)), ZoneOffset.UTC);
            hotel.rating = 4.60;
            hotel.address = new Address();
            hotel.address.streetAddress = "7400 San Pedro Ave";
            hotel.address.city = "San Antonio";
            hotel.address.stateProvince = "TX";
            hotel.address.postalCode = "78216";
            hotel.address.country = "USA";
            hotelList.add(hotel);
    
            var batch = new IndexDocumentsBatch<Hotel>();
            batch.addMergeOrUploadActions(hotelList);
            try
            {
                searchClient.indexDocuments(batch);
            }
            catch (Exception e)
            {
                e.printStackTrace();
                // If for some reason any documents are dropped during indexing, you can compensate by delaying and
                // retrying. This simple demo just logs failure and continues
                System.err.println("Failed to index some of the documents");
            }
        }
    
        // Write search results to console
        private static void WriteSearchResults(SearchPagedIterable searchResults)
        {
            searchResults.iterator().forEachRemaining(result ->
            {
                Hotel hotel = result.getDocument(Hotel.class);
                System.out.println(hotel);
            });
    
            System.out.println();
        }
    
        // Write autocomplete results to console
        private static void WriteAutocompleteResults(AutocompletePagedIterable autocompleteResults)
        {
            autocompleteResults.iterator().forEachRemaining(result ->
            {
                String text = result.getText();
                System.out.println(text);
            });
    
            System.out.println();
        }
    
        // Run queries, use WriteDocuments to print output
        private static void RunQueries(SearchClient searchClient)
        {
            // Query 1
            System.out.println("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n");
    
            SearchOptions options = new SearchOptions();
            options.setIncludeTotalCount(true);
            options.setFilter("");
            options.setOrderBy("");
            options.setSelect("HotelId", "HotelName", "Address/City");
    
            WriteSearchResults(searchClient.search("*", options, Context.NONE));
    
            // Query 2
            System.out.println("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n");
    
            options = new SearchOptions();
            options.setFilter("Rating gt 4");
            options.setOrderBy("Rating desc");
            options.setSelect("HotelId", "HotelName", "Rating");
    
            WriteSearchResults(searchClient.search("hotels", options, Context.NONE));
    
            // Query 3
            System.out.println("Query #3: Limit search to specific fields (pool in Tags field)...\n");
    
            options = new SearchOptions();
            options.setSearchFields("Tags");
    
            options.setSelect("HotelId", "HotelName", "Tags");
    
            WriteSearchResults(searchClient.search("pool", options, Context.NONE));
    
            // Query 4
            System.out.println("Query #4: Facet on 'Category'...\n");
    
            options = new SearchOptions();
            options.setFilter("");
            options.setFacets("Category");
            options.setSelect("HotelId", "HotelName", "Category");
    
            WriteSearchResults(searchClient.search("*", options, Context.NONE));
    
            // Query 5
            System.out.println("Query #5: Look up a specific document...\n");
    
            Hotel lookupResponse = searchClient.getDocument("3", Hotel.class);
            System.out.println(lookupResponse.hotelId);
            System.out.println();
    
             // Query 6
            System.out.println("Query #6: Call Autocomplete on HotelName that starts with 's'...\n");
    
            WriteAutocompleteResults(searchClient.autocomplete("s", "sg"));
        }
    }
    
  2. Créez un fichier nommé Hotel.java et collez le code suivant dans Hotel.java :

    import com.azure.search.documents.indexes.SearchableField;
    import com.azure.search.documents.indexes.SimpleField;
    import com.fasterxml.jackson.annotation.JsonInclude;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.annotation.JsonInclude.Include;
    
    import java.time.OffsetDateTime;
    
    /**
     * Model class representing a hotel.
     */
    @JsonInclude(Include.NON_NULL)
    public class Hotel {
        /**
         * Hotel ID
         */
        @JsonProperty("HotelId")
        @SimpleField(isKey = true)
        public String hotelId;
    
        /**
         * Hotel name
         */
        @JsonProperty("HotelName")
        @SearchableField(isSortable = true)
        public String hotelName;
    
        /**
         * Description
         */
        @JsonProperty("Description")
        @SearchableField(analyzerName = "en.microsoft")
        public String description;
    
        /**
         * French description
         */
        @JsonProperty("DescriptionFr")
        @SearchableField(analyzerName = "fr.lucene")
        public String descriptionFr;
    
        /**
         * Category
         */
        @JsonProperty("Category")
        @SearchableField(isFilterable = true, isSortable = true, isFacetable = true)
        public String category;
    
        /**
         * Tags
         */
        @JsonProperty("Tags")
        @SearchableField(isFilterable = true, isFacetable = true)
        public String[] tags;
    
        /**
         * Whether parking is included
         */
        @JsonProperty("ParkingIncluded")
        @SimpleField(isFilterable = true, isSortable = true, isFacetable = true)
        public Boolean parkingIncluded;
    
        /**
         * Last renovation time
         */
        @JsonProperty("LastRenovationDate")
        @SimpleField(isFilterable = true, isSortable = true, isFacetable = true)
        public OffsetDateTime lastRenovationDate;
    
        /**
         * Rating
         */
        @JsonProperty("Rating")
        @SimpleField(isFilterable = true, isSortable = true, isFacetable = true)
        public Double rating;
    
        /**
         * Address
         */
        @JsonProperty("Address")
        public Address address;
    
        @Override
        public String toString()
        {
            try
            {
                return new ObjectMapper().writeValueAsString(this);
            }
            catch (JsonProcessingException e)
            {
                e.printStackTrace();
                return "";
            }
        }
    }
    
  3. Créez un fichier nommé Address.java et collez le code suivant dans Address.java :

    import com.azure.search.documents.indexes.SearchableField;
    import com.fasterxml.jackson.annotation.JsonInclude;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import com.fasterxml.jackson.annotation.JsonInclude.Include;
    
    /**
     * Model class representing an address.
     */
    @JsonInclude(Include.NON_NULL)
    public class Address {
        /**
         * Street address
         */
        @JsonProperty("StreetAddress")
        @SearchableField
        public String streetAddress;
    
        /**
         * City
         */
        @JsonProperty("City")
        @SearchableField(isFilterable = true, isSortable = true, isFacetable = true)
        public String city;
    
        /**
         * State or province
         */
        @JsonProperty("StateProvince")
        @SearchableField(isFilterable = true, isSortable = true, isFacetable = true)
        public String stateProvince;
    
        /**
         * Postal code
         */
        @JsonProperty("PostalCode")
        @SearchableField(isFilterable = true, isSortable = true, isFacetable = true)
        public String postalCode;
    
        /**
         * Country
         */
        @JsonProperty("Country")
        @SearchableField(isFilterable = true, isSortable = true, isFacetable = true)
        public String country;
    }
    
  4. Exécutez votre nouvelle application console :

    javac Address.java App.java Hotel.java -cp ".;target\dependency\*"
    java -cp ".;target\dependency\*" App
    

Explication du code

Dans les sections précédentes, vous avez créé une nouvelle application console et installé la bibliothèque de client Recherche Azure AI. Vous avez ajouté du code pour créer un index de recherche, le charger avec des documents et exécuter des requêtes. Vous avez exécuté le programme pour afficher les résultats dans la console.

Dans cette section, nous expliquons le code que vous avez ajouté à l’application console.

Créer un client de recherche

Dans App.java vous avez créé deux clients :

  • SearchIndexClient crée l’index.
  • SearchClient charge et interroge un index existant.

Les deux clients ont besoin du point de terminaison de service de recherche et des informations d’identification décrits précédemment dans la section informations sur la ressource.

L’exemple de code de ce guide de démarrage rapide utilise Microsoft Entra pour l’authentification sans clé recommandée. Si vous préférez utiliser une clé API, vous pouvez remplacer l’objet DefaultAzureCredential par un objet AzureKeyCredential.

String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
public static void main(String[] args) {
    // Your search service endpoint
    String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";

    // Use the recommended keyless credential instead of the AzureKeyCredential credential.
    DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
    //AzureKeyCredential credential = new AzureKeyCredential("Your search service admin key");

    // Create a SearchIndexClient to send create/delete index commands
    SearchIndexClient searchIndexClient = new SearchIndexClientBuilder()
        .endpoint(searchServiceEndpoint)
        .credential(credential)
        .buildClient();
    
    // Create a SearchClient to load and query documents
    String indexName = "hotels-quickstart-java";
    SearchClient searchClient = new SearchClientBuilder()
        .endpoint(searchServiceEndpoint)
        .credential(credential)
        .indexName(indexName)
        .buildClient();

    // Create Search Index for Hotel model
    searchIndexClient.createOrUpdateIndex(
        new SearchIndex(indexName, SearchIndexClient.buildSearchFields(Hotel.class, null))
        .setSuggesters(new SearchSuggester("sg", Arrays.asList("HotelName"))));

    // REDACTED FOR BREVITY . . . 
}

Création d'un index

Ce guide de démarrage rapide crée un index Hotels que vous allez charger avec des données sur des hôtels et sur lequel vous allez effectuer des requêtes. Dans cette étape, définissez les champs de l’index. Chaque définition de champ comprend un nom, un type de données et des attributs qui déterminent la façon dont le champ est utilisé.

Dans cet exemple, des méthodes synchrones de la bibliothèque Azure.Search.Documents sont utilisées par souci de simplicité et de lisibilité. En revanche, dans des scénarios de production, vous devez utiliser des méthodes asynchrones pour maintenir la scalabilité et la réactivité de votre application. Par exemple, vous pouvez utiliser CreateIndexAsync au lieu de CreateIndex.

Définir les structures

Vous avez créé deux classes d’assistance, Hotel.java et Address.java pour définir la structure d’un document d’hôtel et son adresse. La classe Hôtel comprend des champs pour un ID d’hôtel, nom, description, catégorie, étiquettes, parking, date de rénovation, évaluation et adresse. La classe Address inclut des champs pour l’adresse postale, la ville, l’état/la province, le code postal et le pays/région.

Dans la bibliothèque de client Azure.Search.Documents, vous pouvez utiliser SearchableField et SimpleField pour simplifier les définitions de champs.

  • SimpleField peut être n’importe quel type de données, ne peut jamais faire l’objet d’une recherche (il est ignoré pour les requêtes de recherche de texte intégral) et peut être récupéré (il n’est pas masqué). Les autres attributs sont désactivés par défaut, mais peuvent être activés. Vous pouvez utiliser un SimpleField pour les ID de document ou les champs utilisés seulement dans des filtres, des facettes ou des profils de scoring. Si c’est le cas, veillez à appliquer tous les attributs nécessaires pour le scénario, comme IsKey = true pour un ID de document.
  • SearchableField doit être une chaîne, et peut toujours faire l’objet d’une recherche et d’une récupération. Les autres attributs sont désactivés par défaut, mais peuvent être activés. Comme ce type de champ peut faire l’objet d’une recherche, il prend en charge les synonymes et l’ensemble complet des propriétés de l’analyseur.

Que vous utilisiez l’API SearchField de base ou un des modèles d’assistance, vous devez activer explicitement les attributs de filtre, de facette et de tri. Par exemple, isFilterable, isSortable et isFacetable doivent être explicitement attribués, comme indiqué dans l’exemple précédent.

Créer l’index de recherche

Dans App.java, créez un objet SearchIndex dans la méthode main, puis appelez la méthode createOrUpdateIndex pour créer l’index dans votre service de recherche. L’index comprend également un SearchSuggester pour activer l’autocomplétion sur les champs spécifiés.

// Create Search Index for Hotel model
searchIndexClient.createOrUpdateIndex(
    new SearchIndex(indexName, SearchIndexClient.buildSearchFields(Hotel.class, null))
    .setSuggesters(new SearchSuggester("sg", Arrays.asList("HotelName"))));

Chargement de documents

La recherche Azure AI effectue des recherches parmi les contenus stockés dans le service. Au cours de cette étape, vous allez charger des documents JSON conformes à l’index de l’hôtel que vous avez créé.

Dans la recherche Azure AI, les documents de recherche sont des structures de données qui sont à la fois des entrées pour l’indexation et des sorties de requêtes. Selon une source de données externe, les entrées de documents peuvent être des lignes dans une base de données, des objets blob dans le Stockage Blob ou des documents JSON sur le disque. Dans cet exemple, nous prenons un raccourci et incorporons des documents JSON pour quatre hôtels dans le code lui-même.

Lors du chargement de documents, vous devez utiliser un objet IndexDocumentsBatch. Un objet IndexDocumentsBatch contient une collection d’IndexActions, chacune contenant un document et une propriété qui indiquent à la recherche Azure AI l’action à effectuer (upload, merge, delete et mergeOrUpload).

Dans App.java, créez des documents et des actions d’index, puis passez-les à IndexDocumentsBatch. Les documents suivants sont conformes à l’index hotels-quickstart, tel que défini par la classe hotel.

private static void uploadDocuments(SearchClient searchClient)
{
    var hotelList = new ArrayList<Hotel>();

    var hotel = new Hotel();
    hotel.hotelId = "1";
    hotel.hotelName = "Stay-Kay City Hotel";
    hotel.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.";
    hotel.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.";
    hotel.category = "Boutique";
    hotel.tags = new String[] { "pool", "air conditioning", "concierge" };
    hotel.parkingIncluded = false;
    hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(1970, 1, 18), LocalTime.of(0, 0)), ZoneOffset.UTC);
    hotel.rating = 3.6;
    hotel.address = new Address();
    hotel.address.streetAddress = "677 5th Ave";
    hotel.address.city = "New York";
    hotel.address.stateProvince = "NY";
    hotel.address.postalCode = "10022";
    hotel.address.country = "USA";
    hotelList.add(hotel);
    
    // REDACTED FOR BREVITY

    var batch = new IndexDocumentsBatch<Hotel>();
    batch.addMergeOrUploadActions(hotelList);
    try
    {
        searchClient.indexDocuments(batch);
    }
    catch (Exception e)
    {
        e.printStackTrace();
        // If for some reason any documents are dropped during indexing, you can compensate by delaying and
        // retrying. This simple demo just logs failure and continues
        System.err.println("Failed to index some of the documents");
    }
}

Une fois que vous avez initialisé l’objet IndexDocumentsBatch, vous pouvez l’envoyer à l’index en appelant indexDocuments sur votre objet SearchClient.

Vous allez charger des documents en utilisant SearchClient dans main(), mais l’opération nécessite également des droits d’administrateur sur le service, généralement associé à SearchIndexClient. Une façon de configurer cette opération consiste à obtenir SearchClient via SearchIndexClient (searchIndexClient dans cet exemple).

uploadDocuments(searchClient);

Étant donné qu’il s’agit d’une application console qui exécute toutes les commandes de manière séquentielle, nous allons ajouter un délai d’attente de 2 secondes entre l’indexation et les requêtes.

// Wait 2 seconds for indexing to complete before starting queries (for demo and console-app purposes only)
System.out.println("Waiting for indexing...\n");
try
{
    Thread.sleep(2000);
}
catch (InterruptedException e)
{
}

Le retard de 2 secondes compense l’indexation, qui est asynchrone, afin que tous les documents puissent être indexés avant l’exécution des requêtes. Le codage dans un retard n’est nécessaire que dans les démonstrations, les tests et les exemples d’applications.

Rechercher dans un index

Vous pouvez obtenir les résultats de la requête dès que le premier document est indexé, mais les tests réels de votre index doivent attendre que tous les documents soient indexés.

Cette section ajoute deux éléments de fonctionnalité : logique de requête et résultats. Pour les requêtes, utilisez la méthode Search. Cette méthode prend le texte de recherche (la chaîne de requête) ainsi que d’autres options.

Dans App.java, la méthode WriteDocuments imprime les résultats de recherche dans la console.

// Write search results to console
private static void WriteSearchResults(SearchPagedIterable searchResults)
{
    searchResults.iterator().forEachRemaining(result ->
    {
        Hotel hotel = result.getDocument(Hotel.class);
        System.out.println(hotel);
    });

    System.out.println();
}

// Write autocomplete results to console
private static void WriteAutocompleteResults(AutocompletePagedIterable autocompleteResults)
{
    autocompleteResults.iterator().forEachRemaining(result ->
    {
        String text = result.getText();
        System.out.println(text);
    });

    System.out.println();
}

Exemple de requête 1

La méthode RunQueries exécute des requêtes et retourne des résultats. Les résultats sont des objets Hotel. Cet exemple montre la signature de la méthode et la première requête. Cette requête montre le paramètre Select qui vous permet de composer le résultat à l’aide de champs sélectionnés dans le document.

// Run queries, use WriteDocuments to print output
private static void RunQueries(SearchClient searchClient)
{
    // Query 1
    System.out.println("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n");

    SearchOptions options = new SearchOptions();
    options.setIncludeTotalCount(true);
    options.setFilter("");
    options.setOrderBy("");
    options.setSelect("HotelId", "HotelName", "Address/City");

    WriteSearchResults(searchClient.search("*", options, Context.NONE));
}

Exemple de requête 2

Dans la deuxième requête, recherchez un terme, ajoutez un filtre qui sélectionne les documents dont l’évaluation (Rating) est supérieure à 4, puis triez par Rating dans l’ordre décroissant. Un filtre est une expression booléenne évaluée sur des champs isFilterable dans un index. Les requêtes de filtre incluent ou excluent des valeurs. Par conséquent, aucun score de pertinence n’est associé à une requête de filtre.

// Query 2
System.out.println("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n");

options = new SearchOptions();
options.setFilter("Rating gt 4");
options.setOrderBy("Rating desc");
options.setSelect("HotelId", "HotelName", "Rating");

WriteSearchResults(searchClient.search("hotels", options, Context.NONE));

Exemple de requête 3

La troisième requête illustre l’utilisation de searchFields, qui sert à limiter l’étendue d’une opération de recherche en texte intégral à des champs spécifiques.

// Query 3
System.out.println("Query #3: Limit search to specific fields (pool in Tags field)...\n");

options = new SearchOptions();
options.setSearchFields("Tags");

options.setSelect("HotelId", "HotelName", "Tags");

WriteSearchResults(searchClient.search("pool", options, Context.NONE));

Exemple de requête 4

La quatrième requête illustre les facets, que vous pouvez utiliser pour créer une structure de navigation par facettes.

// Query 4
System.out.println("Query #4: Facet on 'Category'...\n");

options = new SearchOptions();
options.setFilter("");
options.setFacets("Category");
options.setSelect("HotelId", "HotelName", "Category");

WriteSearchResults(searchClient.search("*", options, Context.NONE));

Exemple de requête 5

La cinquième requête retourne un document spécifique.

// Query 5
System.out.println("Query #5: Look up a specific document...\n");

Hotel lookupResponse = searchClient.getDocument("3", Hotel.class);
System.out.println(lookupResponse.hotelId);
System.out.println();

Exemple de requête 6

La dernière requête montre la syntaxe pour l’autocomplétion, en simulant une entrée utilisateur partielle s qui est résolue en deux correspondances possibles dans les sourceFields associés au suggesteur que vous avez défini dans l’index.

// Query 6
System.out.println("Query #6: Call Autocomplete on HotelName that starts with 's'...\n");

WriteAutocompleteResults(searchClient.autocomplete("s", "sg"));

Résumé des requêtes

Les requêtes précédentes illustrent plusieurs manières d’établir des correspondances entre des termes dans une requête : recherche en texte intégral, filtres et autocomplétion.

La recherche en texte intégral et les filtres sont exécutés à l’aide de la méthode SearchClient.search. Une requête de recherche peut être transmise dans la chaîne searchText, tandis qu’une expression de filtre peut être transmise dans la propriété filter de la classe SearchOptions. Pour filtrer sans effectuer de recherche, transmettez simplement « * » pour le paramètre searchText de la méthode search. Pour effectuer une recherche sans filtrage, ne définissez pas la propriété filter et ne transmettez aucune instance SearchOptions.

Découvrez comment utiliser la bibliothèque cliente Azure.Search.Documents afin de créer, charger et interroger un index de recherche à l’aide d’exemples de données pour la recherche en texte intégral. La recherche en texte intégral utilise Apache Lucene pour l’indexation et les requêtes, ainsi qu’un algorithme de classement BM25 pour le scoring des résultats.

Ce démarrage rapide crée et interroge un petit index de démarrage rapide pour hôtels qui contient des données sur quatre hôtels.

Conseil

Vous pouvez télécharger le code source pour commencer avec un projet terminé ou suivre les étapes décrites dans cet article pour créer votre propre projet.

Prérequis

  • Un abonnement Azure actif - En créer un gratuitement
  • Un service de recherche Azure AI. Créez un service si vous n’en avez pas. Vous pouvez utiliser le niveau gratuit dans le cadre de ce démarrage rapide.

Prérequis pour Microsoft Entra ID

Pour l’authentification sans clé recommandée avec Microsoft Entra ID, vous devez effectuer les tâches suivantes :

  • Installez l’interface Azure CLI utilisée pour l’authentification sans clé avec Microsoft Entra ID.
  • Attribuez Search Service Contributor et Search Index Data Contributor des rôles à votre compte d’utilisateur. Vous pouvez attribuer des rôles dans le Portail Azure sous Contrôle d’accès (IAM)>Ajouter une attribution de rôle. Pour plus d’informations, consultez Se connecter à la Recherche Azure AI à l’aide des rôles.

Récupérer des informations sur les ressources

Vous devez récupérer les informations suivantes pour authentifier votre application auprès de votre ressource Recherche Azure AI :

Nom de la variable Valeur
SEARCH_API_ENDPOINT Cette valeur se trouve dans le portail Azure. Sélectionnez votre service de recherche, puis dans le menu de gauche, sélectionnez Vue d’ensemble. La valeur d’URL sous Essentials est le point de terminaison dont vous avez besoin. Voici un exemple de point de terminaison : https://mydemo.search.windows.net.

En savoir plus sur l’authentification sans clé et la définition de variables d’environnement.

Configurer

  1. Créez un dossier full-text-quickstart pour contenir l’application et ouvrez Visual Studio Code dans ce dossier avec la commande suivante :

    mkdir full-text-quickstart && cd full-text-quickstart
    
  2. Créez le package.json avec la commande suivante :

    npm init -y
    
  3. Installez la bibliothèque de client Recherche Azure AI (Azure.Search.Documents) pour JavaScript avec :

    npm install @azure/search-documents
    
  4. Pour l’authentification sans mot de passe recommandée, installez la bibliothèque de client Azure Identity avec :

    npm install @azure/identity
    

Créer, charger et interroger un index de recherche

Dans la section de configuration précédente, vous avez installé la bibliothèque de client Recherche Azure AI et d’autres dépendances.

Dans cette section, vous ajoutez du code pour créer un index de recherche, chargez-le avec des documents et exécutez des requêtes. Vous exécutez le programme pour afficher les résultats dans la console. Pour obtenir une explication détaillée du code, consultez la section expliquant le code.

L’exemple de code de ce guide de démarrage rapide utilise Microsoft Entra pour l’authentification sans clé recommandée. Si vous préférez utiliser une clé API, vous pouvez remplacer l’objet DefaultAzureCredential par un objet AzureKeyCredential.

String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
  1. Créez un fichier nommé index.js et collez le code suivant dans index.js :

    // Import from the @azure/search-documents library
    import { SearchIndexClient, odata } from "@azure/search-documents";
    // Import from the Azure Identity library
    import { DefaultAzureCredential } from "@azure/identity";
    // Importing the hotels sample data
    import hotelData from './hotels.json' assert { type: "json" };
    // Load the .env file if it exists
    import * as dotenv from "dotenv";
    dotenv.config();
    // Defining the index definition
    const indexDefinition = {
        "name": "hotels-quickstart",
        "fields": [
            {
                "name": "HotelId",
                "type": "Edm.String",
                "key": true,
                "filterable": true
            },
            {
                "name": "HotelName",
                "type": "Edm.String",
                "searchable": true,
                "filterable": false,
                "sortable": true,
                "facetable": false
            },
            {
                "name": "Description",
                "type": "Edm.String",
                "searchable": true,
                "filterable": false,
                "sortable": false,
                "facetable": false,
                "analyzerName": "en.lucene"
            },
            {
                "name": "Description_fr",
                "type": "Edm.String",
                "searchable": true,
                "filterable": false,
                "sortable": false,
                "facetable": false,
                "analyzerName": "fr.lucene"
            },
            {
                "name": "Category",
                "type": "Edm.String",
                "searchable": true,
                "filterable": true,
                "sortable": true,
                "facetable": true
            },
            {
                "name": "Tags",
                "type": "Collection(Edm.String)",
                "searchable": true,
                "filterable": true,
                "sortable": false,
                "facetable": true
            },
            {
                "name": "ParkingIncluded",
                "type": "Edm.Boolean",
                "filterable": true,
                "sortable": true,
                "facetable": true
            },
            {
                "name": "LastRenovationDate",
                "type": "Edm.DateTimeOffset",
                "filterable": true,
                "sortable": true,
                "facetable": true
            },
            {
                "name": "Rating",
                "type": "Edm.Double",
                "filterable": true,
                "sortable": true,
                "facetable": true
            },
            {
                "name": "Address",
                "type": "Edm.ComplexType",
                "fields": [
                    {
                        "name": "StreetAddress",
                        "type": "Edm.String",
                        "filterable": false,
                        "sortable": false,
                        "facetable": false,
                        "searchable": true
                    },
                    {
                        "name": "City",
                        "type": "Edm.String",
                        "searchable": true,
                        "filterable": true,
                        "sortable": true,
                        "facetable": true
                    },
                    {
                        "name": "StateProvince",
                        "type": "Edm.String",
                        "searchable": true,
                        "filterable": true,
                        "sortable": true,
                        "facetable": true
                    },
                    {
                        "name": "PostalCode",
                        "type": "Edm.String",
                        "searchable": true,
                        "filterable": true,
                        "sortable": true,
                        "facetable": true
                    },
                    {
                        "name": "Country",
                        "type": "Edm.String",
                        "searchable": true,
                        "filterable": true,
                        "sortable": true,
                        "facetable": true
                    }
                ]
            }
        ],
        "suggesters": [
            {
                "name": "sg",
                "searchMode": "analyzingInfixMatching",
                "sourceFields": [
                    "HotelName"
                ]
            }
        ]
    };
    async function main() {
        // Your search service endpoint
        const searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
        // Use the recommended keyless credential instead of the AzureKeyCredential credential.
        const credential = new DefaultAzureCredential();
        //const credential = new AzureKeyCredential(Your search service admin key);
        // Create a SearchIndexClient to send create/delete index commands
        const searchIndexClient = new SearchIndexClient(searchServiceEndpoint, credential);
        // Creating a search client to upload documents and issue queries
        const indexName = "hotels-quickstart";
        const searchClient = searchIndexClient.getSearchClient(indexName);
        console.log('Checking if index exists...');
        await deleteIndexIfExists(searchIndexClient, indexName);
        console.log('Creating index...');
        let index = await searchIndexClient.createIndex(indexDefinition);
        console.log(`Index named ${index.name} has been created.`);
        console.log('Uploading documents...');
        let indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotelData['value']);
        console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)} `);
        // waiting one second for indexing to complete (for demo purposes only)
        sleep(1000);
        console.log('Querying the index...');
        console.log();
        await sendQueries(searchClient);
    }
    async function deleteIndexIfExists(searchIndexClient, indexName) {
        try {
            await searchIndexClient.deleteIndex(indexName);
            console.log('Deleting index...');
        }
        catch {
            console.log('Index does not exist yet.');
        }
    }
    async function sendQueries(searchClient) {
        // Query 1
        console.log('Query #1 - search everything:');
        let searchOptions = {
            includeTotalCount: true,
            select: ["HotelId", "HotelName", "Rating"]
        };
        let searchResults = await searchClient.search("*", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log(`Result count: ${searchResults.count}`);
        console.log();
        // Query 2
        console.log('Query #2 - search with filter, orderBy, and select:');
        let state = 'FL';
        searchOptions = {
            filter: odata `Address/StateProvince eq ${state}`,
            orderBy: ["Rating desc"],
            select: ["HotelId", "HotelName", "Rating"]
        };
        searchResults = await searchClient.search("wifi", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log();
        // Query 3
        console.log('Query #3 - limit searchFields:');
        searchOptions = {
            select: ["HotelId", "HotelName", "Rating"],
            searchFields: ["HotelName"]
        };
        searchResults = await searchClient.search("sublime cliff", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log();
        // Query 4
        console.log('Query #4 - limit searchFields and use facets:');
        searchOptions = {
            facets: ["Category"],
            select: ["HotelId", "HotelName", "Rating"],
            searchFields: ["HotelName"]
        };
        searchResults = await searchClient.search("*", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log();
        // Query 5
        console.log('Query #5 - Lookup document:');
        let documentResult = await searchClient.getDocument('3');
        console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`);
        console.log();
    }
    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    main().catch((err) => {
        console.error("The sample encountered an error:", err);
    });
    
  2. Créez un fichier nommé hotels.json et collez le code suivant dans hotels.json :

    {
        "value": [
            {
                "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.",
                "Description_fr": "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": ["pool", "air conditioning", "concierge"],
                "ParkingIncluded": false,
                "LastRenovationDate": "1970-01-18T00:00:00Z",
                "Rating": 3.6,
                "Address": {
                    "StreetAddress": "677 5th Ave",
                    "City": "New York",
                    "StateProvince": "NY",
                    "PostalCode": "10022"
                }
            },
            {
                "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.",
                "Description_fr": "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": ["pool", "free wifi", "concierge"],
                "ParkingIncluded": "false",
                "LastRenovationDate": "1979-02-18T00:00:00Z",
                "Rating": 3.6,
                "Address": {
                    "StreetAddress": "140 University Town Center Dr",
                    "City": "Sarasota",
                    "StateProvince": "FL",
                    "PostalCode": "34243"
                }
            },
            {
                "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.",
                "Description_fr": "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": ["air conditioning", "bar", "continental breakfast"],
                "ParkingIncluded": "true",
                "LastRenovationDate": "2015-09-20T00:00:00Z",
                "Rating": 4.8,
                "Address": {
                    "StreetAddress": "3393 Peachtree Rd",
                    "City": "Atlanta",
                    "StateProvince": "GA",
                    "PostalCode": "30326"
                }
            },
            {
                "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.",
                "Description_fr": "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": ["concierge", "view", "24-hour front desk service"],
                "ParkingIncluded": true,
                "LastRenovationDate": "1960-02-06T00:00:00Z",
                "Rating": 4.6,
                "Address": {
                    "StreetAddress": "7400 San Pedro Ave",
                    "City": "San Antonio",
                    "StateProvince": "TX",
                    "PostalCode": "78216"
                }
            }
        ]
    }
    
  3. Créez un fichier nommé hotels_quickstart_index.json et collez le code suivant dans hotels_quickstart_index.json :

    {
    	"name": "hotels-quickstart",
    	"fields": [
    		{
    			"name": "HotelId",
    			"type": "Edm.String",
    			"key": true,
    			"filterable": true
    		},
    		{
    			"name": "HotelName",
    			"type": "Edm.String",
    			"searchable": true,
    			"filterable": false,
    			"sortable": true,
    			"facetable": false
    		},
    		{
    			"name": "Description",
    			"type": "Edm.String",
    			"searchable": true,
    			"filterable": false,
    			"sortable": false,
    			"facetable": false,
    			"analyzerName": "en.lucene"
    		},
    		{
    			"name": "Description_fr",
    			"type": "Edm.String",
    			"searchable": true,
    			"filterable": false,
    			"sortable": false,
    			"facetable": false,
    			"analyzerName": "fr.lucene"
    		},
    		{
    			"name": "Category",
    			"type": "Edm.String",
    			"searchable": true,
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "Tags",
    			"type": "Collection(Edm.String)",
    			"searchable": true,
    			"filterable": true,
    			"sortable": false,
    			"facetable": true
    		},
    		{
    			"name": "ParkingIncluded",
    			"type": "Edm.Boolean",
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "LastRenovationDate",
    			"type": "Edm.DateTimeOffset",
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "Rating",
    			"type": "Edm.Double",
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "Address",
    			"type": "Edm.ComplexType",
    			"fields": [
    				{
    					"name": "StreetAddress",
    					"type": "Edm.String",
    					"filterable": false,
    					"sortable": false,
    					"facetable": false,
    					"searchable": true
    				},
    				{
    					"name": "City",
    					"type": "Edm.String",
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				},
    				{
    					"name": "StateProvince",
    					"type": "Edm.String",
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				},
    				{
    					"name": "PostalCode",
    					"type": "Edm.String",
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				},
    				{
    					"name": "Country",
    					"type": "Edm.String",
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				}
    			]
    		}
    	],
    	"suggesters": [
    		{
    			"name": "sg",
    			"searchMode": "analyzingInfixMatching",
    			"sourceFields": [
    				"HotelName"
    			]
    		}
    	]
    }
    
  4. Connectez-vous à Azure à l’aide de la commande suivante :

    az login
    
  5. Exécutez le code JavaScript avec la commande suivante :

    node index.js
    

Explication du code

Créer un index

Le fichier hotels_quickstart_index.json définit le fonctionnement de Recherche Azure AI avec les documents que vous chargez à l’étape suivante. Chaque champ est identifié par un name et a un type spécifié. Chaque champ comporte également une série d’attributs d’index qui spécifient si la recherche Azure AI peut effectuer des recherches, filtrer, trier et définir des propriétés de facettes sur le champ. La plupart des champs sont des types de données simples, mais certains, comme AddressType, sont des types complexes qui vous permettent de créer des structures de données riches dans votre index. Vous pouvez en savoir plus sur les types de données pris en charge et les attributs d’index décrits dans Créer un index (REST).

Une fois notre définition d’index en place, nous voulons importer hotels_quickstart_index.json en haut du fichier index.js pour que la fonction main puisse accéder à la définition de l’index.

const indexDefinition = require('./hotels_quickstart_index.json');

Dans la fonction principale, nous créons ensuite un SearchIndexClient, qui est utilisé pour créer et gérer des index pour la recherche Azure AI.

const indexClient = new SearchIndexClient(endpoint, new AzureKeyCredential(apiKey));

Ensuite, nous voulons supprimer l’index s’il existe déjà. Il s’agit d’une opération courante pour le code de test/démonstration.

Pour ce faire, nous définissons une fonction simple qui tente de supprimer l’index.

async function deleteIndexIfExists(indexClient, indexName) {
    try {
        await indexClient.deleteIndex(indexName);
        console.log('Deleting index...');
    } catch {
        console.log('Index does not exist yet.');
    }
}

Pour exécuter la fonction, nous extrayons le nom de l’index de la définition de l’index et nous passons indexName avec indexClient à la fonction deleteIndexIfExists().

const indexName = indexDefinition["name"];

console.log('Checking if index exists...');
await deleteIndexIfExists(indexClient, indexName);

Après cela, nous sommes prêts à créer l’index avec la méthode createIndex().

console.log('Creating index...');
let index = await indexClient.createIndex(indexDefinition);

console.log(`Index named ${index.name} has been created.`);

Chargement de documents

Dans la recherche Azure AI, les documents sont des structures de données qui sont à la fois des entrées pour l’indexation et des sorties de requêtes. Vous pouvez envoyer des données de ce type dans l’index ou utiliser un indexeur. Dans le cas présent, nous envoyons les documents à l’index programmatiquement.

Les entrées de documents peuvent être des lignes dans une base de données, des objets blob dans le Stockage Blob ou, comme dans cet exemple, des documents JSON sur le disque. À l’instar de ce que nous avons fait avec indexDefinition, nous devons également importer hotels.json en haut de index.js pour que les données soient accessibles dans notre fonction main.

const hotelData = require('./hotels.json');

Pour indexer les données dans l’index de recherche, nous devons maintenant créer un SearchClient. Alors que le SearchIndexClient est utilisé pour créer et gérer un index, le SearchClient est utilisé pour charger des documents et interroger l’index.

Il existe deux façons de créer un objet SearchClient. La première option consiste à créer un SearchClient à partir de zéro :

 const searchClient = new SearchClient(endpoint, indexName, new AzureKeyCredential(apiKey));

Vous pouvez également utiliser la méthode getSearchClient() du SearchIndexClient pour créer le SearchClient :

const searchClient = indexClient.getSearchClient(indexName);

Maintenant que le client est défini, chargez les documents dans l’index de recherche. Dans le cas présent, nous utilisons la méthode mergeOrUploadDocuments(), qui charge les documents ou les fusionne avec un document existant si un document ayant la même clé existe déjà.

console.log('Uploading documents...');
let indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotelData['value']);

console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)}`);

Rechercher dans un index

Avec un index créé et des documents chargés, vous êtes prêt à envoyer des requêtes à l’index. Dans cette section, nous envoyons cinq requêtes différentes à l’index de recherche pour illustrer différentes fonctionnalités de requête à votre disposition.

Les requêtes sont écrites dans une fonction sendQueries() que nous appelons dans la fonction main comme suit :

await sendQueries(searchClient);

Les requêtes sont envoyées en utilisant la méthode search() de searchClient. Le premier paramètre est le texte recherché et le deuxième paramètre spécifie les options de recherche.

Exemple de requête 1

La première requête recherche *, ce qui équivaut à effectuer une recherche dans tout, et sélectionne trois des champs de l’index. C’est une bonne pratique que d’appliquer un select seulement aux champs dont vous avez besoin, car l’extraction de données inutiles peut ajouter de la latence à vos requêtes.

Dans les searchOptions de cette requête, includeTotalCount est également défini sur true, ce qui retourne le nombre de résultats correspondants trouvés.

async function sendQueries(searchClient) {
    console.log('Query #1 - search everything:');
    let searchOptions = {
        includeTotalCount: true,
        select: ["HotelId", "HotelName", "Rating"]
    };

    let searchResults = await searchClient.search("*", searchOptions);
    for await (const result of searchResults.results) {
        console.log(`${JSON.stringify(result.document)}`);
    }
    console.log(`Result count: ${searchResults.count}`);

    // remaining queries go here
}

Les requêtes restantes décrites ci-dessous doivent également être ajoutées à la fonction sendQueries(). Elles sont séparées ici seulement pour des raisons de lisibilité.

Exemple de requête 2

Dans la requête suivante, nous spécifions le terme de recherche "wifi" et nous incluons aussi un filtre pour retourner seulement les résultats où l’état est égal à 'FL'. Les résultats sont également classés selon la valeur du champ Rating de l’hôtel.

console.log('Query #2 - Search with filter, orderBy, and select:');
let state = 'FL';
searchOptions = {
    filter: odata`Address/StateProvince eq ${state}`,
    orderBy: ["Rating desc"],
    select: ["HotelId", "HotelName", "Rating"]
};

searchResults = await searchClient.search("wifi", searchOptions);
for await (const result of searchResults.results) {
    console.log(`${JSON.stringify(result.document)}`);
}

Exemple de requête 3

Ensuite, la recherche est limitée à un seul champ pouvant faire l’objet d’une recherche en utilisant le paramètre searchFields. Cette approche constitue une option intéressante pour améliorer l’efficacité de votre requête si vous savez que vous êtes intéressé seulement par les correspondances dans certains champs.

console.log('Query #3 - Limit searchFields:');
searchOptions = {
    select: ["HotelId", "HotelName", "Rating"],
    searchFields: ["HotelName"]
};

searchResults = await searchClient.search("Sublime Palace", searchOptions);
for await (const result of searchResults.results) {
    console.log(`${JSON.stringify(result.document)}`);
}
console.log();

Exemple de requête 4

Une autre option courante à inclure dans une requête est facets. Les facettes vous permettent de créer des filtres sur votre interface utilisateur pour permettre aux utilisateurs de connaître facilement les valeurs sur lesquelles ils peuvent filtrer.

console.log('Query #4 - Use facets:');
searchOptions = {
    facets: ["Category"],
    select: ["HotelId", "HotelName", "Rating"],
    searchFields: ["HotelName"]
};

searchResults = await searchClient.search("*", searchOptions);
for await (const result of searchResults.results) {
    console.log(`${JSON.stringify(result.document)}`);
}

Exemple de requête 5

La dernière requête utilise la méthode getDocument() du searchClient. Ceci vous permet de récupérer efficacement un document en utilisant sa clé.

console.log('Query #5 - Lookup document:');
let documentResult = await searchClient.getDocument(key='3')
console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`)

Résumé des requêtes

Les requêtes précédentes illustrent plusieurs manières d’établir des correspondances entre des termes dans une requête : recherche en texte intégral, filtres et autocomplétion.

La recherche en texte intégral et les filtres sont exécutés en utilisant la méthode searchClient.search. Une requête de recherche peut être transmise dans la chaîne searchText, tandis qu’une expression de filtre peut être transmise dans la propriété filter de la classe SearchOptions. Pour filtrer sans effectuer de recherche, transmettez simplement « * » pour le paramètre searchText de la méthode search. Pour effectuer une recherche sans filtrage, ne définissez pas la propriété filter et ne transmettez aucune instance SearchOptions.

Découvrez comment utiliser la bibliothèque cliente Azure.Search.Documents afin de créer, charger et interroger un index de recherche à l’aide d’exemples de données pour la recherche en texte intégral. La recherche en texte intégral utilise Apache Lucene pour l’indexation et les requêtes, ainsi qu’un algorithme de classement BM25 pour le scoring des résultats.

Ce démarrage rapide crée et interroge un petit index de démarrage rapide pour hôtels qui contient des données sur quatre hôtels.

Conseil

Vous pouvez télécharger et exécuter un notebook terminé.

Prérequis

Prérequis pour Microsoft Entra ID

Pour l’authentification sans clé recommandée avec Microsoft Entra ID, vous devez effectuer les tâches suivantes :

  • Installez l’interface Azure CLI utilisée pour l’authentification sans clé avec Microsoft Entra ID.
  • Attribuez Search Service Contributor et Search Index Data Contributor des rôles à votre compte d’utilisateur. Vous pouvez attribuer des rôles dans le Portail Azure sous Contrôle d’accès (IAM)>Ajouter une attribution de rôle. Pour plus d’informations, consultez Se connecter à la Recherche Azure AI à l’aide des rôles.

Récupérer des informations sur les ressources

Vous devez récupérer les informations suivantes pour authentifier votre application auprès de votre ressource Recherche Azure AI :

Nom de la variable Valeur
SEARCH_API_ENDPOINT Cette valeur se trouve dans le portail Azure. Sélectionnez votre service de recherche, puis dans le menu de gauche, sélectionnez Vue d’ensemble. La valeur d’URL sous Essentials est le point de terminaison dont vous avez besoin. Voici un exemple de point de terminaison : https://mydemo.search.windows.net.

En savoir plus sur l’authentification sans clé et la définition de variables d’environnement.

Configurer votre environnement

Vous exécutez l’exemple de code dans un notebook Jupyter. Vous devez donc configurer votre environnement pour exécuter des notebooks Jupyter.

  1. Téléchargez ou copiez l’exemple de notebook à partir de GitHub.

  2. Ouvrez le notebook dans Visual Studio Code.

  3. Créez un environnement Python à utiliser pour installer le package dont vous avez besoin pour ce tutoriel.

    Important

    N’installez pas de packages dans votre installation globale de Python. Vous devez toujours utiliser un environnement virtuel ou conda lors de l’installation de packages Python. Sinon, vous pouvez interrompre votre installation globale de Python.

    py -3 -m venv .venv
    .venv\scripts\activate
    

    La configuration peut prendre une minute. Si vous rencontrez des problèmes, consultez Environnements Python dans VS Code.

  4. Installez les notebooks Jupyter et le noyau IPython pour les notebooks Jupyter si vous ne les avez pas déjà.

    pip install jupyter
    pip install ipykernel
    python -m ipykernel install --user --name=.venv
    
  5. Sélectionnez le noyau du bloc-notes.

    1. Dans le coin supérieur droit du bloc-notes, sélectionnez Sélectionner le noyau.
    2. Si vous voyez .venv dans la liste, sélectionnez-le. Si vous ne le voyez pas, sélectionnez Sélectionner un autre noyau>Environnements Python>.venv.

Créer, charger et interroger un index de recherche

Dans cette section, vous ajoutez du code pour créer un index de recherche, chargez-le avec des documents et exécutez des requêtes. Vous exécutez le programme pour afficher les résultats dans la console. Pour obtenir une explication détaillée du code, consultez la section expliquant le code.

  1. Assurez-vous que le notebook est ouvert dans le noyau .venv, comme décrit dans la section précédente.

  2. Exécutez la première cellule de code pour installer les packages requis, y compris azure-search-documents.

    ! pip install azure-search-documents==11.6.0b1 --quiet
    ! pip install azure-identity --quiet
    ! pip install python-dotenv --quiet
    
  3. Remplacez le contenu de la deuxième cellule de code par le code suivant en fonction de votre méthode d’authentification.

    Remarque

    L’exemple de code de ce guide de démarrage rapide utilise Microsoft Entra pour l’authentification sans clé recommandée. Si vous préférez utiliser une clé API, vous pouvez remplacer l’objet DefaultAzureCredential par un objet AzureKeyCredential.

    from azure.core.credentials import AzureKeyCredential
    from azure.identity import DefaultAzureCredential, AzureAuthorityHosts
    
    search_endpoint: str = "https://<Put your search service NAME here>.search.windows.net/"
    authority = AzureAuthorityHosts.AZURE_PUBLIC_CLOUD
    credential = DefaultAzureCredential(authority=authority)
    
    index_name: str = "hotels-quickstart-python"
    
  4. Supprimez les deux lignes suivantes de la cellule de code Créer un index. Les informations d’identification sont déjà définies dans la cellule de code précédente.

    from azure.core.credentials import AzureKeyCredential
    credential = AzureKeyCredential(search_api_key)
    
  5. Exécutez la cellule de code Créer un index pour créer un index de recherche.

  6. Exécutez les cellules de code restantes de manière séquentielle pour charger des documents et exécuter des requêtes.

Explication du code

Création d'un index

SearchIndexClient est utilisé pour créer et gérer des index pour Recherche Azure AI. Chaque champ est identifié par un name et a un type spécifié.

Chaque champ comporte également une série d’attributs d’index qui spécifient si la recherche Azure AI peut effectuer des recherches, filtrer, trier et définir des propriétés de facettes sur le champ. La plupart des champs sont des types de données simples, mais certains, comme AddressType, sont des types complexes qui vous permettent de créer des structures de données riches dans votre index. Vous pouvez en savoir plus sur les types de données pris en charge et les attributs d’index décrits dans Créer un index (REST).

Créer une charge utile de documents et charger des documents

Utilisez une action d’index pour le type d’opération, comme upload ou merge-and-upload. Les documents proviennent de l’exemple HotelsData sur GitHub.

Rechercher dans un index

Vous pouvez obtenir les résultats de la requête dès que le premier document est indexé, mais les tests réels de votre index doivent attendre que tous les documents soient indexés.

Utilisez la méthode search de la classe search.client.

Les exemples de requêtes dans le notebook sont les suivants :

  • Requête basique : exécute une recherche vide (search=*), qui retourne une liste non classée (score de recherche = 1.0) de documents arbitraires. Étant donné qu’il n’y a aucun critère, tous les documents sont inclus dans les résultats.
  • Requête de termes : ajoute des termes entiers à l’expression de recherche (« wifi »). Cette requête spécifie que les résultats contiennent uniquement les champs de l’instruction select. Le fait de limiter les champs de retour réduit la quantité de données renvoyées sur le réseau ainsi que la latence de recherche.
  • Requête filtrée : ajoute une expression de filtre afin de retourner uniquement les hôtels ayant une évaluation supérieure à quatre, triés par ordre décroissant.
  • Étendue champée : ajoute search_fields à l’exécution de requête d’étendue à des champs spécifiques.
  • Facettes : génère des facettes pour les correspondances positives trouvées dans les résultats de recherche. Il n’y a pas de correspondance nulle. Si les résultats de la recherche n’incluent pas le terme wifi, alors wifi n’apparaît pas dans la structure de navigation par facettes.
  • Rechercher un document : retourne un document en fonction de sa clé. Cette opération est utile si vous souhaitez fournir une extraction quand un utilisateur sélectionne un élément dans un résultat de recherche.
  • Autocomplétion : fournit des correspondances potentielles au fur et à mesure que l’utilisateur tape dans la zone de recherche. L’autocomplétion utilise un suggesteur (sg) pour savoir quels champs contiennent des correspondances potentielles aux demandes du suggesteur. Dans ce guide de démarrage rapide, ces champs sont Tags, Address/City et Address/Country. Pour simuler l’autocomplétion, passez les lettres sa en tant que chaîne partielle. La méthode d’autocomplétion de SearchClient renvoie les correspondances de termes potentielles.

Supprimer l’index

Si vous avez terminé avec cet index, vous pouvez le supprimer en exécutant la cellule de code Nettoyer. La suppression d’index inutiles libère de l’espace pour parcourir plus de guides de démarrage rapide et de tutoriels.

Découvrez comment utiliser la bibliothèque cliente Azure.Search.Documents afin de créer, charger et interroger un index de recherche à l’aide d’exemples de données pour la recherche en texte intégral. La recherche en texte intégral utilise Apache Lucene pour l’indexation et les requêtes, ainsi qu’un algorithme de classement BM25 pour le scoring des résultats.

Ce démarrage rapide crée et interroge un petit index de démarrage rapide pour hôtels qui contient des données sur quatre hôtels.

Conseil

Vous pouvez télécharger le code source pour commencer avec un projet terminé ou suivre les étapes décrites dans cet article pour créer votre propre projet.

Prérequis

  • Un abonnement Azure actif - En créer un gratuitement
  • Un service de recherche Azure AI. Créez un service si vous n’en avez pas. Vous pouvez utiliser le niveau gratuit dans le cadre de ce démarrage rapide.

Prérequis pour Microsoft Entra ID

Pour l’authentification sans clé recommandée avec Microsoft Entra ID, vous devez effectuer les tâches suivantes :

  • Installez l’interface Azure CLI utilisée pour l’authentification sans clé avec Microsoft Entra ID.
  • Attribuez Search Service Contributor et Search Index Data Contributor des rôles à votre compte d’utilisateur. Vous pouvez attribuer des rôles dans le Portail Azure sous Contrôle d’accès (IAM)>Ajouter une attribution de rôle. Pour plus d’informations, consultez Se connecter à la Recherche Azure AI à l’aide des rôles.

Récupérer des informations sur les ressources

Vous devez récupérer les informations suivantes pour authentifier votre application auprès de votre ressource Recherche Azure AI :

Nom de la variable Valeur
SEARCH_API_ENDPOINT Cette valeur se trouve dans le portail Azure. Sélectionnez votre service de recherche, puis dans le menu de gauche, sélectionnez Vue d’ensemble. La valeur d’URL sous Essentials est le point de terminaison dont vous avez besoin. Voici un exemple de point de terminaison : https://mydemo.search.windows.net.

En savoir plus sur l’authentification sans clé et la définition de variables d’environnement.

Configurer

  1. Créez un dossier full-text-quickstart pour contenir l’application et ouvrez Visual Studio Code dans ce dossier avec la commande suivante :

    mkdir full-text-quickstart && cd full-text-quickstart
    
  2. Créez le package.json avec la commande suivante :

    npm init -y
    
  3. Mettez à jour le package.json vers ECMAScript avec la commande suivante :

    npm pkg set type=module
    
  4. Installez la bibliothèque de client Recherche Azure AI (Azure.Search.Documents) pour JavaScript avec :

    npm install @azure/search-documents
    
  5. Pour l’authentification sans mot de passe recommandée, installez la bibliothèque de client Azure Identity avec :

    npm install @azure/identity
    

Créer, charger et interroger un index de recherche

Dans la section de configuration précédente, vous avez installé la bibliothèque de client Recherche Azure AI et d’autres dépendances.

Dans cette section, vous ajoutez du code pour créer un index de recherche, chargez-le avec des documents et exécutez des requêtes. Vous exécutez le programme pour afficher les résultats dans la console. Pour obtenir une explication détaillée du code, consultez la section expliquant le code.

L’exemple de code de ce guide de démarrage rapide utilise Microsoft Entra pour l’authentification sans clé recommandée. Si vous préférez utiliser une clé API, vous pouvez remplacer l’objet DefaultAzureCredential par un objet AzureKeyCredential.

const searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
const credential = new DefaultAzureCredential();
  1. Créez un fichier nommé index.ts et collez le code suivant dans index.ts :

    // Import from the @azure/search-documents library
    import {
        SearchIndexClient,
        SearchClient,
        SearchFieldDataType,
        AzureKeyCredential,
        odata,
        SearchIndex
    } from "@azure/search-documents";
    
    // Import from the Azure Identity library
    import { DefaultAzureCredential } from "@azure/identity";
    
    // Importing the hotels sample data
    import hotelData from './hotels.json' assert { type: "json" };
    
    // Load the .env file if it exists
    import * as dotenv from "dotenv";
    dotenv.config();
    
    // Defining the index definition
    const indexDefinition: SearchIndex = {
    	"name": "hotels-quickstart",
    	"fields": [
    		{
    			"name": "HotelId",
    			"type": "Edm.String" as SearchFieldDataType,
    			"key": true,
    			"filterable": true
    		},
    		{
    			"name": "HotelName",
    			"type": "Edm.String" as SearchFieldDataType,
    			"searchable": true,
    			"filterable": false,
    			"sortable": true,
    			"facetable": false
    		},
    		{
    			"name": "Description",
    			"type": "Edm.String" as SearchFieldDataType,
    			"searchable": true,
    			"filterable": false,
    			"sortable": false,
    			"facetable": false,
    			"analyzerName": "en.lucene"
    		},
    		{
    			"name": "Description_fr",
    			"type": "Edm.String" as SearchFieldDataType,
    			"searchable": true,
    			"filterable": false,
    			"sortable": false,
    			"facetable": false,
    			"analyzerName": "fr.lucene"
    		},
    		{
    			"name": "Category",
    			"type": "Edm.String" as SearchFieldDataType,
    			"searchable": true,
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "Tags",
    			"type": "Collection(Edm.String)",
    			"searchable": true,
    			"filterable": true,
    			"sortable": false,
    			"facetable": true
    		},
    		{
    			"name": "ParkingIncluded",
    			"type": "Edm.Boolean",
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "LastRenovationDate",
    			"type": "Edm.DateTimeOffset",
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "Rating",
    			"type": "Edm.Double",
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "Address",
    			"type": "Edm.ComplexType",
    			"fields": [
    				{
    					"name": "StreetAddress",
    					"type": "Edm.String" as SearchFieldDataType,
    					"filterable": false,
    					"sortable": false,
    					"facetable": false,
    					"searchable": true
    				},
    				{
    					"name": "City",
    					"type": "Edm.String" as SearchFieldDataType,
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				},
    				{
    					"name": "StateProvince",
    					"type": "Edm.String" as SearchFieldDataType,
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				},
    				{
    					"name": "PostalCode",
    					"type": "Edm.String" as SearchFieldDataType,
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				},
    				{
    					"name": "Country",
    					"type": "Edm.String" as SearchFieldDataType,
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				}
    			]
    		}
    	],
    	"suggesters": [
    		{
    			"name": "sg",
    			"searchMode": "analyzingInfixMatching",
    			"sourceFields": [
    				"HotelName"
    			]
    		}
    	]
    };
    
    async function main() {
    
    	// Your search service endpoint
    	const searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
    
    	// Use the recommended keyless credential instead of the AzureKeyCredential credential.
    	const credential = new DefaultAzureCredential();
    	//const credential = new AzureKeyCredential(Your search service admin key);
    
    	// Create a SearchIndexClient to send create/delete index commands
    	const searchIndexClient: SearchIndexClient = new SearchIndexClient(
    		searchServiceEndpoint,
    		credential
    	);
    
    	// Creating a search client to upload documents and issue queries
    	const indexName: string  = "hotels-quickstart";
        const searchClient: SearchClient<any> = searchIndexClient.getSearchClient(indexName);
    
        console.log('Checking if index exists...');
        await deleteIndexIfExists(searchIndexClient, indexName);
    
        console.log('Creating index...');
        let index: SearchIndex = await searchIndexClient.createIndex(indexDefinition);
        console.log(`Index named ${index.name} has been created.`);
    
        console.log('Uploading documents...');
        let indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotelData['value']);
        console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)} `);
    
        // waiting one second for indexing to complete (for demo purposes only)
        sleep(1000);
    
        console.log('Querying the index...');
        console.log();
        await sendQueries(searchClient);
    }
    
    async function deleteIndexIfExists(searchIndexClient: SearchIndexClient, indexName: string) {
        try {
            await searchIndexClient.deleteIndex(indexName);
            console.log('Deleting index...');
        } catch {
            console.log('Index does not exist yet.');
        }
    }
    
    async function sendQueries(searchClient: SearchClient<any>) {
        // Query 1
        console.log('Query #1 - search everything:');
        let searchOptions: any = {
            includeTotalCount: true,
            select: ["HotelId", "HotelName", "Rating"]
        };
    
        let searchResults = await searchClient.search("*", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log(`Result count: ${searchResults.count}`);
        console.log();
    
    
        // Query 2
        console.log('Query #2 - search with filter, orderBy, and select:');
        let state = 'FL';
        searchOptions = {
            filter: odata`Address/StateProvince eq ${state}`,
            orderBy: ["Rating desc"],
            select: ["HotelId", "HotelName", "Rating"]
        };
    
        searchResults = await searchClient.search("wifi", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log();
    
        // Query 3
        console.log('Query #3 - limit searchFields:');
        searchOptions = {
            select: ["HotelId", "HotelName", "Rating"],
            searchFields: ["HotelName"]
        };
    
        searchResults = await searchClient.search("sublime cliff", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log();
    
        // Query 4
        console.log('Query #4 - limit searchFields and use facets:');
        searchOptions = {
            facets: ["Category"],
            select: ["HotelId", "HotelName", "Rating"],
            searchFields: ["HotelName"]
        };
    
        searchResults = await searchClient.search("*", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log();
    
        // Query 5
        console.log('Query #5 - Lookup document:');
        let documentResult = await searchClient.getDocument('3');
        console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`);
        console.log();
    }
    
    function sleep(ms: number) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    
    main().catch((err) => {
        console.error("The sample encountered an error:", err);
    });
    
  2. Créez un fichier nommé hotels.json et collez le code suivant dans hotels.json :

    {
        "value": [
            {
                "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.",
                "Description_fr": "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": ["pool", "air conditioning", "concierge"],
                "ParkingIncluded": false,
                "LastRenovationDate": "1970-01-18T00:00:00Z",
                "Rating": 3.6,
                "Address": {
                    "StreetAddress": "677 5th Ave",
                    "City": "New York",
                    "StateProvince": "NY",
                    "PostalCode": "10022"
                }
            },
            {
                "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.",
                "Description_fr": "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": ["pool", "free wifi", "concierge"],
                "ParkingIncluded": "false",
                "LastRenovationDate": "1979-02-18T00:00:00Z",
                "Rating": 3.6,
                "Address": {
                    "StreetAddress": "140 University Town Center Dr",
                    "City": "Sarasota",
                    "StateProvince": "FL",
                    "PostalCode": "34243"
                }
            },
            {
                "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.",
                "Description_fr": "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": ["air conditioning", "bar", "continental breakfast"],
                "ParkingIncluded": "true",
                "LastRenovationDate": "2015-09-20T00:00:00Z",
                "Rating": 4.8,
                "Address": {
                    "StreetAddress": "3393 Peachtree Rd",
                    "City": "Atlanta",
                    "StateProvince": "GA",
                    "PostalCode": "30326"
                }
            },
            {
                "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.",
                "Description_fr": "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": ["concierge", "view", "24-hour front desk service"],
                "ParkingIncluded": true,
                "LastRenovationDate": "1960-02-06T00:00:00Z",
                "Rating": 4.6,
                "Address": {
                    "StreetAddress": "7400 San Pedro Ave",
                    "City": "San Antonio",
                    "StateProvince": "TX",
                    "PostalCode": "78216"
                }
            }
        ]
    }
    
  3. Créez le fichier tsconfig.json pour transpiler le code TypeScript et copiez le code suivant pour ECMAScript.

    {
        "compilerOptions": {
          "module": "NodeNext",
          "target": "ES2022", // Supports top-level await
          "moduleResolution": "NodeNext",
          "skipLibCheck": true, // Avoid type errors from node_modules
          "strict": true // Enable strict type-checking options
        },
        "include": ["*.ts"]
    }
    
  4. Transpiler de TypeScript à JavaScript.

    tsc
    
  5. Connectez-vous à Azure à l’aide de la commande suivante :

    az login
    
  6. Exécutez le code JavaScript avec la commande suivante :

    node index.js
    

Explication du code

Créer un index

Créez un fichier hotels_quickstart_index.json. Ce fichier définit le fonctionnement de la Recherche Azure AI avec les documents que vous allez charger à l’étape suivante. Chaque champ est identifié par un name et a un type spécifié. Chaque champ comporte également une série d’attributs d’index qui spécifient si la recherche Azure AI peut effectuer des recherches, filtrer, trier et définir des propriétés de facettes sur le champ. La plupart des champs sont des types de données simples, mais certains, comme AddressType, sont des types complexes qui vous permettent de créer des structures de données riches dans votre index. Vous pouvez en savoir plus sur les types de données pris en charge et les attributs d’index décrits dans Créer un index (REST).

Nous voulons importer hotels_quickstart_index.json afin que la fonction principale puisse accéder à la définition d’index.

import indexDefinition from './hotels_quickstart_index.json';

interface HotelIndexDefinition {
    name: string;
    fields: SimpleField[] | ComplexField[];
    suggesters: SearchSuggester[];
};
const hotelIndexDefinition: HotelIndexDefinition = indexDefinition as HotelIndexDefinition;

Dans la fonction principale, nous créons ensuite un SearchIndexClient, qui est utilisé pour créer et gérer des index pour la recherche Azure AI.

const indexClient = new SearchIndexClient(endpoint, new AzureKeyCredential(apiKey));

Ensuite, nous voulons supprimer l’index s’il existe déjà. Il s’agit d’une opération courante pour le code de test/démonstration.

Pour ce faire, nous définissons une fonction simple qui tente de supprimer l’index.

async function deleteIndexIfExists(indexClient: SearchIndexClient, indexName: string): Promise<void> {
    try {
        await indexClient.deleteIndex(indexName);
        console.log('Deleting index...');
    } catch {
        console.log('Index does not exist yet.');
    }
}

Pour exécuter la fonction, nous extrayons le nom de l’index de la définition de l’index et nous passons indexName avec indexClient à la fonction deleteIndexIfExists().

// Getting the name of the index from the index definition
const indexName: string = hotelIndexDefinition.name;

console.log('Checking if index exists...');
await deleteIndexIfExists(indexClient, indexName);

Après cela, nous sommes prêts à créer l’index avec la méthode createIndex().

console.log('Creating index...');
let index = await indexClient.createIndex(hotelIndexDefinition);

console.log(`Index named ${index.name} has been created.`);

Chargement de documents

Dans la recherche Azure AI, les documents sont des structures de données qui sont à la fois des entrées pour l’indexation et des sorties de requêtes. Vous pouvez envoyer des données de ce type dans l’index ou utiliser un indexeur. Dans le cas présent, nous envoyons les documents à l’index programmatiquement.

Les entrées de documents peuvent être des lignes dans une base de données, des objets blob dans le Stockage Blob ou, comme dans cet exemple, des documents JSON sur le disque. Vous pouvez télécharger hotels.json ou créer votre propre fichier hotels.json avec le contenu suivant :

À l’instar de ce que nous avons fait avec indexDefinition, nous devons aussi importer hotels.json au-dessus de index.ts pour que les données soient accessibles dans notre fonction main.

import hotelData from './hotels.json';

interface Hotel {
    HotelId: string;
    HotelName: string;
    Description: string;
    Description_fr: string;
    Category: string;
    Tags: string[];
    ParkingIncluded: string | boolean;
    LastRenovationDate: string;
    Rating: number;
    Address: {
        StreetAddress: string;
        City: string;
        StateProvince: string;
        PostalCode: string;
    };
};

const hotels: Hotel[] = hotelData["value"];

Pour indexer les données dans l’index de recherche, nous devons maintenant créer un SearchClient. Alors que le SearchIndexClient est utilisé pour créer et gérer un index, le SearchClient est utilisé pour charger des documents et interroger l’index.

Il existe deux façons de créer un objet SearchClient. La première option consiste à créer un SearchClient à partir de zéro :

 const searchClient = new SearchClient<Hotel>(endpoint, indexName, new AzureKeyCredential(apiKey));

Vous pouvez également utiliser la méthode getSearchClient() du SearchIndexClient pour créer le SearchClient :

const searchClient = indexClient.getSearchClient<Hotel>(indexName);

Maintenant que le client est défini, chargez les documents dans l’index de recherche. Dans le cas présent, nous utilisons la méthode mergeOrUploadDocuments(), qui charge les documents ou les fusionne avec un document existant si un document ayant la même clé existe déjà. Vérifiez ensuite que l’opération a réussi, car au moins le premier document existe.

console.log("Uploading documents...");
const indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotels);

console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)}`);

Exécutez à nouveau le programme avec tsc && node index.ts. Vous devriez voir un ensemble de messages légèrement différents de ceux que vous avez vus à l’étape 1. Cette fois, l’index existe et vous voyez normalement un message à propos de sa suppression avant que l’application ne crée l’index et n’y envoie des données.

Avant d’exécuter les requêtes à l’étape suivante, définissez une fonction pour que le programme attende une seconde. Faites-le seulement à des fins de test ou de démonstration pour garantir que l’indexation se termine et que les documents sont disponibles dans l’index pour nos requêtes.

function sleep(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
}

Pour que le programme attende une seconde, appelez la fonction sleep :

sleep(1000);

Rechercher dans un index

Avec un index créé et des documents chargés, vous êtes prêt à envoyer des requêtes à l’index. Dans cette section, nous envoyons cinq requêtes différentes à l’index de recherche pour illustrer différentes fonctionnalités de requête à votre disposition.

Les requêtes sont écrites dans une fonction sendQueries() que nous appelons dans la fonction main comme suit :

await sendQueries(searchClient);

Les requêtes sont envoyées en utilisant la méthode search() de searchClient. Le premier paramètre est le texte recherché et le deuxième paramètre spécifie les options de recherche.

Exemple de requête 1

La première requête recherche *, ce qui équivaut à effectuer une recherche dans tout, et sélectionne trois des champs de l’index. C’est une bonne pratique que d’appliquer un select seulement aux champs dont vous avez besoin, car l’extraction de données inutiles peut ajouter de la latence à vos requêtes.

Les searchOptions de cette requête ont également includeTotalCount défini sur true, ce qui retourne le nombre de résultats correspondants trouvés.

async function sendQueries(
    searchClient: SearchClient<Hotel>
): Promise<void> {

    // Query 1
    console.log('Query #1 - search everything:');
    const selectFields: SearchFieldArray<Hotel> = [
        "HotelId",
        "HotelName",
        "Rating",
    ];
    const searchOptions1 = { 
        includeTotalCount: true, 
        select: selectFields 
    };

    let searchResults = await searchClient.search("*", searchOptions1);
    for await (const result of searchResults.results) {
        console.log(`${JSON.stringify(result.document)}`);
    }
    console.log(`Result count: ${searchResults.count}`);

    // remaining queries go here
}

Les requêtes restantes décrites ci-dessous doivent également être ajoutées à la fonction sendQueries(). Elles sont séparées ici seulement pour des raisons de lisibilité.

Exemple de requête 2

Dans la requête suivante, nous spécifions le terme de recherche "wifi" et nous incluons aussi un filtre pour retourner seulement les résultats où l’état est égal à 'FL'. Les résultats sont également classés selon la valeur du champ Rating de l’hôtel.

console.log('Query #2 - search with filter, orderBy, and select:');
let state = 'FL';
const searchOptions2 = {
    filter: odata`Address/StateProvince eq ${state}`,
    orderBy: ["Rating desc"],
    select: selectFields
};
searchResults = await searchClient.search("wifi", searchOptions2);
for await (const result of searchResults.results) {
    console.log(`${JSON.stringify(result.document)}`);
}

Exemple de requête 3

Ensuite, la recherche est limitée à un seul champ pouvant faire l’objet d’une recherche en utilisant le paramètre searchFields. Cette approche constitue une option intéressante pour améliorer l’efficacité de votre requête si vous savez que vous êtes intéressé seulement par les correspondances dans certains champs.

console.log('Query #3 - limit searchFields:');
const searchOptions3 = {
    select: selectFields,
    searchFields: ["HotelName"] as const
};

searchResults = await searchClient.search("Sublime Palace", searchOptions3);
for await (const result of searchResults.results) {
    console.log(`${JSON.stringify(result.document)}`);
}

Exemple de requête 4

Une autre option courante à inclure dans une requête est facets. Les facettes vous permettent de fournir une exploration auto-dirigée à partir des résultats dans votre interface utilisateur. Les résultats des facettes peuvent être transformés en cases à cocher dans le volet de résultats.

console.log('Query #4 - limit searchFields and use facets:');
const searchOptions4 = {
    facets: ["Category"],
    select: selectFields,
    searchFields: ["HotelName"] as const
};

searchResults = await searchClient.search("*", searchOptions4);
for await (const result of searchResults.results) {
    console.log(`${JSON.stringify(result.document)}`);
}

Exemple de requête 5

La dernière requête utilise la méthode getDocument() du searchClient. Ceci vous permet de récupérer efficacement un document en utilisant sa clé.

console.log('Query #5 - Lookup document:');
let documentResult = await searchClient.getDocument('3')
console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`)

Résumé des requêtes

Les requêtes précédentes illustrent plusieurs manières d’établir des correspondances entre des termes dans une requête : recherche en texte intégral, filtres et autocomplétion.

La recherche en texte intégral et les filtres sont exécutés en utilisant la méthode searchClient.search. Une requête de recherche peut être transmise dans la chaîne searchText, tandis qu’une expression de filtre peut être transmise dans la propriété filter de la classe SearchOptions. Pour filtrer sans effectuer de recherche, transmettez simplement « * » pour le paramètre searchText de la méthode search. Pour effectuer une recherche sans filtrage, ne définissez pas la propriété filter et ne transmettez aucune instance SearchOptions.

Nettoyer les ressources

Lorsque vous travaillez dans votre propre abonnement, il est recommandé, à la fin de chaque projet, de déterminer si vous avez toujours besoin des ressources que vous avez créées. Les ressources laissées en cours d’exécution peuvent vous coûter de l’argent. Vous pouvez supprimer les ressources individuellement, ou supprimer le groupe de ressources pour supprimer l’ensemble des ressources.

Vous pouvez rechercher et gérer des ressources dans le Portail Azure dans le volet de navigation de gauche, en cliquant sur le lien Toutes les ressources ou Groupes de ressources.

Si vous utilisez un service gratuit, n'oubliez pas que vous êtes limité à trois index, indexeurs et sources de données. Vous pouvez supprimer des éléments un par un sur le portail Azure pour ne pas dépasser la limite.

Étape suivante

Dans ce guide de démarrage rapide, vous avez effectué un ensemble de tâches pour créer un index, le charger avec des documents et exécuter des requêtes. À différents stades, nous avons pris des raccourcis afin de simplifier le code pour une meilleure lisibilité et compréhension. Maintenant que vous avez assimilé les concepts de base, essayez de suivre un tutoriel qui appelle les API de recherche Azure AI dans une application web.