Partilhar via


Guia de início rápido: pesquisa de texto completo usando os SDKs do Azure

Saiba como usar a biblioteca de cliente Azure.Search.Documents para criar, carregar e consultar um índice de pesquisa usando dados de exemplo para pesquisa de texto completo. A pesquisa de texto completo usa o Apache Lucene para indexação e consultas, e um algoritmo de classificação BM25 para pontuar resultados.

Este guia de início rápido cria e consulta um pequeno índice de início rápido de hotéis contendo dados sobre quatro hotéis.

Gorjeta

Você pode baixar o código-fonte para começar com um projeto concluído ou seguir estas etapas para criar o seu próprio.

Pré-requisitos

Pré-requisitos do Microsoft Entra ID

Para a autenticação sem chave recomendada com o Microsoft Entra ID, você precisa:

Recuperar informações do recurso

Você precisa recuperar as seguintes informações para autenticar seu aplicativo com seu serviço Azure AI Search:

Nome da variável Value
SEARCH_API_ENDPOINT Esse valor pode ser encontrado no portal do Azure. Selecione o seu serviço de pesquisa e, no menu à esquerda, selecione Visão geral. O valor Url em Essentials é o ponto de extremidade de que você precisa. Um ponto final de exemplo poderá ser parecido com https://mydemo.search.windows.net.

Saiba mais sobre autenticação sem chave e configuração de variáveis de ambiente.

Configurar

  1. Crie uma nova pasta full-text-quickstart para conter o aplicativo e abra o Visual Studio Code nessa pasta com o seguinte comando:

    mkdir full-text-quickstart && cd full-text-quickstart
    
  2. Crie um novo aplicativo de console com o seguinte comando:

    dotnet new console
    
  3. Instale a biblioteca de cliente do Azure AI Search (Azure.Search.Documents) para .NET com:

    dotnet add package Azure.Search.Documents
    
  4. Para a autenticação sem chave recomendada com o Microsoft Entra ID, instale o pacote Azure.Identity com:

    dotnet add package Azure.Identity
    
  5. Para a autenticação sem chave recomendada com o Microsoft Entra ID, entre no Azure com o seguinte comando:

    az login
    

Criar, carregar e consultar um índice de pesquisa

Na seção de configuração anterior, você criou um novo aplicativo de console e instalou a biblioteca de cliente do Azure AI Search.

Nesta seção, você adiciona código para criar um índice de pesquisa, carregá-lo com documentos e executar consultas. Execute o programa para ver os resultados no console. Para obter uma explicação detalhada do código, consulte a seção Explicando o código .

O código de exemplo neste início rápido usa o Microsoft Entra ID para a autenticação sem chave recomendada. Se preferir usar uma chave de API, você pode substituir o DefaultAzureCredential objeto por um AzureKeyCredential objeto.

Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");
DefaultAzureCredential credential = new();
  1. No Program.cs, cole o código a seguir. Edite as variáveis e apiKey com o nome do serviço de pesquisa e a serviceName chave da API de administração.

    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. Na mesma pasta, crie um novo arquivo chamado Hotel.cs e cole o código a seguir. Este código define a estrutura de um documento do hotel.

    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. Crie um novo arquivo chamado Hotel.cs e cole o código a seguir para definir a estrutura de um documento de hotel. Os atributos no campo determinam como ele é usado em um aplicativo. Por exemplo, o IsFilterable atributo deve ser atribuído a cada campo que ofereça suporte a uma expressão de filtro.

    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. Crie um novo arquivo chamado Address.cs e cole o código a seguir para definir a estrutura de um documento de endereço.

    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. Crie um novo arquivo chamado Hotel.Methods.cs e cole o código a seguir para definir uma ToString() substituição para a Hotel classe.

    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. Crie um novo arquivo chamado Address.Methods.cs e cole o código a seguir para definir uma ToString() substituição para a Address classe.

    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. Crie e execute o aplicativo com o seguinte comando:

    dotnet run
    

A saída inclui mensagens de Console.WriteLine, com a adição de informações de consulta e resultados.

Explicação do código

Nas seções anteriores, você criou um novo aplicativo de console e instalou a biblioteca de cliente do Azure AI Search. Você adicionou código para criar um índice de pesquisa, carregá-lo com documentos e executar consultas. Você executou o programa para ver os resultados no console.

Nesta seção, explicamos o código que você adicionou ao aplicativo de console.

Criar um cliente de pesquisa

No Program.cs, você criou dois clientes:

Ambos os clientes precisam do ponto de extremidade do serviço de pesquisa e das credenciais descritas anteriormente na seção de informações do recurso.

O código de exemplo neste início rápido usa o Microsoft Entra ID para a autenticação sem chave recomendada. Se preferir usar uma chave de API, você pode substituir o DefaultAzureCredential objeto por um AzureKeyCredential objeto.

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 . . . 
}

Criar um índice

Este guia de início rápido cria um índice de hotéis que você carrega com dados do hotel e executa consultas. Nesta etapa, você define os campos no índice. Cada definição de campo inclui um nome, tipo de dados e atributos que determinam como o campo é usado.

Neste exemplo, os métodos síncronos da biblioteca Azure.Search.Documents são usados para simplicidade e legibilidade. No entanto, para cenários de produção, você deve usar métodos assíncronos para manter seu aplicativo escalável e responsivo. Por exemplo, você usaria CreateIndexAsync em vez de CreateIndex.

Definir as estruturas

Você criou duas classes auxiliares, Hotel.cs e Address.cs, para definir a estrutura de um documento de hotel e seu endereço. A Hotel classe inclui campos para um ID de hotel, nome, descrição, categoria, tags, estacionamento, data de renovação, classificação e endereço. A Address classe inclui campos para endereço, cidade, estado/província, código postal e país/região.

Na biblioteca de cliente Azure.Search.Documents , você pode usar SearchableField e SimpleField para simplificar as definições de campo. Ambos são derivados de um SearchField e podem simplificar seu código:

  • SimpleField pode ser qualquer tipo de dados, é sempre não pesquisável (ignorado para consultas de pesquisa de texto completo) e é recuperável (não oculto). Outros atributos estão desativados por padrão, mas podem ser habilitados. Você pode usar um SimpleField para IDs de documento ou campos usados apenas em filtros, facetas ou perfis de pontuação. Em caso afirmativo, certifique-se de aplicar todos os atributos necessários para o cenário, como IsKey = true para uma ID de documento. Para obter mais informações, consulte SimpleFieldAttribute.cs no código-fonte.

  • SearchableField deve ser uma cadeia de caracteres e é sempre pesquisável e recuperável. Outros atributos estão desativados por padrão, mas podem ser habilitados. Como esse tipo de campo é pesquisável, ele suporta sinônimos e o complemento completo das propriedades do analisador. Para obter mais informações, consulte o SearchableFieldAttribute.cs no código-fonte.

Se você usa a API básica SearchField ou um dos modelos auxiliares, você deve habilitar explicitamente os atributos de filtro, faceta e classificação. Por exemplo, IsFilterable, IsSortable e IsFacetable devem ser explicitamente atribuídos, conforme mostrado no exemplo anterior.

Criar o índice de pesquisa

No Program.cs, você cria um objeto SearchIndex e, em seguida, chama o método CreateIndex para expressar o índice em seu serviço de pesquisa. O índice também inclui um SearchSuggester para habilitar o preenchimento automático nos campos especificados.

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

Carregar documentos

O Azure AI Search pesquisa sobre o conteúdo armazenado no serviço. Nesta etapa, você carrega documentos JSON que estão em conformidade com o índice de hotel criado.

No Azure AI Search, os documentos de pesquisa são estruturas de dados que são entradas para indexação e saídas de consultas. Conforme obtido de uma fonte de dados externa, as entradas de documentos podem ser linhas em um banco de dados, blobs no armazenamento de Blob ou documentos JSON no disco. Neste exemplo, estamos pegando um atalho e incorporando documentos JSON para quatro hotéis no próprio código.

Ao carregar documentos, você deve usar um objeto IndexDocumentsBatch . Um IndexDocumentsBatch objeto contém uma coleção de Ações, cada uma das quais contém um documento e uma propriedade informando ao Azure AI Search qual ação executar (carregar, mesclar, excluir e mesclarOrUpload).

No Program.cs, você cria uma matriz de documentos e ações de índice e, em seguida, passa a matriz para IndexDocumentsBatch. Os seguintes documentos estão em conformidade com o índice hotels-quickstart, conforme definido pela classe do 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
}

Depois de inicializar o objeto IndexDocumentsBatch, você pode enviá-lo para o índice chamando IndexDocuments no objeto SearchClient.

Você carrega documentos usando SearchClient no Main(), mas a operação também requer direitos de administrador no serviço, que normalmente é associado a SearchIndexClient. Uma maneira de configurar essa operação é obter SearchClient através (SearchIndexClientsearchIndexClientneste exemplo).

SearchClient ingesterClient = searchIndexClient.GetSearchClient(indexName);

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

Como temos um aplicativo de console que executa todos os comandos sequencialmente, adicionamos um tempo de espera de 2 segundos entre a indexação e as consultas.

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

O atraso de 2 segundos compensa a indexação, que é assíncrona, para que todos os documentos possam ser indexados antes que as consultas sejam executadas. A codificação em um atraso normalmente só é necessária em demonstrações, testes e aplicativos de exemplo.

Pesquisar um índice

Você pode obter os resultados da consulta assim que o primeiro documento for indexado, mas o teste real do índice deve aguardar até que todos os documentos sejam indexados.

Esta seção adiciona duas partes de funcionalidade: lógica de consulta e resultados. Para consultas, use o método Search . Esse método usa texto de pesquisa (a cadeia de caracteres de consulta) e outras opções.

A classe SearchResults representa os resultados.

No Program.cs, o método imprime os WriteDocuments resultados da pesquisa no 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();
}

Exemplo de consulta 1

O RunQueries método executa consultas e retorna resultados. Os resultados são objetos do Hotel. Este exemplo mostra a assinatura do método e a primeira consulta. Esta consulta demonstra o Select parâmetro que permite compor o resultado usando campos selecionados do documento.

// 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
}

Exemplo de consulta 2

Na segunda consulta, pesquise um termo, adicione um filtro que selecione documentos em que Classificação é maior que 4 e, em seguida, classifique por Classificação em ordem decrescente. Filter é uma expressão booleana que é avaliada em campos IsFilterable em um índice. As consultas de filtro incluem ou excluem valores. Como tal, não há pontuação de relevância associada a uma consulta de filtro.

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

Exemplo de consulta 3

A terceira consulta demonstra searchFields, usada para definir o escopo de uma operação de pesquisa de texto completo para campos específicos.

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

Exemplo de consulta 4

A quarta consulta demonstra facets, que pode ser usada para estruturar uma estrutura de navegação facetada.

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

Exemplo de consulta 5

Na quinta consulta, retorne um documento específico. Uma pesquisa de documentos é uma resposta típica a OnClick um evento em um conjunto de resultados.

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

Exemplo de consulta 6

A última consulta mostra a sintaxe para preenchimento automático, simulando uma entrada parcial do usuário de sa que resolve para duas correspondências possíveis no sourceFields associado ao sugeridor que você definiu no índice.

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

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

Resumo das consultas

As consultas anteriores mostram várias maneiras de corresponder termos em uma consulta: pesquisa de texto completo, filtros e preenchimento automático.

A pesquisa de texto completo e os filtros são realizados usando o método SearchClient.Search . Uma consulta de pesquisa pode ser passada na searchText cadeia de caracteres, enquanto uma expressão de filtro pode ser passada na propriedade Filter da classe SearchOptions . Para filtrar sem pesquisar, basta passar "*" pelo parâmetro do método SearchsearchText. Para pesquisar sem filtrar, deixe a Filter propriedade desdefinida ou não passe em uma SearchOptions instância.

Saiba como usar a biblioteca de cliente Azure.Search.Documents para criar, carregar e consultar um índice de pesquisa usando dados de exemplo para pesquisa de texto completo. A pesquisa de texto completo usa o Apache Lucene para indexação e consultas, e um algoritmo de classificação BM25 para pontuar resultados.

Este guia de início rápido cria e consulta um pequeno índice de início rápido de hotéis contendo dados sobre quatro hotéis.

Gorjeta

Você pode baixar o código-fonte para começar com um projeto concluído ou seguir estas etapas para criar o seu próprio.

Pré-requisitos

Pré-requisitos do Microsoft Entra ID

Para a autenticação sem chave recomendada com o Microsoft Entra ID, você precisa:

Recuperar informações do recurso

Você precisa recuperar as seguintes informações para autenticar seu aplicativo com seu serviço Azure AI Search:

Nome da variável Value
SEARCH_API_ENDPOINT Esse valor pode ser encontrado no portal do Azure. Selecione o seu serviço de pesquisa e, no menu à esquerda, selecione Visão geral. O valor Url em Essentials é o ponto de extremidade de que você precisa. Um ponto final de exemplo poderá ser parecido com https://mydemo.search.windows.net.

Saiba mais sobre autenticação sem chave e configuração de variáveis de ambiente.

Configurar

O exemplo neste início rápido funciona com o Java Runtime. Instale um Java Development Kit como o Azul Zulu OpenJDK. O Microsoft Build do OpenJDK ou o seu JDK preferido também deve funcionar.

  1. Instale o Apache Maven. Em seguida, execute mvn -v para confirmar a instalação bem-sucedida.

  2. Crie um novo pom.xml arquivo na raiz do seu projeto e copie o seguinte código para ele:

    <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. Instale as dependências, incluindo a biblioteca de cliente do Azure AI Search (Azure.Search.Documents) para Java e a biblioteca de cliente do Azure Identity para Java com:

    mvn clean dependency:copy-dependencies
    
  4. Para a autenticação sem chave recomendada com o Microsoft Entra ID, entre no Azure com o seguinte comando:

    az login
    

Criar, carregar e consultar um índice de pesquisa

Na seção de configuração anterior, você instalou a biblioteca de cliente do Azure AI Search e outras dependências.

Nesta seção, você adiciona código para criar um índice de pesquisa, carregá-lo com documentos e executar consultas. Execute o programa para ver os resultados no console. Para obter uma explicação detalhada do código, consulte a seção Explicando o código .

O código de exemplo neste início rápido usa o Microsoft Entra ID para a autenticação sem chave recomendada. Se preferir usar uma chave de API, você pode substituir o DefaultAzureCredential objeto por um AzureKeyCredential objeto.

String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
  1. Crie um novo arquivo chamado App.java e cole o seguinte código no 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. Crie um novo arquivo chamado Hotel.java e cole o seguinte código no 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. Crie um novo arquivo chamado Address.java e cole o seguinte código no 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. Execute seu novo aplicativo de console:

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

Explicação do código

Nas seções anteriores, você criou um novo aplicativo de console e instalou a biblioteca de cliente do Azure AI Search. Você adicionou código para criar um índice de pesquisa, carregá-lo com documentos e executar consultas. Você executou o programa para ver os resultados no console.

Nesta seção, explicamos o código que você adicionou ao aplicativo de console.

Criar um cliente de pesquisa

No App.java você criou dois clientes:

  • SearchIndexClient cria o índice.
  • SearchClient carrega e consulta um índice existente.

Ambos os clientes precisam do ponto de extremidade do serviço de pesquisa e das credenciais descritas anteriormente na seção de informações do recurso.

O código de exemplo neste início rápido usa o Microsoft Entra ID para a autenticação sem chave recomendada. Se preferir usar uma chave de API, você pode substituir o DefaultAzureCredential objeto por um AzureKeyCredential objeto.

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 . . . 
}

Criar um índice

Este guia de início rápido cria um índice de hotéis que você carrega com dados do hotel e executa consultas. Nesta etapa, você define os campos no índice. Cada definição de campo inclui um nome, tipo de dados e atributos que determinam como o campo é usado.

Neste exemplo, os métodos síncronos da biblioteca Azure.Search.Documents são usados para simplicidade e legibilidade. No entanto, para cenários de produção, você deve usar métodos assíncronos para manter seu aplicativo escalável e responsivo. Por exemplo, você usaria CreateIndexAsync em vez de CreateIndex.

Definir as estruturas

Você criou duas classes auxiliares, Hotel.java e Address.java, para definir a estrutura de um documento de hotel e seu endereço. A classe Hotel inclui campos para um ID do hotel, nome, descrição, categoria, tags, estacionamento, data de renovação, classificação e endereço. A classe Address inclui campos para endereço, cidade, estado/província, código postal e país/região.

Na biblioteca de cliente Azure.Search.Documents, você pode usar SearchableField e SimpleField para simplificar as definições de campo.

  • SimpleField pode ser qualquer tipo de dados, é sempre não pesquisável (ignorado para consultas de pesquisa de texto completo) e é recuperável (não oculto). Outros atributos estão desativados por padrão, mas podem ser habilitados. Você pode usar um SimpleField para IDs de documento ou campos usados apenas em filtros, facetas ou perfis de pontuação. Em caso afirmativo, certifique-se de aplicar todos os atributos necessários para o cenário, como IsKey = true para uma ID de documento.
  • SearchableField deve ser uma cadeia de caracteres e é sempre pesquisável e recuperável. Outros atributos estão desativados por padrão, mas podem ser habilitados. Como esse tipo de campo é pesquisável, ele suporta sinônimos e o complemento completo das propriedades do analisador.

Se você usa a API básica SearchField ou um dos modelos auxiliares, você deve habilitar explicitamente os atributos de filtro, faceta e classificação. Por exemplo, isFilterable, isSortable, e isFacetable deve ser explicitamente atribuído, como mostrado no exemplo anterior.

Criar o índice de pesquisa

No App.java, você cria um SearchIndex objeto no método e, em main seguida, chama o createOrUpdateIndex método para criar o índice em seu serviço de pesquisa. O índice também inclui um SearchSuggester para habilitar o preenchimento automático nos campos especificados.

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

Carregar documentos

O Azure AI Search pesquisa sobre o conteúdo armazenado no serviço. Nesta etapa, você carrega documentos JSON que estão em conformidade com o índice de hotel criado.

No Azure AI Search, os documentos de pesquisa são estruturas de dados que são entradas para indexação e saídas de consultas. Conforme obtido de uma fonte de dados externa, as entradas de documentos podem ser linhas em um banco de dados, blobs no armazenamento de Blob ou documentos JSON no disco. Neste exemplo, estamos pegando um atalho e incorporando documentos JSON para quatro hotéis no próprio código.

Ao carregar documentos, você deve usar um objeto IndexDocumentsBatch . Um IndexDocumentsBatch objeto contém uma coleção de IndexActions, cada um dos quais contém um documento e uma propriedade informando ao Azure AI Search qual ação executar (carregar, mesclar, excluir e mergeOrUpload).

No App.java, você cria documentos e indexa ações e, em seguida, passa-os para IndexDocumentsBatch. Os seguintes documentos estão em conformidade com o índice hotels-quickstart, conforme definido pela classe do 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");
    }
}

Depois de inicializar o IndexDocumentsBatch objeto, você pode enviá-lo para o índice chamando indexDocuments em seu SearchClient objeto.

Você carrega documentos usando SearchClient no main(), mas a operação também requer direitos de administrador no serviço, que normalmente é associado a SearchIndexClient. Uma maneira de configurar essa operação é obter SearchClient através (SearchIndexClientsearchIndexClientneste exemplo).

uploadDocuments(searchClient);

Como temos um aplicativo de console que executa todos os comandos sequencialmente, adicionamos um tempo de espera de 2 segundos entre a indexação e as consultas.

// 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)
{
}

O atraso de 2 segundos compensa a indexação, que é assíncrona, para que todos os documentos possam ser indexados antes que as consultas sejam executadas. A codificação em um atraso normalmente só é necessária em demonstrações, testes e aplicativos de exemplo.

Pesquisar um índice

Você pode obter os resultados da consulta assim que o primeiro documento for indexado, mas o teste real do índice deve aguardar até que todos os documentos sejam indexados.

Esta seção adiciona duas partes de funcionalidade: lógica de consulta e resultados. Para consultas, use o método Search. Esse método usa texto de pesquisa (a cadeia de caracteres de consulta) e outras opções.

No App.java, o método imprime os WriteDocuments resultados da pesquisa no 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();
}

Exemplo de consulta 1

O RunQueries método executa consultas e retorna resultados. Os resultados são objetos do Hotel. Este exemplo mostra a assinatura do método e a primeira consulta. Esta consulta demonstra o Select parâmetro que permite compor o resultado usando campos selecionados do documento.

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

Exemplo de consulta 2

Na segunda consulta, pesquise um termo, adicione um filtro que selecione documentos em que Classificação é maior que 4 e, em seguida, classifique por Classificação em ordem decrescente. Filtro é uma expressão booleana que é avaliada sobre isFilterable campos em um índice. As consultas de filtro incluem ou excluem valores. Como tal, não há pontuação de relevância associada a uma consulta de filtro.

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

Exemplo de consulta 3

A terceira consulta demonstra searchFields, usada para definir o escopo de uma operação de pesquisa de texto completo para campos específicos.

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

Exemplo de consulta 4

A quarta consulta demonstra facets, que pode ser usada para estruturar uma estrutura de navegação facetada.

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

Exemplo de consulta 5

Na quinta consulta, retorne um documento específico.

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

Exemplo de consulta 6

A última consulta mostra a sintaxe para preenchimento automático, simulando uma entrada parcial do usuário de s que resolve para duas correspondências possíveis no sourceFields associado com o sugeridor que você definiu no índice.

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

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

Resumo das consultas

As consultas anteriores mostram várias maneiras de corresponder termos em uma consulta: pesquisa de texto completo, filtros e preenchimento automático.

A pesquisa de texto completo e os filtros são realizados usando o método SearchClient.search . Uma consulta de pesquisa pode ser passada na searchText cadeia de caracteres, enquanto uma expressão de filtro pode ser passada na filter propriedade da classe SearchOptions . Para filtrar sem pesquisar, basta passar "*" para o searchText parâmetro do search método. Para pesquisar sem filtrar, deixe a filter propriedade desdefinida ou não passe em uma SearchOptions instância.

Saiba como usar a biblioteca de cliente Azure.Search.Documents para criar, carregar e consultar um índice de pesquisa usando dados de exemplo para pesquisa de texto completo. A pesquisa de texto completo usa o Apache Lucene para indexação e consultas, e um algoritmo de classificação BM25 para pontuar resultados.

Este guia de início rápido cria e consulta um pequeno índice de início rápido de hotéis contendo dados sobre quatro hotéis.

Gorjeta

Você pode baixar o código-fonte para começar com um projeto concluído ou seguir estas etapas para criar o seu próprio.

Pré-requisitos

Pré-requisitos do Microsoft Entra ID

Para a autenticação sem chave recomendada com o Microsoft Entra ID, você precisa:

Recuperar informações do recurso

Você precisa recuperar as seguintes informações para autenticar seu aplicativo com seu serviço Azure AI Search:

Nome da variável Value
SEARCH_API_ENDPOINT Esse valor pode ser encontrado no portal do Azure. Selecione o seu serviço de pesquisa e, no menu à esquerda, selecione Visão geral. O valor Url em Essentials é o ponto de extremidade de que você precisa. Um ponto final de exemplo poderá ser parecido com https://mydemo.search.windows.net.

Saiba mais sobre autenticação sem chave e configuração de variáveis de ambiente.

Configurar

  1. Crie uma nova pasta full-text-quickstart para conter o aplicativo e abra o Visual Studio Code nessa pasta com o seguinte comando:

    mkdir full-text-quickstart && cd full-text-quickstart
    
  2. Crie o package.json com o seguinte comando:

    npm init -y
    
  3. Instale a biblioteca de cliente do Azure AI Search (Azure.Search.Documents) para JavaScript com:

    npm install @azure/search-documents
    
  4. Para a autenticação sem senha recomendada , instale a biblioteca de cliente do Azure Identity com:

    npm install @azure/identity
    

Criar, carregar e consultar um índice de pesquisa

Na seção de configuração anterior, você instalou a biblioteca de cliente do Azure AI Search e outras dependências.

Nesta seção, você adiciona código para criar um índice de pesquisa, carregá-lo com documentos e executar consultas. Execute o programa para ver os resultados no console. Para obter uma explicação detalhada do código, consulte a seção Explicando o código .

O código de exemplo neste início rápido usa o Microsoft Entra ID para a autenticação sem chave recomendada. Se preferir usar uma chave de API, você pode substituir o DefaultAzureCredential objeto por um AzureKeyCredential objeto.

String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
  1. Crie um novo arquivo chamado index.js e cole o seguinte código no 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. Crie um arquivo chamado hotels.json e cole o seguinte código no 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. Crie um arquivo chamado hotels_quickstart_index.json e cole o seguinte código no 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. Entre no Azure com o seguinte comando:

    az login
    
  5. Execute o código JavaScript com o seguinte comando:

    node index.js
    

Explicação do código

Criar índice

O arquivo hotels_quickstart_index.json define como o Azure AI Search funciona com os documentos carregados na próxima etapa. Cada campo é identificado por um name e tem um especificado type. Cada campo também tem uma série de atributos de índice que especificam se o Azure AI Search pode pesquisar, filtrar, classificar e facetar o campo. A maioria dos campos são tipos de dados simples, mas alguns, como AddressType são tipos complexos que permitem criar estruturas de dados avançadas em seu índice. Você pode ler mais sobre os tipos de dados suportados e atributos de índice descritos em Criar índice (REST).

Com nossa definição de índice em vigor, queremos importar hotels_quickstart_index.json na parte superior da index.js para que a função principal possa acessar a definição de índice.

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

Dentro da função principal, criamos um SearchIndexClient, que é usado para criar e gerenciar índices para o Azure AI Search.

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

Em seguida, queremos excluir o índice, se ele já existir. Esta operação é uma prática comum para o código de teste/demonstração.

Fazemos isso definindo uma função simples que tenta excluir o índice.

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

Para executar a função, extraímos o nome do índice da definição do índice e passamos o indexName junto com o indexClient para a deleteIndexIfExists() função.

const indexName = indexDefinition["name"];

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

Depois disso, estamos prontos para criar o índice com o createIndex() método.

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

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

Carregar documentos

No Azure AI Search, documentos são estruturas de dados que são entradas para indexação e saídas de consultas. Você pode enviar esses dados por push para o índice ou usar um indexador. Nesse caso, enviaremos programaticamente os documentos para o índice.

As entradas de documentos podem ser linhas em um banco de dados, blobs no armazenamento de Blob ou, como neste exemplo, documentos JSON no disco. Semelhante ao que fizemos com o indexDefinition, também precisamos importar hotels.json no topo do index.js para que os dados possam ser acessados em nossa função principal.

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

Para indexar dados no índice de pesquisa, agora precisamos criar um SearchClientarquivo . Enquanto o SearchIndexClient é usado para criar e gerenciar um índice, o SearchClient é usado para carregar documentos e consultar o índice.

Há duas maneiras de criar um SearchClientarquivo . A primeira opção é criar um SearchClient a partir do zero:

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

Como alternativa, você pode usar o getSearchClient() método do SearchIndexClient para criar o SearchClient:

const searchClient = indexClient.getSearchClient(indexName);

Agora que o cliente está definido, carregue os documentos no índice de pesquisa. Neste caso, usamos o mergeOrUploadDocuments() método, que carrega os documentos ou os mescla com um documento existente se já existir um documento com a mesma chave.

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

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

Pesquisar um índice

Com um índice criado e documentos carregados, você está pronto para enviar consultas ao índice. Nesta seção, enviamos cinco consultas diferentes para o índice de pesquisa para demonstrar diferentes partes da funcionalidade de consulta disponível para você.

As consultas são escritas em uma sendQueries() função que chamamos na função principal da seguinte maneira:

await sendQueries(searchClient);

As consultas são enviadas usando o search() método de searchClient. O primeiro parâmetro é o texto da pesquisa e o segundo parâmetro especifica as opções de pesquisa.

Exemplo de consulta 1

A primeira consulta pesquisa *, o que equivale a pesquisar tudo e seleciona três dos campos no índice. É uma prática recomendada apenas select os campos de que você precisa, pois extrair dados desnecessários pode adicionar latência às suas consultas.

O searchOptions para esta consulta também tem includeTotalCount definido como true, que retorna o número de resultados correspondentes encontrados.

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
}

As restantes consultas descritas abaixo também devem ser adicionadas sendQueries() à função. Eles são separados aqui para facilitar a leitura.

Exemplo de consulta 2

Na consulta seguinte, especificamos o termo "wifi" de pesquisa e também incluímos um filtro para retornar resultados somente quando o estado for igual a 'FL'. Os resultados também são ordenados Ratingpelo hotel.

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)}`);
}

Exemplo de consulta 3

Em seguida, a pesquisa é limitada a um único campo pesquisável usando o searchFields parâmetro. Essa abordagem é uma ótima opção para tornar sua consulta mais eficiente se você souber que está interessado apenas em correspondências em determinados campos.

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

Exemplo de consulta 4

Outra opção comum a ser incluída em uma consulta é facets. As facetas permitem que você crie filtros em sua interface do usuário para tornar mais fácil para os usuários saberem quais valores eles podem filtrar.

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)}`);
}

Exemplo de consulta 5

A consulta final usa o getDocument() método do searchClient. Isso permite que você recupere eficientemente um documento por sua chave.

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

Resumo das consultas

As consultas anteriores mostram várias maneiras de corresponder termos em uma consulta: pesquisa de texto completo, filtros e preenchimento automático.

A pesquisa de texto completo e os filtros são realizados usando o searchClient.search método. Uma consulta de pesquisa pode ser passada na searchText cadeia de caracteres, enquanto uma expressão de filtro pode ser passada na filter propriedade da SearchOptions classe. Para filtrar sem pesquisar, basta passar "*" para o searchText parâmetro do search método. Para pesquisar sem filtrar, deixe a filter propriedade desdefinida ou não passe em uma SearchOptions instância.

Saiba como usar a biblioteca de cliente Azure.Search.Documents para criar, carregar e consultar um índice de pesquisa usando dados de exemplo para pesquisa de texto completo. A pesquisa de texto completo usa o Apache Lucene para indexação e consultas, e um algoritmo de classificação BM25 para pontuar resultados.

Este guia de início rápido cria e consulta um pequeno índice de início rápido de hotéis contendo dados sobre quatro hotéis.

Gorjeta

Você pode baixar e executar um bloco de anotações concluído.

Pré-requisitos

Pré-requisitos do Microsoft Entra ID

Para a autenticação sem chave recomendada com o Microsoft Entra ID, você precisa:

Recuperar informações do recurso

Você precisa recuperar as seguintes informações para autenticar seu aplicativo com seu serviço Azure AI Search:

Nome da variável Value
SEARCH_API_ENDPOINT Esse valor pode ser encontrado no portal do Azure. Selecione o seu serviço de pesquisa e, no menu à esquerda, selecione Visão geral. O valor Url em Essentials é o ponto de extremidade de que você precisa. Um ponto final de exemplo poderá ser parecido com https://mydemo.search.windows.net.

Saiba mais sobre autenticação sem chave e configuração de variáveis de ambiente.

Configurar o ambiente

Executar o código de exemplo em um bloco de anotações Jupyter. Então, você precisa configurar seu ambiente para executar blocos de anotações Jupyter.

  1. Baixe ou copie o bloco de anotações de exemplo do GitHub.

  2. Abra o bloco de anotações no Visual Studio Code.

  3. Crie um novo ambiente Python para usar para instalar os pacotes necessários para este tutorial.

    Importante

    Não instale pacotes em sua instalação global do python. Você deve sempre usar um ambiente virtual ou conda ao instalar pacotes python, caso contrário, você pode quebrar sua instalação global do Python.

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

    A configuração pode demorar um minuto. Se você tiver problemas, consulte Ambientes Python no VS Code.

  4. Instale os notebooks Jupyter e o kernel IPython para notebooks Jupyter, se ainda não os tiver.

    pip install jupyter
    pip install ipykernel
    python -m ipykernel install --user --name=.venv
    
  5. Selecione o kernel do bloco de anotações.

    1. No canto superior direito do bloco de anotações, selecione Selecionar kernel.
    2. Se vir .venv na lista, selecione-a. Se você não vê-lo, selecione Select Another Kernel>Python environments.>.venv

Criar, carregar e consultar um índice de pesquisa

Nesta seção, você adiciona código para criar um índice de pesquisa, carregá-lo com documentos e executar consultas. Execute o programa para ver os resultados no console. Para obter uma explicação detalhada do código, consulte a seção Explicando o código .

  1. Verifique se o bloco de anotações está aberto no .venv kernel conforme descrito na seção anterior.

  2. Execute a primeira célula de código para instalar os pacotes necessários, incluindo azure-search-documents.

    ! pip install azure-search-documents==11.6.0b1 --quiet
    ! pip install azure-identity --quiet
    ! pip install python-dotenv --quiet
    
  3. Substitua o conteúdo da segunda célula de código pelo código a seguir, dependendo do seu método de autenticação.

    Nota

    O código de exemplo neste início rápido usa o Microsoft Entra ID para a autenticação sem chave recomendada. Se preferir usar uma chave de API, você pode substituir o DefaultAzureCredential objeto por um AzureKeyCredential objeto.

    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. Remova as duas linhas a seguir da célula Criar um código de índice . As credenciais já estão definidas na célula de código anterior.

    from azure.core.credentials import AzureKeyCredential
    credential = AzureKeyCredential(search_api_key)
    
  5. Execute a célula Criar um código de índice para criar um índice de pesquisa.

  6. Execute as células de código restantes sequencialmente para carregar documentos e executar consultas.

Explicação do código

Criar um índice

SearchIndexClient é usado para criar e gerenciar índices para o Azure AI Search. Cada campo é identificado por um name e tem um especificado type.

Cada campo também tem uma série de atributos de índice que especificam se o Azure AI Search pode pesquisar, filtrar, classificar e facetar o campo. A maioria dos campos são tipos de dados simples, mas alguns, como AddressType são tipos complexos que permitem criar estruturas de dados avançadas em seu índice. Você pode ler mais sobre os tipos de dados suportados e atributos de índice descritos em Criar índice (REST).

Criar uma carga útil de documentos e carregar documentos

Use uma ação de índice para o tipo de operação, como upload ou mesclar e carregar. Os documentos são originários do exemplo HotelsData no GitHub.

Pesquisar um índice

Você pode obter os resultados da consulta assim que o primeiro documento for indexado, mas o teste real do índice deve aguardar até que todos os documentos sejam indexados.

Use o método de pesquisa da classe search.client.

As consultas de exemplo no bloco de anotações são:

  • Consulta básica: Executa uma pesquisa vazia (search=*), retornando uma lista não classificada (pontuação de pesquisa = 1,0) de documentos arbitrários. Como não há critérios, todos os documentos são incluídos nos resultados.
  • Consulta de termos: adiciona termos inteiros à expressão de pesquisa ("wifi"). Esta consulta especifica que os select resultados contêm apenas os campos na instrução. Limitar os campos que voltam minimiza a quantidade de dados enviados de volta por fio e reduz a latência de pesquisa.
  • Consulta filtrada: adicione uma expressão de filtro, retornando apenas os hotéis com classificação superior a quatro, classificados em ordem decrescente.
  • Escopo em campo: adicione search_fields ao escopo a execução da consulta a campos específicos.
  • Facetas: gere facetas para correspondências positivas encontradas nos resultados da pesquisa. Não há zero correspondências. Se os resultados da pesquisa não incluírem o termo wifi, o wifi não aparecerá na estrutura de navegação facetada.
  • Procurar um documento: devolve um documento com base na respetiva chave. Esta operação é útil se você quiser fornecer detalhamento quando um usuário seleciona um item em um resultado de pesquisa.
  • Preenchimento automático: forneça possíveis correspondências à medida que o usuário digita na caixa de pesquisa. O Preenchimento Automático usa um sugeridor (sg) para saber quais campos contêm possíveis correspondências para solicitações de sugestões. Neste início rápido, esses campos são Tags, Address/City, Address/Country. Para simular o preenchimento automático, passe as letras sa como uma cadeia de caracteres parcial. O método de preenchimento automático de SearchClient envia de volta possíveis correspondências de termos.

Remover o índice

Se tiver terminado este índice, pode eliminá-lo executando a célula de código Limpeza . A exclusão de índices desnecessários libera espaço para percorrer mais guias de início rápido e tutoriais.

Saiba como usar a biblioteca de cliente Azure.Search.Documents para criar, carregar e consultar um índice de pesquisa usando dados de exemplo para pesquisa de texto completo. A pesquisa de texto completo usa o Apache Lucene para indexação e consultas, e um algoritmo de classificação BM25 para pontuar resultados.

Este guia de início rápido cria e consulta um pequeno índice de início rápido de hotéis contendo dados sobre quatro hotéis.

Gorjeta

Você pode baixar o código-fonte para começar com um projeto concluído ou seguir estas etapas para criar o seu próprio.

Pré-requisitos

Pré-requisitos do Microsoft Entra ID

Para a autenticação sem chave recomendada com o Microsoft Entra ID, você precisa:

Recuperar informações do recurso

Você precisa recuperar as seguintes informações para autenticar seu aplicativo com seu serviço Azure AI Search:

Nome da variável Value
SEARCH_API_ENDPOINT Esse valor pode ser encontrado no portal do Azure. Selecione o seu serviço de pesquisa e, no menu à esquerda, selecione Visão geral. O valor Url em Essentials é o ponto de extremidade de que você precisa. Um ponto final de exemplo poderá ser parecido com https://mydemo.search.windows.net.

Saiba mais sobre autenticação sem chave e configuração de variáveis de ambiente.

Configurar

  1. Crie uma nova pasta full-text-quickstart para conter o aplicativo e abra o Visual Studio Code nessa pasta com o seguinte comando:

    mkdir full-text-quickstart && cd full-text-quickstart
    
  2. Crie o package.json com o seguinte comando:

    npm init -y
    
  3. Atualize o package.json para ECMAScript com o seguinte comando:

    npm pkg set type=module
    
  4. Instale a biblioteca de cliente do Azure AI Search (Azure.Search.Documents) para JavaScript com:

    npm install @azure/search-documents
    
  5. Para a autenticação sem senha recomendada , instale a biblioteca de cliente do Azure Identity com:

    npm install @azure/identity
    

Criar, carregar e consultar um índice de pesquisa

Na seção de configuração anterior, você instalou a biblioteca de cliente do Azure AI Search e outras dependências.

Nesta seção, você adiciona código para criar um índice de pesquisa, carregá-lo com documentos e executar consultas. Execute o programa para ver os resultados no console. Para obter uma explicação detalhada do código, consulte a seção Explicando o código .

O código de exemplo neste início rápido usa o Microsoft Entra ID para a autenticação sem chave recomendada. Se preferir usar uma chave de API, você pode substituir o DefaultAzureCredential objeto por um AzureKeyCredential objeto.

const searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
const credential = new DefaultAzureCredential();
  1. Crie um novo arquivo chamado index.ts e cole o seguinte código no 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. Crie um arquivo chamado hotels.json e cole o seguinte código no 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. Crie o tsconfig.json arquivo para transpilar o código TypeScript e copie o código a seguir para 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. Transpile de TypeScript para JavaScript.

    tsc
    
  5. Entre no Azure com o seguinte comando:

    az login
    
  6. Execute o código JavaScript com o seguinte comando:

    node index.js
    

Explicação do código

Criar índice

Crie um arquivo hotels_quickstart_index.json. Este arquivo define como o Azure AI Search funciona com os documentos que você carrega na próxima etapa. Cada campo é identificado por um name e tem um especificado type. Cada campo também tem uma série de atributos de índice que especificam se o Azure AI Search pode pesquisar, filtrar, classificar e facetar o campo. A maioria dos campos são tipos de dados simples, mas alguns, como AddressType são tipos complexos que permitem criar estruturas de dados avançadas em seu índice. Você pode ler mais sobre os tipos de dados suportados e atributos de índice descritos em Criar índice (REST).

Queremos importar hotels_quickstart_index.json para que a função principal possa acessar a definição do índice.

import indexDefinition from './hotels_quickstart_index.json';

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

Dentro da função principal, criamos um SearchIndexClient, que é usado para criar e gerenciar índices para o Azure AI Search.

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

Em seguida, queremos excluir o índice, se ele já existir. Esta operação é uma prática comum para o código de teste/demonstração.

Fazemos isso definindo uma função simples que tenta excluir o índice.

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.');
    }
}

Para executar a função, extraímos o nome do índice da definição do índice e passamos o indexName junto com o indexClient para a deleteIndexIfExists() função.

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

Depois disso, estamos prontos para criar o índice com o createIndex() método.

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

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

Carregar documentos

No Azure AI Search, documentos são estruturas de dados que são entradas para indexação e saídas de consultas. Você pode enviar esses dados por push para o índice ou usar um indexador. Nesse caso, enviaremos programaticamente os documentos para o índice.

As entradas de documentos podem ser linhas em um banco de dados, blobs no armazenamento de Blob ou, como neste exemplo, documentos JSON no disco. Você pode baixar hotels.json ou criar seu próprio arquivo hotels.json com o seguinte conteúdo:

Semelhante ao que fizemos com o indexDefinition, também precisamos importar hotels.json no topo do index.ts para que os dados possam ser acessados em nossa função principal.

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"];

Para indexar dados no índice de pesquisa, agora precisamos criar um SearchClientarquivo . Enquanto o SearchIndexClient é usado para criar e gerenciar um índice, o SearchClient é usado para carregar documentos e consultar o índice.

Há duas maneiras de criar um SearchClientarquivo . A primeira opção é criar um SearchClient a partir do zero:

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

Como alternativa, você pode usar o getSearchClient() método do SearchIndexClient para criar o SearchClient:

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

Agora que o cliente está definido, carregue os documentos no índice de pesquisa. Neste caso, usamos o mergeOrUploadDocuments() método, que carrega os documentos ou os mescla com um documento existente se já existir um documento com a mesma chave. Em seguida, verifique se a operação foi bem-sucedida porque pelo menos o primeiro documento existe.

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

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

Execute o programa novamente com tsc && node index.tso . Você deve ver um conjunto de mensagens ligeiramente diferente daquelas que você viu na Etapa 1. Desta vez, o índice existe e você deve ver uma mensagem sobre como excluí-lo antes que o aplicativo crie o novo índice e publique dados nele.

Antes de executarmos as consultas na próxima etapa, defina uma função para que o programa aguarde um segundo. Isso é feito apenas para fins de teste/demonstração para garantir que a indexação termine e que os documentos estejam disponíveis no índice para nossas consultas.

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

Para que o programa aguarde um segundo, chame a sleep função:

sleep(1000);

Pesquisar um índice

Com um índice criado e documentos carregados, você está pronto para enviar consultas ao índice. Nesta seção, enviamos cinco consultas diferentes para o índice de pesquisa para demonstrar diferentes partes da funcionalidade de consulta disponível para você.

As consultas são escritas em uma sendQueries() função que chamamos na função principal da seguinte maneira:

await sendQueries(searchClient);

As consultas são enviadas usando o search() método de searchClient. O primeiro parâmetro é o texto da pesquisa e o segundo parâmetro especifica as opções de pesquisa.

Exemplo de consulta 1

A primeira consulta pesquisa *, o que equivale a pesquisar tudo e seleciona três dos campos no índice. É uma prática recomendada apenas select os campos de que você precisa, pois extrair dados desnecessários pode adicionar latência às suas consultas.

O searchOptions para esta consulta também tem includeTotalCount definido como true, que retornará o número de resultados correspondentes encontrados.

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
}

As restantes consultas descritas abaixo também devem ser adicionadas sendQueries() à função. Eles são separados aqui para facilitar a leitura.

Exemplo de consulta 2

Na consulta seguinte, especificamos o termo "wifi" de pesquisa e também incluímos um filtro para retornar resultados somente quando o estado for igual a 'FL'. Os resultados também são ordenados Ratingpelo hotel.

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)}`);
}

Exemplo de consulta 3

Em seguida, a pesquisa é limitada a um único campo pesquisável usando o searchFields parâmetro. Essa abordagem é uma ótima opção para tornar sua consulta mais eficiente se você souber que está interessado apenas em correspondências em determinados campos.

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)}`);
}

Exemplo de consulta 4

Outra opção comum a ser incluída em uma consulta é facets. As facetas permitem que você forneça uma análise detalhada autodirigida a partir dos resultados em sua interface do usuário. Os resultados das facetas podem ser transformados em caixas de seleção no painel de resultados.

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)}`);
}

Exemplo de consulta 5

A consulta final usa o getDocument() método do searchClient. Isso permite que você recupere eficientemente um documento por sua chave.

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

Resumo das consultas

As consultas anteriores mostram várias maneiras de corresponder termos em uma consulta: pesquisa de texto completo, filtros e preenchimento automático.

A pesquisa de texto completo e os filtros são realizados usando o searchClient.search método. Uma consulta de pesquisa pode ser passada na searchText cadeia de caracteres, enquanto uma expressão de filtro pode ser passada na filter propriedade da SearchOptions classe. Para filtrar sem pesquisar, basta passar "*" para o searchText parâmetro do search método. Para pesquisar sem filtrar, deixe a filter propriedade desdefinida ou não passe em uma SearchOptions instância.

Recursos de limpeza

Ao trabalhar na sua própria subscrição, recomendamos que verifique, depois de concluir um projeto, se ainda vai precisar dos recursos que criou. Os recursos que deixar em execução podem custar dinheiro. Pode eliminar recursos individualmente ou eliminar o grupo de recursos para eliminar todo o conjunto de recursos.

Você pode localizar e gerenciar recursos no portal do Azure, usando o link Todos os recursos ou Grupos de recursos no painel de navegação esquerdo.

Se você estiver usando um serviço gratuito, lembre-se de que está limitado a três índices, indexadores e fontes de dados. Você pode excluir itens individuais no portal do Azure para permanecer abaixo do limite.

Próximo passo

Neste início rápido, você trabalhou em um conjunto de tarefas para criar um índice, carregá-lo com documentos e executar consultas. Em diferentes estágios, pegamos atalhos para simplificar o código para legibilidade e compreensão. Agora que você está familiarizado com os conceitos básicos, experimente um tutorial que chama as APIs do Azure AI Search em um aplicativo Web.