Skip to main content

LlamaIndex와 W&B Weave로 구축하는 RAG 기반 디지털 레스토랑 메뉴

RAG를 기반으로 전통적인 레스토랑 PDF 메뉴를 AI 기반의 인터랙티브 메뉴로 바꿉니다! 이 글은 AI로 번역되었습니다. 오역이 있을 수 있으니 댓글로 알려 주세요.
Created on September 12|Last edited on September 12
거의 모든 비즈니스에서 디지털 편의성은 이제 필수입니다. 하지만 우리가 외식할 때만큼은 큰 변화가 아직 많지 않습니다. COVID 백신이 보급된 이후 레스토랑들이 디지털 메뉴를 도입하긴 했지만, 대부분은 정적인 PDF이거나, 실제로 매장에 있음에도 온라인 주문을 흉내 내는 정도의 경험에 그쳤습니다.
종이 메뉴는 나쁘지 않습니다. 역할을 하죠. 하지만 실제로 더 나아지길 바란다면, 현재 방식들은 핵심을 놓치고 있습니다. 상호작용과 유연성이 제한적입니다. 대부분의 메뉴에서 원하는 항목을 찾으려면 전부 읽어 내려가거나 “Command + F”로 검색해야 하는데, 이는 대다수 모바일 폰에서도 썩 편하지 않습니다.
바로 여기에서 진짜 개선의 단서를 볼 수 있습니다. 의미 기반 검색입니다. 사용자는 숙련된 웨이터와 대화하듯 자연어로 질문하며, 정확히 무엇을 원하는지 몰라도 더 세밀한 방식으로 메뉴와 상호작용할 수 있습니다.
LlamaIndex의 도움으로 W&B Weave바로 오늘 우리가 만들 것입니다.



이번 글에서 다룰 내용




앱의 핵심 기능

이 애플리케이션을 만들기 위해 다음과 같은 핵심 기능들을 포함하겠습니다:
의미 기반 검색 기능사용자가 정확한 단어 일치에만 의존하지 않고, 문맥과 의미를 기반으로 메뉴 항목을 검색할 수 있게 하고자 합니다.
사용자 친화적인 인터페이스간단하고 반응성이 뛰어난 디자인으로 메뉴를 매끄럽게 탐색할 수 있습니다. 앱에는 두 개의 화면이 있으며, 일반 PDF 메뉴 화면과 함께 사용자가 메뉴 내용을 질문할 수 있는 ‘채팅 화면’이 제공됩니다.
앱 데이터 분석사용자 상호작용과 선호도를 분석하여 고객이 실제로 무엇을 원하는지에 대한 유용한 인사이트를 식당에 제공하는 기능!

기술 개요

이런 정적인 메뉴를 지능적이고 검색 가능한 인터페이스로 바꾸려면 단계적인 접근이 필요합니다. 이 프로젝트는 다음과 같은 일련의 단계들을 통해 이를 구현하는 것을 목표로 합니다:
GPT-4를 활용한 데이터 표준화전통적인 메뉴의 다양하고 비정형적인 텍스트를 구조화된 표준 JSON 형식으로 변환하는 것부터 과정이 시작됩니다.
벡터 인덱스 만들기표준화된 데이터는 다음 단계에서 벡터로 변환됩니다. 이 벡터 인덱스는 메뉴 항목의 의미적 내용을 표현하여 효율적이고 정확한 의미 검색을 가능하게 합니다.
벡터 인덱스 질의하기인덱스가 준비되면 사용자는 자연어로 질의할 수 있습니다. 시스템은 단순한 키워드 일치가 아니라 질의의 의미적 내용을 기반으로 가장 관련성이 높은 항목을 검색합니다.
GPT-3.5로 반환 결과 필터링: 검색 결과를 더 정교하게 다듬기 위해 우리는 사용할 것입니다 GPT-3.5 결과를 필터링하기 위해서입니다. 이를 통해 사용자에게 제시되는 최종 출력이 매우 관련성 높고 정확하도록 보장합니다. 벡터 인덱스는 의미적 내용을 기반으로 메뉴 항목을 효율적으로 검색할 수 있지만, 여전히 사용자의 구체적인 질의와 완전히 맞지 않는 결과를 반환할 수 있습니다. GPT-3.5의 언어 이해 능력을 활용하면 이러한 결과를 한 단계 더 면밀히 검토하여 사용자의 의도와 질의 맥락에 더욱 밀접하게 부합하도록 만들 수 있습니다. 이 과정은 전통적인 RAG(검색 증강 생성)라기보다 “검색 증강 삭제”에 가깝습니다. 여기서 GPT-3.5는 새로운 콘텐츠를 생성하기 위한 것이 아니라, 기존 검색 결과를 정제하고 필터링하기 위한 용도로 사용됩니다. 모델은 스마트한 필터처럼 작동하여 검색된 데이터에서 관련성이 낮거나 정확도가 떨어지는 정보를 걸러냅니다. 이러한 방식은 다음과 같은 원칙과 부합합니다. RAG데이터셋이나 데이터베이스에서 검색한 관련 정보를 활용해 생성 과정을 보강하는 것을 목표로 합니다.

왜 벡터 인덱스가 필요할까요?

본격적으로 들어가기 전에, 애초에 왜 벡터 인덱스를 사용하는지부터 설명하는 것이 중요합니다.
아마 이렇게 생각하��� 수 있습니다. 그냥 메뉴를 한 바퀴 돌면서 사용자의 질의와 각 페이지를 모델에 넘긴 다음, 어떤 결과가 관련 있는지 모델에게 묻기만 하면 되지 않을까? 전체 메뉴 내용을 매 질의마다 GPT-3.5로 처리하는 방식의 무차별 대입 접근도 가능하긴 하지만, 여러 가지 이유로 매우 비효율적입니다.
첫째, GPT‑3.5의 과금 방식이 모델이 입력·출력한 토큰 수에 기반하기 때문에, 방대한 메뉴를 처리하려면 매우 많은 토큰이 필요하고 비용이 과도하게 늘어날 수 있습니다. 둘째, 응답 속도가 크게 느려져 결과 지연으로 사용자 경험이 저하됩니다. 대신, 벡터 검색을 사용해 먼저 결과 범위를 좁히면, 가장 관련성 높은 항목만 LLM이 검토하도록 보장할 수 있어 비용 효율성을 유지하면서도 반환되는 검색 결과의 관련성을 높게 유지할 수 있습니다.

GPT-4를 활용한 데이터 표준화: 기반 다지기

벡터 인덱스를 만들기 전에, 메뉴 데이터를 쉽게 접근할 수 있는 형식으로 정리해야 합니다. 이를 위해 현재 PDF 형태인 메뉴를 JSON 형식으로 변환하는 과정이 필요합니다.
이런 작업에는 GPT‑4가 아주 유용합니다. 이 모델과 특별히 설계한 프롬프트를 활용하면 문서를 모델에 입력해 메뉴를 JSON 형���으로 변환할 수 있습니다.
이 글의 핵심은 AI 검색 기능에 있으므로, 메뉴를 JSON 형식으로 변환하는 스크립트의 대부분은 생략하겠습니다. 스크립트는 기본적으로 메뉴의 각 페이지를 순회하며 해당 내용을 GPT‑4에 전달하고, 모델이 메뉴를 JSON 형식으로 변환합니다. 다만 이 스크립트가 모든 PDF 메뉴에서 완벽하게 동작하지 않을 수 있으니, 프롬프트를 조정하거나 경우에 따라서는 내용을 수동으로 JSON 파일에 입력해야 할 수도 있습니다.
스크립트의 일부를 아래에 소개합니다:
with open(pdf_path, 'rb') as file:
reader = PyPDF2.PdfReader(file)
for page_num in range(len(reader.pages)):
page_text = reader.pages[page_num].extract_text()
text_chunks = split_text(page_text)
for chunk in text_chunks:

prompt_text = ("I'm going to give you some text that I want you to convert into a JSON object (list), "
"each item with a title and description. For example, if the text describes various dishes at a restaurant, "
"the JSON output might look like: [{'title': 'Chicken Special', 'description': 'A delicious chicken dish seasoned with herbs and spices.', 'keywords': 'chicken'}, "
"{'title': 'Seafood Platter', 'description': 'An assortment of fresh seafood, including shrimp, scallops, and lobster.', 'keywords': 'seafood, shrimp,platter'}]"
"NOTE: THE ITEM TITLES WILL NOT BE A CATEGORY OF ITEMS, RATHER THEY ARE SPECIFIC DISHES/ENTRES/APETIZERS/DRINKS ETC. -> THEY ARE ONLY SPECIC ITEMS, NOT CATEGORIES"
"NOTE: Keywords should be the a 2-4 categories/common search terms describing the item, for example (but not limited to) -> dessert, side, chicken, sandwhich, cocktail, drink, burger, salad, etc etc -> Try to use multiple categories/descriptors"
"THIS DATA IS CHUNKED SO IF THE DATA AT THE BEGINNING OR END (FOR TITLE OR DESCRIPTION) SEEMS INCOMPLETE COMPARED TO THE REST OF THE ENTRIES, JUST SKIP IT"
"ONLY RESPOND WITH THE JSON OBJECT AND NOTHING ELSE. CONVERT THE FULL DATA. DO NOT TRUNCATE OR STOP EARLY. HERE IS THE DATA I WANT YOU TO CONVERT: " + chunk)

response = client.chat.completions.create(
model="gpt-4-1106-preview",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": prompt_text}
]
)

완벽하게 동작하도록 만들기까지 약간의 프롬프트 엔지니어링이 필요했지만, 몇 번 시도한 끝에 메뉴를 제대로 파싱하는 프롬프트를 구성할 수 있었습니다. 한 번에 모델에 넘기는 내용은 메뉴 본문 중 약 4,000자와, 모델에게 우리가 무엇을 하고 싶은지 설명하는 프롬프트뿐입니다. 이때 모델에 전달되는 구간들 사이에는 일부 내용을 서로 겹치도록 설정해, 관련 항목이 누락되지 않게 합니다. 모델은 이 데이터를 파싱한 뒤 JSON 형식으로 변환합니다. 아래는 변환 결과 파일의 예시입니다:
{
"title": "Roasted Half Chicken",
"description": "Slow-roasted with housemade herb butter. Served with choice of two sides.",
"keywords": "chicken, roasted, herb butter",
"page": 1
},
{
"title": "Parmesan Crusted Chicken Pasta",
"description": "Crispy hand breaded parmesan chicken breast with melted mozzarella and marinara sauce over linguine.",
"keywords": "chicken, pasta, parmesan",
"page": 1
},
참고로, 저는 GPT‑4에 약간의 ‘을 추가하도록 프롬프트를 작성했습니다.keywords, 이는 나중에 벡터 검색 품질을 효율적으로 개선하는 방법임을 확인했습니다. 스크립트를 한 번만 실행하면 되기 때문에 GPT‑4 사용에 따른 비용 증가는 미미합니다.
💡

벡터 인덱스 만들기

메뉴 데이터가 JSON 형식으로 준비되면, 다음 단계는 벡터 인덱스를 만드는 것입니다. 이를 위해 언어 모델을 사용해 메뉴의 각 항목을 벡터로 변환합니다. 이 벡터들은 메뉴 항목의 의미론적 내용을 표현하므로, 단순한 키워드 일치가 아니라 사용자의 질의가 담고 있는 의미를 기반으로 효율적이고 정확한 검색이 가능해집니다.
각 항목을 Llama Index로 ‘문서’ 객체로 변환한 뒤, 이를 임베딩으로 만들어 사용자 질의와 쉽게 비교할 수 있도록 하겠습니다. 아래는 인덱스를 생성하는 코드입니다:
from flask import Flask, render_template, request, jsonify
import json
import os
from llama_index import VectorStoreIndex, ServiceContext, StorageContext, load_index_from_storage
from llama_index.schema import Document
from llama_index.embeddings import HuggingFaceEmbedding
from llama_index.retrievers import VectorIndexRetriever
from llama_index.query_engine import RetrieverQueryEngine
from llama_index.postprocessor import SimilarityPostprocessor
from llama_index import (VectorStoreIndex, get_response_synthesizer)

def create_document_from_json_item(json_item):
ti = json_item['title']
des = json_item['description']
keys = json_item['keywords']

# if des: # depending on the menu/embedding model, this could be helpful
# ti += des

if keys:
ti += "keywords:" + keys
document = Document(text=ti, metadata=json_item)
return document

def generate_embeddings_for_document(document, model_name="BAAI/bge-small-en-v1.5"):
embed_model = HuggingFaceEmbedding(model_name=model_name)
embeddings = embed_model.get_text_embedding(document.text)
return embeddings

file_path = "./gpt4_menu_data_v2.json"
index = None

if not os.path.exists("./index"):
with open(file_path, 'r', encoding='utf-8') as file:
json_data = json.load(file)

documents = []
for item in json_data:
document = create_document_from_json_item(item)
document_embeddings = generate_embeddings_for_document(document)
document.embedding = document_embeddings
documents.append(document)

service_context = ServiceContext.from_defaults(llm=None, embed_model='local')
index = VectorStoreIndex.from_documents(documents, service_context=service_context)
index.storage_context.persist(persist_dir="./index")
else:
storage_context = StorageContext.from_defaults(persist_dir="./index")
service_context = ServiceContext.from_defaults(llm=None, embed_model='local')
index = load_index_from_storage(storage_context, service_context=service_context)

먼저 JSON에 있는 항목들을 변환하는 몇 가지 함수를 소개하겠습니다. document 객체로 만든 뒤 이를 임베딩으로 변환할 수 있으며, 이는 BAAI/bge-small-en-v1.5 모델(이용 가능 위치: Hugging Face). 마지막으로, 벡터 인덱스에는 영구 저장소를 사용해 앱을 실행할 때마다 인덱스를 새로 만들 필요가 없도록 합니다.

벡터 인덱싱은 어떻게 동작하나요?

벡터 인덱싱은 단어 또는 구를 의미적으로 유사한 항목끼리 서로 가깝게 배치되는 고차원 공간에 임베딩함으로써 동작합니다. 이를 통해 검색 알고리즘이 질의의 맥락과 뉘앙스를 이해하여 더 관련성 높고 정확한 결과를 제공합니다.

벡터 인덱스 질의하기

벡터 인덱스가 준비되었으니, 이제 사용자 질의를 받아 인덱스를 질의하는 코드를 작성할 차례입니다.
retriever = VectorIndexRetriever(index=index, similarity_top_k=10)
service_context = ServiceContext.from_defaults(llm=None, embed_model='local')
response_synthesizer = get_response_synthesizer(service_context=service_context)
query_engine = RetrieverQueryEngine(retriever=retriever, response_synthesizer=response_synthesizer, node_postprocessors=[SimilarityPostprocessor(similarity_cutoff=0.6)])

def parse_response_to_json(response_str):
items = response_str.split("title: ")[1:] # Split the response and ignore the first empty chunk
json_list = []

for item in items:
lines = item.strip().split('\n')
item_json = {
"title": lines[0].strip(),
"description": lines[1].replace("description: ", "").strip(),
"keywords": lines[2].replace("keywords: ", "").strip(),
"page": int(lines[3].replace("page: ", "").strip())
}
json_list.append(item_json)

return json_list

def query_index(query):
response = query_engine.query(query)
# Original results parsed to JSON
return parse_response_to_json(str(response))
먼저, 우리는 VectorIndexRetriever 인덱스에서 의미적으로 가장 유사한 상위 10개 항목을 가져오기 위해. The query_engine 그다음 응답 합성기와 유사도 필터를 설정하고, 임계값은 0.6, 가장 관련성 높은 결과만 반환되도록 합니다.
벡터 인덱스가 준비되면 사용자는 자연어로 메뉴를 질의할 수 있습니다. 시스템은 이러한 질의를 해석하고 인덱스에서 가장 관련성 높은 항목을 검색한 뒤, 이해하기 쉬운 형식으로 제공합니다.

GPT-3.5로 검색 관련성 강화하기

검색 결과를 한층 더 정교하게 다듬기 위해 GPT-3.5를 사용합니다. 이 보다 저렴한 모델은 벡터 인덱스가 반환한 결과를 필터링하여 사용자에게 가장 관련성 높은 항목만 제공하도록 돕기에 충분한 성능을 갖추고 있습니다.
이 필터링 단계에는 다른 많은 오픈 소스 LLM도 충분히 사용할 수 있겠지만, 저는 GPT‑3.5 모델을 신뢰합니다(특히 우리 과제에 맞춘 풍부한 파인튜닝 데이터가 없을 때 더욱 그렇습니다). 그래서 오늘은 GPT‑3.5를 사용하겠습니다. 프로덕션 환경에서는 GPT‑3.5 같은 모델로 시작한 뒤 데이터를 수집해 더 작은 오픈 소스 모델을 정렬(alignment)하는 전략도 훌륭한 선택입니다. 이렇게 하면 초기 출시에서 높은 신뢰성을 보장하면서, 이후 비용을 점진적으로 절감할 수 있습니다.

W&B Weave로 앱 사용 기록 로깅하기

검색 가능한 레스토랑 메뉴처럼 AI 기반 애플리케이션에서 데이터를 로깅하는 일은 여러 가지 이유로 중요합니다. 우선, 사용자가 애플리케이션과 상호작용하는 방식을 파악할 수 있는 매우 가치 있는 인사이트를 수집할 수 있습니다. 이 데이터를 분석하면, 비즈니스는 인기 있는 기능, 자주 등장하는 질의, 사용자 선호도를 식별할 수 있습니다.
Weights & Biases Weave 은 우리 데이터 로깅에 훌륭한 선택지입니다! 직관적인 대시보드를 통해 개발자는 실시간 상호작용을 모니터링하고, 질의에 응답하는 AI 모델의 성능을 평가하며, 이상 징후나 최적화가 필요한 지점을 탐지할 수 있습니다. 이러한 수준의 정밀한 분석은 반복적 개발에 매우 유용하며, 실제 사용자 데이터를 바탕으로 지속적인 개선을 가능하게 합니다.
다음은 질의, 필터링, 결과 반환을 수행하는 Flask 앱의 나머지 부분입니다:
# Login to W&B
wandb.login()
# Define constants for the StreamTable
WB_ENTITY = "" # Set your W&B entity name here, or leave it empty to use the current logged-in entity
WB_PROJECT = "ai_menu"
STREAM_TABLE_NAME = "usage_data"
# Define a StreamTable
st = StreamTable(f"{WB_ENTITY}/{WB_PROJECT}/{STREAM_TABLE_NAME}")

app = Flask(__name__)

client = openai.OpenAI(api_key='sk-YOUR_API_KEY')

@app.route('/chat')
def index():
return render_template('chat.html')

@app.route('/')
def menu():
return render_template('index.html')

def describe_items(json_list):
description_str = "Some possible items you might be interested in include the following:<br><br>"
for item in json_list:
description_str += f"<strong>{item['title']}</strong> - {item['description']}<br><br>"
return description_str

def generate_response_gpt(query, original_res):
# Generating prompt for GPT
prompt = f"This is a user at a restaurant searching for items to order. Given these initial results {original_res} for the following user query '{query}', return the JSON object for the items that make sense to include as a response (e.g., remove only items that are not at all relevant to the query='{query}') -- keep in mind that they may all be relevant and its perfectly fine to not remove any items. YOU MUST RETURN THE RESULT IN JSON FORM"
response = client.chat.completions.create(
model="gpt-3.5-turbo-1106",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": prompt}
]
)
if response.choices:
reply = response.choices[0].message.content
filtered_res_json_str = re.search(r"```json(.+?)```", reply, re.DOTALL)

print(filtered_res_json_str)
if filtered_res_json_str:
filtered_res_json = json.loads(filtered_res_json_str.group(1))
if not len(filtered_res_json):
return original_res
else:
filtered_res_json = original_res
return filtered_res_json
else:
return original_res


@app.route('/search', methods=['POST'])
def search():
query = request.form.get('query')
original_res = query_index(query)
filtered_res_json = generate_response_gpt(query, original_res)
st.log({"query": query, "results": describe_items(filtered_res_json)})
return jsonify({'res': describe_items(filtered_res_json)})


if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
코드는 다음과 같이 Weights & Biases를 통합하는 것으로 시작합니다 wandb.login()이 단계에서는 다음과 같은 상수를 정의합니다 WB_ENTITY, WB_PROJECT, 그리고 W&B에서 이름이 “usage_data. 이 표는 Flask 애플리케이션 내에서 사용자 상호작용을 로깅하고 추적하도록 설계되었습니다.
Flask 앱은 다음과 같은 핵심 라우트로 구성됩니다: '/chat'그리고'/일반 PDF 뷰어와 메뉴 채팅 화면을 포함하는 인터페이스용 두 개의 페이지입니다. GPT-3.5 모델의 통합은 다음을 통해 이루어집니다 openai.OpenAI(api_key='sk-YOUR_API_KEY')으로, 애플리케이션이 OpenAI API를 사용할 수 있게 합니다.
사용자가 쿼리를 입력하면, query_index(query) 함수가 트리거됩니다. 이 함수는 사전에 정의된 인덱스에서 초기 검색을 수행하는 역할을 합니다. 사용자의 입력을 바탕으로 관련된 초기 데이터를 추출합니다.
The generate_response_gpt(query, original_res) 함수는 초기 결과를 가져와 query_index(query) 그리고 GPT-3.5를 적용해 이러한 결과를 보강하고 정제합니다. 이를 위해 프롬프트를 생성하고 GPT-3.5의 응답을 처리하여 최종 출력이 사용자 쿼리에 매우 적합하고 맞춤화되도록 보장합니다.
마지막으로, Flask 라우트 /search 은(는) 앞서 언급한 함수들로 사용자 쿼리를 전달하는 진입점입니다. 이 라우트는 다음을 호출합니다 query_index(query) 관련 항목을 수집하고, generate_response_gpt(query, original_res) 검색된 결과를 필터링한 뒤, 최종 출력을 Weights & Biases의 StreamTable에 다음을 사용해 기록합니다 st.log()이 과정은 사용자에게 정제된 답변을 제공할 뿐만 아니라, 분석 및 시스템의 추가 개선을 위한 유용한 데이터도 수집합니다.

왜 결과를 필터링할까?

벡터 인덱스는 의미 기반 콘텐츠를 바탕으로 메뉴 항목을 효율적으로 검색할 수 있지만, 여전히 사용자의 구체적인 쿼리와 완전히 맞지 않는 결과를 반환할 수 있습니다. GPT-3.5의 고급 언어 이해 능력을 활용해 이러한 결과를 한 번 더 엄격하게 검토하면, 사용자의 의도와 쿼리의 맥락에 더 정확하게 부합하도록 정제할 수 있습니다. 이 정제 과정은 검색 결과의 정밀도와 적합성을 높여 줍니다.

앱: 모든 것을 하나로 연결하기

제 웹 개발 경험은 다소 제한적이어서, 이 앱은 개념 증명에 더 가깝습니다.
처음에는 사용자의 쿼리로 반환된 항목을 메뉴에서 직접 하이라이트하는 인터페이스를 구상했습니다. 하지만 제 웹 개발 역량으로는 이 구현이 다소 벅찼습니다. 웹에서 PDF를 다루는 일이 까다롭고, 특히 우리의 주 배포 대상인 모바일 환경에서는 더 어렵기 때문입니다. 그래서 사용자가 채팅 화면과 PDF 메뉴 화면을 오갈 수 있는, ChatGPT와 유사한 인터페이스를 구현하려 합니다.
프론트엔드는 아직 “MVP” 수준이지만, 모든 기능은 정상적으로 동작합니다. 이 프론트엔드의 코드는 GitHub에서 확인할 수 있습니다. 리포지토리. 그래도 우리가 무엇을 만들고 있는지 스크린샷 몇 장으로 살펴보겠습니다.
먼저, 우리의 메뉴입니다:

다음으로, 사용자가 위의 파란색 버튼을 클릭했을 때 화면이 어떻게 보일지 살펴보겠습니다.


그리고 결과는 다음과 같습니다:

사용자가 앱을 이용한 뒤에는 Weave에 로그인해 테이블을 확인하면서 사용자가 무엇을 검색했고, 우리 시스템이 얼마나 잘 동작하는지 분석할 수 있습니다. 아래는 우리 앱의 테이블입니다! AI 기반 검색이 단순 키워드 검색보다 똑똑하게 작동해 구체적인 질의도 잘 처리한다는 것을 확인할 수 있습니다. 예를 들어 “chicken”만 검색하면 다양한 항목이 많이 나오지만, “chicken이 들어간 에피타이저”처럼 범위를 명확히 지정하면 에피타이저 항목만 검색 결과로 반환됩니다.

panel
panel
table
data
query
results
timestamp
1
2
3
4
요약하자면, GPT-4와 GPT-3.5 같은 최신 AI 모델과 RAG를 활용하고 이를 웹 앱에 통합함으로써, 고정적이고 비유연적이던 PDF 메뉴를 인터랙티브한 경험으로 바꿀 수 있었습니다. 이는 고객 경험을 한 단계 끌어올릴 뿐만 아니라, 호스피탈리티 산업 전반에서 더 지능적이고 반응성 높은 디지털 솔루션으로 나아가는 길을 열어 줍니다. 궁금한 점이 있다면 아래 댓글로 자유롭게 질문해 주세요! 여기 이 프로젝트의 리포지토리입니다.

도움이 되는 자료


이 글은 AI로 번역되었습니다. 오역이 있을 경우 댓글로 알려 주세요. 원문 보고서는 아래 링크에서 확인하실 수 있습니다: 원문 보고서 보기
Qasem Nick
Qasem Nick •  
not working. I'm saving you ONE day telling you this.
Reply
ArrowWeaveList