Créez une application console en utilisant la bibliothèque cliente Azure.Search.Documents pour ajouter un classement sémantique à un index de recherche existant.
Alternativement, vous pouvez télécharger le code source pour démarrer avec un projet terminé.
Configurer votre environnement
Démarrez Visual Studio et créez un projet d’application console.
Dans Outils>Gestionnaire de package NuGet, sélectionnez Gérer les packages NuGet pour la solution... .
Sélectionnez Parcourir.
Recherchez le package Azure.Search.Documents et sélectionnez la dernière version stable.
Sélectionnez Installer pour ajouter l’assembly à votre projet et à votre solution.
Créer un client de recherche
Dans Program.cs, ajoutez les directives using
suivantes.
using Azure;
using Azure.Search.Documents;
using Azure.Search.Documents.Indexes;
using Azure.Search.Documents.Indexes.Models;
using Azure.Search.Documents.Models;
Créez deux clients : SearchIndexClient crée l’index et SearchClient charge et interroge un index existant.
Les deux clients ont besoin du point de terminaison de service et d’une clé API d’administrateur pour l’authentification avec des droits de création/suppression. Cependant, le code crée l'URI pour vous, spécifiez donc uniquement le nom du service de recherche pour la propriété serviceName
. Ne pas inclure https://
ou .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);
. . .
}
Création d'un index
Créez ou mettez à jour un schéma d’index pour inclure un SemanticConfiguration
. Si vous mettez à jour un index existant, cette modification ne nécessite pas de réindexation, car la structure de vos documents est inchangée.
// 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);
}
Le code suivant crée l’index sur votre service de recherche :
// Create index
Console.WriteLine("{0}", "Creating index...\n");
CreateIndex(indexName, adminClient);
SearchClient ingesterClient = adminClient.GetSearchClient(indexName);
Chargement de documents
Recherche Azure AI effectue des recherches parmi les contenus stockés dans le service. Le code de chargement de documents est identique à celui du guide de démarrage rapide C# pour la recherche en texte intégral. Nous n’avons donc pas besoin de le dupliquer ici. Vous devriez obtenir quatre hôtels ainsi que leurs noms, leurs adresses et leurs descriptions. Votre solution devrait présenter des types d’hôtels et d’adresses.
Rechercher dans un index
Voici une requête qui appelle le classeur sémantique, avec des options de recherche permettant de spécifier des paramètres :
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);
À titre de comparaison, voici les résultats d’une requête qui utilise le classement BM25 par défaut en fonction de la fréquence et de la proximité des termes. À partir de la requête « hôtel qui propose un bon restaurant sur place », l’algorithme de classement BM25 retourne des correspondances dans l’ordre indiqué dans cette capture d’écran :
En revanche, lorsque le classement sémantique est appliqué à la même requête (« hôtel qui propose un bon restaurant sur place »), les résultats sont reclassés en fonction de la pertinence sémantique de la requête. Cette fois, le premier résultat est l’hôtel avec le restaurant, qui s’aligne mieux sur les attentes des utilisateurs.
Exécuter le programme
Appuyez sur F5 pour regénérer l’application et exécuter le programme dans son intégralité.
La sortie comprend des messages de la méthode Console.WriteLine, avec en plus des informations et des résultats de requête.
Utilisez un notebook Jupyter et la bibliothèque azure-search-documents dans le Kit de développement logiciel (SDK) Azure pour Python pour en savoir plus sur le classement sémantique.
Alternativement, vous pouvez télécharger et exécuter un notebook terminé.
Paramétrer votre environnement
Utilisez Visual Studio Code avec l’extension Python, ou un IDE équivalent, avec Python 3.10 ou version ultérieure.
Nous vous recommandons un environnement virtuel pour ce démarrage rapide :
Démarrez Visual Studio Code.
Créer un nouveau fichier ipynb.
Ouvrez la palette de commandes en utilisant Ctrl+Maj+P.
Recherchez Python : Créer un environnement.
Sélectionnez Venv.
Sélectionnez un interpréteur Python. Choisissez la version 3.10 ou ultérieure.
La configuration peut prendre une minute. Si vous rencontrez des problèmes, consultez Environnements Python dans VS Code.
Installer des packages et définir des variables
Installez des packages, y compris azure-search-documents.
! pip install azure-search-documents==11.6.0b1 --quiet
! pip install azure-identity --quiet
! pip install python-dotenv --quiet
Fournissez votre point de terminaison et vos clés 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"
Création d'un index
Créez ou mettez à jour un schéma d’index pour inclure un SemanticConfiguration
. Si vous mettez à jour un index existant, cette modification ne nécessite pas de réindexation, car la structure de vos documents est inchangée.
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')
Créer une charge utile de documents
Vous pouvez transférer des documents JSON à un index de recherche. Les documents doivent correspondre au schéma d’index.
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"
}
}
]
Charger des documents dans l’index
search_client = SearchClient(endpoint=search_endpoint,
index_name=index_name,
credential=credential)
try:
result = search_client.upload_documents(documents=documents)
print("Upload of new document succeeded: {}".format(result[0].succeeded))
except Exception as ex:
print (ex.message)
index_client = SearchIndexClient(
endpoint=search_endpoint, credential=credential)
Exécuter votre première requête
Commencez par effectuer une requête vide pour vérifier que l’index est opérationnel. Vous devez obtenir une liste non triée de noms et de descriptions d’hôtels, ainsi que le chiffre 4 qui indique que l’index comporte quatre documents.
# 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']}")
Exécuter une requête de texte
À des fins de comparaison, exécutez une requête de texte avec le score de pertinence BM25. La recherche en texte intégral est appelée lorsque vous fournissez une chaîne de requête. La réponse se compose de résultats classés, où des scores plus élevés sont attribués aux documents comportant davantage d’instances de termes correspondants, ou comportant des termes plus importants.
Dans cette requête pour savoir quel hôtel dispose d'un bon restaurant sur place, Sublime Palace Hotel arrive en tête car sa description inclut le site. Les termes qui apparaissent rarement augmentent le score de recherche du document.
# 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']}")
Exécuter une requête sémantique
À présent, ajoutez un classement sémantique. Les nouveaux paramètres incluent query_type
et semantic_configuration_name
.
Bien qu’il s’agisse de la même requête, notez que le classement sémantique identifie correctement Gastronomic Landscape Hotel comme un résultat plus pertinent compte tenu de la requête initiale. Cette requête retourne également les légendes générées par les modèles. Dans cet exemple, les entrées sont trop minimales pour créer des légendes intéressantes, mais l’exemple parvient à démontrer la syntaxe.
# 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")
Retourner des réponses sémantiques
Dans cette dernière requête, renvoyez des réponses sémantiques.
Le classeur sémantique peut générer des réponses à une chaîne de requête qui présente les caractéristiques d’une question. La réponse générée est extraite textuellement de votre contenu. Pour obtenir une réponse sémantique, la question et la réponse doivent être étroitement alignées, et le modèle doit trouver du contenu qui répond clairement à la question. Si les réponses potentielles ne parviennent pas à atteindre un certain seuil de confiance, le modèle ne retourne pas de réponse. À des fins de démonstration, la question de cet exemple renvoie une réponse vous permettant d’examiner la syntaxe.
# 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")