Améliorez Amazon Lex avec les LLM et améliorez l'expérience de la FAQ à l'aide de l'ingestion d'URL | Services Web Amazon

Améliorez Amazon Lex avec les LLM et améliorez l'expérience de la FAQ à l'aide de l'ingestion d'URL | Services Web Amazon

Dans le monde numérique d'aujourd'hui, la plupart des consommateurs préfèrent trouver eux-mêmes des réponses à leurs questions sur le service client plutôt que de prendre le temps de contacter des entreprises et/ou des fournisseurs de services. Ce billet de blog explore une solution innovante pour créer un chatbot de questions et réponses dans Amazon Lex qui utilise les FAQ existantes de votre site Web. Cet outil alimenté par l'IA peut fournir des réponses rapides et précises aux demandes du monde réel, permettant au client de résoudre rapidement et facilement des problèmes courants de manière indépendante.

Ingestion d'URL unique

De nombreuses entreprises publient un ensemble de réponses aux FAQ pour leurs clients disponibles sur leur site Web. Dans ce cas, nous voulons offrir aux clients un chatbot qui peut répondre à leurs questions à partir de nos FAQ publiées. Dans le billet de blog intitulé Améliorez Amazon Lex avec des fonctionnalités de FAQ conversationnelles à l'aide de LLM, nous avons montré comment vous pouvez utiliser une combinaison d'Amazon Lex et de LlamaIndex pour créer un chatbot alimenté par vos sources de connaissances existantes, telles que des documents PDF ou Word. Pour prendre en charge une FAQ simple, basée sur un site Web de FAQ, nous devons créer un processus d'ingestion capable d'explorer le site Web et de créer des intégrations pouvant être utilisées par LlamaIndex pour répondre aux questions des clients. Dans ce cas, nous nous baserons sur le bot créé dans le Blog post précédent, qui interroge ces représentations incorporées avec l'énoncé d'un utilisateur et renvoie la réponse à partir de la FAQ du site Web.

Le diagramme suivant montre comment le processus d'ingestion et le bot Amazon Lex fonctionnent ensemble pour notre solution.

Améliorez Amazon Lex avec les LLM et améliorez l'expérience des FAQ grâce à l'ingestion d'URL | Amazon Web Services PlatoBlockchain Data Intelligence. Recherche verticale. Aï.

Dans le flux de travail de la solution, le site Web avec les FAQ est ingéré via AWS Lambda. Cette fonction Lambda explore le site Web et stocke le texte résultant dans un Service de stockage simple Amazon (Amazon S3). Le compartiment S3 déclenche ensuite une fonction Lambda qui utilise LlamaIndex pour créer des incorporations qui sont stockées dans Amazon S3. Lorsqu'une question d'un utilisateur final arrive, telle que « Quelle est votre politique de retour ? », le bot Amazon Lex utilise sa fonction Lambda pour interroger les intégrations à l'aide d'une approche basée sur RAG avec LlamaIndex. Pour plus d'informations sur cette approche et les pré-requis, reportez-vous à l'article de blog, Améliorez Amazon Lex avec des fonctionnalités de FAQ conversationnelles à l'aide de LLM.

Une fois les prérequis du blog susmentionné remplis, la première étape consiste à ingérer les FAQ dans un référentiel de documents qui peut être vectorisé et indexé par LlamaIndex. Le code suivant montre comment y parvenir :

import logging
import sys
import requests
import html2text
from llama_index.readers.schema.base import Document
from llama_index import GPTVectorStoreIndex
from typing import List logging.basicConfig(stream=sys.stdout, level=logging.INFO)
logging.getLogger().addHandler(logging.StreamHandler(stream=sys.stdout)) class EZWebLoader: def __init__(self, default_header: str = None): self._html_to_text_parser = html2text() if default_header is None: self._default_header = {"User-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.80 Safari/537.36"} else: self._default_header = default_header def load_data(self, urls: List[str], headers: str = None) -> List[Document]: if headers is None: headers = self._default_header documents = [] for url in urls: response = requests.get(url, headers=headers).text response = self._html2text.html2text(response) documents.append(Document(response)) return documents url = "http://www.zappos.com/general-questions"
loader = EZWebLoader()
documents = loader.load_data([url])
index = GPTVectorStoreIndex.from_documents(documents)

Dans l'exemple précédent, nous prenons une URL de site Web FAQ prédéfinie de Zappos et l'ingérons en utilisant le EZWebLoader classe. Avec cette classe, nous avons navigué jusqu'à l'URL et chargé toutes les questions de la page dans un index. Nous pouvons maintenant poser une question comme "Est-ce que Zappos a des cartes-cadeaux?" et obtenez les réponses directement à partir de notre FAQ sur le site Web. La capture d'écran suivante montre la console de test de bot Amazon Lex répondant à cette question de la FAQ.

Améliorez Amazon Lex avec les LLM et améliorez l'expérience des FAQ grâce à l'ingestion d'URL | Amazon Web Services PlatoBlockchain Data Intelligence. Recherche verticale. Aï.

Nous avons pu y parvenir car nous avons exploré l'URL dans la première étape et créé des intégrations que LlamaIndex pourrait utiliser pour rechercher la réponse à notre question. La fonction Lambda de notre bot montre comment cette recherche est exécutée chaque fois que l'intention de secours est renvoyée :

import time
import json
import os
import logging
import boto3
from llama_index import StorageContext, load_index_from_storage logger = logging.getLogger()
logger.setLevel(logging.DEBUG) def download_docstore(): # Create an S3 client s3 = boto3.client('s3') # List all objects in the S3 bucket and download each one
try: bucket_name = 'faq-bot-storage-001' s3_response = s3.list_objects_v2(Bucket=bucket_name) if 'Contents' in s3_response: for item in s3_response['Contents']: file_name = item['Key'] logger.debug("Downloading to /tmp/" + file_name) s3.download_file(bucket_name, file_name, '/tmp/' + file_name) logger.debug('All files downloaded from S3 and written to local filesystem.') except Exception as e: logger.error(e)
raise e #download the doc store locally
download_docstore() storage_context = StorageContext.from_defaults(persist_dir="/tmp/")
# load index
index = load_index_from_storage(storage_context)
query_engine = index.as_query_engine() def lambda_handler(event, context): """
Route the incoming request based on intent.
The JSON body of the request is provided in the event slot. """ # By default, treat the user request as coming from the America/New_York time zone. os.environ['TZ'] = 'America/New_York' time.tzset() logger.debug("===== START LEX FULFILLMENT ====") logger.debug(event) slots = {} if "currentIntent" in event and "slots" in event["currentIntent"]: slots = event["currentIntent"]["slots"] intent = event["sessionState"]["intent"] dialogaction = {"type": "Delegate"} message = [] if str.lower(intent["name"]) == "fallbackintent": #execute query from the input given by the user response = str.strip(query_engine.query(event["inputTranscript"]).response) dialogaction["type"] = "Close" message.append({'content': f'{response}', 'contentType': 'PlainText'}) final_response = { "sessionState": { "dialogAction": dialogaction, "intent": intent }, "messages": message } logger.debug(json.dumps(final_response, indent=1)) logger.debug("===== END LEX FULFILLMENT ====") return final_response

Cette solution fonctionne bien lorsqu'une seule page Web contient toutes les réponses. Cependant, la plupart des sites de FAQ ne sont pas construits sur une seule page. Par exemple, dans notre exemple Zappos, si nous posons la question "Avez-vous une politique d'alignement des prix ?", alors nous obtenons une réponse moins que satisfaisante, comme le montre la capture d'écran suivante.

Améliorez Amazon Lex avec les LLM et améliorez l'expérience des FAQ grâce à l'ingestion d'URL | Amazon Web Services PlatoBlockchain Data Intelligence. Recherche verticale. Aï.

Dans l'interaction précédente, la réponse relative à la politique de correspondance des prix n'est pas utile pour notre utilisateur. Cette réponse est courte car la FAQ référencée est un lien vers une page spécifique sur la politique d'alignement des prix et notre exploration du Web ne concernait qu'une seule page. Pour obtenir de meilleures réponses, il faudra également explorer ces liens. La section suivante montre comment obtenir des réponses aux questions qui nécessitent au moins deux niveaux de profondeur de page.

Exploration de niveau N

Lorsque nous explorons une page Web pour connaître la FAQ, les informations que nous voulons peuvent être contenues dans des pages liées. Par exemple, dans notre exemple Zappos, nous posons la question « Avez-vous une politique d'alignement des prix ? » et la réponse est "Oui s'il vous plaît visitez pour apprendre plus." Si quelqu'un demande "Quelle est votre politique d'alignement des prix ?" alors nous voulons donner une réponse complète avec la politique. Pour y parvenir, nous devons parcourir des liens pour obtenir les informations réelles pour notre utilisateur final. Pendant le processus d'ingestion, nous pouvons utiliser notre chargeur Web pour trouver les liens d'ancrage vers d'autres pages HTML, puis les parcourir. Le changement de code suivant de notre robot d'exploration Web nous permet de trouver des liens dans les pages que nous explorons. Il inclut également une logique supplémentaire pour éviter l'exploration circulaire et permettre un filtre par préfixe.

import logging
import requests
import html2text
from llama_index.readers.schema.base import Document
from typing import List
import re def find_http_urls_in_parentheses(s: str, prefix: str = None): pattern = r'((https?://[^)]+))' urls = re.findall(pattern, s) matched = [] if prefix is not None: for url in urls: if str(url).startswith(prefix): matched.append(url) else: matched = urls return list(set(matched)) # remove duplicates by converting to set, then convert back to list class EZWebLoader: def __init__(self, default_header: str = None): self._html_to_text_parser = html2text if default_header is None: self._default_header = {"User-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.80 Safari/537.36"} else: self._default_header = default_header def load_data(self, urls: List[str], num_levels: int = 0, level_prefix: str = None, headers: str = None) -> List[Document]: logging.info(f"Number of urls: {len(urls)}.") if headers is None: headers = self._default_header documents = [] visited = {} for url in urls: q = [url] depth = num_levels for page in q: if page not in visited: #prevent cycles by checking to see if we already crawled a link logging.info(f"Crawling {page}") visited[page] = True #add entry to visited to prevent re-crawling pages response = requests.get(page, headers=headers).text response = self._html_to_text_parser.html2text(response) #reduce html to text documents.append(Document(response)) if depth > 0: #crawl linked pages ingest_urls = find_http_urls_in_parentheses(response, level_prefix) logging.info(f"Found {len(ingest_urls)} pages to crawl.") q.extend(ingest_urls) depth -= 1 #reduce the depth counter so we go only num_levels deep in our crawl else: logging.info(f"Skipping {page} as it has already been crawled") logging.info(f"Number of documents: {len(documents)}.") return documents url = "http://www.zappos.com/general-questions"
loader = EZWebLoader()
#crawl the site with 1 level depth and prefix of "/c/" for customer service root
documents = loader.load_data([url], num_levels=1, level_prefix="https://www.zappos.com/c/")
index = GPTVectorStoreIndex.from_documents(documents)

Dans le code précédent, nous introduisons la possibilité d'explorer N niveaux de profondeur et nous donnons un préfixe qui nous permet de limiter l'exploration aux seuls éléments commençant par un certain modèle d'URL. Dans notre exemple Zappos, les pages de service client sont toutes enracinées à partir de zappos.com/c, nous l'incluons donc comme préfixe pour limiter nos analyses à un sous-ensemble plus petit et plus pertinent. Le code montre comment nous pouvons ingérer jusqu'à deux niveaux de profondeur. La logique Lambda de notre bot reste la même car rien n'a changé sauf que le crawler ingère plus de documents.

Nous avons maintenant tous les documents indexés et nous pouvons poser une question plus détaillée. Dans la capture d'écran suivante, notre bot fournit la bonne réponse à la question "Avez-vous une politique d'alignement des prix ?"

Améliorez Amazon Lex avec les LLM et améliorez l'expérience des FAQ grâce à l'ingestion d'URL | Amazon Web Services PlatoBlockchain Data Intelligence. Recherche verticale. Aï.

Nous avons maintenant une réponse complète à notre question sur l'alignement des prix. Au lieu de simplement se faire dire "Oui, consultez notre politique", il nous donne les détails de l'exploration de deuxième niveau.

Nettoyer

Pour éviter d'engager des dépenses futures, procédez à la suppression de toutes les ressources qui ont été déployées dans le cadre de cet exercice. Nous avons fourni un script pour arrêter le point de terminaison Sagemaker en douceur. Les détails d'utilisation sont dans le README. De plus, pour supprimer toutes les autres ressources que vous pouvez exécuter cdk destroy dans le même répertoire que les autres commandes cdk pour déprovisionner toutes les ressources de votre pile.

Conclusion

La possibilité d'ingérer un ensemble de FAQ dans un chatbot permet à vos clients de trouver les réponses à leurs questions avec des requêtes simples et en langage naturel. En combinant la prise en charge intégrée d'Amazon Lex pour la gestion de secours avec une solution RAG telle qu'un LlamaIndex, nous pouvons fournir à nos clients un moyen rapide d'obtenir des réponses satisfaisantes, organisées et approuvées aux FAQ. En appliquant l'exploration de niveau N dans notre solution, nous pouvons permettre des réponses qui pourraient éventuellement s'étendre sur plusieurs liens FAQ et fournir des réponses plus approfondies aux requêtes de nos clients. En suivant ces étapes, vous pouvez intégrer de manière transparente de puissantes fonctionnalités de questions et réponses basées sur LLM et une ingestion d'URL efficace dans votre chatbot Amazon Lex. Cela se traduit par des interactions plus précises, complètes et contextuelles avec les utilisateurs.


À propos des auteurs

Améliorez Amazon Lex avec les LLM et améliorez l'expérience des FAQ grâce à l'ingestion d'URL | Amazon Web Services PlatoBlockchain Data Intelligence. Recherche verticale. Aï.Max Henkel Wallace est ingénieur en développement logiciel chez AWS Lex. Il aime travailler en tirant parti de la technologie pour maximiser le succès des clients. En dehors du travail, il est passionné par la cuisine, passer du temps avec des amis et faire de la randonnée.

Améliorez Amazon Lex avec les LLM et améliorez l'expérience des FAQ grâce à l'ingestion d'URL | Amazon Web Services PlatoBlockchain Data Intelligence. Recherche verticale. Aï.Chanson Feng est un scientifique appliqué senior chez AWS AI Labs, spécialisé dans le traitement du langage naturel et l'intelligence artificielle. Ses recherches explorent divers aspects de ces domaines, notamment la modélisation de dialogues basés sur des documents, le raisonnement pour des dialogues axés sur les tâches et la génération de textes interactifs à l'aide de données multimodales.

Améliorez Amazon Lex avec les LLM et améliorez l'expérience des FAQ grâce à l'ingestion d'URL | Amazon Web Services PlatoBlockchain Data Intelligence. Recherche verticale. Aï.John Baker est un SDE principal chez AWS où il travaille sur le traitement du langage naturel, les grands modèles de langage et d'autres projets liés au ML/AI. Il travaille chez Amazon depuis plus de 9 ans et a travaillé sur AWS, Alexa et Amazon.com. Dans ses temps libres, John aime faire du ski et d'autres activités de plein air dans tout le nord-ouest du Pacifique.

Horodatage:

Plus de Apprentissage automatique AWS