Recherche de termes partiels et modèles avec des caractères spéciaux (traits d’union, caractères génériques, expressions régulières, modèles)
Une recherche de terme partiel fait référence à des requêtes composées de fragments de termes où, au lieu d’un terme entier, vous pouvez avoir juste le début, le milieu ou la fin du terme (elle est parfois appelée requête de préfixe, d’infixe ou de suffixe). Une recherche de terme partiel peut inclure une combinaison de fragments, souvent avec des caractères spéciaux comme des traits d’union, des tirets ou des barres obliques qui font partie de la chaîne de requête. Les cas d’usage courants incluent les parties d’un numéro de téléphone, une URL, des codes ou des mots composés avec trait d’union.
Les termes partiels et les caractères spéciaux peuvent poser problème si l’index n’a pas de jeton représentant le fragment de texte que vous souhaitez rechercher. Pendant la phase d’analyse lexicale de l’indexation des mots clés (en supposant une utilisation de l’analyseur standard par défaut), les caractères spéciaux sont ignorés, les mots composés sont fractionnés et l’espace blanc est supprimé. Si vous recherchez un fragment de texte modifié lors de l’analyse lexicale, la requête échoue car aucune correspondance n’est trouvée. Par exemple, un numéro de téléphone comme +1 (425) 703-6214
(segmenté en jetons "1"
, "425"
, "703"
, "6214"
) n’apparaît pas dans une requête "3-62"
, car ce contenu n’existe pas réellement dans l’index.
La solution consiste à appeler pendant l’indexation un analyseur qui conserve une chaîne complète, y compris les espaces et les caractères spéciaux si nécessaire, afin que vous puissiez inclure les espaces et les caractères dans votre chaîne de requête. Le fait d’avoir une chaîne complète non segmentée autorise des critères spéciaux pour les requêtes « commence par » ou « se termine par », où le critère que vous fournissez peut être évalué par rapport à un terme qui n’est pas transformé par l’analyse lexicale.
Si vous devez prendre en charge les scénarios de recherche qui appellent du contenu analysé et non analysé, envisagez de créer deux champs dans votre index, un pour chaque scénario. Un champ subit une analyse lexicale. Le deuxième champ stocke une chaîne intacte à l’aide d’un analyseur avec préservation du contenu qui émet des jetons de chaîne entière pour les critères spéciaux.
À propos de la recherche de terme partiel
La Recherche Azure AI recherche des termes entiers segmentés dans l’index et ne trouvera pas de correspondance sur un terme partiel, sauf si vous incluez des opérateurs génériques d’espace réservé (*
et ?
) ou mettez en forme la requête en tant qu’expression régulière.
Les termes partiels sont spécifiés à l’aide des techniques suivantes :
Les requêtes d’expression régulière peuvent être n’importe quelle expression régulière valide sous Apache Lucene.
Les opérateurs au caractère générique avec correspondance de préfixe font référence à un modèle généralement reconnu qui comprend le début d’un terme, suivi d’opérateurs de suffixe
*
ou?
, comme pour la mise en correspondance desearch=cap*
sur « Cap’n Jack’s Waterfront Inn » ou « Highline Capital ». La mise en correspondance de préfixe est prise en charge dans la syntaxe de requête Lucene simple et complète.La mise en correspondance générique avec infixe et suffixe place les opérateurs
*
et?
à l’intérieur ou au début d’un terme, et nécessite une syntaxe d’expression régulière (où l’expression est entourée de barres obliques). Par exemple, la chaîne de requête (search=/.*numeric.*/
) retourne les résultats sur « alphanumeric » et « alphanumerical » en tant que correspondances de suffixe et d’infixe.
Pour les expressions régulières, les caractères génériques et la recherche approximative, les analyseurs ne sont pas utilisés au moment de la requête. Pour ces formes de requête, que l’analyseur détecte par la présence d’opérateurs et de délimiteurs, la chaîne de requête est passée au moteur sans analyse lexicale. Pour ces formes de requête, l’analyseur spécifié sur le champ est ignoré.
Remarque
Quand une chaîne de requête partielle inclut des caractères, tels que des barres obliques dans un fragment d’URL, vous devrez peut-être ajouter des caractères d’échappement. Dans JSON, une barre oblique /
est placée dans une séquence d’échappement avec une barre oblique inverse \
. Par conséquent, search=/.*microsoft.com\/azure\/.*/
est la syntaxe pour le fragment d’URL « microsoft.com/azure/ ».
Résolution des problèmes liés à la recherche partielle/de modèles
Quand vous devez effectuer une recherche sur des fragments, modèles ou caractères spéciaux, vous pouvez remplacer l’analyseur par défaut par un analyseur personnalisé qui fonctionne selon des règles de tokenisation plus simples, préservant la chaîne entière dans l’index.
L’approche ressemble à ceci :
- Définir un deuxième champ pour stocker une version intacte de la chaîne (en supposant que vous voulez du texte analysé et non analysé au moment de la requête)
- Évaluer et choisir parmi les différents analyseurs qui émettent des jetons au niveau de précision approprié
- Attribuer l’analyseur au champ
- Générer et tester l’index
1 - Créer un champ dédié
Les analyseurs déterminent comment les termes sont tokenisés dans un index. Les analyseurs étant affectés par champ, vous pouvez créer des champs dans votre index pour optimiser les différents scénarios. Par exemple, vous pouvez définir « featureCode » et « featureCodeRegex » pour prendre en charge une recherche en texte intégral normale sur le premier et des critères spéciaux avancés sur le second. Les analyseurs affectés à chaque champ déterminent comment le contenu de chaque champ est tokenisé dans l’index.
{
"name": "featureCode",
"type": "Edm.String",
"retrievable": true,
"searchable": true,
"analyzer": null
},
{
"name": "featureCodeRegex",
"type": "Edm.String",
"retrievable": true,
"searchable": true,
"analyzer": "my_custom_analyzer"
},
2 - Définir un analyseur
Lors du choix d’un analyseur qui produit des jetons à terme entier, les analyseurs suivants sont des choix courants :
Analyseur | Comportements |
---|---|
Analyseurs de langage | Conserve les traits d’Union dans les mots composés, les chaînes, les mutations de voyelles et les formes verbales. Si les modèles de requête incluent des tirets, l’utilisation d’un analyseur de langage peut suffire. |
mot clé | Le contenu du champ entier est segmenté en jetons en tant que terme unique. |
whitespace | Sépare sur les espaces blancs seulement. Les termes qui incluent des tirets ou d’autres caractères sont traités comme un jeton unique. |
analyseur personnalisé | (recommandé) La création d’un analyseur personnalisé vous permet de spécifier à la fois le générateur de jetons et le filtre de jeton. Les analyseurs précédents doivent être utilisés tels quels. Un analyseur personnalisé vous permet de choisir les générateurs et les filtres de jetons à utiliser. Une combinaison recommandée est le générateur de jetons keyword avec un filtre de jeton lowercase. En soi, l’analyseur keyword intégré ne met pas en minuscules les caractères majuscules, ce qui peut entraîner l’échec des requêtes. Un analyseur personnalisé vous donne un mécanisme pour ajouter le filtre de jeton lowercase. |
En utilisant un client REST, vous pouvez ajouter l’appel REST de l’analyseur de test pour inspecter la sortie tokenisée.
L’index doit exister sur le service de recherche, mais il peut être vide. À partir d’un index existant et d’un champ contenant des tirets ou des termes partiels, vous pouvez essayer différents analyseurs sur des termes spécifiques pour voir quels jetons sont émis.
Vérifiez d’abord l’analyseur Standard pour voir comment les termes sont tokenisés par défaut.
{ "text": "SVP10-NOR-00", "analyzer": "standard" }
Évaluez la réponse pour voir comment le texte est segmenté en jetons dans l’index. Notez que chaque terme est en minuscules, que les traits d’union ont été supprimés et les sous-chaînes divisées en jetons individuels. Seules les requêtes qui correspondent à ces jetons retourneront ce document dans les résultats. Une requête qui comprend « 10-NOR » échouera.
{ "tokens": [ { "token": "svp10", "startOffset": 0, "endOffset": 5, "position": 0 }, { "token": "nor", "startOffset": 6, "endOffset": 9, "position": 1 }, { "token": "00", "startOffset": 10, "endOffset": 12, "position": 2 } ] }
Maintenant, modifiez la requête pour utiliser l’analyseur
whitespace
oukeyword
:{ "text": "SVP10-NOR-00", "analyzer": "keyword" }
Cette fois, la réponse se compose d’un jeton unique, en majuscules, avec des tirets conservés comme faisant partie de la chaîne. Si vous devez effectuer une recherche sur un modèle ou un terme partiel tel que « 10-NOR », le moteur de requête dispose maintenant de la base pour trouver une correspondance.
{ "tokens": [ { "token": "SVP10-NOR-00", "startOffset": 0, "endOffset": 12, "position": 0 } ] }
Important
N’oubliez pas que les analyseurs de requêtes mettent souvent les termes en minuscules dans une expression de recherche lors de la création de l’arborescence de requêtes. Si vous utilisez un analyseur qui ne met pas les entrées de texte en minuscules durant l’indexation, et que vous n’obtenez pas les résultats attendus, cela peut être la raison. La solution consiste à ajouter un filtre de jeton lowercase, comme décrit dans la section « Utiliser des analyseurs personnalisés », ci-dessous.
3 - Configurer un analyseur
Que vous évaluiez des analyseurs ou que vous adoptiez une configuration spécifique, vous devez spécifier l’analyseur sur la définition de champ et éventuellement configurer l’analyseur lui-même si vous n’utilisez pas d’analyseur intégré. Lorsque vous changez d’analyseur, vous devez généralement reconstruire l’index (supprimer, recréer et recharger).
Utiliser des analyseurs intégrés
Les analyseurs intégrés peuvent être spécifiés par leur nom dans la propriété analyzer
d’une définition de champ, sans qu’une configuration supplémentaire ne soit requise dans l’index. L’exemple suivant montre comment définir l’analyseur whitespace
sur un champ.
Pour d’autres scénarios et pour en savoir plus sur les autres analyseurs intégrés, consultez Analyseurs intégrés.
{
"name": "phoneNumber",
"type": "Edm.String",
"key": false,
"retrievable": true,
"searchable": true,
"analyzer": "whitespace"
}
Utiliser des analyseurs personnalisés
Si vous utilisez un analyseur personnalisé, définissez-le dans l’index avec une combinaison définie par l’utilisateur d’un générateur de jetons et d’un filtre de jeton, avec les paramètres de configuration possibles. Ensuite, référencez-le sur une définition de champ, comme vous le feriez pour un analyseur intégré.
Lorsque l’objectif est la segmentation du texte en termes entiers, un analyseur personnalisé qui se compose d’un générateur de jetons keyword et d’un filtre de jeton lowercase est recommandé.
- Le générateur de jetons keyword crée un jeton unique pour tout le contenu d’un champ.
- Le filtre de jeton lowercase transforme les lettres majuscules en caractères minuscules. Les analyseurs de requêtes mettent généralement en minuscules toutes les entrées de texte qui sont en majuscules. La mise en minuscules homogénéise les entrées avec les termes tokenisés.
L’exemple suivant illustre un analyseur personnalisé qui fournit le générateur de jetons keyword et un filtre de jetons lowercase.
{
"fields": [
{
"name": "accountNumber",
"analyzer":"myCustomAnalyzer",
"type": "Edm.String",
"searchable": true,
"filterable": true,
"retrievable": true,
"sortable": false,
"facetable": false
}
],
"analyzers": [
{
"@odata.type":"#Microsoft.Azure.Search.CustomAnalyzer",
"name":"myCustomAnalyzer",
"charFilters":[],
"tokenizer":"keyword_v2",
"tokenFilters":["lowercase"]
}
],
"tokenizers":[],
"charFilters": [],
"tokenFilters": []
}
Remarque
Le générateur de jetons keyword_v2
et le filtre de jeton lowercase
sont connus du système et utilisent leurs configurations par défaut, c’est pourquoi vous pouvez les référencer par leur nom sans avoir à les définir au préalable.
4 - Créer et tester
Une fois que vous avez défini un index avec des analyseurs et des définitions de champ qui prennent en charge votre scénario, chargez des documents contenant des chaînes représentatives afin de pouvoir tester des requêtes de chaînes partielles.
Utilisez un client REST pour interroger les termes partiels et les caractères spéciaux décrits dans cet article.
Les sections précédentes ont expliqué la logique. Cette section parcourt chaque API que vous devez appeler lors du test de votre solution.
Supprimer l’index supprime un index existant du même nom afin que vous puissiez le recréer.
Créer un index crée la structure d’index sur votre service de recherche, notamment des définitions et des champs d’analyseur avec la spécification d’un analyseur.
Charger des documents importe des documents ayant la même structure que votre index, ainsi que du contenu pouvant faire l’objet d’une recherche. Après cette étape, votre index est prêt à être interrogé ou testé.
L’analyseur de test a été introduit dans Définir un analyseur. Testez certaines chaînes de votre index à l’aide de divers analyseurs pour comprendre comment les termes sont segmentés en jetons.
Rechercher dans les documents explique comment construire une demande de requête, en utilisant une syntaxe simple ou la syntaxe Lucene complète pour les expressions génériques et les expressions régulières.
Pour les requêtes de termes partiels, telles que l’interrogation « 3-6214 » pour trouver une correspondance sur « + 1 (425) 703-6214 », vous pouvez utiliser la syntaxe simple :
search=3-6214&queryType=simple
.Pour les requêtes d’infixe et de suffixe, telles que l’interrogation « num » ou « numérique pour trouver une correspondance sur «alphanumérique », utilisez la syntaxe Lucene complète et une expression régulière :
search=/.*num.*/&queryType=full
Optimiser les requêtes de préfixe et de suffixe
La mise en correspondance des préfixes et suffixes à l’aide de l’analyseur par défaut nécessite des fonctionnalités de requête supplémentaires. Les préfixes nécessitent la fonctionnalité de recherche par caractères génériques, et les suffixes nécessitent la fonctionnalité de recherche d’expressions régulières. Ces deux fonctionnalités peuvent réduire les performances des requêtes.
L’exemple suivant ajoute un EdgeNGramTokenFilter
pour accélérer la mise en correspondance des préfixes et des suffixes. Les jetons sont générés dans des combinaisons de 2 à 25 caractères qui incluent des caractères. Voici un exemple de progression de deux à sept jetons : MS, MSF, MSFT, MSFT/, MSFT/S, MSFT/SQ, MSFT/SQL. EdgeNGramTokenFilter
nécessite un paramètre side
qui détermine à partir de quel côté les combinaisons de caractères de chaîne sont générées. Utilisez front
pour les requêtes de préfixe et back
pour les requêtes de suffixe.
Une segmentation du texte en unités lexicales supplémentaire donne un plus grand index. Si vous disposez d’une capacité suffisante pour accueillir l’index plus grand, cette approche offrant un temps de réponse plus rapide peut constituer la meilleure solution.
{
"fields": [
{
"name": "accountNumber_prefix",
"indexAnalyzer": "ngram_front_analyzer",
"searchAnalyzer": "keyword",
"type": "Edm.String",
"searchable": true,
"filterable": false,
"retrievable": true,
"sortable": false,
"facetable": false
},
{
"name": "accountNumber_suffix",
"indexAnalyzer": "ngram_back_analyzer",
"searchAnalyzer": "keyword",
"type": "Edm.String",
"searchable": true,
"filterable": false,
"retrievable": true,
"sortable": false,
"facetable": false
}
],
"analyzers": [
{
"@odata.type":"#Microsoft.Azure.Search.CustomAnalyzer",
"name":"ngram_front_analyzer",
"charFilters":[],
"tokenizer":"keyword_v2",
"tokenFilters":["lowercase", "front_edgeNGram"]
},
{
"@odata.type":"#Microsoft.Azure.Search.CustomAnalyzer",
"name":"ngram_back_analyzer",
"charFilters":[],
"tokenizer":"keyword_v2",
"tokenFilters":["lowercase", "back_edgeNGram"]
}
],
"tokenizers":[],
"charFilters": [],
"tokenFilters": [
{
"@odata.type":"#Microsoft.Azure.Search.EdgeNGramTokenFilterV2",
"name":"front_edgeNGram",
"minGram": 2,
"maxGram": 25,
"side": "front"
},
{
"@odata.type":"#Microsoft.Azure.Search.EdgeNGramTokenFilterV2",
"name":"back_edgeNGram",
"minGram": 2,
"maxGram": 25,
"side": "back"
}
]
}
Pour rechercher les numéros de compte qui commencent par 123
, vous pouvez utiliser la requête suivante :
{
"search": "123",
"searchFields": "accountNumber_prefix"
}
Pour rechercher les numéros de compte qui se terminent par 456
, vous pouvez utiliser la requête suivante :
{
"search": "456",
"searchFields": "accountNumber_suffix"
}
Étapes suivantes
Cet article explique comment les analyseurs contribuent aux problèmes de requête et à les résoudre. En guise de prochaine étape, examinez de plus près l’impact de l’analyseur sur l’indexation et le traitement des requêtes.