Início rápido: pesquisa de texto completa usando os SDKs do Azure
Saiba como usar a biblioteca de clientes 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 completa usa o Apache Lucene para indexação e consultas, e um algoritmo de classificação BM25 para pontuar resultados.
Este início rápido cria e consulta um pequeno índice de início rápido de hotéis que contém dados sobre quatro hotéis.
Dica
Você pode baixar o código-fonte para começar com um projeto concluído, ou seguir as etapas neste artigo para criar o seu.
Pré-requisitos
- Uma assinatura ativa do Azure: crie uma gratuitamente
- Um serviço do Azure AI Search. Crie um serviço, se não tiver um. É possível usar uma camada gratuita para este início rápido.
Pré-requisitos do Microsoft Entra ID
Para a autenticação sem chave recomendada com o Microsoft Entra ID, você precisa:
- Instale a CLI do Azure usada para autenticação sem chave com o Microsoft Entra ID.
- Atribua as funções
Search Service Contributor
eSearch Index Data Contributor
à sua conta de usuário. Você pode atribuir funções no portal do Azure em Controle de acesso (IAM)>Adicionar atribuição de função. Para obter mais informações, confira Conectar-se à Pesquisa de IA do Azure usando funções.
Recuperar as informações do recurso
Você precisa recuperar as seguintes informações para autenticar seu aplicativo com o serviço de Pesquisa de IA do Azure:
Nome da variável | Valor |
---|---|
SEARCH_API_ENDPOINT |
Esse valor pode ser encontrado no portal do Azure. Selecione o serviço de pesquisa e, no menu à esquerda, selecione Visão geral. O valor do URL em Essentials é o ponto de extremidade que você precisa. Um ponto de extremidade de exemplo pode parecer com https://mydemo.search.windows.net . |
Saiba mais sobre autenticação sem chave e configuração de variáveis de ambiente.
Configuração
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
Crie um novo aplicativo de console com o seguinte comando:
dotnet new console
Instale a biblioteca de cliente da Pesquisa de IA do Azure (Azure.Search.Documents) para .NET com:
dotnet add package Azure.Search.Documents
Para a autenticação sem chave recomendada com o Microsoft Entra ID, instale o pacote Azure.Identity com:
dotnet add package Azure.Identity
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 vetor
Na seção de configuração anterior, você criou um novo aplicativo de console e instalou a biblioteca de cliente da Pesquisa de IA do Azure.
Nesta seção, você adicionará o 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, confira 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 você preferir usar uma chave de API, poderá substituir o objeto DefaultAzureCredential
por um objeto AzureKeyCredential
.
Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");
DefaultAzureCredential credential = new();
Em Program.cs, cole o código a seguir. Edite as variáveis
serviceName
eapiKey
com o nome do serviço de pesquisa e a chave de API do administrador.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(); } } }
Na mesma pasta, crie um novo arquivo chamado Hotel.cs e cole o código a seguir. Esse 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; } } }
Crie um novo arquivo chamado Hotel.cs e cole o código a seguir para definir a estrutura de um documento do hotel. Os atributos no campo determinam como ele é usado em um aplicativo. Por exemplo, o atributo
IsFilterable
deve ser atribuído a cada campo que dá 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; } } }
Crie um novo arquivo chamado Address.cs e cole o código a seguir para definir a estrutura de um documento do 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; } } }
Crie um novo arquivo chamado Hotel.Methods.cs e cole o código a seguir para definir uma substituição
ToString()
para a classeHotel
.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(); } } }
Crie um novo arquivo chamado Address.Methods.cs e cole o código a seguir para definir uma substituição
ToString()
para a classeAddress
.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); } }
Crie e execute o aplicativo com o seguinte comando:
dotnet run
A saída inclui mensagens de Console.WriteLine, com a adição de resultados e informações de consulta.
Explicando o código
Nas seções anteriores, você criou um novo aplicativo de console e instalou a biblioteca de clientes da Pesquisa de IA do Azure. 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, explicaremos o código que você adicionou ao aplicativo de console.
Criar um cliente de pesquisa
Em Program.cs, 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 você preferir usar uma chave de API, poderá substituir o objeto DefaultAzureCredential
por um objeto AzureKeyCredential
.
Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");
DefaultAzureCredential credential = new();
static void Main(string[] args)
{
// Your search service endpoint
Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");
// Use the recommended keyless credential instead of the AzureKeyCredential credential.
DefaultAzureCredential credential = new();
//AzureKeyCredential credential = new AzureKeyCredential("Your search service admin key");
// Create a SearchIndexClient to send create/delete index commands
SearchIndexClient searchIndexClient = new SearchIndexClient(serviceEndpoint, credential);
// Create a SearchClient to load and query documents
string indexName = "hotels-quickstart";
SearchClient searchClient = new SearchClient(serviceEndpoint, indexName, credential);
// REDACTED FOR BREVITY . . .
}
Crie um índice
Este guia de início rápido cria um índice de Hotéis que você carrega com os dados de hotéis e no qual executará consultas. Nesta etapa, você define os campos no índice. Cada definição de campo inclui nome, tipo de dados e atributos que determinam como o campo é usado.
Nesse exemplo, 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 escaloná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 classe Hotel
inclui campos para uma ID do hotel, nome, descrição, categoria, marcas, estacionamento, data de reforma, classificação e endereço. A classe Address
inclui campos para endereço de rua, cidade, estado/província, CEP 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 derivativos de um SearchField e podem simplificar o código:
SimpleField
pode ser qualquer tipo de dados, é sempre não pesquisável (ignorado para consultas de pesquisa de texto completo) e pode ser recuperado (não oculto). Os outros atributos estão desativados por padrão, mas podem ser habilitados. Você pode usar umSimpleField
para IDs ou campos de documentos usados somente em filtros, facetas ou perfis de pontuação. Nesse caso, lembre-se de aplicar todos os atributos necessários para o cenário, comoIsKey = true
para uma ID de documento. Para obter mais informações, confira SimpleFieldAttribute.cs no código-fonte.SearchableField
precisa ser uma cadeia de caracteres e sempre é pesquisável e recuperável. Os outros atributos estão desativados por padrão, mas podem ser habilitados. Como esse tipo de campo é pesquisável, ele dá suporte a sinônimos e ao complemento completo de propriedades do analisador. Para obter mais informações, confira SearchableFieldAttribute.cs no código-fonte.
Se você usar a API SearchField
básica ou um dos modelos auxiliares, precisará habilitar explicitamente os atributos de filtro, faceta e classificação. Por exemplo, IsFilterable, IsSortable e IsFacetable devem ser explicitamente atribuídos, conforme mostrado na amostra anterior.
Criar o índice de pesquisa
Em Program.cs, você cria um objeto SearchIndex e cham\ o método CreateIndex para expressar o índice no 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);
}
Carregue os documentos
O Azure AI Search pesquisa o conteúdo armazenado no serviço. Nesta etapa, você carrega documentos JSON que estão em conformidade com o índice do hotel que você criou.
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 documento podem ser linhas em um banco de dados, blobs no Armazenamento de Blobs ou documentos JSON no disco. Neste exemplo, estamos usando um atalho e inserindo documentos JSON para quatro hotéis no próprio código.
Ao carregar documentos, você deve usar um objeto IndexDocumentsBatch. Um objeto IndexDocumentsBatch
contém uma coleção de Ações, cada uma contendo um documento e uma propriedade informando ao Azure AI Search qual ação executar (carregar, mesclagem, exclusão e mergeOrUpload).
Em Program.cs, você cria uma matriz de documentos e ações de índice e passa a matriz para IndexDocumentsBatch
. Os documentos a seguir estão em conformidade com o índice hotéis-início rápido, 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 ao índice chamando IndexDocuments no objeto SearchClient.
Você carrega documentos usando o SearchClient em Main()
, mas a operação também requer direitos de administrador no serviço, que normalmente está associado ao SearchIndexClient. Uma maneira de configurar essa operação é obter o SearchClient SearchIndexClient
(searchIndexClient
nesse 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 indexação e 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, de modo que todos os documentos possam ser indexados antes da execução das consultas. Normalmente, a codificação em um atraso só é necessária em demonstrações, testes e aplicativos de exemplo.
Pesquisar um índice
Você poderá obter os resultados da consulta, assim que o primeiro documento for indexado, mas o teste real do índice deverá aguardar até todos os documentos serem indexados.
Esta seção adiciona duas funcionalidades: lógica da consulta e resultados. Para consultas, use o método Search. Esse método usa o texto de pesquisa (a cadeia de consulta) e outras opções.
A classe SearchResults representa os resultados.
Em Program.cs, o método WriteDocuments
imprime os 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 método RunQueries
executa as consultas e retorna resultados. Os resultados são os objetos Hotel. Este exemplo mostra a assinatura do método e a primeira consulta. Essa consulta demonstra o parâmetro Select
que permite compor o resultando usando os campos 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 onde Classificação seja maior que 4 e, em seguida, classifique por Classificação em ordem decrescente. Um filtro é uma expressão booliana avaliada em campos IsFilterable em um índice. As consultas de filtro incluem ou excluem valores. Assim, não há nenhuma 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
, usado para definir o escopo de uma operação de pesquisa de texto completo em 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 usado 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 documento é uma resposta típica ao evento OnClick
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 duas correspondências possíveis nos sourceFields associados ao sugestivo 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 executados usando o método SearchClient.Search. Uma consulta de pesquisa pode ser passada na cadeia de caracteres searchText
, enquanto uma expressão de filtro pode ser passada na propriedade Filtro da classe SearchOptions. Para filtrar sem pesquisar, basta passar "*"
para o parâmetro searchText
do método de Search. Para pesquisar sem filtrar, deixe a propriedade Filter
não definida ou simplesmente não passe uma instância SearchOptions
.
Saiba como usar a biblioteca de clientes 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 completa usa o Apache Lucene para indexação e consultas, e um algoritmo de classificação BM25 para pontuar resultados.
Este início rápido cria e consulta um pequeno índice de início rápido de hotéis que contém dados sobre quatro hotéis.
Dica
Você pode baixar o código-fonte para começar com um projeto concluído, ou seguir as etapas neste artigo para criar o seu.
Pré-requisitos
- Uma assinatura ativa do Azure: crie uma gratuitamente
- Um serviço do Azure AI Search. Crie um serviço, se não tiver um. É possível usar uma camada gratuita para este início rápido.
Pré-requisitos do Microsoft Entra ID
Para a autenticação sem chave recomendada com o Microsoft Entra ID, você precisa:
- Instale a CLI do Azure usada para autenticação sem chave com o Microsoft Entra ID.
- Atribua as funções
Search Service Contributor
eSearch Index Data Contributor
à sua conta de usuário. Você pode atribuir funções no portal do Azure em Controle de acesso (IAM)>Adicionar atribuição de função. Para obter mais informações, confira Conectar-se à Pesquisa de IA do Azure usando funções.
Recuperar as informações do recurso
Você precisa recuperar as seguintes informações para autenticar seu aplicativo com o serviço de Pesquisa de IA do Azure:
Nome da variável | Valor |
---|---|
SEARCH_API_ENDPOINT |
Esse valor pode ser encontrado no portal do Azure. Selecione o serviço de pesquisa e, no menu à esquerda, selecione Visão geral. O valor do URL em Essentials é o ponto de extremidade que você precisa. Um ponto de extremidade de exemplo pode parecer com https://mydemo.search.windows.net . |
Saiba mais sobre autenticação sem chave e configuração de variáveis de ambiente.
Configuração
O exemplo deste guia de início rápido funciona com o Runtime Java. Instalar um Kit de Desenvolvimento do Java, como o Azul Zulu OpenJDK. O Build da Microsoft do OpenJDK ou seu JDK preferencial também devem funcionar.
Instale o Apache Maven. Em seguida, execute
mvn -v
para confirmar a instalação bem-sucedida.Crie um novo arquivo
pom.xml
na raiz do projeto e copie nele o seguinte código:<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>
Instale as dependências, incluindo a biblioteca de clientes da Pesquisa de IA do Azure (Azure.Search.Documents) para Java e a biblioteca de clientes do Azure Identity para Java com:
mvn clean dependency:copy-dependencies
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 vetor
Na seção de configuração anterior, você instalou a biblioteca de clientes da Pesquisa de IA do Azure e outras dependências.
Nesta seção, você adicionará o 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, confira 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 você preferir usar uma chave de API, poderá substituir o objeto DefaultAzureCredential
por um objeto AzureKeyCredential
.
String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
Crie um novo arquivo chamado App.java e cole o seguinte código em 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")); } }
Crie um novo arquivo chamado Hotel.java e cole o seguinte código em 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 ""; } } }
Crie um novo arquivo chamado Address.java e cole o seguinte código em 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; }
Execute seu novo aplicativo de console:
javac Address.java App.java Hotel.java -cp ".;target\dependency\*" java -cp ".;target\dependency\*" App
Explicando o código
Nas seções anteriores, você criou um novo aplicativo de console e instalou a biblioteca de clientes da Pesquisa de IA do Azure. 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, explicaremos o código que você adicionou ao aplicativo de console.
Criar um cliente de pesquisa
Em 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 você preferir usar uma chave de API, poderá substituir o objeto DefaultAzureCredential
por um objeto AzureKeyCredential
.
String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
public static void main(String[] args) {
// Your search service endpoint
String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
// Use the recommended keyless credential instead of the AzureKeyCredential credential.
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
//AzureKeyCredential credential = new AzureKeyCredential("Your search service admin key");
// Create a SearchIndexClient to send create/delete index commands
SearchIndexClient searchIndexClient = new SearchIndexClientBuilder()
.endpoint(searchServiceEndpoint)
.credential(credential)
.buildClient();
// Create a SearchClient to load and query documents
String indexName = "hotels-quickstart-java";
SearchClient searchClient = new SearchClientBuilder()
.endpoint(searchServiceEndpoint)
.credential(credential)
.indexName(indexName)
.buildClient();
// Create Search Index for Hotel model
searchIndexClient.createOrUpdateIndex(
new SearchIndex(indexName, SearchIndexClient.buildSearchFields(Hotel.class, null))
.setSuggesters(new SearchSuggester("sg", Arrays.asList("HotelName"))));
// REDACTED FOR BREVITY . . .
}
Crie um índice
Este guia de início rápido cria um índice de Hotéis que você carrega com os dados de hotéis e no qual executará consultas. Nesta etapa, você define os campos no índice. Cada definição de campo inclui nome, tipo de dados e atributos que determinam como o campo é usado.
Nesse exemplo, 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 escaloná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 do hotel e seu endereço. A classe Hotel inclui campos para uma ID do hotel, nome, descrição, categoria, marcas, estacionamento, data de reforma, classificação e endereço. A classe Endereço inclui campos para endereço de rua, cidade, estado/província, CEP e país/região.
Na biblioteca de clientes Azure.Search.Documents, use 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 pode ser recuperado (não oculto). Os outros atributos estão desativados por padrão, mas podem ser habilitados. Você pode usar um SimpleField para IDs ou campos de documento usados somente em filtros, facetas ou perfis de pontuação. Nesse caso, aplique todos os atributos necessários para o cenário, como IsKey = true para uma ID de documento. -
SearchableField
precisa ser uma cadeia de caracteres e sempre é pesquisável e recuperável. Os outros atributos estão desativados por padrão, mas podem ser habilitados. Como esse tipo de campo é pesquisável, ele dá suporte a sinônimos e ao complemento completo de propriedades do analisador.
Se você usar a API SearchField
básica ou um dos modelos auxiliares, precisará habilitar explicitamente os atributos de filtro, faceta e classificação. Por exemplo, isFilterable
, isSortable
, e isFacetable
e devem ser atribuídos explicitamente, conforme mostrado no exemplo anterior.
Criar o índice de pesquisa
Em App.java
, você cria um objeto SearchIndex
no método main
e, em seguida, chama o método createOrUpdateIndex
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"))));
Carregue os documentos
O Azure AI Search pesquisa o conteúdo armazenado no serviço. Nesta etapa, você carrega documentos JSON que estão em conformidade com o índice do hotel que você criou.
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 documento podem ser linhas em um banco de dados, blobs no Armazenamento de Blobs ou documentos JSON no disco. Neste exemplo, estamos usando um atalho e inserindo documentos JSON para quatro hotéis no próprio código.
Ao carregar documentos, você deve usar um objeto IndexDocumentsBatch. Um objeto IndexDocumentsBatch
contém uma coleção de IndexActions, cada uma contendo um documento e uma propriedade informando ao Azure AI Search qual ação executar (carregar, mesclar, excluir e mesclarOrUpload).
Em App.java
, você cria documentos e ações de índice e, em seguida, passa-os para IndexDocumentsBatch
. Os documentos a seguir estão em conformidade com o índice hotéis-início rápido, 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 objeto IndexDocumentsBatch
, você poderá enviá-lo ao índice chamando indexDocuments no objeto SearchClient
.
Você carrega documentos usando o SearchClient em main()
, mas a operação também requer direitos de administrador no serviço, que normalmente está associado ao SearchIndexClient. Uma maneira de configurar essa operação é obter o SearchClient SearchIndexClient
(searchIndexClient
nesse exemplo).
uploadDocuments(searchClient);
Como temos um aplicativo de console que executa todos os comandos sequencialmente, adicionamos um tempo de espera de 2 segundos entre indexação e 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, de modo que todos os documentos possam ser indexados antes da execução das consultas. Normalmente, a codificação em um atraso só é necessária em demonstrações, testes e aplicativos de exemplo.
Pesquisar um índice
Você poderá obter os resultados da consulta, assim que o primeiro documento for indexado, mas o teste real do índice deverá aguardar até todos os documentos serem indexados.
Esta seção adiciona duas funcionalidades: lógica da consulta e resultados. Para consultas, use o método Search. Esse método usa o texto de pesquisa (a cadeia de consulta) e outras opções.
Em App.java
, o método WriteDocuments
imprime os 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 método RunQueries
executa as consultas e retorna resultados. Os resultados são os objetos Hotel. Este exemplo mostra a assinatura do método e a primeira consulta. Essa consulta demonstra o parâmetro Select
que permite compor o resultando usando os campos 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 cuja classificação seja maior que 4 e, em seguida, classifique por Classificação em ordem decrescente. Um filtro é uma expressão booliana avaliada em campos isFilterable
em um índice. As consultas de filtro incluem ou excluem valores. Assim, não há nenhuma 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
, usado para definir o escopo de uma operação de pesquisa de texto completo em 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 usado 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 duas correspondências possíveis no sourceFields
associado ao sugestivo 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 executados usando o método SearchClient.search. Uma consulta de pesquisa pode ser passada na cadeia de caracteres searchText
, enquanto uma expressão de filtro pode ser passada na propriedade filter
da classe SearchOptions. Para filtrar sem pesquisar, basta passar "*" ao parâmetro searchText
do método search
. Para pesquisar sem filtrar, deixe a propriedade filter
não definida ou simplesmente não passe uma instância SearchOptions
.
Saiba como usar a biblioteca de clientes 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 completa usa o Apache Lucene para indexação e consultas, e um algoritmo de classificação BM25 para pontuar resultados.
Este início rápido cria e consulta um pequeno índice de início rápido de hotéis que contém dados sobre quatro hotéis.
Dica
Você pode baixar o código-fonte para começar com um projeto concluído, ou seguir as etapas neste artigo para criar o seu.
Pré-requisitos
- Uma assinatura ativa do Azure: crie uma gratuitamente
- Um serviço do Azure AI Search. Crie um serviço, se não tiver um. É possível usar uma camada gratuita para este início rápido.
Pré-requisitos do Microsoft Entra ID
Para a autenticação sem chave recomendada com o Microsoft Entra ID, você precisa:
- Instale a CLI do Azure usada para autenticação sem chave com o Microsoft Entra ID.
- Atribua as funções
Search Service Contributor
eSearch Index Data Contributor
à sua conta de usuário. Você pode atribuir funções no portal do Azure em Controle de acesso (IAM)>Adicionar atribuição de função. Para obter mais informações, confira Conectar-se à Pesquisa de IA do Azure usando funções.
Recuperar as informações do recurso
Você precisa recuperar as seguintes informações para autenticar seu aplicativo com o serviço de Pesquisa de IA do Azure:
Nome da variável | Valor |
---|---|
SEARCH_API_ENDPOINT |
Esse valor pode ser encontrado no portal do Azure. Selecione o serviço de pesquisa e, no menu à esquerda, selecione Visão geral. O valor do URL em Essentials é o ponto de extremidade que você precisa. Um ponto de extremidade de exemplo pode parecer com https://mydemo.search.windows.net . |
Saiba mais sobre autenticação sem chave e configuração de variáveis de ambiente.
Configuração
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
Crie o
package.json
com o seguinte comando:npm init -y
Instale a biblioteca de cliente da Pesquisa de IA do Azure (Azure.Search.Documents) para JavaScript com:
npm install @azure/search-documents
Para a autenticação sem senha recomendada, instale a biblioteca de clientes do Azure Identity com:
npm install @azure/identity
Criar, carregar e consultar um índice de vetor
Na seção de configuração anterior, você instalou a biblioteca de clientes da Pesquisa de IA do Azure e outras dependências.
Nesta seção, você adicionará o 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, confira 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 você preferir usar uma chave de API, poderá substituir o objeto DefaultAzureCredential
por um objeto AzureKeyCredential
.
String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
Crie um novo arquivo chamado index.js e cole o seguinte código em 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); });
Crie um arquivo chamado hotels.json e cole o seguinte código em 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" } } ] }
Crie um arquivo chamado hotels_quickstart_index.json e cole o seguinte código em 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" ] } ] }
Entre no Azure com o seguinte comando:
az login
Execute o código JavaScript com o seguinte comando:
node index.js
Explicando o código
Criar índice
O arquivo hotels_quickstart_index.json define como a Pesquisa de IA do Azure 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 é composta por tipos de dados simples, mas alguns, como AddressType
, são tipos complexos que permitem que você crie estruturas de dados avançadas em seu índice. Leia mais sobre os tipos de dados compatíveis e os 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 do index.js para que a função principal possa acessar a definição do índice.
const indexDefinition = require('./hotels_quickstart_index.json');
Na 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, caso ele já exista. Essa operação é uma prática comum para 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 função deleteIndexIfExists()
.
const indexName = indexDefinition["name"];
console.log('Checking if index exists...');
await deleteIndexIfExists(indexClient, indexName);
Depois disso, estamos prontos para criar o índice com o método createIndex()
.
console.log('Creating index...');
let index = await indexClient.createIndex(indexDefinition);
console.log(`Index named ${index.name} has been created.`);
Carregue os documentos
No Azure AI Search, os documentos são estruturas de dados que são entradas para indexação e saídas de consultas. Você pode enviar esses dados para o índice ou usar um indexador. Nesse caso, programaticamente enviaremos os documentos para o índice.
As entradas de documento podem ser linhas em um banco de dados, blobs no armazenamento de Blobs ou, como neste exemplo, documentos JSON em 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 SearchClient
. Embora o SearchIndexClient
seja usado para criar e gerenciar um índice, o SearchClient
é usado para carregar documentos e consultar o índice.
Há duas maneiras de criar um SearchClient
. A primeira opção é criar um SearchClient
do zero:
const searchClient = new SearchClient(endpoint, indexName, new AzureKeyCredential(apiKey));
Como alternativa, você pode usar o método getSearchClient()
de SearchIndexClient
para criar o SearchClient
:
const searchClient = indexClient.getSearchClient(indexName);
Agora que o cliente está definido, carregue os documentos no índice de pesquisa. Nesse caso, usamos o método mergeOrUploadDocuments()
, que carrega os documentos ou os mescla com um documento existente, caso já exista 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 para o índice. Nessa seção, enviamos cinco consultas diferentes ao índice de pesquisa para demonstrar diferentes funcionalidades de consulta disponíveis para você.
As consultas são escritas em uma função sendQueries()
que chamamos na função principal da seguinte maneira:
await sendQueries(searchClient);
As consultas são enviadas usando o método search()
de searchClient
. O primeiro parâmetro é o texto de pesquisa, e o segundo parâmetro especifica opções de pesquisa.
Exemplo de consulta 1
A primeira consulta pesquisa *
, que é equivalente a pesquisar tudo e seleciona três dos campos no índice. É uma prática recomendada a apenas select
dos campos de que você precisa, pois a extração de dados desnecessários pode adicionar latência às suas consultas.
O searchOptions
para essa 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 consultas restantes descritas abaixo também devem ser adicionadas à função sendQueries()
. Elas são separadas aqui para facilitar a leitura.
Exemplo de consulta 2
Na próxima consulta, especificamos o termo de pesquisa "wifi"
e incluímos um filtro para retornar apenas os resultados em que o estado é igual a 'FL'
. Os resultados também são ordenados pela Rating
do 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 parâmetro searchFields
. Essa abordagem será 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 método getDocument()
de searchClient
. Isso permite que você recupere com eficiência 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.
Os filtros e a pesquisa de texto completo são executados usando o método searchClient.search
. Uma consulta de pesquisa pode ser passada na cadeia de caracteres searchText
, enquanto uma expressão de filtro pode ser passada na propriedade filter
da classe SearchOptions
. Para filtrar sem pesquisar, basta passar "*" ao parâmetro searchText
do método search
. Para pesquisar sem filtrar, deixe a propriedade filter
não definida ou simplesmente não passe uma instância SearchOptions
.
Saiba como usar a biblioteca de clientes 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 completa usa o Apache Lucene para indexação e consultas, e um algoritmo de classificação BM25 para pontuar resultados.
Este início rápido cria e consulta um pequeno índice de início rápido de hotéis que contém dados sobre quatro hotéis.
Dica
Você pode baixar e executar um notebook concluído.
Pré-requisitos
- Uma assinatura ativa do Azure: crie uma gratuitamente
- Um serviço do Azure AI Search. Crie um serviço, se não tiver um. É possível usar uma camada gratuita para este início rápido.
- Visual Studio Code com a extensão Python (ou um IDE equivalente), com Python 3.10 ou posterior. Se você não tiver uma versão adequada do Python instalada, poderá seguir as instruções no Tutorial do Python do VS Code.
Pré-requisitos do Microsoft Entra ID
Para a autenticação sem chave recomendada com o Microsoft Entra ID, você precisa:
- Instale a CLI do Azure usada para autenticação sem chave com o Microsoft Entra ID.
- Atribua as funções
Search Service Contributor
eSearch Index Data Contributor
à sua conta de usuário. Você pode atribuir funções no portal do Azure em Controle de acesso (IAM)>Adicionar atribuição de função. Para obter mais informações, confira Conectar-se à Pesquisa de IA do Azure usando funções.
Recuperar as informações do recurso
Você precisa recuperar as seguintes informações para autenticar seu aplicativo com o serviço de Pesquisa de IA do Azure:
Nome da variável | Valor |
---|---|
SEARCH_API_ENDPOINT |
Esse valor pode ser encontrado no portal do Azure. Selecione o serviço de pesquisa e, no menu à esquerda, selecione Visão geral. O valor do URL em Essentials é o ponto de extremidade que você precisa. Um ponto de extremidade de exemplo pode parecer com https://mydemo.search.windows.net . |
Saiba mais sobre autenticação sem chave e configuração de variáveis de ambiente.
Configure seu ambiente
Você executa o código de exemplo em um Jupyter Notebook. Portanto, você precisa configurar seu ambiente para executar Jupyter notebooks.
Baixe ou copie o notebook de exemplo do GitHub.
Abra o notebook no Visual Studio Code.
Crie um novo ambiente Python que será usado para instalar o pacote necessário para este tutorial.
Importante
Não instale pacotes na instalação global do Python. Você sempre deve usar um ambiente virtual ou conda ao instalar pacotes do Python, caso contrário, poderá interromper a instalação global do Python.
Pode levar um minuto para ser configurado. Se você tiver problemas, consulte os Ambientes do Python no VS Code.
Instale Jupyter notebooks e o Kernel IPython para Jupyter notebooks se você ainda não os tiver.
pip install jupyter pip install ipykernel python -m ipykernel install --user --name=.venv
Selecione o kernel do notebook.
- No canto superior direito do notebook, escolha Selecionar Kernel.
- Se você vir
.venv
na lista, selecione-o. Se você não vê-lo, selecione Selecionar Outro Kernel>Ambientes Python>.venv
.
Criar, carregar e consultar um índice de vetor
Nesta seção, você adicionará o 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, confira a seção explicando o código.
Verifique se o notebook está aberto no kernel
.venv
conforme descrito na seção anterior.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
Substitua o conteúdo da segunda célula de código pelo código a seguir, dependendo do método de autenticação.
Observação
O código de exemplo neste início rápido usa o Microsoft Entra ID para a autenticação sem chave recomendada. Se você preferir usar uma chave de API, poderá substituir o objeto
DefaultAzureCredential
por um objetoAzureKeyCredential
.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"
Remova as duas linhas a seguir da célula do código Criar um índice. As credenciais já estão definidas na célula do código anterior.
from azure.core.credentials import AzureKeyCredential credential = AzureKeyCredential(search_api_key)
Execute a célula do código Criar um índice para criar um índice de pesquisa.
Execute as células do código restantes sequencialmente para carregar documentos e executar consultas.
Explicando o código
Crie um índice
SearchIndexClient
é usado para criar e gerenciar índices para a Pesquisa de IA do Azure. 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 é composta por tipos de dados simples, mas alguns, como AddressType
, são tipos complexos que permitem que você crie estruturas de dados avançadas em seu índice. Leia mais sobre os tipos de dados compatíveis e os atributos de índice descritos em Criar índice (REST).
Criar um conteúdo de documentos e carregar documentos
Use uma ação de índice para o tipo de operação, como upload ou mesclar e fazer upload. Os documentos são originários do exemplo HotelsData no GitHub.
Pesquisar um índice
Você poderá obter os resultados da consulta, assim que o primeiro documento for indexado, mas o teste real do índice deverá aguardar até todos os documentos serem indexados.
Use o método search da classe search.client.
As consultas de exemplo no notebook 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á nenhum critério, todos os documentos são incluídos nos resultados. - Consulta de termo: adiciona termos inteiros à expressão de pesquisa ("wifi"). Essa consulta especifica que os resultados contêm apenas os campos na instrução
select
. Limitar os campos retornados minimiza a quantidade de dados enviados de volta pela rede e reduz a latência de pesquisa. - Consulta filtrada: adiciona uma expressão de filtro, retornando apenas os hotéis com uma classificação maior que quatro, classificada em ordem decrescente.
- Escopo em campo: adiciona
search_fields
à execução da consulta do escopo a campos específicos. - Facetas: gera 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, wifi não aparecerá na estrutura de navegação facetada.
- Pesquisar um documento: retorna um documento com base em sua chave. Essa operação é útil se você quiser fornecer detalhamento quando um usuário seleciona um item em um resultado de pesquisa.
- Preenchimento automático: fornece possíveis correspondências conforme o usuário digita na caixa de pesquisa. O preenchimento automático usa um sugestor (
sg
) para saber quais campos contêm possíveis correspondências para solicitações sugestivas. Neste início rápido, esses campos sãoTags
,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 correspondências de potenciais termos.
Remover o índice
Se você terminar esse índice, poderá excluí-lo executando a célula do código Limpar. 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 clientes 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 completa usa o Apache Lucene para indexação e consultas, e um algoritmo de classificação BM25 para pontuar resultados.
Este início rápido cria e consulta um pequeno índice de início rápido de hotéis que contém dados sobre quatro hotéis.
Dica
Você pode baixar o código-fonte para começar com um projeto concluído, ou seguir as etapas neste artigo para criar o seu.
Pré-requisitos
- Uma assinatura ativa do Azure: crie uma gratuitamente
- Um serviço do Azure AI Search. Crie um serviço, se não tiver um. É possível usar uma camada gratuita para este início rápido.
Pré-requisitos do Microsoft Entra ID
Para a autenticação sem chave recomendada com o Microsoft Entra ID, você precisa:
- Instale a CLI do Azure usada para autenticação sem chave com o Microsoft Entra ID.
- Atribua as funções
Search Service Contributor
eSearch Index Data Contributor
à sua conta de usuário. Você pode atribuir funções no portal do Azure em Controle de acesso (IAM)>Adicionar atribuição de função. Para obter mais informações, confira Conectar-se à Pesquisa de IA do Azure usando funções.
Recuperar as informações do recurso
Você precisa recuperar as seguintes informações para autenticar seu aplicativo com o serviço de Pesquisa de IA do Azure:
Nome da variável | Valor |
---|---|
SEARCH_API_ENDPOINT |
Esse valor pode ser encontrado no portal do Azure. Selecione o serviço de pesquisa e, no menu à esquerda, selecione Visão geral. O valor do URL em Essentials é o ponto de extremidade que você precisa. Um ponto de extremidade de exemplo pode parecer com https://mydemo.search.windows.net . |
Saiba mais sobre autenticação sem chave e configuração de variáveis de ambiente.
Configuração
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
Crie o
package.json
com o seguinte comando:npm init -y
Atualize o
package.json
para ECMAScript com o seguinte comando:npm pkg set type=module
Instale a biblioteca de cliente da Pesquisa de IA do Azure (Azure.Search.Documents) para JavaScript com:
npm install @azure/search-documents
Para a autenticação sem senha recomendada, instale a biblioteca de clientes do Azure Identity com:
npm install @azure/identity
Criar, carregar e consultar um índice de vetor
Na seção de configuração anterior, você instalou a biblioteca de clientes da Pesquisa de IA do Azure e outras dependências.
Nesta seção, você adicionará o 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, confira 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 você preferir usar uma chave de API, poderá substituir o objeto DefaultAzureCredential
por um objeto AzureKeyCredential
.
const searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
const credential = new DefaultAzureCredential();
Crie um novo arquivo chamado index.ts e cole o seguinte código em 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); });
Crie um arquivo chamado hotels.json e cole o seguinte código em 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" } } ] }
Crie o arquivo
tsconfig.json
para transpilar o código TypeScript e copiar 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"] }
Transpile de TypeScript para JavaScript.
tsc
Entre no Azure com o seguinte comando:
az login
Execute o código JavaScript com o seguinte comando:
node index.js
Explicando o código
Criar índice
Crie um arquivo hotels_quickstart_index. JSON. Este arquivo define como a Pesquisa de IA do Azure 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 é composta por tipos de dados simples, mas alguns, como AddressType
, são tipos complexos que permitem que você crie estruturas de dados avançadas em seu índice. Leia mais sobre os tipos de dados compatíveis e os 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 de índice.
import indexDefinition from './hotels_quickstart_index.json';
interface HotelIndexDefinition {
name: string;
fields: SimpleField[] | ComplexField[];
suggesters: SearchSuggester[];
};
const hotelIndexDefinition: HotelIndexDefinition = indexDefinition as HotelIndexDefinition;
Na 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, caso ele já exista. Essa operação é uma prática comum para 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 função deleteIndexIfExists()
.
// Getting the name of the index from the index definition
const indexName: string = hotelIndexDefinition.name;
console.log('Checking if index exists...');
await deleteIndexIfExists(indexClient, indexName);
Depois disso, estamos prontos para criar o índice com o método createIndex()
.
console.log('Creating index...');
let index = await indexClient.createIndex(hotelIndexDefinition);
console.log(`Index named ${index.name} has been created.`);
Carregue os documentos
No Azure AI Search, os documentos são estruturas de dados que são entradas para indexação e saídas de consultas. Você pode enviar esses dados para o índice ou usar um indexador. Nesse caso, programaticamente enviaremos os documentos para o índice.
As entradas de documento podem ser linhas em um banco de dados, blobs no armazenamento de Blobs ou, como neste exemplo, documentos JSON em disco. Você pode baixar hotels.json ou criar seu próprio arquivo hotels.json com o seguinte conteúdo:
Semelhante ao que fizemos com indexDefinition, também precisamos importar hotels.json
no topo de 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 SearchClient
. Embora o SearchIndexClient
seja usado para criar e gerenciar um índice, o SearchClient
é usado para carregar documentos e consultar o índice.
Há duas maneiras de criar um SearchClient
. A primeira opção é criar um SearchClient
do zero:
const searchClient = new SearchClient<Hotel>(endpoint, indexName, new AzureKeyCredential(apiKey));
Como alternativa, você pode usar o método getSearchClient()
de SearchIndexClient
para criar o SearchClient
:
const searchClient = indexClient.getSearchClient<Hotel>(indexName);
Agora que o cliente está definido, carregue os documentos no índice de pesquisa. Nesse caso, usamos o método mergeOrUploadDocuments()
, que carrega os documentos ou os mescla com um documento existente, caso já exista 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.ts
. Você verá um conjunto de mensagens ligeiramente diferente daquelas vistas na Etapa 1. Desta vez, o índice existe e você deverá ver uma mensagem sobre como excluí-lo antes que o aplicativo crie o novo índice e poste 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 seja concluída 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 para o índice. Nessa seção, enviamos cinco consultas diferentes ao índice de pesquisa para demonstrar diferentes funcionalidades de consulta disponíveis para você.
As consultas são escritas em uma função sendQueries()
que chamamos na função principal da seguinte maneira:
await sendQueries(searchClient);
As consultas são enviadas usando o método search()
de searchClient
. O primeiro parâmetro é o texto de pesquisa, e o segundo parâmetro especifica opções de pesquisa.
Exemplo de consulta 1
A primeira consulta pesquisa *
, que é equivalente a pesquisar tudo e seleciona três dos campos no índice. É uma prática recomendada a apenas select
dos campos de que você precisa, pois a extração de dados desnecessários pode adicionar latência às suas consultas.
O searchOptions
para essa consulta também tem includeTotalCount
definido como true
, o 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 consultas restantes descritas abaixo também devem ser adicionadas à função sendQueries()
. Elas são separadas aqui para facilitar a leitura.
Exemplo de consulta 2
Na próxima consulta, especificamos o termo de pesquisa "wifi"
e incluímos um filtro para retornar apenas os resultados em que o estado é igual a 'FL'
. Os resultados também são ordenados pela Rating
do 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 parâmetro searchFields
. Essa abordagem será 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 detalhamento autodirigido dos resultados em sua IU. 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 método getDocument()
de searchClient
. Isso permite que você recupere com eficiência 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.
Os filtros e a pesquisa de texto completo são executados usando o método searchClient.search
. Uma consulta de pesquisa pode ser passada na cadeia de caracteres searchText
, enquanto uma expressão de filtro pode ser passada na propriedade filter
da classe SearchOptions
. Para filtrar sem pesquisar, basta passar "*" ao parâmetro searchText
do método search
. Para pesquisar sem filtrar, deixe a propriedade filter
não definida ou simplesmente não passe uma instância SearchOptions
.
Recursos de limpeza
Quando você está trabalhando em sua própria assinatura, é uma boa ideia identificar, no final de um projeto, se você ainda precisa dos recursos criados. Recursos deixados em execução podem custar dinheiro. É possível excluir os recursos individualmente ou excluir o grupo de recursos para excluir todo o conjunto de recursos.
Você pode encontrar e gerenciar recursos no portal do Azure usando o link Todos os recursos ouGrupos de recursos no painel de navegação à esquerda.
Se você estiver usando um serviço gratuito, estará 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óxima etapa
Neste guia de início rápido, você trabalhou com um conjunto de tarefas para criar um índice, carregá-lo com documentos e executar consultas. Em diferentes estágios, usamos atalhos para simplificar o código a fim de facilitar a leitura e a 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.