Compilare un'applicazione console usando la libreria client Azure.Search.Documents per aggiungere una classificazione semantica a un indice di ricerca esistente.
In alternativa, è possibile scaricare il codice sorgente per iniziare con un progetto completato.
Configurazione dell'ambiente
Avviare Visual Studio e creare un nuovo progetto per un'app console.
In Strumenti>Gestione pacchetti NuGet, selezionare Gestisci pacchetti NuGet per la soluzione.
Selezionare Sfoglia.
Cercare il pacchetto Azure.Search.Documents e selezionare la versione stabile più recente.
Selezionare Installa per aggiungere l'assembly al progetto e alla soluzione.
Creare un client di ricerca
In Program.cs aggiungere le seguenti direttive using
.
using Azure;
using Azure.Search.Documents;
using Azure.Search.Documents.Indexes;
using Azure.Search.Documents.Indexes.Models;
using Azure.Search.Documents.Models;
Creare due client: SearchIndexClient crea l'indice, mentre SearchClient carica un indice esistente ed esegue query su un indice esistente.
Entrambi i client necessitano dell'endpoint di servizio e di una chiave API amministratore per l'autenticazione con diritti di creazione/eliminazione. Tuttavia, il codice compila automaticamente l'URI, quindi specificare solo il nome del servizio di ricerca per la serviceName
proprietà . Non includere https://
o .search.windows.net
.
static void Main(string[] args)
{
string serviceName = "<YOUR-SEARCH-SERVICE-NAME>";
string apiKey = "<YOUR-SEARCH-ADMIN-API-KEY>";
string indexName = "hotels-quickstart";
// Create a SearchIndexClient to send create/delete index commands
Uri serviceEndpoint = new Uri($"https://{serviceName}.search.windows.net/");
AzureKeyCredential credential = new AzureKeyCredential(apiKey);
SearchIndexClient adminClient = new SearchIndexClient(serviceEndpoint, credential);
// Create a SearchClient to load and query documents
SearchClient srchclient = new SearchClient(serviceEndpoint, indexName, credential);
. . .
}
Creare un indice
Creare o aggiornare uno schema di indice per includere un oggetto SemanticConfiguration
. Se si aggiorna un indice esistente, questa modifica non richiede una reindicizzazione perché la struttura dei documenti rimane invariata.
// Create hotels-quickstart index
private static void CreateIndex(string indexName, SearchIndexClient adminClient)
{
FieldBuilder fieldBuilder = new FieldBuilder();
var searchFields = fieldBuilder.Build(typeof(Hotel));
var definition = new SearchIndex(indexName, searchFields);
var suggester = new SearchSuggester("sg", new[] { "HotelName", "Category", "Address/City", "Address/StateProvince" });
definition.Suggesters.Add(suggester);
definition.SemanticSearch = new SemanticSearch
{
Configurations =
{
new SemanticConfiguration("my-semantic-config", new()
{
TitleField = new SemanticField("HotelName"),
ContentFields =
{
new SemanticField("Description"),
new SemanticField("Description_fr")
},
KeywordsFields =
{
new SemanticField("Tags"),
new SemanticField("Category")
}
})
}
};
adminClient.CreateOrUpdateIndex(definition);
}
Il codice seguente crea l'indice nel servizio di ricerca:
// Create index
Console.WriteLine("{0}", "Creating index...\n");
CreateIndex(indexName, adminClient);
SearchClient ingesterClient = adminClient.GetSearchClient(indexName);
Caricare i documenti
Azure AI Search esegue ricerche sul contenuto archiviato nel servizio. Il codice per il caricamento dei documenti è identico a quello della guida introduttiva C# per la ricerca full-text, quindi non è necessario duplicarlo qui. Dovrebbero essere presenti quattro hotel con nomi, indirizzi e descrizioni. La soluzione deve avere tipi per Hotel e Indirizzi.
Eseguire la ricerca in un indice
Ecco una query che richiama il classificatore semantico, con opzioni di ricerca per specificare i parametri:
Console.WriteLine("Example of a semantic query.");
options = new SearchOptions()
{
QueryType = Azure.Search.Documents.Models.SearchQueryType.Semantic,
SemanticSearch = new()
{
SemanticConfigurationName = "my-semantic-config",
QueryCaption = new(QueryCaptionType.Extractive)
}
};
options.Select.Add("HotelName");
options.Select.Add("Category");
options.Select.Add("Description");
// response = srchclient.Search<Hotel>("*", options);
response = srchclient.Search<Hotel>("what hotel has a good restaurant on site", options);
WriteDocuments(response);
Per un confronto, ecco i risultati di una query che usa la classificazione BM25 predefinita, in base alla frequenza dei termini e alla prossimità. Data la query "quale hotel ha un buon ristorante in loco", l'algoritmo di classificazione BM25 restituisce corrispondenze nell'ordine illustrato in questo screenshot:
Al contrario, quando la classificazione semantica viene applicata alla stessa query ("quale hotel ha un buon ristorante in loco"), i risultati vengono classificati nuovamente in base alla pertinenza semantica della query. Questa volta, il risultato principale è l'hotel con il ristorante, che si allinea meglio alle aspettative degli utenti.
Eseguire il programma
Premere F5 per ricompilare l'app ed eseguire il programma completo.
L'output include i messaggi restituiti da Console.WriteLine, con l'aggiunta di informazioni sulle query e i risultati.
Usare un Jupyter Notebook e la libreria azure-search-documents in Azure SDK per Python, per trovare informazioni sulla classificazione semantica.
In alternativa, è possibile scaricare ed eseguire un notebook completato.
Configurazione dell'ambiente
Usare Visual Studio Code con l'estensione Python o IDE equivalente con Python 3.10 o versione successiva.
Per questo avvio rapido è consigliabile usare un ambiente virtuale:
Avviare Visual Studio Code.
Creare un nuovo file ipynb.
Aprire il riquadro comandi usando CTRL+MAIUSC+P.
Cercare Python: Crea ambiente.
Selezionare Venv.
Selezionare un interprete Python. Scegliere 3.10 o versione successiva.
La configurazione può richiedere alcuni minuti. Se si verificano problemi, vedere Ambienti Python in VS Code.
Installare i pacchetti e impostare le variabili
Installare i pacchetti, inclusi azure-search-documents.
! pip install azure-search-documents==11.6.0b1 --quiet
! pip install azure-identity --quiet
! pip install python-dotenv --quiet
Specificare l'endpoint e le chiavi API:
search_endpoint: str = "PUT-YOUR-SEARCH-SERVICE-ENDPOINT-HERE"
search_api_key: str = "PUT-YOUR-SEARCH-SERVICE-ADMIN-API-KEY-HERE"
index_name: str = "hotels-quickstart"
Creare un indice
Creare o aggiornare uno schema di indice per includere un oggetto SemanticConfiguration
. Se si aggiorna un indice esistente, questa modifica non richiede una reindicizzazione perché la struttura dei documenti rimane invariata.
from azure.search.documents.indexes import SearchIndexClient
from azure.search.documents import SearchClient
from azure.search.documents.indexes.models import (
ComplexField,
SimpleField,
SearchFieldDataType,
SearchableField,
SearchIndex,
SemanticConfiguration,
SemanticField,
SemanticPrioritizedFields,
SemanticSearch
)
# Create a search schema
index_client = SearchIndexClient(
endpoint=search_endpoint, credential=credential)
fields = [
SimpleField(name="HotelId", type=SearchFieldDataType.String, key=True),
SearchableField(name="HotelName", type=SearchFieldDataType.String, sortable=True),
SearchableField(name="Description", type=SearchFieldDataType.String, analyzer_name="en.lucene"),
SearchableField(name="Description_fr", type=SearchFieldDataType.String, analyzer_name="fr.lucene"),
SearchableField(name="Category", type=SearchFieldDataType.String, facetable=True, filterable=True, sortable=True),
SearchableField(name="Tags", collection=True, type=SearchFieldDataType.String, facetable=True, filterable=True),
SimpleField(name="ParkingIncluded", type=SearchFieldDataType.Boolean, facetable=True, filterable=True, sortable=True),
SimpleField(name="LastRenovationDate", type=SearchFieldDataType.DateTimeOffset, facetable=True, filterable=True, sortable=True),
SimpleField(name="Rating", type=SearchFieldDataType.Double, facetable=True, filterable=True, sortable=True),
ComplexField(name="Address", fields=[
SearchableField(name="StreetAddress", type=SearchFieldDataType.String),
SearchableField(name="City", type=SearchFieldDataType.String, facetable=True, filterable=True, sortable=True),
SearchableField(name="StateProvince", type=SearchFieldDataType.String, facetable=True, filterable=True, sortable=True),
SearchableField(name="PostalCode", type=SearchFieldDataType.String, facetable=True, filterable=True, sortable=True),
SearchableField(name="Country", type=SearchFieldDataType.String, facetable=True, filterable=True, sortable=True),
])
]
semantic_config = SemanticConfiguration(
name="my-semantic-config",
prioritized_fields=SemanticPrioritizedFields(
title_field=SemanticField(field_name="HotelName"),
keywords_fields=[SemanticField(field_name="Category")],
content_fields=[SemanticField(field_name="Description")]
)
)
# Create the semantic settings with the configuration
semantic_search = SemanticSearch(configurations=[semantic_config])
semantic_settings = SemanticSearch(configurations=[semantic_config])
scoring_profiles = []
suggester = [{'name': 'sg', 'source_fields': ['Tags', 'Address/City', 'Address/Country']}]
# Create the search index with the semantic settings
index = SearchIndex(name=index_name, fields=fields, suggesters=suggester, scoring_profiles=scoring_profiles, semantic_search=semantic_search)
result = index_client.create_or_update_index(index)
print(f' {result.name} created')
Creare un payload di documenti
È possibile eseguire il push di documenti JSON in un indice di ricerca. I documenti devono corrispondere allo schema dell'indice.
documents = [
{
"@search.action": "upload",
"HotelId": "1",
"HotelName": "Stay-Kay City Hotel",
"Description": "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.",
"Description_fr": "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de New York. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de New York l'une des villes les plus attractives et cosmopolites de l'Amérique.",
"Category": "Boutique",
"Tags": [ "pool", "air conditioning", "concierge" ],
"ParkingIncluded": "false",
"LastRenovationDate": "1970-01-18T00:00:00Z",
"Rating": 3.60,
"Address": {
"StreetAddress": "677 5th Ave",
"City": "New York",
"StateProvince": "NY",
"PostalCode": "10022",
"Country": "USA"
}
},
{
"@search.action": "upload",
"HotelId": "2",
"HotelName": "Old Century Hotel",
"Description": "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts.",
"Description_fr": "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.",
"Category": "Boutique",
"Tags": [ "pool", "free wifi", "concierge" ],
"ParkingIncluded": "false",
"LastRenovationDate": "1979-02-18T00:00:00Z",
"Rating": 3.60,
"Address": {
"StreetAddress": "140 University Town Center Dr",
"City": "Sarasota",
"StateProvince": "FL",
"PostalCode": "34243",
"Country": "USA"
}
},
{
"@search.action": "upload",
"HotelId": "3",
"HotelName": "Gastronomic Landscape Hotel",
"Description": "The Hotel stands out for its gastronomic excellence under the management of William Dough, who advises on and oversees all of the Hotel's restaurant services.",
"Description_fr": "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.",
"Category": "Resort and Spa",
"Tags": [ "air conditioning", "bar", "continental breakfast" ],
"ParkingIncluded": "true",
"LastRenovationDate": "2015-09-20T00:00:00Z",
"Rating": 4.80,
"Address": {
"StreetAddress": "3393 Peachtree Rd",
"City": "Atlanta",
"StateProvince": "GA",
"PostalCode": "30326",
"Country": "USA"
}
},
{
"@search.action": "upload",
"HotelId": "4",
"HotelName": "Sublime Palace Hotel",
"Description": "Sublime Palace Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Palace is part of a lovingly restored 1800 palace.",
"Description_fr": "Le Sublime Palace Hotel est situé au coeur du centre historique de sublime dans un quartier extrêmement animé et vivant, à courte distance de marche des sites et monuments de la ville et est entouré par l'extraordinaire beauté des églises, des bâtiments, des commerces et Monuments. Sublime Palace fait partie d'un Palace 1800 restauré avec amour.",
"Category": "Boutique",
"Tags": [ "concierge", "view", "24-hour front desk service" ],
"ParkingIncluded": "true",
"LastRenovationDate": "1960-02-06T00:00:00Z",
"Rating": 4.60,
"Address": {
"StreetAddress": "7400 San Pedro Ave",
"City": "San Antonio",
"StateProvince": "TX",
"PostalCode": "78216",
"Country": "USA"
}
}
]
Caricare documenti nell'indice
search_client = SearchClient(endpoint=search_endpoint,
index_name=index_name,
credential=credential)
try:
result = search_client.upload_documents(documents=documents)
print("Upload of new document succeeded: {}".format(result[0].succeeded))
except Exception as ex:
print (ex.message)
index_client = SearchIndexClient(
endpoint=search_endpoint, credential=credential)
Eseguire la prima query
Iniziare con una query vuota come passaggio di verifica, dimostrando che l'indice è operativo. Si dovrebbe ottenere un elenco non ordinato di nomi e descrizioni degli hotel, con un conteggio pari a 4 che indica che ci sono quattro documenti nell'indice.
# Run an empty query (returns selected fields, all documents)
results = search_client.search(query_type='simple',
search_text="*" ,
select='HotelName,Description',
include_total_count=True)
print ('Total Documents Matching Query:', results.get_count())
for result in results:
print(result["@search.score"])
print(result["HotelName"])
print(f"Description: {result['Description']}")
Eseguire una query di testo
A scopo di confronto, eseguire una query di testo con punteggio di pertinenza BM25. La ricerca full-text viene richiamata quando si specifica una stringa di query. La risposta è costituita da risultati classificati, in cui i punteggi più alti vengono assegnati ai documenti con più istanze di termini corrispondenti o termini più importanti.
In questa query per quale hotel ha un buon ristorante in loco, Sublime Palace Hotel viene fuori in cima perché la sua descrizione include sito. I termini che si presentano raramente aumentano il punteggio di ricerca del documento.
# Run a text query (returns a BM25-scored result set)
results = search_client.search(query_type='simple',
search_text="what hotel has a good restaurant on site" ,
select='HotelName,HotelId,Description',
include_total_count=True)
for result in results:
print(result["@search.score"])
print(result["HotelName"])
print(f"Description: {result['Description']}")
Eseguire una query semantica
Aggiungere ora la classificazione semantica. I nuovi parametri includono query_type
e semantic_configuration_name
.
Si tratta della stessa query, ma si noti che il rango semantico identifica correttamente Gastronomic Landscape Hotel come risultato più pertinente in base alla query iniziale. Questa query restituisce anche sottotitoli generati dai modelli. In questo esempio, gli input sono troppo minimi per creare sottotitoli interessanti, ma l'esempio riesce a dimostrare la sintassi.
# Runs a semantic query (runs a BM25-ranked query and promotes the most relevant matches to the top)
results = search_client.search(query_type='semantic', semantic_configuration_name='my-semantic-config',
search_text="what hotel has a good restaurant on site",
select='HotelName,Description,Category', query_caption='extractive')
for result in results:
print(result["@search.reranker_score"])
print(result["HotelName"])
print(f"Description: {result['Description']}")
captions = result["@search.captions"]
if captions:
caption = captions[0]
if caption.highlights:
print(f"Caption: {caption.highlights}\n")
else:
print(f"Caption: {caption.text}\n")
Restituire risposte semantiche
In questa query finale, restituire risposte semantiche.
Il classificatore semantico può generare risposte a una stringa di query con le caratteristiche di una domanda. La risposta generata viene estratta verbatim dal contenuto. Per ottenere una risposta semantica, la domanda e la risposta devono essere strettamente allineate e il modello deve trovare contenuto che risponda chiaramente alla domanda. Se le risposte potenziali non soddisfano una soglia di confidenza, il modello non restituisce una risposta. A scopo dimostrativo, la domanda in questo esempio è progettata per ottenere una risposta in modo che sia possibile visualizzare la sintassi.
# Run a semantic query that returns semantic answers
results = search_client.search(query_type='semantic', semantic_configuration_name='my-semantic-config',
search_text="what hotel is in a historic building",
select='HotelName,Description,Category', query_caption='extractive', query_answer="extractive",)
semantic_answers = results.get_answers()
for answer in semantic_answers:
if answer.highlights:
print(f"Semantic Answer: {answer.highlights}")
else:
print(f"Semantic Answer: {answer.text}")
print(f"Semantic Answer Score: {answer.score}\n")
for result in results:
print(result["@search.reranker_score"])
print(result["HotelName"])
print(f"Description: {result['Description']}")
captions = result["@search.captions"]
if captions:
caption = captions[0]
if caption.highlights:
print(f"Caption: {caption.highlights}\n")
else:
print(f"Caption: {caption.text}\n")