반응형

[AIF] 챗GPT 점메추 메뉴판, 예산입력하고 점심 메뉴 추천받자

  • AIF에서 발표한 내용을 다시 작성한 Post입니다, 해당 강의는 PDF를 통해 RAG를 하는 과정을 버거킹 메뉴추천으로 설명하면 이해가 쉬울것이라 생각해서 강의를 기획하게 되었습니다.
  • 버거킹 메뉴판에서 Text 추출 > Text 정제 > Chunk 분할 > Chain설정 > Test 까지의 과정입니다.
  • 이해가 가지 않는 부분은 발표한 내용을 참조해 주시면 감사하겠습니다.

필요한 패키지 설치

  • 버전이 달라졌을수 있습니다. 2023-06-07 강연시에는 정상 작동했습니다.
!pip install langchain
!pip install pypdf
!pip install openai
!pip install gdown
!pip install chromadb
!pip install tiktoken

사용할 데이터 소개

  • 해당 데이터는 강남역 버거킹 메장의 요기요 메뉴판을 사용했습니다.
id = "11U7let6PY_YCJpgRT0Dpr5DXSO3Ceqep"
file_name = "버거킹.pdf"
gdown.download(id=id, output=file_name, quiet=False)

loader = PyPDFLoader(file_name)
documents = loader.load()

데이터 전처리

  • PDF 버거킹 메뉴판은 불용어가 많기 때문에 re 패키지를 통해 전처리를 수행했습니다.
output = []
# text 정제
for page in documents:
    text = page.page_content
    text = re.sub(r"(\w+)-\n(\w+)", r"\1\2", text)   # 안녕-\n하세요 -> 안녕하세요  
    text = re.sub(r"(?<!\n\s)\n(?!\s\n)", " ", text.strip()) # "인\n공\n\n지능펙\n토리 -> 인공지능펙토리
    text = re.sub(r"\n\s*\n", "\n\n", text) # \n버\n\n거\n\n킹\n -> 버\n거\n킹
    output.append(text)

Text -> Chunk

  • vectorDB의 Document 형태로 만들기 위해서 Chunk를 분할하는 과정입니다.
    • 10개의 단어를 2 길이만큼 분할하면 5 Chunk가 생성됩니다.
  • Document 작업이 끝나면 Chroma DB의 index로 변경합니다. (Chain에 연결하기 위함)
doc_chunks = []

for line in output:
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=2000, # 최대 청크 길이
        separators=["\n\n", "\n", ".", "!", "?", ",", " ", ""], #  텍스트를 청크로 분할하는 데 사용되는 문자 목록
        chunk_overlap=0, # 인접한 청크 간에 중복되는 문자 수
    )
    chunks = text_splitter.split_text(line)
    for i, chunk in enumerate(chunks):
        doc = Document(
            page_content=chunk, metadata={"page": i, "source": file_name}
        )
        doc_chunks.append(doc)

embeddings = OpenAIEmbeddings()
index = Chroma.from_documents(doc_chunks, embeddings)

Prompt 및 Chain 설정

  • 사용한 Prompt의 내용입니다. 기본적으로 RAG의 형태이며 ChatGPT가 답변을 생성할수 있기 때문에 모르면 모른다고 말하세요라는 내용이 포함되어 있습니다.
  • 이번 예시에서는 버거킹의 메뉴를 단순히 추천해주는 것을 넘어서 가격을 넘지않게 추천하는 것이 목표이기 때문에 Few Shot을 활용하여 example을 넣어서 GPT가 해야할 역할에 대해서 설명 하는 것이 해당 Prompt의 핵심입니다.
system_template="""To answer the question at the end, use the following context. If you don't know the answer, just say you don't know and don't try to make up an answer.
I want you to act as my Burger King menu recommender. It tells you your budget and suggests what to buy. You should only reply to items you recommend. Don't write a description.

Below is an example.
“My budget is 10,000 won, and it is the best menu combination within the budget."

you only answer in Korean

{summaries}
"""
messages = [
    SystemMessagePromptTemplate.from_template(system_template),
    HumanMessagePromptTemplate.from_template("{question}")
]
prompt = ChatPromptTemplate.from_messages(messages)


chain_type_kwargs = {"prompt": prompt}
bk_chain = RetrievalQAWithSourcesChain.from_chain_type(
    ChatOpenAI(temperature=0), 
    chain_type="stuff", 
    retriever=index.as_retriever(),
    chain_type_kwargs=chain_type_kwargs,
    #reduce_k_below_max_tokens=True # 답변의 길이가 넘어서 오류가 난다면 주석 해제
)

result = bk_chain({"question": '총 예산 30,000원을 넘지않게 메뉴조합, 조합가격, 메뉴설명을 수행해주세요.'})

print(f"질문 : {result['question']}")
print()
print(f"답변 : {result['answer']}")

>> 질문 : 총 예산 30,000원을 넘지않게 메뉴조합, 조합가격, 메뉴설명을 수행해주세요.

답변 : 30,000원 이내로 추천해드리겠습니다.

1. 와퍼 세트 (10,500원) + 너겟킹 8조각 (5,300원) + 쉐이킹프라이 (3,300원) = 19,100원
- 와퍼 세트: 불에 직접 구운 순 쇠고기 패티에 싱싱한 야채가 한가득 버거킹의 대표 메뉴, 와퍼!
- 너겟킹 8조각: 바삭하고 촉촉한 부드러운 너겟킹!
- 쉐이킹프라이: 구운갈릭 깊은 풍미가 느껴지는 시즈닝으로 취향 저격! 구운 마늘의 깊은 풍미!

2. 콰트로치즈와퍼 세트 (11,300원) + 어니언링 (3,300원) + 너겟킹 4조각 (3,100원) = 17,700원
- 콰트로치즈와퍼 세트: 네가지 고품격 치즈와 100% 순쇠고기 패티로 탄생한 버거킹의 스테디 셀러, 콰트로치즈와퍼!
- 어니언링: 크기도 UP! 맛도 UP! 고소한 진한 풍미가 일품인 어니언링!
- 너겟킹 4조각: 바삭하고 촉촉한 부드러운 너겟킹!

3. 통새우와퍼 세트 (11,300원) + 21치즈스틱 (3,400원) + 스윗어니언 시즈닝 (300원) = 15,000원
- 통새우와퍼 세트: 입안에서 톡톡 터지는 통새우가 매콤한 스파이시 토마토소스와 100% 순쇠고기 패티를 만나 완성한 통새우와퍼!
- 21치즈스틱: 21cm의 역대급 사이즈! 진하고 고소한 자연 모짜렐라가 가득한 21치즈스틱!
- 스윗어니언 시즈닝: 고소한 치즈맛에 매콤함까지!!

위 세 가지 조합 중에서 선택하시면 예산 내에서 맛있게 즐길 수 있습니다.

FakeAgent 사용법

  • 뒤에는 FakeAgent를 사용하여 이미지 까지 같이 출력해주는 방법을 사용했습니다.
    • 물론 VectorDB의 기능을 사용한 것이지만, 여기서의 핵심은 VectorDB 자체로도 충분히 좋은 성능이 있다는 것 입니다.
result = agent_executor.run("와퍼주니어 설명해줘")


print(f"질문 : {result[0]['question']}")
print('----------------------------')
print(f"답변 : {result[0]['answer']}")
print('----------------------------')
print("이미지")
Image.open(requests.get(result[1], stream=True).raw)
  • 출력
  • Entering new AgentExecutor chain... ({'question': '와퍼주니어 설명해줘', 'answer': '와퍼주니어는 버거킹의 대표적인 메뉴 중 하나로, 작지만 꽉 찬 100% 순 쇠고기 패티가 들어간 버거입니다. 불에 직접 구워져서 고소한 맛이 나며, 싱싱한 야채와 함께 새콤한 마요네즈 소스가 들어가 맛을 더해줍니다. 와퍼보다는 작지만, 가격도 저렴하고 먹기에 편리한 사이즈로 인기가 많습니다.', 'sources': ''}, 'https://d1cua0vf0mkpiy.cloudfront.net/images/menu/normal/917a636b-4711-486c-8d47-17031d4a6933.png')

Finished chain.
질문 : 와퍼주니어 설명해줘


답변 : 와퍼주니어는 버거킹의 대표적인 메뉴 중 하나로, 작지만 꽉 찬 100% 순 쇠고기 패티가 들어간 버거입니다. 불에 직접 구워져서 고소한 맛이 나며, 싱싱한 야채와 함께 새콤한 마요네즈 소스가 들어가 맛을 더해줍니다. 와퍼보다는 작지만, 가격도 저렴하고 먹기에 편리한 사이즈로 인기가 많습니다.

이미지

전체 과정 한번에 보기

이번 강연에서는 GPT를 활용한 RAG 시스템을 소개하는 형태로 준비했습니다.

물론 실제 서비스로 간다면 RAG 시스템을 구체적으로 개발해야합니다. 다만 이번 강연에서 가장 핵심으로 생각한 것은 Prompt를 잘 작성하는것이 전체 시스템의 성능에 큰 영향을 준다는것 입니다.

감사합니다.

반응형