Escolha uma linguagem de programação para a próxima etapa. As bibliotecas de cliente Azure.Search.Documents estão disponíveis nos SDKs do Azure para .NET, Python, Java e JavaScript/Typescript.
Crie um aplicativo de console usando a biblioteca de clientes Azure.Search.Documents para criar, carregar e consultar um índice de pesquisa.
Como alternativa, 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.
Configure seu ambiente
Inicie o Visual Studio e crie um novo projeto para um aplicativo de console.
Em Ferramentas>Gerenciador de Pacotes NuGet, selecione Gerenciar Pacotes NuGet para a Solução... .
Selecione Procurar.
Pesquise pelo pacote Azure.Search.Documents e selecione a versão 11.0 ou posterior.
Selecione Instalar para adicionar o assembly ao projeto e à solução.
Criar um cliente de pesquisa
Em Program.cs, altere o namespace para AzureSearch.SDK.Quickstart.v11
e, em seguida, adicione as seguintes diretivas using
.
using Azure;
using Azure.Search.Documents;
using Azure.Search.Documents.Indexes;
using Azure.Search.Documents.Indexes.Models;
using Azure.Search.Documents.Models;
Copie o código a seguir para criar dois clientes. SearchIndexClient cria o índice e SearchClient carrega e consulta um índice existente. Ambos precisam do ponto de extremidade de serviço e de uma chave de API de administração para autenticação com direitos de criação/exclusão.
Como o código cria o URI para você, especifique apenas o nome do serviço de pesquisa na propriedade serviceName
.
static void Main(string[] args)
{
string serviceName = "<your-search-service-name>";
string apiKey = "<your-search-service-admin-api-key>";
string indexName = "hotels-quickstart";
// Create a SearchIndexClient to send create/delete index commands
Uri serviceEndpoint = new Uri($"https://{serviceName}.search.windows.net/");
AzureKeyCredential credential = new AzureKeyCredential(apiKey);
SearchIndexClient adminClient = new SearchIndexClient(serviceEndpoint, credential);
// Create a SearchClient to load and query documents
SearchClient srchclient = new SearchClient(serviceEndpoint, indexName, credential);
. . .
}
Crie um índice
Este guia de início rápido cria um índice de Hotéis que você carregará com os dados de hotéis e no qual executará consultas. Nesta etapa, defina 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.
Adicione uma definição de classe vazia ao seu projeto: Hotel.cs
Copie o código a seguir para Hotel.cs 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; }
}
}
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 em consultas de pesquisa de texto completo) e é recuperável (não está oculto). Os outros atributos estão desativados por padrão, mas podem ser habilitados. Você pode usar um SimpleField
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, como IsKey = 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.
Adicione uma segunda definição de classe vazia ao seu projeto: Address.cs. Copie o código a seguir para a classe.
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 mais duas classes: Hotel.Methods.cs e Address.Methods.cs para ToString()
substituições. Essas classes são usadas para renderizar os resultados da pesquisa na saída do console. O conteúdo dessas classes não é fornecido neste artigo, mas você pode copiar o código dos arquivos no GitHub.
Em Program.cs, crie um objeto SearchIndex e chame 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 adminClient)
{
FieldBuilder fieldBuilder = new FieldBuilder();
var searchFields = fieldBuilder.Build(typeof(Hotel));
var definition = new SearchIndex(indexName, searchFields);
var suggester = new SearchSuggester("sg", new[] { "HotelName", "Category", "Address/City", "Address/StateProvince" });
definition.Suggesters.Add(suggester);
adminClient.CreateOrUpdateIndex(definition);
}
Carregue os documentos
O Azure AI Search pesquisa o conteúdo armazenado no serviço. Nesta etapa, você carregará documentos JSON que estão em conformidade com o índice do hotel que você acabou de criar.
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, crie uma matriz de documentos e ações de índice e passe 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"
}
}),
IndexDocumentsAction.Upload(
new Hotel()
{
HotelId = "2",
HotelName = "Old Century 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.",
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 = "Gastronomic 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 Palace 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.",
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.",
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}");
}
}
Depois de inicializar o objeto IndexDocumentsBatch, você pode enviá-lo ao índice chamando IndexDocuments no objeto SearchClient.
Adicione as linhas a seguir a Main()
. O carregamento de documentos é feito usando SearchClient, mas a operação também requer direitos de administrador no serviço, que normalmente é associado a SearchIndexClient. Uma maneira de configurar essa operação é obter o SearchClient SearchIndexClient
(adminClient
nesse exemplo).
SearchClient ingesterClient = adminClient.GetSearchClient(indexName);
// Load documents
Console.WriteLine("{0}", "Uploading documents...\n");
UploadDocuments(ingesterClient);
Como esse é um aplicativo de console que executa todos os comandos sequencialmente, adicione um tempo de espera de dois segundos entre a indexação e as consultas.
// Wait 2 seconds for indexing to complete before starting queries (for demo and console-app purposes only)
Console.WriteLine("Waiting for indexing...\n");
System.Threading.Thread.Sleep(2000);
O atraso de 2 segundos compensa a indexação, que é assíncrona, 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, crie um WriteDocuments
método que imprima 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();
}
Crie um método RunQueries
para executar consultas e retornar 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 srchclient)
{
SearchOptions options;
SearchResults<Hotel> response;
// Query 1
Console.WriteLine("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n");
options = new SearchOptions()
{
IncludeTotalCount = true,
Filter = "",
OrderBy = { "" }
};
options.Select.Add("HotelId");
options.Select.Add("HotelName");
options.Select.Add("Address/City");
response = srchclient.Search<Hotel>("*", options);
WriteDocuments(response);
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 = srchclient.Search<Hotel>("hotels", options);
WriteDocuments(response);
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 = srchclient.Search<Hotel>("pool", options);
WriteDocuments(response);
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 = srchclient.Search<Hotel>("*", options);
WriteDocuments(response);
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 = srchclient.GetDocument<Hotel>("3");
Console.WriteLine(lookupResponse.Value.HotelId);
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 = srchclient.Autocomplete("sa", "sg");
WriteDocuments(autoresponse);
Adicione RunQueries
a Main()
.
// Call the RunQueries method to invoke a series of queries
Console.WriteLine("Starting queries...\n");
RunQueries(srchclient);
// End the program
Console.WriteLine("{0}", "Complete. Press any key to end this program...\n");
Console.ReadKey();
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
.
Executar o programa
Pressione F5 para reconstruir o aplicativo e executar o programa por completo.
A saída inclui mensagens de Console.WriteLine, com a adição de resultados e informações de consulta.
Use um Jupyter notebook e a biblioteca azure-search-documents no SDK do Azure para Python para criar, carregar e consultar um índice de pesquisa.
Como alternativa, é possível baixar e executar um notebook concluído.
Configure seu ambiente
Use o Visual Studio Code com a extensão Python ou um IDE equivalente com Python 3.10 ou posterior.
É recomendável um ambiente virtual para este início rápido:
Inicie o Visual Studio Code.
Abra a paleta de comandos (Ctrl+Shift+P).
Pesquisar Python: criar ambiente.
Selecione Venv.
Selecione um interpretador do Python. Escolha a versão 3.10 ou posterior.
Pode levar um minuto para ser configurado. Se você tiver problemas, consulte os Ambientes do Python no VS Code.
Instalar pacotes e definir variáveis
Instale pacotes, incluindo azure-search-documents.
! pip install azure-search-documents==11.6.0b1 --quiet
! pip install azure-identity --quiet
! pip install python-dotenv --quiet
Forneça o ponto de extremidade e a chave de API para seu serviço:
search_endpoint: str = "PUT-YOUR-SEARCH-SERVICE-ENDPOINT-HERE"
search_api_key: str = "PUT-YOUR-SEARCH-SERVICE-ADMIN-API-KEY-HERE"
index_name: str = "hotels-quickstart"
Crie um índice
from azure.core.credentials import AzureKeyCredential
credential = AzureKeyCredential(search_api_key)
from azure.search.documents.indexes import SearchIndexClient
from azure.search.documents import SearchClient
from azure.search.documents.indexes.models import (
ComplexField,
SimpleField,
SearchFieldDataType,
SearchableField,
SearchIndex
)
# Create a search schema
index_client = SearchIndexClient(
endpoint=search_endpoint, credential=credential)
fields = [
SimpleField(name="HotelId", type=SearchFieldDataType.String, key=True),
SearchableField(name="HotelName", type=SearchFieldDataType.String, sortable=True),
SearchableField(name="Description", type=SearchFieldDataType.String, analyzer_name="en.lucene"),
SearchableField(name="Description_fr", type=SearchFieldDataType.String, analyzer_name="fr.lucene"),
SearchableField(name="Category", type=SearchFieldDataType.String, facetable=True, filterable=True, sortable=True),
SearchableField(name="Tags", collection=True, type=SearchFieldDataType.String, facetable=True, filterable=True),
SimpleField(name="ParkingIncluded", type=SearchFieldDataType.Boolean, facetable=True, filterable=True, sortable=True),
SimpleField(name="LastRenovationDate", type=SearchFieldDataType.DateTimeOffset, facetable=True, filterable=True, sortable=True),
SimpleField(name="Rating", type=SearchFieldDataType.Double, facetable=True, filterable=True, sortable=True),
ComplexField(name="Address", fields=[
SearchableField(name="StreetAddress", type=SearchFieldDataType.String),
SearchableField(name="City", type=SearchFieldDataType.String, facetable=True, filterable=True, sortable=True),
SearchableField(name="StateProvince", type=SearchFieldDataType.String, facetable=True, filterable=True, sortable=True),
SearchableField(name="PostalCode", type=SearchFieldDataType.String, facetable=True, filterable=True, sortable=True),
SearchableField(name="Country", type=SearchFieldDataType.String, facetable=True, filterable=True, sortable=True),
])
]
scoring_profiles = []
suggester = [{'name': 'sg', 'source_fields': ['Tags', 'Address/City', 'Address/Country']}]
# Create the search index
index = SearchIndex(name=index_name, fields=fields, suggesters=suggester, scoring_profiles=scoring_profiles)
result = index_client.create_or_update_index(index)
print(f' {result.name} created')
Criar um conteúdo de 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.
# Create a documents payload
documents = [
{
"@search.action": "upload",
"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.",
"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.60,
"Address": {
"StreetAddress": "677 5th Ave",
"City": "New York",
"StateProvince": "NY",
"PostalCode": "10022",
"Country": "USA"
}
},
{
"@search.action": "upload",
"HotelId": "2",
"HotelName": "Old Century 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.",
"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.60,
"Address": {
"StreetAddress": "140 University Town Center Dr",
"City": "Sarasota",
"StateProvince": "FL",
"PostalCode": "34243",
"Country": "USA"
}
},
{
"@search.action": "upload",
"HotelId": "3",
"HotelName": "Gastronomic 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.80,
"Address": {
"StreetAddress": "3393 Peachtree Rd",
"City": "Atlanta",
"StateProvince": "GA",
"PostalCode": "30326",
"Country": "USA"
}
},
{
"@search.action": "upload",
"HotelId": "4",
"HotelName": "Sublime Palace 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.",
"Description_fr": "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.",
"Category": "Boutique",
"Tags": [ "concierge", "view", "24-hour front desk service" ],
"ParkingIncluded": "true",
"LastRenovationDate": "1960-02-06T00:00:00Z",
"Rating": 4.60,
"Address": {
"StreetAddress": "7400 San Pedro Ave",
"City": "San Antonio",
"StateProvince": "TX",
"PostalCode": "78216",
"Country": "USA"
}
}
]
Carregar documentos
# Upload documents to the index
search_client = SearchClient(endpoint=search_endpoint,
index_name=index_name,
credential=credential)
try:
result = search_client.upload_documents(documents=documents)
print("Upload of new document succeeded: {}".format(result[0].succeeded))
except Exception as ex:
print (ex.message)
index_client = SearchIndexClient(
endpoint=search_endpoint, credential=credential)
Executar a primeira consulta
Use o método search da classe search.client.
Essa consulta 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.
# Run an empty query (returns selected fields, all documents)
results = search_client.search(query_type='simple',
search_text="*" ,
select='HotelName,Description',
include_total_count=True)
print ('Total Documents Matching Query:', results.get_count())
for result in results:
print(result["@search.score"])
print(result["HotelName"])
print(f"Description: {result['Description']}")
Executar uma consulta de termo
A próxima consulta 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.
results = search_client.search(query_type='simple',
search_text="wifi" ,
select='HotelName,Description,Tags',
include_total_count=True)
print ('Total Documents Matching Query:', results.get_count())
for result in results:
print(result["@search.score"])
print(result["HotelName"])
print(f"Description: {result['Description']}")
Adicionar um filtro
Adicione uma expressão de filtro, que retorna apenas os hotéis com uma classificação maior que quatro, organizados em ordem decrescente.
# Add a filter
results = search_client.search(
search_text="hotels",
select='HotelId,HotelName,Rating',
filter='Rating gt 4',
order_by='Rating desc')
for result in results:
print("{}: {} - {} rating".format(result["HotelId"], result["HotelName"], result["Rating"]))
Adicionar escopo de campo
Adicionar search_fields
à execução da consulta de escopo nos campos específicos.
# Add search_fields to scope query matching to the HotelName field
results = search_client.search(
search_text="sublime",
search_fields=['HotelName'],
select='HotelId,HotelName')
for result in results:
print("{}: {}".format(result["HotelId"], result["HotelName"]))
Adicionar facetas
As facetas são geradas 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.
# Return facets
results = search_client.search(search_text="*", facets=["Category"])
facets = results.get_facets()
for facet in facets["Category"]:
print(" {}".format(facet))
Pesquisar um documento
Retornar um documento com base na respectiva chave. Essa operação é útil se você quiser fornecer detalhamento quando um usuário seleciona um item em um resultado de pesquisa.
# Look up a specific document by ID
result = search_client.get_document(key="3")
print("Details for hotel '3' are:")
print("Name: {}".format(result["HotelName"]))
print("Rating: {}".format(result["Rating"]))
print("Category: {}".format(result["Category"]))
Adicionar o preenchimento automático
O preenchimento automático pode fornecer 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ão Tags
, Address/City
, Address/Country
.
Para simular o preenchimento automático, passe as letras sa como uma cadeia de caracteres parcial. O método de preenchimento automático de SearchClient envia de volta correspondências de potenciais termos.
# Autocomplete a query
search_suggestion = 'sa'
results = search_client.autocomplete(
search_text=search_suggestion,
suggester_name="sg",
mode='twoTerms')
print("Autocomplete for:", search_suggestion)
for result in results:
print (result['text'])
Crie um aplicativo de console Java usando a biblioteca Azure.Search.Documents para criar, carregar e consultar um índice de pesquisa.
Como alternativa, 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.
Configure seu ambiente
Use as seguintes ferramentas para criar este início rápido.
Criar o projeto
Inicie o Visual Studio Code.
Abra a Paleta de Comandos usando Ctrl+Shift+P. Procure Criar Projeto Java.
Selecione Maven.
Selecione maven-archetype-quickstart.
Selecione a última versão, que no momento é 1.4.
Insira azure.search.sample como a ID do grupo.
Insira azuresearchquickstart como a ID do artefato.
Selecione a pasta na qual o projeto será criado.
Conclua a criação do projeto no terminal integrado. Pressione Enter para aceitar o padrão para "1.0-SNAPSHOT" e digite "y" para confirmar as propriedades do projeto.
Abra a pasta na qual você criou o projeto.
Especificar as dependências do Maven
Abra o arquivo pom.xml e adicione as seguintes dependências.
<dependencies>
<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>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
Altere a versão do compilador Java para 11.
<maven.compiler.source>1.11</maven.compiler.source>
<maven.compiler.target>1.11</maven.compiler.target>
Criar um cliente de pesquisa
Abra a classe App
em src, main, java, azure, search, sample. Adicione as seguintes diretivas de importação.
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.credential.AzureKeyCredential;
import com.azure.core.util.Context;
import com.azure.search.documents.SearchClient;
import com.azure.search.documents.SearchClientBuilder;
import com.azure.search.documents.models.SearchOptions;
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.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;
O exemplo a seguir inclui espaços reservados para um nome de serviço de pesquisa, uma chave de API de administrador que concede permissões de criação e exclusão e um nome do índice. Substitua os três espaços reservados por valores válidos. Crie dois clientes: SearchIndexClient cria o índice e SearchClient carrega e consulta um índice existente. Os dois precisam do ponto de extremidade de serviço e de uma chave de API de administração para autenticação com direitos de criação e exclusão.
public static void main(String[] args) {
var searchServiceEndpoint = "<YOUR-SEARCH-SERVICE-URL>";
var adminKey = new AzureKeyCredential("<YOUR-SEARCH-SERVICE-ADMIN-KEY>");
String indexName = "<YOUR-SEARCH-INDEX-NAME>";
SearchIndexClient searchIndexClient = new SearchIndexClientBuilder()
.endpoint(searchServiceEndpoint)
.credential(adminKey)
.buildClient();
SearchClient searchClient = new SearchClientBuilder()
.endpoint(searchServiceEndpoint)
.credential(adminKey)
.indexName(indexName)
.buildClient();
}
Crie um índice
Este guia de início rápido cria um índice de Hotéis que você carregará com os dados de hotéis e no qual executará consultas. Nesta etapa, defina os campos no índice. Cada definição de campo inclui nome, tipo de dados e atributos que determinam como o campo é usado.
Neste exemplo, os métodos síncronos da biblioteca azure-search-documents são usados para simplificar e facilitar a 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 SearchAsyncClient em vez de SearchClient.
Adicione uma definição de classe vazia ao seu projeto: Hotel.java
Copie o código a seguir em Hotel.java
para definir a estrutura de um documento de hotel. Os atributos no campo determinam como ele é usado em um aplicativo. Por exemplo, a anotação IsFilterable precisa ser atribuída a cada campo que dá suporte a uma expressão de filtro
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package azure.search.sample;
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 "";
}
}
}
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 em consultas de pesquisa de texto completo) e é recuperável (não está 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.
Adicione uma segunda definição de classe vazia ao seu projeto: Address.java
Copie o código a seguir para a classe.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package azure.search.sample;
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;
}
Em App.java
, crie um objeto SearchIndex
no método main
e, em seguida, chame o método createOrUpdateIndex
para criar o índice no 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ê carregará documentos JSON que estão em conformidade com o índice do hotel que você acabou de criar.
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
, crie documentos e indexe ações e, em seguida, passe-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.
// 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");
}
}
Depois de inicializar o objeto IndexDocumentsBatch
, você poderá enviá-lo ao índice chamando indexDocuments no objeto SearchClient
.
Adicione as linhas a seguir a Main()
. O carregamento de documentos é feito usando SearchClient
.
// Upload sample hotel documents to the Search Index
uploadDocuments(searchClient);
Como esse é um aplicativo de console que executa todos os comandos sequencialmente, adicione um tempo de espera de dois segundos entre a indexação e as consultas.
// Wait 2 seconds for indexing to complete before starting queries (for demo and console-app purposes only)
System.out.println("Waiting for indexing...\n");
try
{
Thread.sleep(2000);
}
catch (InterruptedException e)
{
}
O atraso de 2 segundos compensa a indexação, que é assíncrona, 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
, crie um método WriteDocuments
que imprima 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();
}
Crie um método RunQueries
para executar consultas e retornar resultados. Os resultados são 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));
}
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));
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));
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));
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();
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"));
Adicione RunQueries
a Main()
.
// 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");
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
.
Executar o programa
Pressione F5 para recompilar o aplicativo e executar o programa em sua totalidade.
A saída inclui mensagens de System.out.println
, com a adição de informações de consulta e resultados.
Crie um aplicativo Node.js usando a biblioteca @azure/search-documents para criar, carregar e consultar um índice de pesquisa.
Como alternativa, 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.
Configure seu ambiente
Usamos as ferramentas a seguir para criar este início rápido.
Criar o projeto
Inicie o Visual Studio Code.
Abra a Paleta de Comandos usando Ctrl+Shift+P e abra o terminal integrado.
Crie um diretório de desenvolvimento, dando-lhe o nome início rápido:
mkdir quickstart
cd quickstart
Inicialize um projeto vazio com npm executando o comando a seguir. Para inicializar completamente o projeto, pressione Enter várias vezes para aceitar os valores padrão, exceto a Licença, que você deve definir como MIT.
npm init
Instale o @azure/search-documents
, o JavaScript/TypeScript SDK no Azure Cognitive Search.
npm install @azure/search-documents
Instale o dotenv
, que é usado para importar as variáveis de ambiente, como o nome do serviço de pesquisa e a chave de API.
npm install dotenv
Navegue até o diretório de início rápido e confirme se você configurou o projeto e suas dependências, verificando se o arquivo package.json é semelhante ao seguinte json:
{
"name": "quickstart",
"version": "1.0.0",
"description": "Azure AI Search Quickstart",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"Azure",
"Search"
],
"author": "Your Name",
"license": "MIT",
"dependencies": {
"@azure/search-documents": "^11.3.0",
"dotenv": "^16.0.2"
}
}
Crie um arquivo .env para manter os parâmetros do serviço de pesquisa:
SEARCH_API_KEY=<YOUR-SEARCH-ADMIN-API-KEY>
SEARCH_API_ENDPOINT=<YOUR-SEARCH-SERVICE-URL>
Substitua o valor YOUR-SEARCH-SERVICE-URL
pelo nome do URL do ponto de extremidade do seu serviço de pesquisa. Substitua <YOUR-SEARCH-ADMIN-API-KEY>
pela chave de administrador que você registrou anteriormente.
Criar o arquivo index.js
Em seguida, criamos um arquivo index.js, que é o arquivo principal que hospeda nosso código.
Na parte superior desse arquivo, importe a biblioteca @azure/search-documents
:
const { SearchIndexClient, SearchClient, AzureKeyCredential, odata } = require("@azure/search-documents");
Em seguida, precisamos exigir que o pacote dotenv
leia os parâmetros do arquivo .env da seguinte maneira:
// Load the .env file if it exists
require("dotenv").config();
// Getting endpoint and apiKey from .env file
const endpoint = process.env.SEARCH_API_ENDPOINT || "";
const apiKey = process.env.SEARCH_API_KEY || "";
Com nossas importações e variáveis de ambiente em vigor, estamos prontos para definir a função principal.
A maior parte da funcionalidade no SDK é assíncrona, portanto, tornamos nossa função principal async
. Também incluímos um main().catch()
abaixo da função principal para detectar e registrar quaisquer erros encontrados:
async function main() {
console.log(`Running Azure AI Search JavaScript quickstart...`);
if (!endpoint || !apiKey) {
console.log("Make sure to set valid values for endpoint and apiKey with proper authorization.");
return;
}
// remaining quickstart code will go here
}
main().catch((err) => {
console.error("The sample encountered an error:", err);
});
Com isso em vigor, estamos prontos para criar um índice.
Criar índice
Crie um arquivo hotels_quickstart_index. JSON. Este arquivo define como o Azure AI Search funciona com os documentos que você carregará na próxima etapa. Cada campo será identificado por um name
e terá um type
especificado. 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).
Adicione o seguinte conteúdo a hotels_quickstart_index.json ou baixe o arquivo.
{
"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"
]
}
]
}
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.`);
Execute o exemplo
E, neste ponto, você já pode executar o exemplo. Use uma janela de terminal para executar o comando a seguir:
node index.js
Se você baixou o código-fonte e ainda não instalou os pacotes necessários, execute npm install
primeiro.
Você deve ver uma série de mensagens que descrevem as ações que estão sendo executadas pelo programa.
Abra a Visão geral do serviço de pesquisa no portal do Azure. Selecione a guia Índices. Você verá algo semelhante ao seguinte exemplo:
Na próxima etapa, você adicionará dados ao índice.
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:
{
"value": [
{
"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.",
"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": "Old Century 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.",
"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": "Gastronomic 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 Palace 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.",
"Description_fr": "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.",
"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"
}
}
]
}
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)}`);
Execute o programa novamente com node index.js
. 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) {
var d = new Date();
var d2 = null;
do {
d2 = new Date();
} while (d2 - d < ms);
}
Para que o programa aguarde um segundo, chame a função sleep
, como abaixo:
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.
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.
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)}`);
}
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();
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)}`);
}
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}`)
Execute o exemplo
Execute o programa usando node index.js
. Agora, além das etapas anteriores, as consultas são enviadas e os resultados gravados no console.
Crie um aplicativo Node.js usando a biblioteca @azure/search-documents para criar, carregar e consultar um índice de pesquisa.
Como alternativa, 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.
Configure seu ambiente
Usamos as ferramentas a seguir para criar este início rápido.
Criar o projeto
Inicie o Visual Studio Code.
Abra a Paleta de Comandos usando Ctrl+Shift+P e abra o terminal integrado.
Crie um diretório de desenvolvimento, dando-lhe o nome início rápido:
mkdir quickstart
cd quickstart
Inicialize um projeto vazio com npm executando o comando a seguir. Para inicializar completamente o projeto, pressione Enter várias vezes para aceitar os valores padrão, exceto a Licença, que você deve definir como MIT.
npm init
Instale o @azure/search-documents
, o JavaScript/TypeScript SDK no Azure Cognitive Search.
npm install @azure/search-documents
Instale o dotenv
, que é usado para importar as variáveis de ambiente, como o nome do serviço de pesquisa e a chave de API.
npm install dotenv
Navegue até o diretório de início rápido e confirme se você configurou o projeto e suas dependências, verificando se o arquivo package.json é semelhante ao seguinte json:
{
"name": "quickstart",
"version": "1.0.0",
"description": "Azure AI Search Quickstart",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"Azure",
"Search"
],
"author": "Your Name",
"license": "MIT",
"dependencies": {
"@azure/search-documents": "^12.1.0",
"dotenv": "^16.4.5"
}
}
Crie um arquivo .env para manter os parâmetros do serviço de pesquisa:
SEARCH_API_KEY=<YOUR-SEARCH-ADMIN-API-KEY>
SEARCH_API_ENDPOINT=<YOUR-SEARCH-SERVICE-URL>
Substitua o valor YOUR-SEARCH-SERVICE-URL
pelo nome do URL do ponto de extremidade do seu serviço de pesquisa. Substitua <YOUR-SEARCH-ADMIN-API-KEY>
pela chave de administrador que você registrou anteriormente.
Crie o arquivo index.ts
Em seguida, criamos um arquivo index.ts, que é o arquivo principal que hospeda nosso código.
Na parte superior desse arquivo, importe a biblioteca @azure/search-documents
:
import {
AzureKeyCredential,
ComplexField,
odata,
SearchClient,
SearchFieldArray,
SearchIndex,
SearchIndexClient,
SearchSuggester,
SimpleField
} from "@azure/search-documents";
Em seguida, precisamos exigir que o pacote dotenv
leia os parâmetros do arquivo .env da seguinte maneira:
// Load the .env file if it exists
import dotenv from 'dotenv';
dotenv.config();
// Getting endpoint and apiKey from .env file
const endpoint = process.env.SEARCH_API_ENDPOINT || "";
const apiKey = process.env.SEARCH_API_KEY || "";
Com nossas importações e variáveis de ambiente em vigor, estamos prontos para definir a função principal.
A maior parte da funcionalidade no SDK é assíncrona, portanto, tornamos nossa função principal async
. Também incluímos um main().catch()
abaixo da função principal para detectar e registrar quaisquer erros encontrados:
async function main() {
console.log(`Running Azure AI Search JavaScript quickstart...`);
if (!endpoint || !apiKey) {
console.log("Make sure to set valid values for endpoint and apiKey with proper authorization.");
return;
}
// remaining quickstart code will go here
}
main().catch((err) => {
console.error("The sample encountered an error:", err);
});
Com isso em vigor, estamos prontos para criar um índice.
Criar índice
Crie um arquivo hotels_quickstart_index. JSON. Este arquivo define como o Azure AI Search funciona com os documentos que você carregará na próxima etapa. Cada campo será identificado por um name
e terá um type
especificado. 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).
Adicione o seguinte conteúdo a hotels_quickstart_index.json ou baixe o arquivo.
{
"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"
]
}
]
}
Com nossa definição de índice implementada, queremos importar hotels_quickstart_index.json no topo de index.ts para que a função principal possa acessar a definição do índice.
// Importing the index definition and sample data
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.`);
Execute o exemplo
Nesse ponto, você está pronto para criar e executar o exemplo. Use uma janela de terminal para executar os seguintes comandos para construir sua fonte com tsc
e então execute sua fonte com node
:
tsc
node index.ts
Se você baixou o código-fonte e ainda não instalou os pacotes necessários, execute npm install
primeiro.
Você deve ver uma série de mensagens que descrevem as ações que estão sendo executadas pelo programa.
Abra a Visão geral do serviço de pesquisa no portal do Azure. Selecione a guia Índices. Você verá algo semelhante ao seguinte exemplo:
Na próxima etapa, você adicionará dados ao índice.
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:
{
"value": [
{
"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.",
"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": "Old Century 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.",
"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": "Gastronomic 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 Palace 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.",
"Description_fr": "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.",
"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"
}
}
]
}
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.
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.
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)}`);
}
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)}`);
}
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)}`);
}
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}`)
Execute novamente a amostra
Construa e execute o programa com tsc && node index.ts
. Agora, além das etapas anteriores, as consultas são enviadas e os resultados gravados no console.
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. Você pode excluir os recursos individualmente ou excluir o grupo de recursos para excluir todo o conjunto de recursos.
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 para permanecer abaixo do limite.
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.