Avvio rapido: Ricerca full-text con gli SDK di Azure
Informazioni su come usare la libreria client Azure.Search.Documents per creare, caricare ed eseguire query su un indice di ricerca usando dati di esempio per la ricerca full-text. La ricerca full-text usa Apache Lucene per l'indicizzazione e le query, e un algoritmo di priorità BM25 per i risultati del punteggio.
Questa guida introduttiva crea ed esegue query su un piccolo indice hotels-quickstart contenente i dati relativi a quattro hotel.
Suggerimento
È possibile scaricare il codice sorgente per iniziare con un progetto completato o seguire questa procedura per creare un progetto personalizzato.
Prerequisiti
- Una sottoscrizione di Azure attiva - Creare un account gratuito
- Un servizio di Azure AI Search. Creare un servizio se non ne è disponibile uno. Per questa guida di avvio rapido è possibile usare un livello gratuito.
Prerequisiti di Microsoft Entra ID
Per l'autenticazione senza chiave consigliata con Microsoft Entra ID, è necessario:
- Installare l'interfaccia della riga di comando di Azure usata per l'autenticazione senza chiave con Microsoft Entra ID.
- Assegnare entrambi i
Search Service Contributor
ruoli eSearch Index Data Contributor
all'account utente. È possibile assegnare ruoli nella portale di Azure in Controllo di accesso (IAM)>Aggiungere un'assegnazione di ruolo. Per altre informazioni, vedere Connettersi a Ricerca di intelligenza artificiale di Azure usando i ruoli.
Recuperare le informazioni sulle risorse
È necessario recuperare le informazioni seguenti per autenticare l'applicazione con l'servizio di ricerca di intelligenza artificiale di Azure:
Nome variabile | Valore |
---|---|
SEARCH_API_ENDPOINT |
Questo valore è disponibile nella portale di Azure. Selezionare il servizio di ricerca e quindi nel menu a sinistra selezionare Panoramica. Il valore url in Informazioni di base è l'endpoint necessario. Un endpoint di esempio potrebbe essere simile a https://mydemo.search.windows.net . |
Altre informazioni sull'autenticazione senza chiave e sull'impostazione delle variabili di ambiente.
Impostazione
Creare una nuova cartella
full-text-quickstart
per contenere l'applicazione e aprire Visual Studio Code in tale cartella con il comando seguente:mkdir full-text-quickstart && cd full-text-quickstart
Creare una nuova applicazione console con il comando seguente:
dotnet new console
Installare la libreria client di Ricerca intelligenza artificiale di Azure (Azure.Search.Documents) per .NET con:
dotnet add package Azure.Search.Documents
Per l'autenticazione senza chiave consigliata con Microsoft Entra ID, installare il pacchetto Azure.Identity con:
dotnet add package Azure.Identity
Per l'autenticazione senza chiave consigliata con Microsoft Entra ID, accedere ad Azure con il comando seguente:
az login
Creare, caricare ed eseguire query su un indice di ricerca
Nella sezione di configurazione precedente è stata creata una nuova applicazione console e è stata installata la libreria client di Ricerca intelligenza artificiale di Azure.
In questa sezione si aggiunge codice per creare un indice di ricerca, caricarlo con documenti ed eseguire query. Eseguire il programma per visualizzare i risultati nella console. Per una spiegazione dettagliata del codice, vedere la sezione che illustra il codice .
Il codice di esempio in questa guida introduttiva usa l'ID Microsoft Entra per l'autenticazione senza chiave consigliata. Se si preferisce usare una chiave API, è possibile sostituire l'oggetto DefaultAzureCredential
con un AzureKeyCredential
oggetto .
Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");
DefaultAzureCredential credential = new();
In Program.cs incollare il codice seguente. Modificare le
serviceName
variabili eapiKey
con il nome del servizio di ricerca e la chiave API amministratore.using System; using Azure; using Azure.Identity; using Azure.Search.Documents; using Azure.Search.Documents.Indexes; using Azure.Search.Documents.Indexes.Models; using Azure.Search.Documents.Models; namespace AzureSearch.Quickstart { class Program { static void Main(string[] args) { // Your search service endpoint Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/"); // Use the recommended keyless credential instead of the AzureKeyCredential credential. DefaultAzureCredential credential = new(); //AzureKeyCredential credential = new AzureKeyCredential("Your search service admin key"); // Create a SearchIndexClient to send create/delete index commands SearchIndexClient searchIndexClient = new SearchIndexClient(serviceEndpoint, credential); // Create a SearchClient to load and query documents string indexName = "hotels-quickstart"; SearchClient searchClient = new SearchClient(serviceEndpoint, indexName, credential); // Delete index if it exists Console.WriteLine("{0}", "Deleting index...\n"); DeleteIndexIfExists(indexName, searchIndexClient); // Create index Console.WriteLine("{0}", "Creating index...\n"); CreateIndex(indexName, searchIndexClient); SearchClient ingesterClient = searchIndexClient.GetSearchClient(indexName); // Load documents Console.WriteLine("{0}", "Uploading documents...\n"); UploadDocuments(ingesterClient); // Wait 2 secondsfor indexing to complete before starting queries (for demo and console-app purposes only) Console.WriteLine("Waiting for indexing...\n"); System.Threading.Thread.Sleep(2000); // Call the RunQueries method to invoke a series of queries Console.WriteLine("Starting queries...\n"); RunQueries(searchClient); // End the program Console.WriteLine("{0}", "Complete. Press any key to end this program...\n"); Console.ReadKey(); } // Delete the hotels-quickstart index to reuse its name private static void DeleteIndexIfExists(string indexName, SearchIndexClient searchIndexClient) { searchIndexClient.GetIndexNames(); { searchIndexClient.DeleteIndex(indexName); } } // Create hotels-quickstart index private static void CreateIndex(string indexName, SearchIndexClient searchIndexClient) { FieldBuilder fieldBuilder = new FieldBuilder(); var searchFields = fieldBuilder.Build(typeof(Hotel)); var definition = new SearchIndex(indexName, searchFields); var suggester = new SearchSuggester("sg", new[] { "HotelName", "Category", "Address/City", "Address/StateProvince" }); definition.Suggesters.Add(suggester); searchIndexClient.CreateOrUpdateIndex(definition); } // Upload documents in a single Upload request. private static void UploadDocuments(SearchClient searchClient) { IndexDocumentsBatch<Hotel> batch = IndexDocumentsBatch.Create( IndexDocumentsAction.Upload( new Hotel() { HotelId = "1", HotelName = "Secret Point Motel", Description = "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.", DescriptionFr = "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de New York. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de New York l'une des villes les plus attractives et cosmopolites de l'Amérique.", Category = "Boutique", Tags = new[] { "pool", "air conditioning", "concierge" }, ParkingIncluded = false, LastRenovationDate = new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero), Rating = 3.6, Address = new Address() { StreetAddress = "677 5th Ave", City = "New York", StateProvince = "NY", PostalCode = "10022", Country = "USA" } }), IndexDocumentsAction.Upload( new Hotel() { HotelId = "2", HotelName = "Twin Dome Motel", Description = "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts.", DescriptionFr = "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.", Category = "Boutique", Tags = new[] { "pool", "free wifi", "concierge" }, ParkingIncluded = false, LastRenovationDate = new DateTimeOffset(1979, 2, 18, 0, 0, 0, TimeSpan.Zero), Rating = 3.60, Address = new Address() { StreetAddress = "140 University Town Center Dr", City = "Sarasota", StateProvince = "FL", PostalCode = "34243", Country = "USA" } }), IndexDocumentsAction.Upload( new Hotel() { HotelId = "3", HotelName = "Triple Landscape Hotel", Description = "The Hotel stands out for its gastronomic excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services.", DescriptionFr = "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.", Category = "Resort and Spa", Tags = new[] { "air conditioning", "bar", "continental breakfast" }, ParkingIncluded = true, LastRenovationDate = new DateTimeOffset(2015, 9, 20, 0, 0, 0, TimeSpan.Zero), Rating = 4.80, Address = new Address() { StreetAddress = "3393 Peachtree Rd", City = "Atlanta", StateProvince = "GA", PostalCode = "30326", Country = "USA" } }), IndexDocumentsAction.Upload( new Hotel() { HotelId = "4", HotelName = "Sublime Cliff Hotel", Description = "Sublime Cliff Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 1800 palace.", DescriptionFr = "Le sublime Cliff Hotel est situé au coeur du centre historique de sublime dans un quartier extrêmement animé et vivant, à courte distance de marche des sites et monuments de la ville et est entouré par l'extraordinaire beauté des églises, des bâtiments, des commerces et Monuments. Sublime Cliff fait partie d'un Palace 1800 restauré avec amour.", Category = "Boutique", Tags = new[] { "concierge", "view", "24-hour front desk service" }, ParkingIncluded = true, LastRenovationDate = new DateTimeOffset(1960, 2, 06, 0, 0, 0, TimeSpan.Zero), Rating = 4.60, Address = new Address() { StreetAddress = "7400 San Pedro Ave", City = "San Antonio", StateProvince = "TX", PostalCode = "78216", Country = "USA" } }) ); try { IndexDocumentsResult result = searchClient.IndexDocuments(batch); } catch (Exception) { // If for some reason any documents are dropped during indexing, you can compensate by delaying and // retrying. This simple demo just logs the failed document keys and continues. Console.WriteLine("Failed to index some of the documents: {0}"); } } // Run queries, use WriteDocuments to print output private static void RunQueries(SearchClient searchClient) { SearchOptions options; SearchResults<Hotel> response; // Query 1 Console.WriteLine("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n"); options = new SearchOptions() { IncludeTotalCount = true, Filter = "", OrderBy = { "" } }; options.Select.Add("HotelId"); options.Select.Add("HotelName"); options.Select.Add("Rating"); response = searchClient.Search<Hotel>("*", options); WriteDocuments(response); // Query 2 Console.WriteLine("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n"); options = new SearchOptions() { Filter = "Rating gt 4", OrderBy = { "Rating desc" } }; options.Select.Add("HotelId"); options.Select.Add("HotelName"); options.Select.Add("Rating"); response = searchClient.Search<Hotel>("hotels", options); WriteDocuments(response); // Query 3 Console.WriteLine("Query #3: Limit search to specific fields (pool in Tags field)...\n"); options = new SearchOptions() { SearchFields = { "Tags" } }; options.Select.Add("HotelId"); options.Select.Add("HotelName"); options.Select.Add("Tags"); response = searchClient.Search<Hotel>("pool", options); WriteDocuments(response); // Query 4 - Use Facets to return a faceted navigation structure for a given query // Filters are typically used with facets to narrow results on OnClick events Console.WriteLine("Query #4: Facet on 'Category'...\n"); options = new SearchOptions() { Filter = "" }; options.Facets.Add("Category"); options.Select.Add("HotelId"); options.Select.Add("HotelName"); options.Select.Add("Category"); response = searchClient.Search<Hotel>("*", options); WriteDocuments(response); // Query 5 Console.WriteLine("Query #5: Look up a specific document...\n"); Response<Hotel> lookupResponse; lookupResponse = searchClient.GetDocument<Hotel>("3"); Console.WriteLine(lookupResponse.Value.HotelId); // Query 6 Console.WriteLine("Query #6: Call Autocomplete on HotelName...\n"); var autoresponse = searchClient.Autocomplete("sa", "sg"); WriteDocuments(autoresponse); } // Write search results to console private static void WriteDocuments(SearchResults<Hotel> searchResults) { foreach (SearchResult<Hotel> result in searchResults.GetResults()) { Console.WriteLine(result.Document); } Console.WriteLine(); } private static void WriteDocuments(AutocompleteResults autoResults) { foreach (AutocompleteItem result in autoResults.Results) { Console.WriteLine(result.Text); } Console.WriteLine(); } } }
Nella stessa cartella creare un nuovo file denominato Hotel.cs e incollare il codice seguente. Questo codice definisce la struttura di un documento di hotel.
using System; using System.Text.Json.Serialization; using Azure.Search.Documents.Indexes; using Azure.Search.Documents.Indexes.Models; namespace AzureSearch.Quickstart { public partial class Hotel { [SimpleField(IsKey = true, IsFilterable = true)] public string HotelId { get; set; } [SearchableField(IsSortable = true)] public string HotelName { get; set; } [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.EnLucene)] public string Description { get; set; } [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.FrLucene)] [JsonPropertyName("Description_fr")] public string DescriptionFr { get; set; } [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public string Category { get; set; } [SearchableField(IsFilterable = true, IsFacetable = true)] public string[] Tags { get; set; } [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public bool? ParkingIncluded { get; set; } [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public DateTimeOffset? LastRenovationDate { get; set; } [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public double? Rating { get; set; } [SearchableField] public Address Address { get; set; } } }
Creare un nuovo file denominato Hotel.cs e incollare il codice seguente per definire la struttura di un documento di hotel. Gli attributi nel campo ne determinano in che modo è usato in un'applicazione. L'attributo
IsFilterable
, ad esempio, deve essere assegnato a ogni campo che supporta un'espressione 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; } } }
Creare un nuovo file denominato Address.cs e incollare il codice seguente per definire la struttura di un documento di indirizzo.
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; } } }
Creare un nuovo file denominato Hotel.Methods.cs e incollare il codice seguente per definire un
ToString()
override per laHotel
classe .using System; using System.Text; namespace AzureSearch.Quickstart { public partial class Hotel { public override string ToString() { var builder = new StringBuilder(); if (!String.IsNullOrEmpty(HotelId)) { builder.AppendFormat("HotelId: {0}\n", HotelId); } if (!String.IsNullOrEmpty(HotelName)) { builder.AppendFormat("Name: {0}\n", HotelName); } if (!String.IsNullOrEmpty(Description)) { builder.AppendFormat("Description: {0}\n", Description); } if (!String.IsNullOrEmpty(DescriptionFr)) { builder.AppendFormat("Description (French): {0}\n", DescriptionFr); } if (!String.IsNullOrEmpty(Category)) { builder.AppendFormat("Category: {0}\n", Category); } if (Tags != null && Tags.Length > 0) { builder.AppendFormat("Tags: [ {0} ]\n", String.Join(", ", Tags)); } if (ParkingIncluded.HasValue) { builder.AppendFormat("Parking included: {0}\n", ParkingIncluded.Value ? "yes" : "no"); } if (LastRenovationDate.HasValue) { builder.AppendFormat("Last renovated on: {0}\n", LastRenovationDate); } if (Rating.HasValue) { builder.AppendFormat("Rating: {0}\n", Rating); } if (Address != null && !Address.IsEmpty) { builder.AppendFormat("Address: \n{0}\n", Address.ToString()); } return builder.ToString(); } } }
Creare un nuovo file denominato Address.Methods.cs e incollare il codice seguente per definire un
ToString()
override per laAddress
classe .using System; using System.Text; using System.Text.Json.Serialization; namespace AzureSearch.Quickstart { public partial class Address { public override string ToString() { var builder = new StringBuilder(); if (!IsEmpty) { builder.AppendFormat("{0}\n{1}, {2} {3}\n{4}", StreetAddress, City, StateProvince, PostalCode, Country); } return builder.ToString(); } [JsonIgnore] public bool IsEmpty => String.IsNullOrEmpty(StreetAddress) && String.IsNullOrEmpty(City) && String.IsNullOrEmpty(StateProvince) && String.IsNullOrEmpty(PostalCode) && String.IsNullOrEmpty(Country); } }
Compilare ed eseguire l'applicazione con il comando seguente:
dotnet run
L'output include i messaggi restituiti da Console.WriteLine, con l'aggiunta di informazioni sulle query e i risultati.
Spiegazione del codice
Nelle sezioni precedenti è stata creata una nuova applicazione console e è stata installata la libreria client di Ricerca intelligenza artificiale di Azure. È stato aggiunto il codice per creare un indice di ricerca, caricarlo con documenti ed eseguire query. È stato eseguito il programma per visualizzare i risultati nella console.
In questa sezione viene illustrato il codice aggiunto all'applicazione console.
Creare un client di ricerca
In Program.cs sono stati creati due client:
- SearchIndexClient crea l'indice.
- SearchClient carica ed esegue query su un indice esistente.
Entrambi i client necessitano dell'endpoint del servizio di ricerca e delle credenziali descritte in precedenza nella sezione informazioni sulle risorse.
Il codice di esempio in questa guida introduttiva usa l'ID Microsoft Entra per l'autenticazione senza chiave consigliata. Se si preferisce usare una chiave API, è possibile sostituire l'oggetto DefaultAzureCredential
con un AzureKeyCredential
oggetto .
Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");
DefaultAzureCredential credential = new();
static void Main(string[] args)
{
// Your search service endpoint
Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");
// Use the recommended keyless credential instead of the AzureKeyCredential credential.
DefaultAzureCredential credential = new();
//AzureKeyCredential credential = new AzureKeyCredential("Your search service admin key");
// Create a SearchIndexClient to send create/delete index commands
SearchIndexClient searchIndexClient = new SearchIndexClient(serviceEndpoint, credential);
// Create a SearchClient to load and query documents
string indexName = "hotels-quickstart";
SearchClient searchClient = new SearchClient(serviceEndpoint, indexName, credential);
// REDACTED FOR BREVITY . . .
}
Creare un indice
Questa guida introduttiva crea un indice hotels caricato con i dati dell'hotel ed esegue query su . In questo passaggio si definiscono i campi nell'indice. Ogni definizione di campo include un nome, un tipo di dati e gli attributi che determinano come viene usato il campo.
In questo esempio, i metodi sincroni della libreria Azure.Search.Documents vengono usati per semplicità e leggibilità. Per scenari di produzione, tuttavia, è consigliabile usare metodi asincroni per mantenere la scalabilità e la reattività dell'app. Ad esempio, usare CreateIndexAsync invece di CreateIndex.
Definire le strutture
Sono state create due classi helper, Hotel.cs e Address.cs, per definire la struttura di un documento di hotel e il relativo indirizzo. La Hotel
classe include campi per un ID hotel, nome, descrizione, categoria, tag, parcheggio, data di ristrutturazione, valutazione e indirizzo. La Address
classe include campi per indirizzo, città, stato/provincia, codice postale e paese/area geografica.
Nella libreria client Azure.Search.Documents è possibile usare SearchableField e SimpleField per semplificare le definizioni dei campi. Entrambi derivano da un oggetto SearchField e possono semplificare il codice:
SimpleField
può essere qualsiasi tipo di dati, è sempre non ricercabile (ignorato per le query di ricerca full-text) ed è recuperabile (non nascosto). Altri attributi sono disattivati per impostazione predefinita, ma possono essere attivati. Si potrebbe usare un oggettoSimpleField
per gli ID di documento o i campi usati solo in filtri, facet o profili di punteggio. In tal caso, assicurarsi di applicare tutti gli attributi necessari per lo scenario, ad esempioIsKey = true
per un ID documento. Per altre informazioni, vedere SimpleFieldAttribute.cs nel codice sorgente.SearchableField
deve essere una stringa ed è sempre ricercabile e recuperabile. Altri attributi sono disattivati per impostazione predefinita, ma possono essere attivati. Poiché questo tipo di campo è ricercabile, supporta i sinonimi e tutte le proprietà dell'analizzatore. Per altre informazioni, vedere SearchableFieldAttribute.cs nel codice sorgente.
Sia che si usi l'API SearchField
di base o uno dei modelli di supporto, è necessario abilitare in modo esplicito gli attributi di filtro, facet e ordinamento. Ad esempio, IsFilterable, IsSortable e IsFacetable devono essere attribuiti in modo esplicito, come illustrato nell'esempio precedente.
Creare l'indice di ricerca
In Program.cs si crea un oggetto SearchIndex e quindi si chiama il metodo CreateIndex per esprimere l'indice nel servizio di ricerca. L'indice include anche un oggetto SearchSuggester per abilitare il completamento automatico nei campi specificati.
// Create hotels-quickstart index
private static void CreateIndex(string indexName, SearchIndexClient searchIndexClient)
{
FieldBuilder fieldBuilder = new FieldBuilder();
var searchFields = fieldBuilder.Build(typeof(Hotel));
var definition = new SearchIndex(indexName, searchFields);
var suggester = new SearchSuggester("sg", new[] { "HotelName", "Category", "Address/City", "Address/StateProvince" });
definition.Suggesters.Add(suggester);
searchIndexClient.CreateOrUpdateIndex(definition);
}
Caricare i documenti
Azure AI Search esegue ricerche sul contenuto archiviato nel servizio. In questo passaggio si caricano documenti JSON conformi all'indice dell'hotel creato.
In Azure AI Search i documenti di ricerca sono strutture di dati che costituiscono sia l'input per l'indicizzazione che l'output restituito dalle query. In quanto ottenuti da un'origine dati esterna, gli input dei documenti possono essere righe in un database, BLOB nell'archiviazione BLOB o documenti JSON nel disco. Per brevità, in questo esempio i documenti JSON per i quattro alberghi verranno incorporati nel codice stesso.
Quando si caricano i documenti, è necessario usare un oggetto IndexDocumentsBatch. Un oggetto IndexDocumentsBatch
contiene una raccolta di Azioni, ognuna delle quali contiene un documento e una proprietà che indicano ad Azure AI Search quale azione eseguire (caricare, unire, eliminare e mergeOrUpload).
In Program.cs si crea una matrice di documenti e azioni di indice e quindi si passa la matrice a IndexDocumentsBatch
. I documenti seguenti sono conformi all'indice hotels-quickstart, come definito dalla classe hotel.
// Upload documents in a single Upload request.
private static void UploadDocuments(SearchClient searchClient)
{
IndexDocumentsBatch<Hotel> batch = IndexDocumentsBatch.Create(
IndexDocumentsAction.Upload(
new Hotel()
{
HotelId = "1",
HotelName = "Stay-Kay City Hotel",
Description = "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.",
DescriptionFr = "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de New York. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de New York l'une des villes les plus attractives et cosmopolites de l'Amérique.",
Category = "Boutique",
Tags = new[] { "pool", "air conditioning", "concierge" },
ParkingIncluded = false,
LastRenovationDate = new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero),
Rating = 3.6,
Address = new Address()
{
StreetAddress = "677 5th Ave",
City = "New York",
StateProvince = "NY",
PostalCode = "10022",
Country = "USA"
}
}),
// REDACTED FOR BREVITY
}
Dopo aver inizializzato l'oggetto IndexDocumentsBatch, è possibile inviarlo all'indice chiamando IndexDocuments nell'oggetto SearchClient.
I documenti vengono caricati usando SearchClient in Main()
, ma l'operazione richiede anche diritti di amministratore per il servizio, che in genere è associato a SearchIndexClient. Un modo per configurare questa operazione consiste nel ottenere SearchClient tramite SearchIndexClient
(searchIndexClient
in questo esempio).
SearchClient ingesterClient = searchIndexClient.GetSearchClient(indexName);
// Load documents
Console.WriteLine("{0}", "Uploading documents...\n");
UploadDocuments(ingesterClient);
Poiché è disponibile un'app console che esegue tutti i comandi in sequenza, viene aggiunto un tempo di attesa di 2 secondi tra indicizzazione e query.
// 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);
Il ritardo di due secondi compensa l'indicizzazione, che è asincrona, in modo che tutti i documenti possano essere indicizzati prima dell'esecuzione delle query. La scrittura di codice in un ritardo è in genere necessaria solo in applicazioni di esempio, test e demo.
Eseguire la ricerca in un indice
È possibile ottenere risultati della query subito dopo l'indicizzazione del primo documento, ma per il test effettivo dell'indice è necessario attendere il completamento dell'indicizzazione di tutti i documenti.
In questa sezione vengono aggiunte elementi di funzionalità, ovvero la logica di query e i risultati. Per le query usare il metodo Search. Questo metodo accetta il testo da cercare (stringa di query) e altre opzioni.
La classe SearchResults rappresenta i risultati.
In Program.cs il WriteDocuments
metodo stampa i risultati della ricerca nella 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();
}
Esempio di query 1
Il RunQueries
metodo esegue query e restituisce i risultati. I risultati sono oggetti Hotel. Questo esempio mostra la firma del metodo e la prima query. Questa query illustra il parametro Select
e consente di comporre il risultato usando i campi selezionati dal documento.
// Run queries, use WriteDocuments to print output
private static void RunQueries(SearchClient searchClient)
{
SearchOptions options;
SearchResults<Hotel> response;
// Query 1
Console.WriteLine("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n");
options = new SearchOptions()
{
IncludeTotalCount = true,
Filter = "",
OrderBy = { "" }
};
options.Select.Add("HotelId");
options.Select.Add("HotelName");
options.Select.Add("Address/City");
response = searchClient.Search<Hotel>("*", options);
WriteDocuments(response);
// REDACTED FOR BREVITY
}
Esempio di query 2
Nella seconda query cercare un termine, aggiungere un filtro che seleziona i documenti in cui Rating è maggiore di 4 e quindi ordinare in base a Rating in ordine decrescente. Un filtro è un'espressione booleana che viene valutata sui campi IsFilterable di un indice. Le query di filtro includono o escludono valori. Di conseguenza, a una query di filtro non è associato alcun punteggio della rilevanza.
// Query 2
Console.WriteLine("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n");
options = new SearchOptions()
{
Filter = "Rating gt 4",
OrderBy = { "Rating desc" }
};
options.Select.Add("HotelId");
options.Select.Add("HotelName");
options.Select.Add("Rating");
response = searchClient.Search<Hotel>("hotels", options);
WriteDocuments(response);
Esempio di query 3
La terza query illustra searchFields
, che si usa per impostare l'ambito di un'operazione di ricerca full-text su specifici campi.
// Query 3
Console.WriteLine("Query #3: Limit search to specific fields (pool in Tags field)...\n");
options = new SearchOptions()
{
SearchFields = { "Tags" }
};
options.Select.Add("HotelId");
options.Select.Add("HotelName");
options.Select.Add("Tags");
response = searchClient.Search<Hotel>("pool", options);
WriteDocuments(response);
Esempio di query 4
La quarta query illustra facets
, che può essere usata per strutturare una struttura di spostamento in base a facet.
// Query 4
Console.WriteLine("Query #4: Facet on 'Category'...\n");
options = new SearchOptions()
{
Filter = ""
};
options.Facets.Add("Category");
options.Select.Add("HotelId");
options.Select.Add("HotelName");
options.Select.Add("Category");
response = searchClient.Search<Hotel>("*", options);
WriteDocuments(response);
Esempio di query 5
La quinta query restituisce un documento specifico. Una ricerca di documenti è una risposta tipica all'evento OnClick
in un set di risultati.
// Query 5
Console.WriteLine("Query #5: Look up a specific document...\n");
Response<Hotel> lookupResponse;
lookupResponse = searchClient.GetDocument<Hotel>("3");
Console.WriteLine(lookupResponse.Value.HotelId);
Esempio di query 6
L'ultima query mostra la sintassi per il completamento automatico, simulando un input utente parziale di sa che risolve due possibili corrispondenze nei campi di origine associati al suggerimento definito nell'indice.
// Query 6
Console.WriteLine("Query #6: Call Autocomplete on HotelName that starts with 'sa'...\n");
var autoresponse = searchClient.Autocomplete("sa", "sg");
WriteDocuments(autoresponse);
Riepilogo delle query
Le query precedenti mostrano più modi per trovare termini corrispondenti in una query: ricerca full-text, filtri e completamento automatico.
La ricerca full-text e i filtri vengono eseguiti con il metodo SearchClient.Search. Una query di ricerca può essere passata nella stringa searchText
, mentre un'espressione filtro può essere passata nella proprietà Filter della classe SearchOptions. Per filtrare senza eseguire ricerche, passare soltanto "*"
per il parametro searchText
del metodo Search. Per eseguire una ricerca senza filtrare, lasciare la proprietà Filter
non impostata oppure non passare un'istanza di SearchOptions
.
Informazioni su come usare la libreria client Azure.Search.Documents per creare, caricare ed eseguire query su un indice di ricerca usando dati di esempio per la ricerca full-text. La ricerca full-text usa Apache Lucene per l'indicizzazione e le query, e un algoritmo di priorità BM25 per i risultati del punteggio.
Questa guida introduttiva crea ed esegue query su un piccolo indice hotels-quickstart contenente i dati relativi a quattro hotel.
Suggerimento
È possibile scaricare il codice sorgente per iniziare con un progetto completato o seguire questa procedura per creare un progetto personalizzato.
Prerequisiti
- Una sottoscrizione di Azure attiva - Creare un account gratuito
- Un servizio di Azure AI Search. Creare un servizio se non ne è disponibile uno. Per questa guida di avvio rapido è possibile usare un livello gratuito.
Prerequisiti di Microsoft Entra ID
Per l'autenticazione senza chiave consigliata con Microsoft Entra ID, è necessario:
- Installare l'interfaccia della riga di comando di Azure usata per l'autenticazione senza chiave con Microsoft Entra ID.
- Assegnare entrambi i
Search Service Contributor
ruoli eSearch Index Data Contributor
all'account utente. È possibile assegnare ruoli nella portale di Azure in Controllo di accesso (IAM)>Aggiungere un'assegnazione di ruolo. Per altre informazioni, vedere Connettersi a Ricerca di intelligenza artificiale di Azure usando i ruoli.
Recuperare le informazioni sulle risorse
È necessario recuperare le informazioni seguenti per autenticare l'applicazione con l'servizio di ricerca di intelligenza artificiale di Azure:
Nome variabile | Valore |
---|---|
SEARCH_API_ENDPOINT |
Questo valore è disponibile nella portale di Azure. Selezionare il servizio di ricerca e quindi nel menu a sinistra selezionare Panoramica. Il valore url in Informazioni di base è l'endpoint necessario. Un endpoint di esempio potrebbe essere simile a https://mydemo.search.windows.net . |
Altre informazioni sull'autenticazione senza chiave e sull'impostazione delle variabili di ambiente.
Impostazione
L'esempio in questo avvio rapido funziona con Runtime Java. Installare un Development Kit di Java, come Azul Zulu OpenJDK. Microsoft Build di OpenJDK o il proprio JDK di scelta dovrebbero funzionare allo stesso modo.
Installare Apache Maven. Quindi eseguire
mvn -v
per confermare l'installazione corretta.Creare un nuovo file
pom.xml
nella radice del progetto copiando il codice seguente:<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>azure.search.sample</groupId> <artifactId>azuresearchquickstart</artifactId> <version>1.0.0-SNAPSHOT</version> <build> <sourceDirectory>src</sourceDirectory> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>com.azure</groupId> <artifactId>azure-search-documents</artifactId> <version>11.7.3</version> </dependency> <dependency> <groupId>com.azure</groupId> <artifactId>azure-core</artifactId> <version>1.53.0</version> </dependency> <dependency> <groupId>com.azure</groupId> <artifactId>azure-identity</artifactId> <version>1.15.1</version> </dependency> </dependencies> </project>
Installare le dipendenze, inclusa la libreria client di Ricerca intelligenza artificiale di Azure (Azure.Search.Documents) per Java e la libreria client di Identità di Azure per Java con:
mvn clean dependency:copy-dependencies
Per l'autenticazione senza chiave consigliata con Microsoft Entra ID, accedere ad Azure con il comando seguente:
az login
Creare, caricare ed eseguire query su un indice di ricerca
Nella sezione di configurazione precedente è stata installata la libreria client di Ricerca intelligenza artificiale di Azure e altre dipendenze.
In questa sezione si aggiunge codice per creare un indice di ricerca, caricarlo con documenti ed eseguire query. Eseguire il programma per visualizzare i risultati nella console. Per una spiegazione dettagliata del codice, vedere la sezione che illustra il codice .
Il codice di esempio in questa guida introduttiva usa l'ID Microsoft Entra per l'autenticazione senza chiave consigliata. Se si preferisce usare una chiave API, è possibile sostituire l'oggetto DefaultAzureCredential
con un AzureKeyCredential
oggetto .
String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
Creare un nuovo file denominato App.java e incollare il codice seguente in App.java:
import java.util.Arrays; import java.util.ArrayList; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.LocalDateTime; import java.time.LocalDate; import java.time.LocalTime; import com.azure.core.util.Configuration; import com.azure.core.util.Context; import com.azure.identity.DefaultAzureCredential; import com.azure.identity.DefaultAzureCredentialBuilder; import com.azure.search.documents.SearchClient; import com.azure.search.documents.SearchClientBuilder; import com.azure.search.documents.indexes.SearchIndexClient; import com.azure.search.documents.indexes.SearchIndexClientBuilder; import com.azure.search.documents.indexes.models.IndexDocumentsBatch; import com.azure.search.documents.models.SearchOptions; import com.azure.search.documents.indexes.models.SearchIndex; import com.azure.search.documents.indexes.models.SearchSuggester; import com.azure.search.documents.util.AutocompletePagedIterable; import com.azure.search.documents.util.SearchPagedIterable; public class App { public static void main(String[] args) { // Your search service endpoint "https://<Put your search service NAME here>.search.windows.net/"; // Use the recommended keyless credential instead of the AzureKeyCredential credential. DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build(); //AzureKeyCredential credential = new AzureKeyCredential("<Your search service admin key>"); // Create a SearchIndexClient to send create/delete index commands SearchIndexClient searchIndexClient = new SearchIndexClientBuilder() .endpoint(searchServiceEndpoint) .credential(credential) .buildClient(); // Create a SearchClient to load and query documents String indexName = "hotels-quickstart-java"; SearchClient searchClient = new SearchClientBuilder() .endpoint(searchServiceEndpoint) .credential(credential) .indexName(indexName) .buildClient(); // Create Search Index for Hotel model searchIndexClient.createOrUpdateIndex( new SearchIndex(indexName, SearchIndexClient.buildSearchFields(Hotel.class, null)) .setSuggesters(new SearchSuggester("sg", Arrays.asList("HotelName")))); // Upload sample hotel documents to the Search Index uploadDocuments(searchClient); // Wait 2 seconds for indexing to complete before starting queries (for demo and console-app purposes only) System.out.println("Waiting for indexing...\n"); try { Thread.sleep(2000); } catch (InterruptedException e) { } // Call the RunQueries method to invoke a series of queries System.out.println("Starting queries...\n"); RunQueries(searchClient); // End the program System.out.println("Complete.\n"); } // Upload documents in a single Upload request. private static void uploadDocuments(SearchClient searchClient) { var hotelList = new ArrayList<Hotel>(); var hotel = new Hotel(); hotel.hotelId = "1"; hotel.hotelName = "Stay-Kay City Hotel"; hotel.description = "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities."; hotel.descriptionFr = "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de New York. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de New York l'une des villes les plus attractives et cosmopolites de l'Amérique."; hotel.category = "Boutique"; hotel.tags = new String[] { "pool", "air conditioning", "concierge" }; hotel.parkingIncluded = false; hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(1970, 1, 18), LocalTime.of(0, 0)), ZoneOffset.UTC); hotel.rating = 3.6; hotel.address = new Address(); hotel.address.streetAddress = "677 5th Ave"; hotel.address.city = "New York"; hotel.address.stateProvince = "NY"; hotel.address.postalCode = "10022"; hotel.address.country = "USA"; hotelList.add(hotel); hotel = new Hotel(); hotel.hotelId = "2"; hotel.hotelName = "Old Century Hotel"; hotel.description = "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts."; hotel.descriptionFr = "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne."; hotel.category = "Boutique"; hotel.tags = new String[] { "pool", "free wifi", "concierge" }; hotel.parkingIncluded = false; hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(1979, 2, 18), LocalTime.of(0, 0)), ZoneOffset.UTC); hotel.rating = 3.60; hotel.address = new Address(); hotel.address.streetAddress = "140 University Town Center Dr"; hotel.address.city = "Sarasota"; hotel.address.stateProvince = "FL"; hotel.address.postalCode = "34243"; hotel.address.country = "USA"; hotelList.add(hotel); hotel = new Hotel(); hotel.hotelId = "3"; hotel.hotelName = "Gastronomic Landscape Hotel"; hotel.description = "The Hotel stands out for its gastronomic excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services."; hotel.descriptionFr = "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne."; hotel.category = "Resort and Spa"; hotel.tags = new String[] { "air conditioning", "bar", "continental breakfast" }; hotel.parkingIncluded = true; hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(2015, 9, 20), LocalTime.of(0, 0)), ZoneOffset.UTC); hotel.rating = 4.80; hotel.address = new Address(); hotel.address.streetAddress = "3393 Peachtree Rd"; hotel.address.city = "Atlanta"; hotel.address.stateProvince = "GA"; hotel.address.postalCode = "30326"; hotel.address.country = "USA"; hotelList.add(hotel); hotel = new Hotel(); hotel.hotelId = "4"; hotel.hotelName = "Sublime Palace Hotel"; hotel.description = "Sublime Palace Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Palace is part of a lovingly restored 1800 palace."; hotel.descriptionFr = "Le Sublime Palace Hotel est situé au coeur du centre historique de sublime dans un quartier extrêmement animé et vivant, à courte distance de marche des sites et monuments de la ville et est entouré par l'extraordinaire beauté des églises, des bâtiments, des commerces et Monuments. Sublime Palace fait partie d'un Palace 1800 restauré avec amour."; hotel.category = "Boutique"; hotel.tags = new String[] { "concierge", "view", "24-hour front desk service" }; hotel.parkingIncluded = true; hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(1960, 2, 06), LocalTime.of(0, 0)), ZoneOffset.UTC); hotel.rating = 4.60; hotel.address = new Address(); hotel.address.streetAddress = "7400 San Pedro Ave"; hotel.address.city = "San Antonio"; hotel.address.stateProvince = "TX"; hotel.address.postalCode = "78216"; hotel.address.country = "USA"; hotelList.add(hotel); var batch = new IndexDocumentsBatch<Hotel>(); batch.addMergeOrUploadActions(hotelList); try { searchClient.indexDocuments(batch); } catch (Exception e) { e.printStackTrace(); // If for some reason any documents are dropped during indexing, you can compensate by delaying and // retrying. This simple demo just logs failure and continues System.err.println("Failed to index some of the documents"); } } // Write search results to console private static void WriteSearchResults(SearchPagedIterable searchResults) { searchResults.iterator().forEachRemaining(result -> { Hotel hotel = result.getDocument(Hotel.class); System.out.println(hotel); }); System.out.println(); } // Write autocomplete results to console private static void WriteAutocompleteResults(AutocompletePagedIterable autocompleteResults) { autocompleteResults.iterator().forEachRemaining(result -> { String text = result.getText(); System.out.println(text); }); System.out.println(); } // Run queries, use WriteDocuments to print output private static void RunQueries(SearchClient searchClient) { // Query 1 System.out.println("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n"); SearchOptions options = new SearchOptions(); options.setIncludeTotalCount(true); options.setFilter(""); options.setOrderBy(""); options.setSelect("HotelId", "HotelName", "Address/City"); WriteSearchResults(searchClient.search("*", options, Context.NONE)); // Query 2 System.out.println("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n"); options = new SearchOptions(); options.setFilter("Rating gt 4"); options.setOrderBy("Rating desc"); options.setSelect("HotelId", "HotelName", "Rating"); WriteSearchResults(searchClient.search("hotels", options, Context.NONE)); // Query 3 System.out.println("Query #3: Limit search to specific fields (pool in Tags field)...\n"); options = new SearchOptions(); options.setSearchFields("Tags"); options.setSelect("HotelId", "HotelName", "Tags"); WriteSearchResults(searchClient.search("pool", options, Context.NONE)); // Query 4 System.out.println("Query #4: Facet on 'Category'...\n"); options = new SearchOptions(); options.setFilter(""); options.setFacets("Category"); options.setSelect("HotelId", "HotelName", "Category"); WriteSearchResults(searchClient.search("*", options, Context.NONE)); // Query 5 System.out.println("Query #5: Look up a specific document...\n"); Hotel lookupResponse = searchClient.getDocument("3", Hotel.class); System.out.println(lookupResponse.hotelId); System.out.println(); // Query 6 System.out.println("Query #6: Call Autocomplete on HotelName that starts with 's'...\n"); WriteAutocompleteResults(searchClient.autocomplete("s", "sg")); } }
Creare un nuovo file denominato Hotel.java e incollare il codice seguente in Hotel.java:
import com.azure.search.documents.indexes.SearchableField; import com.azure.search.documents.indexes.SimpleField; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.annotation.JsonInclude.Include; import java.time.OffsetDateTime; /** * Model class representing a hotel. */ @JsonInclude(Include.NON_NULL) public class Hotel { /** * Hotel ID */ @JsonProperty("HotelId") @SimpleField(isKey = true) public String hotelId; /** * Hotel name */ @JsonProperty("HotelName") @SearchableField(isSortable = true) public String hotelName; /** * Description */ @JsonProperty("Description") @SearchableField(analyzerName = "en.microsoft") public String description; /** * French description */ @JsonProperty("DescriptionFr") @SearchableField(analyzerName = "fr.lucene") public String descriptionFr; /** * Category */ @JsonProperty("Category") @SearchableField(isFilterable = true, isSortable = true, isFacetable = true) public String category; /** * Tags */ @JsonProperty("Tags") @SearchableField(isFilterable = true, isFacetable = true) public String[] tags; /** * Whether parking is included */ @JsonProperty("ParkingIncluded") @SimpleField(isFilterable = true, isSortable = true, isFacetable = true) public Boolean parkingIncluded; /** * Last renovation time */ @JsonProperty("LastRenovationDate") @SimpleField(isFilterable = true, isSortable = true, isFacetable = true) public OffsetDateTime lastRenovationDate; /** * Rating */ @JsonProperty("Rating") @SimpleField(isFilterable = true, isSortable = true, isFacetable = true) public Double rating; /** * Address */ @JsonProperty("Address") public Address address; @Override public String toString() { try { return new ObjectMapper().writeValueAsString(this); } catch (JsonProcessingException e) { e.printStackTrace(); return ""; } } }
Creare un nuovo file denominato Address.java e incollare il codice seguente in Address.java:
import com.azure.search.documents.indexes.SearchableField; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonInclude.Include; /** * Model class representing an address. */ @JsonInclude(Include.NON_NULL) public class Address { /** * Street address */ @JsonProperty("StreetAddress") @SearchableField public String streetAddress; /** * City */ @JsonProperty("City") @SearchableField(isFilterable = true, isSortable = true, isFacetable = true) public String city; /** * State or province */ @JsonProperty("StateProvince") @SearchableField(isFilterable = true, isSortable = true, isFacetable = true) public String stateProvince; /** * Postal code */ @JsonProperty("PostalCode") @SearchableField(isFilterable = true, isSortable = true, isFacetable = true) public String postalCode; /** * Country */ @JsonProperty("Country") @SearchableField(isFilterable = true, isSortable = true, isFacetable = true) public String country; }
Eseguire la nuova applicazione console:
javac Address.java App.java Hotel.java -cp ".;target\dependency\*" java -cp ".;target\dependency\*" App
Spiegazione del codice
Nelle sezioni precedenti è stata creata una nuova applicazione console e è stata installata la libreria client di Ricerca intelligenza artificiale di Azure. È stato aggiunto il codice per creare un indice di ricerca, caricarlo con documenti ed eseguire query. È stato eseguito il programma per visualizzare i risultati nella console.
In questa sezione viene illustrato il codice aggiunto all'applicazione console.
Creare un client di ricerca
In App.java sono stati creati due client:
- SearchIndexClient crea l'indice.
- SearchClient carica ed esegue query su un indice esistente.
Entrambi i client necessitano dell'endpoint del servizio di ricerca e delle credenziali descritte in precedenza nella sezione informazioni sulle risorse.
Il codice di esempio in questa guida introduttiva usa l'ID Microsoft Entra per l'autenticazione senza chiave consigliata. Se si preferisce usare una chiave API, è possibile sostituire l'oggetto DefaultAzureCredential
con un AzureKeyCredential
oggetto .
String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
public static void main(String[] args) {
// Your search service endpoint
String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
// Use the recommended keyless credential instead of the AzureKeyCredential credential.
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
//AzureKeyCredential credential = new AzureKeyCredential("Your search service admin key");
// Create a SearchIndexClient to send create/delete index commands
SearchIndexClient searchIndexClient = new SearchIndexClientBuilder()
.endpoint(searchServiceEndpoint)
.credential(credential)
.buildClient();
// Create a SearchClient to load and query documents
String indexName = "hotels-quickstart-java";
SearchClient searchClient = new SearchClientBuilder()
.endpoint(searchServiceEndpoint)
.credential(credential)
.indexName(indexName)
.buildClient();
// Create Search Index for Hotel model
searchIndexClient.createOrUpdateIndex(
new SearchIndex(indexName, SearchIndexClient.buildSearchFields(Hotel.class, null))
.setSuggesters(new SearchSuggester("sg", Arrays.asList("HotelName"))));
// REDACTED FOR BREVITY . . .
}
Creare un indice
Questa guida introduttiva crea un indice hotels caricato con i dati dell'hotel ed esegue query su . In questo passaggio si definiscono i campi nell'indice. Ogni definizione di campo include un nome, un tipo di dati e gli attributi che determinano come viene usato il campo.
In questo esempio, i metodi sincroni della libreria Azure.Search.Documents vengono usati per semplicità e leggibilità. Per scenari di produzione, tuttavia, è consigliabile usare metodi asincroni per mantenere la scalabilità e la reattività dell'app. Ad esempio, usare CreateIndexAsync invece di CreateIndex.
Definire le strutture
Sono state create due classi helper, Hotel.java e Address.java, per definire la struttura di un documento di hotel e il relativo indirizzo. La classe Hotel include campi per un ID hotel, nome, descrizione, categoria, tag, parcheggio, data di ristrutturazione, valutazione e indirizzo. La classe Address include campi per indirizzo, città, stato/provincia, codice postale e paese/area geografica.
Nella libreria client Azure.Search.Documents è possibile usare SearchableField e SimpleField per semplificare le definizioni dei campi.
-
SimpleField
può essere qualsiasi tipo di dati, è sempre non ricercabile (ignorato per le query di ricerca full-text) ed è recuperabile (non nascosto). Altri attributi sono disattivati per impostazione predefinita, ma possono essere attivati. Si potrebbe usare un oggetto SimpleField per gli ID di documento o i campi usati solo in filtri, facet o profili di punteggio. In tal caso, assicurarsi di applicare gli attributi necessari per lo scenario, ad esempio IsKey = true per un ID documento. -
SearchableField
deve essere una stringa ed è sempre ricercabile e recuperabile. Altri attributi sono disattivati per impostazione predefinita, ma possono essere attivati. Poiché questo tipo di campo è ricercabile, supporta i sinonimi e tutte le proprietà dell'analizzatore.
Sia che si usi l'API SearchField
di base o uno dei modelli di supporto, è necessario abilitare in modo esplicito gli attributi di filtro, facet e ordinamento. Ad esempio, isFilterable
, isSortable
e isFacetable
devono essere attribuiti in modo esplicito, come illustrato nell'esempio precedente.
Creare l'indice di ricerca
In App.java
si crea un SearchIndex
oggetto nel main
metodo e quindi si chiama il createOrUpdateIndex
metodo per creare l'indice nel servizio di ricerca. L'indice include anche un SearchSuggester
per abilitare il completamento automatico nei campi specificati.
// Create Search Index for Hotel model
searchIndexClient.createOrUpdateIndex(
new SearchIndex(indexName, SearchIndexClient.buildSearchFields(Hotel.class, null))
.setSuggesters(new SearchSuggester("sg", Arrays.asList("HotelName"))));
Caricare i documenti
Azure AI Search esegue ricerche sul contenuto archiviato nel servizio. In questo passaggio si caricano documenti JSON conformi all'indice dell'hotel creato.
In Azure AI Search i documenti di ricerca sono strutture di dati che costituiscono sia l'input per l'indicizzazione che l'output restituito dalle query. In quanto ottenuti da un'origine dati esterna, gli input dei documenti possono essere righe in un database, BLOB nell'archiviazione BLOB o documenti JSON nel disco. Per brevità, in questo esempio i documenti JSON per i quattro alberghi verranno incorporati nel codice stesso.
Quando si caricano i documenti, è necessario usare un oggetto IndexDocumentsBatch. Un oggetto IndexDocumentsBatch
contiene una raccolta di IndexActions, ognuna delle quali contiene un documento e una proprietà che indicano ad Azure AI Search quale azione eseguire (caricare, unire, eliminare e mergeOrUpload).
In App.java
è possibile creare documenti e azioni di indice e quindi passarli a IndexDocumentsBatch
. I documenti seguenti sono conformi all'indice hotels-quickstart, come definito dalla classe hotel.
private static void uploadDocuments(SearchClient searchClient)
{
var hotelList = new ArrayList<Hotel>();
var hotel = new Hotel();
hotel.hotelId = "1";
hotel.hotelName = "Stay-Kay City Hotel";
hotel.description = "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.";
hotel.descriptionFr = "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de New York. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de New York l'une des villes les plus attractives et cosmopolites de l'Amérique.";
hotel.category = "Boutique";
hotel.tags = new String[] { "pool", "air conditioning", "concierge" };
hotel.parkingIncluded = false;
hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(1970, 1, 18), LocalTime.of(0, 0)), ZoneOffset.UTC);
hotel.rating = 3.6;
hotel.address = new Address();
hotel.address.streetAddress = "677 5th Ave";
hotel.address.city = "New York";
hotel.address.stateProvince = "NY";
hotel.address.postalCode = "10022";
hotel.address.country = "USA";
hotelList.add(hotel);
// REDACTED FOR BREVITY
var batch = new IndexDocumentsBatch<Hotel>();
batch.addMergeOrUploadActions(hotelList);
try
{
searchClient.indexDocuments(batch);
}
catch (Exception e)
{
e.printStackTrace();
// If for some reason any documents are dropped during indexing, you can compensate by delaying and
// retrying. This simple demo just logs failure and continues
System.err.println("Failed to index some of the documents");
}
}
Dopo aver inizializzato l'oggetto IndexDocumentsBatch
, è possibile inviarlo all'indice chiamando indexDocuments sull'oggetto SearchClient
.
I documenti vengono caricati usando SearchClient in main()
, ma l'operazione richiede anche diritti di amministratore per il servizio, che in genere è associato a SearchIndexClient. Un modo per configurare questa operazione consiste nel ottenere SearchClient tramite SearchIndexClient
(searchIndexClient
in questo esempio).
uploadDocuments(searchClient);
Poiché è disponibile un'app console che esegue tutti i comandi in sequenza, viene aggiunto un tempo di attesa di 2 secondi tra indicizzazione e query.
// 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)
{
}
Il ritardo di due secondi compensa l'indicizzazione, che è asincrona, in modo che tutti i documenti possano essere indicizzati prima dell'esecuzione delle query. La scrittura di codice in un ritardo è in genere necessaria solo in applicazioni di esempio, test e demo.
Eseguire la ricerca in un indice
È possibile ottenere risultati della query subito dopo l'indicizzazione del primo documento, ma per il test effettivo dell'indice è necessario attendere il completamento dell'indicizzazione di tutti i documenti.
In questa sezione vengono aggiunte due funzionalità: logica di query e risultati. Per le query usare il metodo Search. Questo metodo accetta il testo da cercare (stringa di query) e altre opzioni.
In App.java
il WriteDocuments
metodo stampa i risultati della ricerca nella 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();
}
Esempio di query 1
Il RunQueries
metodo esegue query e restituisce i risultati. I risultati sono oggetti Hotel. Questo esempio mostra la firma del metodo e la prima query. Questa query illustra il parametro Select
e consente di comporre il risultato usando i campi selezionati dal 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));
}
Esempio di query 2
Nella seconda query cercare un termine, aggiungere un filtro che seleziona i documenti in cui Rating è maggiore di 4 e quindi ordinare in base a Rating in ordine decrescente. Filtro è un'espressione booleana che viene valutata in base ai campi isFilterable
in un indice. Le query di filtro includono o escludono valori. Di conseguenza, a una query di filtro non è associato alcun punteggio della rilevanza.
// 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));
Esempio di query 3
La terza query illustra searchFields
, che si usa per impostare l'ambito di un'operazione di ricerca full-text su specifici campi.
// 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));
Esempio di query 4
La quarta query illustra facets
, che può essere usata per strutturare una struttura di spostamento in base a facet.
// 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));
Esempio di query 5
La quinta query restituisce un documento specifico.
// 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();
Esempio di query 6
L'ultima query mostra la sintassi per il completamento automatico, simulando un input utente parziale di s che risolve due possibili corrispondenze nell'oggetto sourceFields
associato al suggerimento definito nell'indice.
// Query 6
System.out.println("Query #6: Call Autocomplete on HotelName that starts with 's'...\n");
WriteAutocompleteResults(searchClient.autocomplete("s", "sg"));
Riepilogo delle query
Le query precedenti mostrano più modi per trovare termini corrispondenti in una query: ricerca full-text, filtri e completamento automatico.
La ricerca full-text e i filtri vengono eseguiti con il metodo SearchClient.search. Una query di ricerca può essere passata nella stringa searchText
, mentre un'espressione di filtro può essere passata nella proprietà filter
della classe SearchOptions. Per filtrare senza eseguire ricerche, passare semplicemente "*" per il parametro searchText
del metodo search
. Per eseguire una ricerca senza filtrare, lasciare la proprietà filter
non impostata oppure non passare un'istanza di SearchOptions
.
Informazioni su come usare la libreria client Azure.Search.Documents per creare, caricare ed eseguire query su un indice di ricerca usando dati di esempio per la ricerca full-text. La ricerca full-text usa Apache Lucene per l'indicizzazione e le query, e un algoritmo di priorità BM25 per i risultati del punteggio.
Questa guida introduttiva crea ed esegue query su un piccolo indice hotels-quickstart contenente i dati relativi a quattro hotel.
Suggerimento
È possibile scaricare il codice sorgente per iniziare con un progetto completato o seguire questa procedura per creare un progetto personalizzato.
Prerequisiti
- Una sottoscrizione di Azure attiva - Creare un account gratuito
- Un servizio di Azure AI Search. Creare un servizio se non ne è disponibile uno. Per questa guida di avvio rapido è possibile usare un livello gratuito.
Prerequisiti di Microsoft Entra ID
Per l'autenticazione senza chiave consigliata con Microsoft Entra ID, è necessario:
- Installare l'interfaccia della riga di comando di Azure usata per l'autenticazione senza chiave con Microsoft Entra ID.
- Assegnare entrambi i
Search Service Contributor
ruoli eSearch Index Data Contributor
all'account utente. È possibile assegnare ruoli nella portale di Azure in Controllo di accesso (IAM)>Aggiungere un'assegnazione di ruolo. Per altre informazioni, vedere Connettersi a Ricerca di intelligenza artificiale di Azure usando i ruoli.
Recuperare le informazioni sulle risorse
È necessario recuperare le informazioni seguenti per autenticare l'applicazione con l'servizio di ricerca di intelligenza artificiale di Azure:
Nome variabile | Valore |
---|---|
SEARCH_API_ENDPOINT |
Questo valore è disponibile nella portale di Azure. Selezionare il servizio di ricerca e quindi nel menu a sinistra selezionare Panoramica. Il valore url in Informazioni di base è l'endpoint necessario. Un endpoint di esempio potrebbe essere simile a https://mydemo.search.windows.net . |
Altre informazioni sull'autenticazione senza chiave e sull'impostazione delle variabili di ambiente.
Impostazione
Creare una nuova cartella
full-text-quickstart
per contenere l'applicazione e aprire Visual Studio Code in tale cartella con il comando seguente:mkdir full-text-quickstart && cd full-text-quickstart
package.json
Creare con il comando seguente:npm init -y
Installare la libreria client di Ricerca intelligenza artificiale di Azure (Azure.Search.Documents) per JavaScript con:
npm install @azure/search-documents
Per l'autenticazione senza password consigliata , installare la libreria client di Azure Identity con:
npm install @azure/identity
Creare, caricare ed eseguire query su un indice di ricerca
Nella sezione di configurazione precedente è stata installata la libreria client di Ricerca intelligenza artificiale di Azure e altre dipendenze.
In questa sezione si aggiunge codice per creare un indice di ricerca, caricarlo con documenti ed eseguire query. Eseguire il programma per visualizzare i risultati nella console. Per una spiegazione dettagliata del codice, vedere la sezione che illustra il codice .
Il codice di esempio in questa guida introduttiva usa l'ID Microsoft Entra per l'autenticazione senza chiave consigliata. Se si preferisce usare una chiave API, è possibile sostituire l'oggetto DefaultAzureCredential
con un AzureKeyCredential
oggetto .
String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
Creare un nuovo file denominato index.js e incollare il codice seguente in index.js:
// Import from the @azure/search-documents library import { SearchIndexClient, odata } from "@azure/search-documents"; // Import from the Azure Identity library import { DefaultAzureCredential } from "@azure/identity"; // Importing the hotels sample data import hotelData from './hotels.json' assert { type: "json" }; // Load the .env file if it exists import * as dotenv from "dotenv"; dotenv.config(); // Defining the index definition const indexDefinition = { "name": "hotels-quickstart", "fields": [ { "name": "HotelId", "type": "Edm.String", "key": true, "filterable": true }, { "name": "HotelName", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": true, "facetable": false }, { "name": "Description", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": false, "facetable": false, "analyzerName": "en.lucene" }, { "name": "Description_fr", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": false, "facetable": false, "analyzerName": "fr.lucene" }, { "name": "Category", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "Tags", "type": "Collection(Edm.String)", "searchable": true, "filterable": true, "sortable": false, "facetable": true }, { "name": "ParkingIncluded", "type": "Edm.Boolean", "filterable": true, "sortable": true, "facetable": true }, { "name": "LastRenovationDate", "type": "Edm.DateTimeOffset", "filterable": true, "sortable": true, "facetable": true }, { "name": "Rating", "type": "Edm.Double", "filterable": true, "sortable": true, "facetable": true }, { "name": "Address", "type": "Edm.ComplexType", "fields": [ { "name": "StreetAddress", "type": "Edm.String", "filterable": false, "sortable": false, "facetable": false, "searchable": true }, { "name": "City", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "StateProvince", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "PostalCode", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "Country", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true } ] } ], "suggesters": [ { "name": "sg", "searchMode": "analyzingInfixMatching", "sourceFields": [ "HotelName" ] } ] }; async function main() { // Your search service endpoint const searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/"; // Use the recommended keyless credential instead of the AzureKeyCredential credential. const credential = new DefaultAzureCredential(); //const credential = new AzureKeyCredential(Your search service admin key); // Create a SearchIndexClient to send create/delete index commands const searchIndexClient = new SearchIndexClient(searchServiceEndpoint, credential); // Creating a search client to upload documents and issue queries const indexName = "hotels-quickstart"; const searchClient = searchIndexClient.getSearchClient(indexName); console.log('Checking if index exists...'); await deleteIndexIfExists(searchIndexClient, indexName); console.log('Creating index...'); let index = await searchIndexClient.createIndex(indexDefinition); console.log(`Index named ${index.name} has been created.`); console.log('Uploading documents...'); let indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotelData['value']); console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)} `); // waiting one second for indexing to complete (for demo purposes only) sleep(1000); console.log('Querying the index...'); console.log(); await sendQueries(searchClient); } async function deleteIndexIfExists(searchIndexClient, indexName) { try { await searchIndexClient.deleteIndex(indexName); console.log('Deleting index...'); } catch { console.log('Index does not exist yet.'); } } async function sendQueries(searchClient) { // Query 1 console.log('Query #1 - search everything:'); let searchOptions = { includeTotalCount: true, select: ["HotelId", "HotelName", "Rating"] }; let searchResults = await searchClient.search("*", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(`Result count: ${searchResults.count}`); console.log(); // Query 2 console.log('Query #2 - search with filter, orderBy, and select:'); let state = 'FL'; searchOptions = { filter: odata `Address/StateProvince eq ${state}`, orderBy: ["Rating desc"], select: ["HotelId", "HotelName", "Rating"] }; searchResults = await searchClient.search("wifi", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(); // Query 3 console.log('Query #3 - limit searchFields:'); searchOptions = { select: ["HotelId", "HotelName", "Rating"], searchFields: ["HotelName"] }; searchResults = await searchClient.search("sublime cliff", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(); // Query 4 console.log('Query #4 - limit searchFields and use facets:'); searchOptions = { facets: ["Category"], select: ["HotelId", "HotelName", "Rating"], searchFields: ["HotelName"] }; searchResults = await searchClient.search("*", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(); // Query 5 console.log('Query #5 - Lookup document:'); let documentResult = await searchClient.getDocument('3'); console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`); console.log(); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } main().catch((err) => { console.error("The sample encountered an error:", err); });
Creare un file denominato hotels.json e incollare il codice seguente in hotels.json:
{ "value": [ { "HotelId": "1", "HotelName": "Secret Point Motel", "Description": "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.", "Description_fr": "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de New York. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de New York l'une des villes les plus attractives et cosmopolites de l'Amérique.", "Category": "Boutique", "Tags": ["pool", "air conditioning", "concierge"], "ParkingIncluded": false, "LastRenovationDate": "1970-01-18T00:00:00Z", "Rating": 3.6, "Address": { "StreetAddress": "677 5th Ave", "City": "New York", "StateProvince": "NY", "PostalCode": "10022" } }, { "HotelId": "2", "HotelName": "Twin Dome Motel", "Description": "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts.", "Description_fr": "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.", "Category": "Boutique", "Tags": ["pool", "free wifi", "concierge"], "ParkingIncluded": "false", "LastRenovationDate": "1979-02-18T00:00:00Z", "Rating": 3.6, "Address": { "StreetAddress": "140 University Town Center Dr", "City": "Sarasota", "StateProvince": "FL", "PostalCode": "34243" } }, { "HotelId": "3", "HotelName": "Triple Landscape Hotel", "Description": "The Hotel stands out for its gastronomic excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services.", "Description_fr": "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.", "Category": "Resort and Spa", "Tags": ["air conditioning", "bar", "continental breakfast"], "ParkingIncluded": "true", "LastRenovationDate": "2015-09-20T00:00:00Z", "Rating": 4.8, "Address": { "StreetAddress": "3393 Peachtree Rd", "City": "Atlanta", "StateProvince": "GA", "PostalCode": "30326" } }, { "HotelId": "4", "HotelName": "Sublime Cliff Hotel", "Description": "Sublime Cliff Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 1800 palace.", "Description_fr": "Le sublime Cliff Hotel est situé au coeur du centre historique de sublime dans un quartier extrêmement animé et vivant, à courte distance de marche des sites et monuments de la ville et est entouré par l'extraordinaire beauté des églises, des bâtiments, des commerces et Monuments. Sublime Cliff fait partie d'un Palace 1800 restauré avec amour.", "Category": "Boutique", "Tags": ["concierge", "view", "24-hour front desk service"], "ParkingIncluded": true, "LastRenovationDate": "1960-02-06T00:00:00Z", "Rating": 4.6, "Address": { "StreetAddress": "7400 San Pedro Ave", "City": "San Antonio", "StateProvince": "TX", "PostalCode": "78216" } } ] }
Creare un file denominato hotels_quickstart_index.json e incollare il codice seguente in hotels_quickstart_index.json:
{ "name": "hotels-quickstart", "fields": [ { "name": "HotelId", "type": "Edm.String", "key": true, "filterable": true }, { "name": "HotelName", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": true, "facetable": false }, { "name": "Description", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": false, "facetable": false, "analyzerName": "en.lucene" }, { "name": "Description_fr", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": false, "facetable": false, "analyzerName": "fr.lucene" }, { "name": "Category", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "Tags", "type": "Collection(Edm.String)", "searchable": true, "filterable": true, "sortable": false, "facetable": true }, { "name": "ParkingIncluded", "type": "Edm.Boolean", "filterable": true, "sortable": true, "facetable": true }, { "name": "LastRenovationDate", "type": "Edm.DateTimeOffset", "filterable": true, "sortable": true, "facetable": true }, { "name": "Rating", "type": "Edm.Double", "filterable": true, "sortable": true, "facetable": true }, { "name": "Address", "type": "Edm.ComplexType", "fields": [ { "name": "StreetAddress", "type": "Edm.String", "filterable": false, "sortable": false, "facetable": false, "searchable": true }, { "name": "City", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "StateProvince", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "PostalCode", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "Country", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true } ] } ], "suggesters": [ { "name": "sg", "searchMode": "analyzingInfixMatching", "sourceFields": [ "HotelName" ] } ] }
Accedere ad Azure con il comando seguente:
az login
Eseguire il codice JavaScript con il comando seguente:
node index.js
Spiegazione del codice
Creare un indice
Il file hotels_quickstart_index.json definisce il funzionamento di Ricerca di intelligenza artificiale di Azure con i documenti caricati nel passaggio successivo. Ogni campo è identificato da un name
oggetto e ha un oggetto specificato type
. Ogni campo dispone anche di una serie di attributi di indice che specificano se Azure AI Search può eseguire ricerche, applicare filtri, eseguire l'ordinamento e applicare facet nel campo. Quasi tutti i campi sono tipi di dati semplici, ma alcuni come AddressType
sono tipi complessi che consentono di creare strutture di dati avanzate nell'indice. Per altre informazioni sui tipi di dati supportati e sugli attributi di indice descritti, vedere Creare un indice (REST).
Una volta configurata la definizione dell'indice, importare hotels_quickstart_index.json all'inizio di index.js in modo che la funzione main possa accedere alla definizione dell'indice.
const indexDefinition = require('./hotels_quickstart_index.json');
All'interno della funzione main creare quindi un SearchIndexClient
, che viene usato per creare e gestire gli indici per Azure AI Search.
const indexClient = new SearchIndexClient(endpoint, new AzureKeyCredential(apiKey));
A questo punto eliminare l'indice, se esiste già. Questa operazione è una procedura comune per il codice di test/demo.
A questo scopo occorre definire una funzione semplice che tenta di eliminare l'indice.
async function deleteIndexIfExists(indexClient, indexName) {
try {
await indexClient.deleteIndex(indexName);
console.log('Deleting index...');
} catch {
console.log('Index does not exist yet.');
}
}
Per eseguire la funzione, estrarre il nome dell'indice dalla definizione dell'indice e quindi passare indexName
insieme a indexClient
alla funzione deleteIndexIfExists()
.
const indexName = indexDefinition["name"];
console.log('Checking if index exists...');
await deleteIndexIfExists(indexClient, indexName);
A questo punto è possibile creare l'indice con il metodo createIndex()
.
console.log('Creating index...');
let index = await indexClient.createIndex(indexDefinition);
console.log(`Index named ${index.name} has been created.`);
Caricare i documenti
In Azure AI Search i documenti sono strutture dei dati che costituiscono sia l'input per l'indicizzazione che l'output restituito dalle query. È possibile eseguire il push di questi dati nell'indice oppure usare un indicizzatore. In questo caso si eseguirà il push dei documenti nell'indice a livello di codice.
Gli input dei documenti possono essere righe in un database, BLOB nell'archiviazione BLOB o, come in questo esempio, documenti JSON nel disco. Analogamente a quanto fatto con , indexDefinition
è necessario importare hotels.json
anche nella parte superiore di index.js in modo che i dati possano essere accessibili nella funzione principale.
const hotelData = require('./hotels.json');
Per indicizzare i dati nell'indice di ricerca, è ora necessario creare un SearchClient
. Mentre SearchIndexClient
viene usato per creare e gestire un indice, SearchClient
viene usato per caricare i documenti ed eseguire query sull'indice.
Esistono due modi per creare un elemento SearchClient
. La prima opzione consiste nel creare un SearchClient
da zero:
const searchClient = new SearchClient(endpoint, indexName, new AzureKeyCredential(apiKey));
In alternativa, è possibile usare il metodo getSearchClient()
di SearchIndexClient
per creare SearchClient
:
const searchClient = indexClient.getSearchClient(indexName);
Una volta definito il client, caricare i documenti nell'indice di ricerca. In questo caso viene usato il mergeOrUploadDocuments()
metodo , che carica i documenti o li unisce a un documento esistente se esiste già un documento con la stessa chiave.
console.log('Uploading documents...');
let indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotelData['value']);
console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)}`);
Eseguire la ricerca in un indice
Dopo aver creato un indice e aver caricato i documenti, è possibile iniziare a inviare query all'indice. In questa sezione vengono inviate cinque query diverse all'indice di ricerca per illustrare diverse funzionalità di query disponibili.
Le query vengono scritte in una sendQueries()
funzione chiamata nella funzione main come indicato di seguito:
await sendQueries(searchClient);
Le query vengono inviate usando il metodo search()
di searchClient
. Il primo parametro è il testo della ricerca, mentre il secondo parametro specifica le opzioni di ricerca.
Esempio di query 1
La prima query cerca *
, che equivale alla ricerca di tutti gli elementi, e seleziona tre dei campi nell'indice. È consigliabile select
solo i campi necessari, in quanto il pull di dati non necessari può aggiungere latenza alle query.
Per searchOptions
questa query è includeTotalCount
impostato anche su true
, che restituisce il numero di risultati corrispondenti trovati.
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
}
Anche le query rimanenti riportate di seguito dovrebbero essere aggiunte alla funzione sendQueries()
. Qui sono separate per migliorare la leggibilità.
Esempio di query 2
Nella query successiva viene specificato il termine di ricerca "wifi"
e viene incluso anche un filtro per restituire solo i risultati in cui lo stato è uguale a 'FL'
. I risultati sono inoltre ordinati in base al Rating
dell'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)}`);
}
Esempio di query 3
La ricerca viene quindi limitata a un singolo campo ricercabile con il parametro searchFields
. Questo approccio è un'ottima opzione per rendere la query più efficiente, se si è interessati solo alle corrispondenze in determinati campi.
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();
Esempio di query 4
Un'altra opzione comune da includere in una query è facets
. I facet consentono di creare filtri nell'interfaccia utente per consentire agli utenti di individuare più facilmente i valori che possono filtrare.
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)}`);
}
Esempio di query 5
La query finale usa il metodo getDocument()
di searchClient
. Questo consente di recuperare in modo efficiente un documento in base alla relativa chiave.
console.log('Query #5 - Lookup document:');
let documentResult = await searchClient.getDocument(key='3')
console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`)
Riepilogo delle query
Le query precedenti mostrano più modi per trovare termini corrispondenti in una query: ricerca full-text, filtri e completamento automatico.
La ricerca full-text e i filtri vengono eseguiti usando il searchClient.search
metodo . È possibile passare una query di ricerca nella searchText
stringa, mentre un'espressione di filtro può essere passata nella filter
proprietà della SearchOptions
classe . Per filtrare senza eseguire ricerche, passare semplicemente "*" per il parametro searchText
del metodo search
. Per eseguire una ricerca senza filtrare, lasciare la proprietà filter
non impostata oppure non passare un'istanza di SearchOptions
.
Informazioni su come usare la libreria client Azure.Search.Documents per creare, caricare ed eseguire query su un indice di ricerca usando dati di esempio per la ricerca full-text. La ricerca full-text usa Apache Lucene per l'indicizzazione e le query, e un algoritmo di priorità BM25 per i risultati del punteggio.
Questa guida introduttiva crea ed esegue query su un piccolo indice hotels-quickstart contenente i dati relativi a quattro hotel.
Suggerimento
È possibile scaricare ed eseguire un notebook completato.
Prerequisiti
- Una sottoscrizione di Azure attiva - Creare un account gratuito
- Un servizio di Azure AI Search. Creare un servizio se non ne è disponibile uno. Per questa guida di avvio rapido è possibile usare un livello gratuito.
- Visual Studio Code con l'estensione Python o un IDE equivalente con Python 3.10 o versione successiva. Se non è installata una versione appropriata di Python, è possibile seguire le istruzioni riportate nell'esercitazione su Python di VS Code.
Prerequisiti di Microsoft Entra ID
Per l'autenticazione senza chiave consigliata con Microsoft Entra ID, è necessario:
- Installare l'interfaccia della riga di comando di Azure usata per l'autenticazione senza chiave con Microsoft Entra ID.
- Assegnare entrambi i
Search Service Contributor
ruoli eSearch Index Data Contributor
all'account utente. È possibile assegnare ruoli nella portale di Azure in Controllo di accesso (IAM)>Aggiungere un'assegnazione di ruolo. Per altre informazioni, vedere Connettersi a Ricerca di intelligenza artificiale di Azure usando i ruoli.
Recuperare le informazioni sulle risorse
È necessario recuperare le informazioni seguenti per autenticare l'applicazione con l'servizio di ricerca di intelligenza artificiale di Azure:
Nome variabile | Valore |
---|---|
SEARCH_API_ENDPOINT |
Questo valore è disponibile nella portale di Azure. Selezionare il servizio di ricerca e quindi nel menu a sinistra selezionare Panoramica. Il valore url in Informazioni di base è l'endpoint necessario. Un endpoint di esempio potrebbe essere simile a https://mydemo.search.windows.net . |
Altre informazioni sull'autenticazione senza chiave e sull'impostazione delle variabili di ambiente.
Configurare l'ambiente
Il codice di esempio viene eseguito in un notebook jupyter. È quindi necessario configurare l'ambiente per eseguire notebook jupyter.
Scaricare o copiare il notebook di esempio da GitHub.
Aprire il notebook in Visual Studio Code.
Creare un nuovo ambiente Python da usare per installare i pacchetti necessari per questa esercitazione.
Importante
Non installare pacchetti nell'installazione globale di Python. Quando si installano i pacchetti Python, è necessario usare sempre un ambiente virtuale o Conda, altrimenti si rischia di interrompere l'installazione di Python nel sistema.
La configurazione può richiedere alcuni minuti. Se si verificano problemi, vedere Ambienti Python in VS Code.
Installare notebook jupyter e i notebook IPython per Jupyter, se non sono già disponibili.
pip install jupyter pip install ipykernel python -m ipykernel install --user --name=.venv
Selezionare il kernel del notebook.
- Nell'angolo in alto a destra del notebook selezionare Seleziona kernel.
- Se viene visualizzato
.venv
nell'elenco, selezionarlo. Se non viene visualizzato, selezionare Seleziona un altro ambiente>.venv
Python kernel.>
Creare, caricare ed eseguire query su un indice di ricerca
In questa sezione si aggiunge codice per creare un indice di ricerca, caricarlo con documenti ed eseguire query. Eseguire il programma per visualizzare i risultati nella console. Per una spiegazione dettagliata del codice, vedere la sezione che illustra il codice .
Assicurarsi che il notebook sia aperto nel
.venv
kernel come descritto nella sezione precedente.Eseguire la prima cella di codice per installare i pacchetti necessari, inclusi azure-search-documents.
! pip install azure-search-documents==11.6.0b1 --quiet ! pip install azure-identity --quiet ! pip install python-dotenv --quiet
Sostituire il contenuto della seconda cella di codice con il codice seguente a seconda del metodo di autenticazione.
Nota
Il codice di esempio in questa guida introduttiva usa l'ID Microsoft Entra per l'autenticazione senza chiave consigliata. Se si preferisce usare una chiave API, è possibile sostituire l'oggetto
DefaultAzureCredential
con unAzureKeyCredential
oggetto .from azure.core.credentials import AzureKeyCredential from azure.identity import DefaultAzureCredential, AzureAuthorityHosts search_endpoint: str = "https://<Put your search service NAME here>.search.windows.net/" authority = AzureAuthorityHosts.AZURE_PUBLIC_CLOUD credential = DefaultAzureCredential(authority=authority) index_name: str = "hotels-quickstart-python"
Rimuovere le due righe seguenti dalla cella Crea codice indice . Le credenziali sono già impostate nella cella di codice precedente.
from azure.core.credentials import AzureKeyCredential credential = AzureKeyCredential(search_api_key)
Eseguire la cella Create an index code (Crea codice indice ) per creare un indice di ricerca.
Eseguire le celle di codice rimanenti in sequenza per caricare i documenti ed eseguire query.
Spiegazione del codice
Creare un indice
SearchIndexClient
viene usato per creare e gestire indici per Ricerca di intelligenza artificiale di Azure. Ogni campo è identificato da un name
oggetto e ha un oggetto specificato type
.
Ogni campo dispone anche di una serie di attributi di indice che specificano se Azure AI Search può eseguire ricerche, applicare filtri, eseguire l'ordinamento e applicare facet nel campo. Quasi tutti i campi sono tipi di dati semplici, ma alcuni come AddressType
sono tipi complessi che consentono di creare strutture di dati avanzate nell'indice. Per altre informazioni sui tipi di dati supportati e sugli attributi di indice descritti, vedere Creare un indice (REST).
Creare un payload di documenti e caricare documenti
Usare un'azione di indice per il tipo di operazione, ad esempio upload o merge-and-upload. I documenti provengono dall'esempio HotelsData su GitHub.
Eseguire la ricerca in un indice
È possibile ottenere risultati della query subito dopo l'indicizzazione del primo documento, ma per il test effettivo dell'indice è necessario attendere il completamento dell'indicizzazione di tutti i documenti.
Usare il metodo di ricerca della classe search.client.
Le query di esempio nel notebook sono:
- Query di base: esegue una ricerca vuota (
search=*
), restituendo un elenco non classificato (punteggio di ricerca = 1,0) di documenti arbitrari. Non essendoci criteri, nei risultati vengono inclusi tutti i documenti. - Query termine: aggiunge termini interi all'espressione di ricerca ("wifi"). Questa query specifica che i risultati devono contenere solo i campi inclusi nell'istruzione
select
. Limitando i campi restituiti, è possibile ridurre la quantità di dati inviati tramite rete e di conseguenza la latenza della ricerca. - Query filtrata: aggiungere un'espressione di filtro, restituendo solo gli hotel con una classificazione maggiore di quattro, ordinati in ordine decrescente.
- Ambito campiato: aggiungere
search_fields
all'ambito l'esecuzione di query a campi specifici. - Facet: genera facet per le corrispondenze positive trovate nei risultati della ricerca. Non esistono corrispondenze pari a zero. Se i risultati della ricerca non includono il termine wifi, wifi non viene visualizzato nella struttura di spostamento in base a facet.
- Cerca un documento: restituisce un documento in base alla relativa chiave. Questa operazione è utile se si vuole fornire il drill-through quando un utente seleziona un elemento in un risultato della ricerca.
- Completamento automatico: specificare potenziali corrispondenze quando l'utente digita nella casella di ricerca. Completamento automatico usa uno strumento suggerimenti (
sg
) per sapere quali campi contengono potenziali corrispondenze alle richieste dello strumento suggerimenti. In questo Avvio rapido, questi campi sonoTags
,Address/City
eAddress/Country
. Per simulare il completamento automatico, passare le lettere sa come stringa parziale. Il metodo di completamento automatico di SearchClient restituisce le possibile corrispondenze del termine.
Rimuovere l'indice
Se l'indice è terminato, è possibile eliminarlo eseguendo la cella di codice Pulisci . L'eliminazione di indici non necessari libera spazio per eseguire altre guide introduttive ed esercitazioni.
Informazioni su come usare la libreria client Azure.Search.Documents per creare, caricare ed eseguire query su un indice di ricerca usando dati di esempio per la ricerca full-text. La ricerca full-text usa Apache Lucene per l'indicizzazione e le query, e un algoritmo di priorità BM25 per i risultati del punteggio.
Questa guida introduttiva crea ed esegue query su un piccolo indice hotels-quickstart contenente i dati relativi a quattro hotel.
Suggerimento
È possibile scaricare il codice sorgente per iniziare con un progetto completato o seguire questa procedura per creare un progetto personalizzato.
Prerequisiti
- Una sottoscrizione di Azure attiva - Creare un account gratuito
- Un servizio di Azure AI Search. Creare un servizio se non ne è disponibile uno. Per questa guida di avvio rapido è possibile usare un livello gratuito.
Prerequisiti di Microsoft Entra ID
Per l'autenticazione senza chiave consigliata con Microsoft Entra ID, è necessario:
- Installare l'interfaccia della riga di comando di Azure usata per l'autenticazione senza chiave con Microsoft Entra ID.
- Assegnare entrambi i
Search Service Contributor
ruoli eSearch Index Data Contributor
all'account utente. È possibile assegnare ruoli nella portale di Azure in Controllo di accesso (IAM)>Aggiungere un'assegnazione di ruolo. Per altre informazioni, vedere Connettersi a Ricerca di intelligenza artificiale di Azure usando i ruoli.
Recuperare le informazioni sulle risorse
È necessario recuperare le informazioni seguenti per autenticare l'applicazione con l'servizio di ricerca di intelligenza artificiale di Azure:
Nome variabile | Valore |
---|---|
SEARCH_API_ENDPOINT |
Questo valore è disponibile nella portale di Azure. Selezionare il servizio di ricerca e quindi nel menu a sinistra selezionare Panoramica. Il valore url in Informazioni di base è l'endpoint necessario. Un endpoint di esempio potrebbe essere simile a https://mydemo.search.windows.net . |
Altre informazioni sull'autenticazione senza chiave e sull'impostazione delle variabili di ambiente.
Impostazione
Creare una nuova cartella
full-text-quickstart
per contenere l'applicazione e aprire Visual Studio Code in tale cartella con il comando seguente:mkdir full-text-quickstart && cd full-text-quickstart
package.json
Creare con il comando seguente:npm init -y
Aggiornare in
package.json
ECMAScript con il comando seguente:npm pkg set type=module
Installare la libreria client di Ricerca intelligenza artificiale di Azure (Azure.Search.Documents) per JavaScript con:
npm install @azure/search-documents
Per l'autenticazione senza password consigliata , installare la libreria client di Azure Identity con:
npm install @azure/identity
Creare, caricare ed eseguire query su un indice di ricerca
Nella sezione di configurazione precedente è stata installata la libreria client di Ricerca intelligenza artificiale di Azure e altre dipendenze.
In questa sezione si aggiunge codice per creare un indice di ricerca, caricarlo con documenti ed eseguire query. Eseguire il programma per visualizzare i risultati nella console. Per una spiegazione dettagliata del codice, vedere la sezione che illustra il codice .
Il codice di esempio in questa guida introduttiva usa l'ID Microsoft Entra per l'autenticazione senza chiave consigliata. Se si preferisce usare una chiave API, è possibile sostituire l'oggetto DefaultAzureCredential
con un AzureKeyCredential
oggetto .
const searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
const credential = new DefaultAzureCredential();
Creare un nuovo file denominato index.ts e incollare il codice seguente in index.ts:
// Import from the @azure/search-documents library import { SearchIndexClient, SearchClient, SearchFieldDataType, AzureKeyCredential, odata, SearchIndex } from "@azure/search-documents"; // Import from the Azure Identity library import { DefaultAzureCredential } from "@azure/identity"; // Importing the hotels sample data import hotelData from './hotels.json' assert { type: "json" }; // Load the .env file if it exists import * as dotenv from "dotenv"; dotenv.config(); // Defining the index definition const indexDefinition: SearchIndex = { "name": "hotels-quickstart", "fields": [ { "name": "HotelId", "type": "Edm.String" as SearchFieldDataType, "key": true, "filterable": true }, { "name": "HotelName", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": false, "sortable": true, "facetable": false }, { "name": "Description", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": false, "sortable": false, "facetable": false, "analyzerName": "en.lucene" }, { "name": "Description_fr", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": false, "sortable": false, "facetable": false, "analyzerName": "fr.lucene" }, { "name": "Category", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "Tags", "type": "Collection(Edm.String)", "searchable": true, "filterable": true, "sortable": false, "facetable": true }, { "name": "ParkingIncluded", "type": "Edm.Boolean", "filterable": true, "sortable": true, "facetable": true }, { "name": "LastRenovationDate", "type": "Edm.DateTimeOffset", "filterable": true, "sortable": true, "facetable": true }, { "name": "Rating", "type": "Edm.Double", "filterable": true, "sortable": true, "facetable": true }, { "name": "Address", "type": "Edm.ComplexType", "fields": [ { "name": "StreetAddress", "type": "Edm.String" as SearchFieldDataType, "filterable": false, "sortable": false, "facetable": false, "searchable": true }, { "name": "City", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "StateProvince", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "PostalCode", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "Country", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": true, "sortable": true, "facetable": true } ] } ], "suggesters": [ { "name": "sg", "searchMode": "analyzingInfixMatching", "sourceFields": [ "HotelName" ] } ] }; async function main() { // Your search service endpoint const searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/"; // Use the recommended keyless credential instead of the AzureKeyCredential credential. const credential = new DefaultAzureCredential(); //const credential = new AzureKeyCredential(Your search service admin key); // Create a SearchIndexClient to send create/delete index commands const searchIndexClient: SearchIndexClient = new SearchIndexClient( searchServiceEndpoint, credential ); // Creating a search client to upload documents and issue queries const indexName: string = "hotels-quickstart"; const searchClient: SearchClient<any> = searchIndexClient.getSearchClient(indexName); console.log('Checking if index exists...'); await deleteIndexIfExists(searchIndexClient, indexName); console.log('Creating index...'); let index: SearchIndex = await searchIndexClient.createIndex(indexDefinition); console.log(`Index named ${index.name} has been created.`); console.log('Uploading documents...'); let indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotelData['value']); console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)} `); // waiting one second for indexing to complete (for demo purposes only) sleep(1000); console.log('Querying the index...'); console.log(); await sendQueries(searchClient); } async function deleteIndexIfExists(searchIndexClient: SearchIndexClient, indexName: string) { try { await searchIndexClient.deleteIndex(indexName); console.log('Deleting index...'); } catch { console.log('Index does not exist yet.'); } } async function sendQueries(searchClient: SearchClient<any>) { // Query 1 console.log('Query #1 - search everything:'); let searchOptions: any = { includeTotalCount: true, select: ["HotelId", "HotelName", "Rating"] }; let searchResults = await searchClient.search("*", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(`Result count: ${searchResults.count}`); console.log(); // Query 2 console.log('Query #2 - search with filter, orderBy, and select:'); let state = 'FL'; searchOptions = { filter: odata`Address/StateProvince eq ${state}`, orderBy: ["Rating desc"], select: ["HotelId", "HotelName", "Rating"] }; searchResults = await searchClient.search("wifi", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(); // Query 3 console.log('Query #3 - limit searchFields:'); searchOptions = { select: ["HotelId", "HotelName", "Rating"], searchFields: ["HotelName"] }; searchResults = await searchClient.search("sublime cliff", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(); // Query 4 console.log('Query #4 - limit searchFields and use facets:'); searchOptions = { facets: ["Category"], select: ["HotelId", "HotelName", "Rating"], searchFields: ["HotelName"] }; searchResults = await searchClient.search("*", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(); // Query 5 console.log('Query #5 - Lookup document:'); let documentResult = await searchClient.getDocument('3'); console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`); console.log(); } function sleep(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } main().catch((err) => { console.error("The sample encountered an error:", err); });
Creare un file denominato hotels.json e incollare il codice seguente in hotels.json:
{ "value": [ { "HotelId": "1", "HotelName": "Secret Point Motel", "Description": "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.", "Description_fr": "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de New York. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de New York l'une des villes les plus attractives et cosmopolites de l'Amérique.", "Category": "Boutique", "Tags": ["pool", "air conditioning", "concierge"], "ParkingIncluded": false, "LastRenovationDate": "1970-01-18T00:00:00Z", "Rating": 3.6, "Address": { "StreetAddress": "677 5th Ave", "City": "New York", "StateProvince": "NY", "PostalCode": "10022" } }, { "HotelId": "2", "HotelName": "Twin Dome Motel", "Description": "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts.", "Description_fr": "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.", "Category": "Boutique", "Tags": ["pool", "free wifi", "concierge"], "ParkingIncluded": "false", "LastRenovationDate": "1979-02-18T00:00:00Z", "Rating": 3.6, "Address": { "StreetAddress": "140 University Town Center Dr", "City": "Sarasota", "StateProvince": "FL", "PostalCode": "34243" } }, { "HotelId": "3", "HotelName": "Triple Landscape Hotel", "Description": "The Hotel stands out for its gastronomic excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services.", "Description_fr": "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.", "Category": "Resort and Spa", "Tags": ["air conditioning", "bar", "continental breakfast"], "ParkingIncluded": "true", "LastRenovationDate": "2015-09-20T00:00:00Z", "Rating": 4.8, "Address": { "StreetAddress": "3393 Peachtree Rd", "City": "Atlanta", "StateProvince": "GA", "PostalCode": "30326" } }, { "HotelId": "4", "HotelName": "Sublime Cliff Hotel", "Description": "Sublime Cliff Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 1800 palace.", "Description_fr": "Le sublime Cliff Hotel est situé au coeur du centre historique de sublime dans un quartier extrêmement animé et vivant, à courte distance de marche des sites et monuments de la ville et est entouré par l'extraordinaire beauté des églises, des bâtiments, des commerces et Monuments. Sublime Cliff fait partie d'un Palace 1800 restauré avec amour.", "Category": "Boutique", "Tags": ["concierge", "view", "24-hour front desk service"], "ParkingIncluded": true, "LastRenovationDate": "1960-02-06T00:00:00Z", "Rating": 4.6, "Address": { "StreetAddress": "7400 San Pedro Ave", "City": "San Antonio", "StateProvince": "TX", "PostalCode": "78216" } } ] }
Creare il file per eseguire la
tsconfig.json
transpile del codice TypeScript e copiare il codice seguente per ECMAScript.{ "compilerOptions": { "module": "NodeNext", "target": "ES2022", // Supports top-level await "moduleResolution": "NodeNext", "skipLibCheck": true, // Avoid type errors from node_modules "strict": true // Enable strict type-checking options }, "include": ["*.ts"] }
Transpile da TypeScript a JavaScript.
tsc
Accedere ad Azure con il comando seguente:
az login
Eseguire il codice JavaScript con il comando seguente:
node index.js
Spiegazione del codice
Creare un indice
Creare un file hotels_quickstart_index.json. Questo file definisce il funzionamento di Ricerca intelligenza artificiale di Azure con i documenti caricati nel passaggio successivo. Ogni campo è identificato da un name
oggetto e ha un oggetto specificato type
. Ogni campo dispone anche di una serie di attributi di indice che specificano se Azure AI Search può eseguire ricerche, applicare filtri, eseguire l'ordinamento e applicare facet nel campo. Quasi tutti i campi sono tipi di dati semplici, ma alcuni come AddressType
sono tipi complessi che consentono di creare strutture di dati avanzate nell'indice. Per altre informazioni sui tipi di dati supportati e sugli attributi di indice descritti, vedere Creare un indice (REST).
Si vuole importare hotels_quickstart_index.json in modo che la funzione principale possa accedere alla definizione dell'indice.
import indexDefinition from './hotels_quickstart_index.json';
interface HotelIndexDefinition {
name: string;
fields: SimpleField[] | ComplexField[];
suggesters: SearchSuggester[];
};
const hotelIndexDefinition: HotelIndexDefinition = indexDefinition as HotelIndexDefinition;
All'interno della funzione main creare quindi un SearchIndexClient
, che viene usato per creare e gestire gli indici per Azure AI Search.
const indexClient = new SearchIndexClient(endpoint, new AzureKeyCredential(apiKey));
A questo punto eliminare l'indice, se esiste già. Questa operazione è una procedura comune per il codice di test/demo.
A questo scopo occorre definire una funzione semplice che tenta di eliminare l'indice.
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.');
}
}
Per eseguire la funzione, estrarre il nome dell'indice dalla definizione dell'indice e quindi passare indexName
insieme a indexClient
alla funzione 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);
A questo punto è possibile creare l'indice con il metodo createIndex()
.
console.log('Creating index...');
let index = await indexClient.createIndex(hotelIndexDefinition);
console.log(`Index named ${index.name} has been created.`);
Caricare i documenti
In Azure AI Search i documenti sono strutture dei dati che costituiscono sia l'input per l'indicizzazione che l'output restituito dalle query. È possibile eseguire il push di questi dati nell'indice oppure usare un indicizzatore. In questo caso si eseguirà il push dei documenti nell'indice a livello di codice.
Gli input dei documenti possono essere righe in un database, BLOB nell'archiviazione BLOB o, come in questo esempio, documenti JSON nel disco. È possibile scaricare hotels.json o creare il proprio file hotels.json con il contenuto seguente:
Analogamente a quanto è stato fatto con indexDefinition, occorre anche importare hotels.json
all'inizio del file index.ts in modo che i dati siano accessibili nella funzione main.
import hotelData from './hotels.json';
interface Hotel {
HotelId: string;
HotelName: string;
Description: string;
Description_fr: string;
Category: string;
Tags: string[];
ParkingIncluded: string | boolean;
LastRenovationDate: string;
Rating: number;
Address: {
StreetAddress: string;
City: string;
StateProvince: string;
PostalCode: string;
};
};
const hotels: Hotel[] = hotelData["value"];
Per indicizzare i dati nell'indice di ricerca, è ora necessario creare un SearchClient
. Mentre SearchIndexClient
viene usato per creare e gestire un indice, SearchClient
viene usato per caricare i documenti ed eseguire query sull'indice.
Esistono due modi per creare un elemento SearchClient
. La prima opzione consiste nel creare un SearchClient
da zero:
const searchClient = new SearchClient<Hotel>(endpoint, indexName, new AzureKeyCredential(apiKey));
In alternativa, è possibile usare il metodo getSearchClient()
di SearchIndexClient
per creare SearchClient
:
const searchClient = indexClient.getSearchClient<Hotel>(indexName);
Una volta definito il client, caricare i documenti nell'indice di ricerca. In questo caso viene usato il mergeOrUploadDocuments()
metodo , che carica i documenti o li unisce a un documento esistente se esiste già un documento con la stessa chiave. Verificare quindi che l'operazione sia andata a buon fine perché almeno il primo documento esiste.
console.log("Uploading documents...");
const indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotels);
console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)}`);
Eseguire di nuovo il programma con tsc && node index.ts
. Verrà visualizzato un set di messaggi leggermente diverso rispetto a quanto visualizzato nel passaggio 1. Questa volta l'indice esiste e dovrebbe quindi essere visualizzato un messaggio relativo alla sua eliminazione prima che l'app crei il nuovo indice e vi inserisca i dati.
Prima di eseguire le query nel passaggio successivo, occorre definire una funzione che indichi al programma di attendere un secondo. Questa operazione viene eseguita solo a scopo di test/dimostrativo per assicurarsi che l'indicizzazione venga completata e che i documenti siano disponibili nell'indice per le query.
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
Per fare in modo che il programma attenda un secondo, chiamare la sleep
funzione :
sleep(1000);
Eseguire la ricerca in un indice
Dopo aver creato un indice e aver caricato i documenti, è possibile iniziare a inviare query all'indice. In questa sezione vengono inviate cinque query diverse all'indice di ricerca per illustrare diverse funzionalità di query disponibili.
Le query vengono scritte in una sendQueries()
funzione chiamata nella funzione main come indicato di seguito:
await sendQueries(searchClient);
Le query vengono inviate usando il metodo search()
di searchClient
. Il primo parametro è il testo della ricerca, mentre il secondo parametro specifica le opzioni di ricerca.
Esempio di query 1
La prima query cerca *
, che equivale alla ricerca di tutti gli elementi, e seleziona tre dei campi nell'indice. È consigliabile select
solo i campi necessari, in quanto il pull di dati non necessari può aggiungere latenza alle query.
searchOptions
per questa query contiene anche includeTotalCount
impostato su true
, che restituirà il numero di risultati corrispondenti trovati.
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
}
Anche le query rimanenti riportate di seguito dovrebbero essere aggiunte alla funzione sendQueries()
. Qui sono separate per migliorare la leggibilità.
Esempio di query 2
Nella query successiva viene specificato il termine di ricerca "wifi"
e viene incluso anche un filtro per restituire solo i risultati in cui lo stato è uguale a 'FL'
. I risultati sono inoltre ordinati in base al Rating
dell'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)}`);
}
Esempio di query 3
La ricerca viene quindi limitata a un singolo campo ricercabile con il parametro searchFields
. Questo approccio è un'ottima opzione per rendere la query più efficiente, se si è interessati solo alle corrispondenze in determinati campi.
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)}`);
}
Esempio di query 4
Un'altra opzione comune da includere in una query è facets
. I facet consentono di fornire il drill-down autoindirizzato dai risultati nella propria interfaccia utente. I risultati dei facet possono essere trasformati in caselle di controllo nel riquadro dei risultati.
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)}`);
}
Esempio di query 5
La query finale usa il metodo getDocument()
di searchClient
. Questo consente di recuperare in modo efficiente un documento in base alla relativa chiave.
console.log('Query #5 - Lookup document:');
let documentResult = await searchClient.getDocument('3')
console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`)
Riepilogo delle query
Le query precedenti mostrano più modi per trovare termini corrispondenti in una query: ricerca full-text, filtri e completamento automatico.
La ricerca full-text e i filtri vengono eseguiti usando il searchClient.search
metodo . È possibile passare una query di ricerca nella searchText
stringa, mentre un'espressione di filtro può essere passata nella filter
proprietà della SearchOptions
classe . Per filtrare senza eseguire ricerche, passare semplicemente "*" per il parametro searchText
del metodo search
. Per eseguire una ricerca senza filtrare, lasciare la proprietà filter
non impostata oppure non passare un'istanza di SearchOptions
.
Pulire le risorse
Quando si lavora nella propria sottoscrizione, al termine di un progetto è buona norma determinare se le risorse create sono ancora necessarie. Le risorse che rimangono in esecuzione hanno un costo. È possibile eliminare risorse singole oppure gruppi di risorse per eliminare l'intero set di risorse.
È possibile trovare e gestire le risorse nella portale di Azure, usando il collegamento Tutte le risorse o Gruppi di risorse nel riquadro di spostamento a sinistra.
Se si usa un servizio gratuito, tenere presente che il numero di indicizzatori e origini dati è limitato a tre. È possibile eliminare singoli elementi nel portale di Azure per rimanere al di sotto del limite.
Passaggio successivo
In questo Avvio rapido è stata eseguita una serie di attività per creare un indice, caricarvi documenti ed eseguire query. In diverse fasi sono state adottate delle scorciatoie per semplificare il codice e renderlo più leggibile e comprensibile. Ora che si ha familiarità con i concetti di base, provare un'esercitazione che chiama le API di Azure AI Search in un'app Web.