LLM으로 Amazon Lex를 개선하고 URL 수집을 사용하여 FAQ 환경 개선 | 아마존 웹 서비스

LLM으로 Amazon Lex를 개선하고 URL 수집을 사용하여 FAQ 환경 개선 | 아마존 웹 서비스

오늘날의 디지털 세계에서 대부분의 소비자는 기업 및/또는 서비스 제공업체에 연락하는 데 시간을 들이기보다는 스스로 고객 서비스 질문에 대한 답을 찾는 편입니다. 이 블로그 게시물은 질문 및 답변 챗봇을 구축하기 위한 혁신적인 솔루션을 탐색합니다. 아마존 렉스 웹사이트의 기존 FAQ를 사용합니다. 이 AI 기반 도구는 실제 질문에 빠르고 정확한 응답을 제공하여 고객이 일반적인 문제를 독립적으로 빠르고 쉽게 해결할 수 있도록 합니다.

단일 URL 수집

많은 기업이 고객을 위해 웹 사이트에서 사용할 수 있는 FAQ에 대한 답변 세트를 공개했습니다. 이 경우 게시된 FAQ에서 고객의 질문에 답변할 수 있는 챗봇을 고객에게 제공하고자 합니다. 제목의 블로그 게시물에서 LLM을 사용하는 대화식 FAQ 기능으로 Amazon Lex 향상, 우리는 Amazon Lex와 LlamaIndex의 조합을 사용하여 PDF 또는 Word 문서와 같은 기존 지식 소스로 구동되는 챗봇을 구축하는 방법을 시연했습니다. FAQ 웹사이트를 기반으로 간단한 FAQ를 지원하려면 웹사이트를 크롤링하고 LlamaIndex에서 고객 질문에 답변하는 데 사용할 수 있는 임베딩을 생성할 수 있는 수집 프로세스를 만들어야 합니다. 이 경우, 우리는 이전 블로그 게시물, 사용자 발화로 임베딩을 쿼리하고 웹 사이트 FAQ에서 답변을 반환합니다.

다음 다이어그램은 수집 프로세스와 Amazon Lex 봇이 우리 솔루션에서 함께 작동하는 방식을 보여줍니다.

LLM으로 Amazon Lex를 강화하고 URL 수집을 사용하여 FAQ 환경 개선 | Amazon Web Services PlatoBlockchain 데이터 인텔리전스. 수직 검색. 일체 포함.

솔루션 워크플로우에서 FAQ가 있는 웹사이트는 다음을 통해 수집됩니다. AWS 람다. 이 Lambda 함수는 웹 사이트를 크롤링하고 결과 텍스트를 아마존 단순 스토리지 서비스 (Amazon S3) 버킷. 그런 다음 S3 버킷은 LlamaIndex를 사용하여 Amazon S3에 저장되는 임베딩을 생성하는 Lambda 함수를 트리거합니다. "귀하의 반품 정책은 무엇입니까?"와 같은 최종 사용자의 질문이 도착하면 Amazon Lex 봇은 Lambda 함수를 사용하여 LlamaIndex와 함께 RAG 기반 접근 방식을 사용하여 임베딩을 쿼리합니다. 이 접근 방식 및 전제 조건에 대한 자세한 내용은 다음 블로그 게시물을 참조하십시오. LLM을 사용하는 대화식 FAQ 기능으로 Amazon Lex 향상.

앞서 언급한 블로그의 전제 조건이 완료된 후 첫 번째 단계는 FAQ를 LlamaIndex에서 벡터화하고 인덱싱할 수 있는 문서 저장소로 수집하는 것입니다. 다음 코드는 이를 수행하는 방법을 보여줍니다.

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)

이전 예에서는 Zappos에서 사전 정의된 FAQ 웹 사이트 URL을 가져와 다음을 사용하여 수집합니다. EZWebLoader 수업. 이 클래스를 사용하여 URL로 이동하고 페이지에 있는 모든 질문을 인덱스로 로드했습니다. 이제 "Zappos에 기프트 카드가 있나요?"와 같은 질문을 할 수 있습니다. 웹 사이트의 FAQ에서 직접 답변을 얻으십시오. 다음 스크린샷은 FAQ에서 해당 질문에 대답하는 Amazon Lex 봇 테스트 콘솔을 보여줍니다.

LLM으로 Amazon Lex를 강화하고 URL 수집을 사용하여 FAQ 환경 개선 | Amazon Web Services PlatoBlockchain 데이터 인텔리전스. 수직 검색. 일체 포함.

첫 번째 단계에서 URL을 크롤링하고 LlamaIndex가 질문에 대한 답변을 검색하는 데 사용할 수 있는 임베딩을 생성했기 때문에 이를 달성할 수 있었습니다. 봇의 Lambda 함수는 폴백 인텐트가 반환될 때마다 이 검색이 어떻게 실행되는지 보여줍니다.

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

이 솔루션은 단일 웹 페이지에 모든 답이 있을 때 잘 작동합니다. 그러나 대부분의 FAQ 사이트는 단일 페이지에 구축되지 않습니다. 예를 들어 Zappos 예에서 "가격 매칭 정책이 있습니까?"라는 질문을 하면 다음 스크린샷과 같이 만족스럽지 못한 답변을 얻습니다.

LLM으로 Amazon Lex를 강화하고 URL 수집을 사용하여 FAQ 환경 개선 | Amazon Web Services PlatoBlockchain 데이터 인텔리전스. 수직 검색. 일체 포함.

이전 상호작용에서 가격 매칭 정책 답변은 사용자에게 도움이 되지 않습니다. 참조된 FAQ가 가격 매칭 정책에 대한 특정 페이지에 대한 링크이고 웹 크롤링이 단일 페이지에 대해서만 수행되었기 때문에 이 답변은 짧습니다. 더 나은 답변을 얻으려면 이러한 링크도 크롤링해야 합니다. 다음 섹션에서는 XNUMX개 이상의 페이지 깊이 수준이 필요한 질문에 대한 답변을 얻는 방법을 보여줍니다.

N 수준 크롤링

FAQ 지식을 얻기 위해 웹 페이지를 크롤링할 때 원하는 정보가 링크된 페이지에 포함될 수 있습니다. 예를 들어 Zappos 예시에서 "가격 매칭 정책이 있습니까?"라는 질문을 합니다. 대답은 "네, 방문해주세요. 더 알아보기 위해.” 누군가 "당신의 가격 매칭 정책은 무엇입니까?"라고 묻는다면 그런 다음 정책으로 완전한 답변을 제공하고 싶습니다. 이를 달성한다는 것은 최종 사용자를 위한 실제 정보를 얻기 위해 링크를 통과해야 한다는 것을 의미합니다. 수집 프로세스 중에 웹 로더를 사용하여 다른 HTML 페이지에 대한 앵커 링크를 찾은 다음 탐색할 수 있습니다. 웹 크롤러에 대한 다음 코드 변경을 통해 크롤링하는 페이지에서 링크를 찾을 수 있습니다. 또한 순환 크롤링을 방지하고 접두사로 필터링을 허용하는 몇 가지 추가 논리가 포함되어 있습니다.

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)

이전 코드에서는 N 수준 깊이를 크롤링하는 기능을 소개하고 특정 URL 패턴으로 시작하는 항목으로만 크롤링을 제한할 수 있는 접두사를 제공합니다. Zappos 예에서 고객 서비스 페이지는 모두 zappos.com/c, 그래서 우리는 크롤링을 더 작고 관련성이 높은 하위 집합으로 제한하기 위해 접두사로 포함합니다. 이 코드는 최대 XNUMX단계까지 수집할 수 있는 방법을 보여줍니다. 크롤러가 더 많은 문서를 수집하는 것 외에는 변경된 것이 없기 때문에 봇의 Lambda 논리는 동일하게 유지됩니다.

이제 모든 문서의 색인이 생성되었으며 더 자세한 질문을 할 수 있습니다. 다음 스크린샷에서 우리 봇은 "가격 일치 정책이 있습니까?"라는 질문에 정답을 제공합니다.

LLM으로 Amazon Lex를 강화하고 URL 수집을 사용하여 FAQ 환경 개선 | Amazon Web Services PlatoBlockchain 데이터 인텔리전스. 수직 검색. 일체 포함.

이제 가격 매칭에 대한 질문에 대한 완전한 답을 얻었습니다. 단순히 "예, 우리 정책을 참조하십시오"라고 말하는 대신 두 번째 수준 크롤링의 세부 정보를 제공합니다.

정리

향후 비용 발생을 방지하려면 이 연습의 일부로 배포된 모든 리소스 삭제를 진행합니다. Sagemaker 엔드포인트를 정상적으로 종료하는 스크립트를 제공했습니다. 사용 세부 정보는 README에 있습니다. 또한 실행할 수 있는 다른 모든 리소스를 제거하려면 cdk destroy 다른 cdk 명령과 동일한 디렉터리에서 스택의 모든 리소스를 프로비저닝 해제합니다.

결론

일련의 FAQ를 챗봇에 수집하는 기능을 통해 고객은 간단한 자연어 쿼리로 질문에 대한 답을 찾을 수 있습니다. LlamaIndex와 같은 RAG 솔루션과 폴백 처리를 위한 Amazon Lex의 기본 제공 지원을 결합하여 고객이 FAQ에 대해 만족스럽고 큐레이션되고 승인된 답변을 얻을 수 있는 빠른 경로를 제공할 수 있습니다. 솔루션에 N 레벨 크롤링을 적용함으로써 여러 FAQ 링크에 걸쳐 있을 수 있는 답변을 허용하고 고객의 쿼리에 대한 더 깊은 답변을 제공할 수 있습니다. 이러한 단계를 따르면 강력한 LLM 기반 Q 및 A 기능과 효율적인 URL 수집을 Amazon Lex 챗봇에 원활하게 통합할 수 있습니다. 그 결과 사용자와의 보다 정확하고 포괄적이며 상황에 맞는 상호 작용이 가능합니다.


저자 소개

LLM으로 Amazon Lex를 강화하고 URL 수집을 사용하여 FAQ 환경 개선 | Amazon Web Services PlatoBlockchain 데이터 인텔리전스. 수직 검색. 일체 포함.맥스 헨켈-월리스 AWS Lex의 소프트웨어 개발 엔지니어입니다. 그는 고객 성공을 극대화하기 위해 기술을 활용하는 작업을 즐깁니다. 일 외에는 요리, 친구들과 시간 보내기, 배낭 여행에 열정적입니다.

LLM으로 Amazon Lex를 강화하고 URL 수집을 사용하여 FAQ 환경 개선 | Amazon Web Services PlatoBlockchain 데이터 인텔리전스. 수직 검색. 일체 포함.송펑 자연어 처리 및 인공 지능을 전문으로 하는 AWS AI Labs의 선임 응용 과학자입니다. 그녀의 연구는 문서 기반 대화 모델링, 작업 중심 대화에 대한 추론, 다중 모드 데이터를 사용한 대화형 텍스트 생성을 포함하여 이러한 분야의 다양한 측면을 탐구합니다.

LLM으로 Amazon Lex를 강화하고 URL 수집을 사용하여 FAQ 환경 개선 | Amazon Web Services PlatoBlockchain 데이터 인텔리전스. 수직 검색. 일체 포함.존 베이커 AWS의 수석 SDE로서 자연어 처리, 대규모 언어 모델 및 기타 ML/AI 관련 프로젝트를 담당합니다. 그는 Amazon에서 9년 이상 근무했으며 AWS, Alexa 및 Amazon.com에서 일했습니다. 여가 시간에 John은 태평양 북서부 전역에서 스키 및 기타 야외 활동을 즐깁니다.

타임 스탬프 :

더보기 AWS 기계 학습