💡 여러분은 자동차를 사면 설명서를 꼭 읽어보시나요?
대부분의 사람들은 기쁜 마음으로, 동네 드라이브부터 나갈 거라 생각해요. 그도 그럴것이, 새 차를 운전하는 데는 생각보다 설명서의 정보가 필요하지 않거든요. 스마트폰, 에어컨 등 다른 전자기기도 마찬가지 일거에요.
그런데 가끔은 기기의 공식 설명서가 필요한 순간들이 꼭 있습니다.
예를 들어 차 계기판에 뜬 경고 문자가 무슨 의미인지, 차량의 특정 버튼이 어떤 기능인지 등등이요. 물론 해당 궁금증을 차량 커뮤니티에 올리면 전문가가 답변을 달아놓겠지만, 이 과정이 생각보다 번거롭고 시간이 걸립니다. 그렇다고 아래와 같은 수백 페이지의 공식 설명서를 읽자니.. 한국 사람으로서 벌써부터 치가 떨립니다.
그냥 해당 설명서를 잘 숙지하고 있는 전문가를 주머니 속에 놓고,필요할 때마다 물어본다면 얼마나 좋을까요? 그래서 만들어 봤습니다. 수백 페이지의 사용 설명서를 기반으로 질의응답이 가능한 챗봇.
우리가 사용 설명서를 귀찮게 들여다볼 필요가 없습니다. 단지, 질문을 입력하면 챗봇이 대신 사용설명서를 훑어보고 답변을 생성해 주는 것이죠. 그냥 ChatGPT한테 물어보면 그만 아니야? 라고 생각할 수 있습니다. 그 유명한 ChatGPT와의 차이점은 1. 실제 차량의 사용설명서를 기반으로 답변을 해준다는 점과 2. 답변을 생성할 때, 설명서의 어떤 내용을 참고했는지 보여줄 수 있다는 점입니다. 이러한 Chatbot 서비스를 구현하기 위해서는 RAG(Retrieval-Augmented Generation)라는 핵심기술이 필요합니다. 지금부터 위의 챗봇 서비스를 구현하기 위해, RAG의 개념과 실제 구현 방법들을 함께 살펴보도록 하겠습니다.
💡 RAG란?
RAG는 외부 데이터베이스에서 관련 정보를 검색하여, 이를 기반으로 답변을 생성해 준다
ChatGPT와 같은 일반 LLM(Large Language Model)은 범용적인 측면에서 유용하지만, 맞춤형 LLM으로는 한계가 있습니다. 예를 들어, OO은행이 고객을 위한 맞춤형 챗봇을 구축하려고 할 때, ChatGPT를 그대로 활용하기에는 OO은행에 대한 정보가 부족할 수 있습니다. 그렇다고 ChatGPT를 따로 학습시키기에는 아래와 같은 문제점들이 있죠.
- 많은 자원 소모 : LLM을 학습(또는 파인튜닝)하는 데는 많은 시간과 비용이 듭니다.
- 빈번한 재학습 필요 : OO은행에 대한 정보가 업데이트될 때마다 모델을 재학습시켜줘야 합니다.
- 데이터 구축의 어려움 : 학습시킬 양질의 데이터를 구축하기가 쉽지 않습니다.
이때 OO은행은 RAG 시스템을 도입하여 별도로 LLM을 학습시키지 않고도 맞춤형 챗봇을 구현할 수 있습니다. RAG는 외부 데이터베이스에서 관련 정보를 검색하여, 이를 기반으로 답변을 생성해 주기 때문이죠. RAG 시스템의 작동 방식은 아래와 같습니다.
기존의 LLM과 비교해 봤을 때, 사용자의 Query와 함께 관련된 데이터를 외부 데이터베이스로부터 검색하는 단계가 추가되었습니다. 이렇게 검색된 데이터를 Query와 함께 LLM에 입력하면, 해당 정보를 기반으로 답변을 생성해 줄 수 있게 되는 것이죠. 이러한 RAG는 아래와 같은 이점이 있습니다.
- 비용 감축: 별도의 학습 과정 없이 실시간으로 최신 데이터를 반영할 수 있어 시간과 비용을 절감할 수 있습니다.
- 최신 정보 반영: 데이터베이스에서 최신 정보를 검색하여 사용자의 질문에 답변하므로, 항상 최신 정보를 제공합니다.
- 데이터 보안: 민감한 데이터가 LLM에 직접 학습되지 않고, 검색된 데이터를 기반으로 처리되므로 데이터 보안 측면에서도 유리합니다.
- 신뢰성 증가: 답변의 근거를 알 수 없는 일반 LLM과는 달리, RAG는 어떤 데이터를 참고해서 답변을 생성해 냈는지 보여줄 수 있습니다.
💡 RAG, 더 자세히 살펴봅시다.
위에선 RAG를 쉽게 이해하고자 단순한 도식화로 설명을 드렸지만, RAG는 생각보다 많은 요소들의 집합체입니다. 수많은 부품으로 이루어진 자동차처럼 말이죠. 그렇기에 RAG를 잘 다루려면, 기본적인 파이프라인을 이해하는 것이 매우 중요합니다.
꽤 복잡해 보이는 파이프라인이지만, 크게 Embedding과 VectorDB 그리고 Retrieve, 이 3가지를 중심으로 살펴보고 가겠습니다.
1️⃣ Embedding
Embedding은 문장을 Vector 형식으로 변환하여, 문장에 대한 의미를 수치적으로 나타낼 수 있도록 해줍니다. 이러한 Embedding은 왜 하는 걸까요? 바로, 문장과 문장 사이의 유사도를 계산하기 위해서입니다. 점과 점 사이의 거리를 구할 수 있듯이, 벡터와 벡터사이의 거리를 구할 수 있는 것이죠. Embedding 모델은 의미가 비슷한 문장들이 서로 가깝게 위치할 수 있도록 학습되었기 때문에, 이 거리가 짧을수록 유사한 의미를 가진 문장이라 판단할 수 있습니다. 추후 Retrieve 단계에서 살펴볼 테지만, 이 거리 개념을 활용해서 Query와 관련된 문장들을 데이터베이스로부터 검색할 수 있게 됩니다.
2️⃣ VectorDB
VectorDB는 검색될 문서들이 Vector형식으로 저장되어 있는 저장소입니다. 저는 현대 자동차의 챗봇을 구축할 계획이기에, 차량 사용 설명서와 관련된 문서들을 VectorDB에 구축해 둬야겠지요. 이때, 차량 사용 설명서를 통째로 embedding 하여 하나의 vector를 만들어내진 않습니다. 해당 문서들을 더욱더 작은 단위인 Chunk로 쪼개서 embedding을 하게 되죠. Chunk를 하는 이유는 아래와 같습니다.
- 텍스트가 너무 긴 경우에는 핵심 정보 이외에 불필요한 정보들이 많아, embedding 된 vector의 품질이 낮아질 수 있습니다.
- 언어 모델에 입력할 수 있는 토큰의 개수는 한정적입니다.
3️⃣ Retrieve
사용자의 Query와 가장 유사한 자료들을 VectorDB로부터 찾아내야 합니다. 유사한 기준은 각 벡터 간의 거리가 좁은 것으로 계산할 수 있다고 했습니다. 벡터간의 거리를 측정하기 위해, Cosine 유사도, 유클리드 거리와 같은 다양한 계산식들이 존재합니다. 각각의 계산법과 서로의 장단점은 해당 블로그에 잘 정리되어 있어, 공부가 필요하시다면 읽어보길 추천드립니다.
이렇게 벡터 간의 거리를 구함으로써, 문장 간의 유사도를 측정할 수 있게 되었지만 문제가 한 가지 있습니다. 벡터 DB에 있는 수많은 벡터들에 대해서 각각 유사도를 다 측정하게 되면 시간이 엄청 오래 걸립니다. 빠른 검색 결과를 원하는 현대 시대에, 이는 적합하지 않죠.
이를 위한 한 가지 해결법으로, ANN(Approximate Nearest Neighbor) 알고리즘들을 적용할 수 있습니다. ANN(Approximate Nearest Neighbor) 알고리즘은 VectorDB에 저장된 모든 벡터와 일일이 비교하는 정확한 방식이 아니라, 빠른 속도로 찾아내기 위해 대략적인 계산(동시에 정확하진 않을 수 있는) 방식을 사용합니다. 이를 통해 검색 속도를 크게 향상할 수 있으며, 특히 대규모 데이터셋에서 효율적입니다. 이러한 ANN은 완벽한 정확도를 제공하지 않을 수 있지만, 대부분의 애플리케이션에서 충분히 유용한 결과를 보여주고 있습니다.
💡 현대자동차 챗봇, 직접 구현해 보기
이제 이 추상적인 RAG 파이프라인을 코드로 직접 구현해볼 차례입니다. 요즘엔 Langchain, LLamaindex와 같은 고마운 라이브러리들이 있어, RAG와 같은 LLM 애플리케이션 코드를 비교적 쉽게 구현할 수 있게 되었습니다. 저희는 LLamaindex를 활용해 현대자동차 챗봇을 구현해 보도록 하겠습니다.
1️⃣ Documents Load
현대자동차 챗봇을 위해 현대자동차 고객지원 사이트에서 차량 사용 설명서를 다운로드합니다. 저는 싼타페 하이브리드 차량의 사용 설명서를 PDF로 다운로드하였습니다. 아래 코드는 다운로드한 PDF문서를 텍스트 형식으로 내려받은 다음, 특정 chunk 크기로 텍스트를 잘라내는 과정입니다. (llamaindex에서는 잘린 텍스트를 node 또는 chunk라 부릅니다.)
# load documents
documents = SimpleDirectoryReader("file_path",recursive=True, exclude_hidden=True).load_data()
splitter = SentenceSplitter(chunk_size=1024)
nodes = splitter.get_nodes_from_documents(documents)
2️⃣ VectorDB & Index
이렇게 청킹 된 node들을 임베딩하여 vector로 만들고, 이를 vectorDB에 저장해둘 겁니다. 여기서 저희는 어떤 임베딩 모델을 사용할지, 어떤 VectorDB를 사용할지 선정해야 합니다. 임베딩 모델은 다국어에 대한 임베딩 성능이 뛰어난 OpenAI의 text-embedding-3-large임베딩 모델을 사용하였습니다. 해당 임베딩 모델은 텍스트를 3072차원의 벡터로 변환해 줍니다.
VectorDB로는 오픈소스로 활용할 수 있으며, 비교적 개발자 커뮤니티가 크게 활성화되어 있는 Milvus를 선택하였습니다.
(Milvus VectorDB 호스팅 방법)
추후 VectorDB에서 검색이 이루어질 땐, Cosine유사도 방식으로 계산될 수 있도록 구성해 주었습니다.
vector_store = MilvusVectorStore(
uri="<http://localhost:19530>", collection_name = "hyundaiRag", dim=3072, similarity_metric="COSINE")
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex(nodes, embed_model=embed_model, storage_context=storage_context)
3️⃣ Retreive
이제 사용자 query를 입력받아, vectorDB에 저장된 문서들 중 가장 유사한 node 3개를 검색해 보겠습니다.
vector_search = index.as_retriever(similarity_top_k=3)
nodes = vector_search.retrieve("타이어가 펑크났어. 해결책을 알려줘")
for node in nodes:
print(node)
4️⃣ Using LLM
이제 사용자의 query, 검색된 node들을 활용해 prompt를 만들고, LLM에게 답변을 생성해 달라고 요구해 보겠습니다. llm 모델로는 openai가 최근 발표한 gpt-4o모델을 활용합니다.
llm = OpenAI(model="gpt-4o")
rag_engine = index.as_query_engine(similarity_top_k=3, llm=llm)
response = rag_engine.query("창문에 서리가 자꾸 껴")
print(response.response)
print("====")
for node in response.source_nodes:
print(node)
이때, query와 node가 어떻게 prompt에 녹여질지 확인해 보는 것도 큰 의미가 있습니다. llamaindex에서 RAG활용 시, 기본값으로 아래 프롬프트 템플릿을 활용합니다.
Context information is below. --------------------- {context_str} <- 여기에 검색된 node들이 들어가게 됩니다. --------------------- Given the context information and not prior knowledge, answer the query. Query: {query_str} <- 여기에 사용자의 query가 들어가게 됩니다. Answer: |
당연히 해당 프롬프트 템플릿은 커스터마이징이 가능하며, 애플리케이션에 알맞은 템플릿을 구성할 수 있습니다. 위의 코드 결과는 아래와 같습니다.
💡 Done!
이렇게 현대자동차의 사용 설명서 챗봇을 구현해 봤습니다. 구현하면서 제가 느낀 RAG의 이점과, 더 고려해 볼 사항을 정리해 보면 아래와 같습니다.
[RAG의 이점]
위의 사례 중에서 “싼타페는 유류가 몇 리터까지 들어가지?”라는 질문을 검색했을 때, 67L라는 답변은 실제로 사용설명서에 작성되어 있는 문서를 잘 가져와서 답변해주고 있습니다.
위의 사례처럼, 답변에 대한 근거를 바로 보여줄 수 있다는 신뢰성이 RAG의 큰 이점이라 생각해요. 아무리 잘 학습된 LLM이라 하더라도, 특정 최신 문서에 대해서는 학습이 되어있지 않았을 수 있고, 답변의 근거를 사용자에게 확인시켜 줄 수가 없죠.
[고민해 보면 좋을 점]
앞서 RAG는 생각보다 많은 요소들의 집합체라고 했습니다. 해당 포스팅에서 설명드린 RAG파이프라인은 가장 기본적인 파이프라인일 뿐,
RAG성능을 높이기 위한 다양한 요소들이 존재합니다. 아래는 RAG 성능을 높이기 위해 고려해 볼 수 있는 요소들의 예시입니다.
- PDF추출 방식(표, 그림, 단락을 어떻게 추출할 것인가?)
- Chunk size 최적화
- Prompt 템플릿을 어떻게 수정해 볼 수 있을까
- 검색 성능을 향상하기 위한 방법 (rerank, fine-tuning)
이렇게 RAG를 활용해 현대자동차의 챗봇을 구현해 봤는데요,
이러한 RAG를 활용해, 사람들을 편하게 해 줄 수 있는 분야가 또 뭐가 있을까? 고민해 보는 것은 AI 개발자로서 굉장히 흥미로운 고민거리입니다. 앞으로도 RAG, LLM을 활용한 재밌고 유익한 것들을 함께 공유해 보도록 하겠습니다.
긴 글 읽어주셔서 감사합니다 🤗
'Tech > 현대자동차 설명서 챗봇' 카테고리의 다른 글
RAG | 현대자동차 챗봇 구현기 - 성능 최적화(feat. 평가데이터 전처리 툴) (4) | 2024.11.10 |
---|---|
RAG | 현대자동차 챗봇 구현기 - PDF를 잘 추출 해야 하는 이유 (0) | 2024.06.23 |