RAG 개선하기

RAG어플리케이션의 성능을 어떻게 개선할 수 있을까요?
신은성's avatar
Sep 30, 2024
RAG 개선하기

저번 글에서는 직접 RAG 어플리케이션을 만들어보았습니다. 최신 부동산 정보를 벡터DB에 저장하여 요약정리해주는 어플리케이션이였습니다. 잘 동작하긴 하지만, 여러모로 개선할 부분이 많아보였는데, 이번 글에서는 개선할 수 있는 부분들을 정리하고 좀 더 성숙한 어플리케이션으로 업그레이드해보겠습니다. 모든 코드는 깃헙 리포지토리에서 확인가능합니다.

서비스 기획 변경

본격적으로 RAG의 성능을 고려하기 전에, 임시로 만든 우리 앱부터 수정해야했습니다. 우선 챗봇 형태로 구성되어있는데, 이런 형태는 사용자가 이용하는만큼 openAI api를 호출해야하기 때문에, 혼자서 운영비를 감당해야하는 입장에서 금액이 조금 부담스럽다는 생각이 들었습니다. 그래서 API 호출을 최소한으로 줄이고, 직접 호출횟수를 지정할 수 있도록 뉴스레터 형식으로 바꾸기로 결정했습니다. 메인 페이지에서 뉴스레터 리스트에 본인의 이메일을 추가하여 구독하면, 매일 정해진 시간에 '부동산 관련 뉴스를 반영한 정보'들을 제공해주는 겁니다.

이렇게 되면 사용자들이 API 호출을 할 수 있는 자유도는 떨어지긴 하지만, 개발입장에서 API 호출을 제한할 수 있기 때문에 더 저렴하게 운영이 가능합니다.

챗봇(API 호출 자유로움 ) => 뉴스레터(API 호출 제한)

코드 정리

서비스 기획을 변경하고나서는 코드를 정리하였습니다. 변경한 서비스의 프로세스는 크게 정해진 시간에 뉴스 정보들을 긁어오고, 벡터DB에 반영하는 프로세스서비스의 끝단에서 사용자들의 이메일을 관리하는 프로세스 그리고 벡터 DB에 반영한 정보를 이메일 리스트에 보내주는 프로세스로 나뉩니다. 이에 맞춰 폴더 구조를 변경하였습니다.

서비스 구성

실제 운영을 위해, 각각의 프로세스를 도커 이미지로 패키지하여 docker-compose로 구성하고, AWS의 EC2에 올렸습니다. github action을 사용하여 ECR에 이미지를 업로드하고, EC2 인스턴스에 docker-compose를 다시 올리는 식으로 CI/CD를 구성하였습니다. 사용자가 보는 페이지는 정말 간단한 이메일 입력란만 제공할 것이기 때문에, S3에서 index.html만 작성하여 정적으로 호스팅하였습니다. 등록한 이메일은 정말 단순히 기본적인 정보만 관리할 것이기 때문에, 기본 요금+알파가 드는 RDS 대신 트래픽에 따라 가변적으로 요금을 부과하는 키-값 기반의 간단한 쿼리를 제공하는 DynamoDB를 사용합니다. 그리고 가장 중요한 벡터DB인 Chroma는 현재 AWS에서는 도커 이미지로만 제공을 합니다. 서비스를 띄운 같은 EC2 인스턴스에 docker-compose로 함께 Chroma 인스턴스를 띄워줍니다.

RAG 개선

그럼 이제 본격적으로 RAG를 개선해봅시다.

개선할 수 있는 부분은 다음과 같습니다.

  • 모니터링 - LangSmith

  • 프롬프트 엔지니어리

  • 벡터DB 적재

RAG 개선 - 모니터링

개선하기 위해서는 우선 측정이 가능해야합니다. LangSmith라는 LLM 어플리케이션 모니터링 툴을 적용하기로 합니다. 체인을 작성하기 위해 사용한 langchain과 연동성이 좋기 때문에, LLM 호출 시에 Langsmith로 래핑만 해주면, 바로 모니터링 기능을 사용할 수 있습니다. 아래는 현재 작성된 chain 호출 시 연산에 걸리는 시간과 비용을 나타낸 것입니다. 체인의 각 단계별 소비되는 시간과 토큰을 한 번에 볼 수 있기 때문에 개선 포인트를 확인하기에 유용합니다.

RAG 개선 - 프롬프트 엔지니어링

프롬프트 엔지니어링은 LLM 어플리케이션의 인풋을 조정하는 기술입니다. 사람이 인식하기에는 같은 내용이라해도, 인풋에 따라 예상 결과가 천차만별로 바뀝니다. 가장 손쉽게 어플리케이션의 성능을 개선할 수 있는 방법이라고도 할 수 있습니다.

현재 우리 어플리케이션의 프롬프트는 다음과 같이 작성되어있습니다.

_TEMPLATE = """Given the following conversation and a follow up question, rephrase the

follow up question to be a standalone question, in its original language.

Chat History:

{chat_history}

Follow Up Input: {question}

Standalone question:"""

CONDENSE_QUESTION_PROMPT = PromptTemplate.from_template(_TEMPLATE)

ANSWER_TEMPLATE = """Answer the question based only on the following context:

{context}

Question: {question}

"""

보시는 것과 같이 처음에 챗봇으로 어플리케이션을 구성했기 때문에, 인풋뿐만 아니라 이전의 대화 기록이 전부 포함되고, standalone question형식으로 현재 인풋이 쿼리되는 것을 확인할 수 있습니다. 이전의 대화기록이 매 인풋마다 포함된다면, 대화가 길어질수록 금액적으로 더 부담이 될 수 있습니다. 비즈니스 로직면에서 몇 스텝 안에 대화를 끝낼 수 있도록 구성하거나, 아니면 명시적으로 대화의 길이를 제한하는 방법을 생각해볼 수 있습니다. 다행히 우리는 챗봇 형식에서 뉴스레터 형식으로 바꿨기 때문에 더 이상 급증하는 대화 기록에 대해 고려하지 않아도 됩니다.

그렇다면 뉴스레터 작성을 위해 어떤 프롬프트가 필요할까요? 사용자들은 뉴스를 읽지 못 하는 게 아닙니다. 그저 모든 뉴스를 다 챙겨보기 귀찮을 뿐이죠. 우리가 제공해야하는 것은 편리함입니다. 크롤링한 뉴스 정보에 대한 간략하고 유용한 요약이 필요합니다. 뉴스 정보를 받아 요약하는 프롬프트를 작성해봅니다.

"""Write a concise summary of the following: /n/n/n {context}"""

요약을 요청하는 프롬프트를 작성했습니다. (요약하려는 뉴스가 길어지면 쪼개서 각 요약한 후 합쳐서 다시 요약하는 mapreduce단계를 거치면 더 효율적인 프롬프트 작성이 가능합니다. 포스트가 길어지므로 해당 내용은 생략하겠습니다.) context 안에 들어가야하는 내용은 어떤 걸까요? 벡터 DB에서 뉴스레터 작성을 위해 적절한 쿼리가 필요합니다. 이 부분은 날짜나 지역, 또는 주거형태 등 다양한 정보들을 쿼리할 수 있기 때문에 유연하게 변경 가능한 부분입니다.

RAG 개선 - 벡터DB에 적재

최종 결과물인 뉴스 요약이 나오기 전에 요약할 내용이 필요합니다. 벡터 DB에서 꺼내오는 내용들입니다. 저장한 내용의 퀄리티가 좋을수록, 그리고 꺼내온 내용이 정확할수록 좋겠죠. 벡터DB에서 저장하는 방식, 그리고 꺼내오는 방식 또한 성능을 개선할 수 있는 부분입니다.

그럼 먼저 저장하는 방식부터 살펴봅시다. 현재 구성된 저장 방식은 크롤링 후, 별도의 처리없이 바로 벡터 DB에 넣는 형식입니다. 전처리가 없다보니, 아래 그림과 같이('이 기사가 마음에 들었다면, 좋아요를 눌러주세요.', '인기뉴스' 등) 쓸모없는 내용들이 포함된 걸 확인할 수 있습니다. 그리고, 간혹 뉴스의 길이가 길면 중간에 인풋이 잘리거나, 문맥 요약의 정확도가 떨어지기도 합니다.

해결 방법은 생각보다 간단합니다. LLM을 활용하는 것이죠. 벡터 DB 적재 전에 인풋을 요약하고 임베딩하는 겁니다. 이렇게 되면, 수기 로직 작성을 고려할 필요없이 손쉽고 정확하게 전처리가 가능합니다. 추가적인 호출로 인한 비용이 발생하긴 하겠지만, 챗봇 형식에서 뉴스레터 형식으로 변경하면서 비용을 어느정도 절약했기 때문에 감당 가능합니다.

요약 전처리를 수행할 때 꼭 필요한 부분은 위에서 설명한 것처럼 조건(날짜, 지역, 주거형태 등)에 따라 알맞게 질의할 수 있도록 필요한 정보를 포함하는 것입니다. LLM 덕분에 자연어를 활용할 수 있게 된 것 말고는, 일반 DB를 구성할 때 속성값을 설정하고 그에 따라 쿼리하는 것과 같다고 볼 수 있습니다.

추가적인 개선

지금까지 어떻게 RAG 성능을 개선할지 알아보았습니다. 성능 개선을 위해 모니터링 툴인 LangSmith를 적용하고, 서비스 형태, 프롬프트 엔지니어링, 벡터 DB 적재 방식 관점에서 성능 개선을 진행했습니다. 초기 서비스 형태는 단순 구현에 가까웠지만, 이제 제법 그럴듯한 틀을 갖춘듯 합니다.

서비스가 어느정도 틀을 잡았으니 더 개선해나갈 수 있습니다. 몇 가지 생각해본 개선 방향은 다음과 같습니다. 현재는 범용적인 정보를 제공하고 있는데, 사용자별로 조금 더 개인화된 서비스를 제공할 수도 있습니다. 개인 정보에 관심있는 지역이나 주거 형태를 추가하면 관련된 정보를 제공하는 등의 방법입니다.

두번째는 네이버 부동산 API 등을 연동하여 크롤링하는 정보의 종류를 확장할 수 있습니다. LLM에 제공하는 정보를 뉴스 정보에서 실거래가나 거주 정보, 리뷰 등을 추가하는 겁니다.

세번째는 부동산을 알아볼 때, 보통 지도를 살펴보고, 또 그 지역 근처 관련 정보들을 검색하여 살펴봅니다. 지도 API를 연동하여 사용자가 보고있는 지역의 관련 뉴스나 검색 정보들을 제공하는 식으로 확장한다면, 유저 사용경험을 개선할 수 있습니다.

Share article
Subscribe to our newsletter

직계약으로 끝까지 책임지는 매칭 플랫폼, 스파르타빌더스