Qdrant, Ollama, C#으로 만들어보고 있는 로컬 RAG 시스템

C#, Qdrant, Ollama를 이용해서 로컬 자원만으로 구동되는 RAG 시스템을 만들어볼 수 있지 않을까? 하는 생각으로 맨바닥에서부터 구현을 시작해보고 있습니다.

감사하게도 위키백과에서 이런 시스템을 구현하기에 적합한 데이터셋을 제공하고 있어서 시작해보고 있습니다.

생성형 AI에서 중요한 부분이라 할 수 있는 검색 증강 생성 (RAG)은 (1) float 배열을 저장한 뒤 유사도 검색을 수행할 수 있도록 돕는 벡터 DB, (2) 원본 데이터 (텍스트, 이미지, 동영상 따위)에서 의미에 맞는 float 배열 값을 생성하는 임베딩 모델, (3) 벡터 DB에서 찾은 내용과 연결되는 원본 데이터를 보관하는 관계형 DB 혹은 NoSQL DB, (4) 마지막으로 언어 모델에 전달할 프롬프트의 설계, 이렇게 네 가지 요소가 결합되어 만들어지는 시스템으로 알고 있습니다.

쉽게 생각하면, 기존에 자연어 검색을 위해서 문서를 인덱싱하던 개념을 대체하는 것과 비슷한 상황인데, 이 관점에서 실제로 RAG 시스템을 시험삼아 구현해보고 테스트해보면 좋겠다고 생각하여 맨바닥에서 시작해보고 있습니다.

첫 MVP를 만들어본 다음에는 파켓 파일을 통해서 Qdrant와 Ollama를 디커플링해보고, 임베딩을 할 때 더 많은 부가 데이터를 넣어 매칭 가능성을 높이는 옵션을 추가하는 등 여러 가지 재미있는 아이디어를 더해서 하나의 완성된 데스크톱 소프트웨어로도 디자인해보려 합니다.

코드 샘플을 공유합니다!


9개의 좋아요

오랜만에 댓글을 달아 봅니다. 저 살아 있습니다.
문득 눈에 띄는 글이 있어서 AI에 요약해 달라고 요청했더니…

RAG 시스템:

RAG 시스템은 정보 검색과 생성 모델을 결합하여 사용자가 질의한 내용을 더 풍부하고 정확하게 답변할 수 있도록 돕는 시스템입니다. RAG 시스템은 크게 네 가지 주요 구성 요소로 이루어집니다:

  1. 벡터 데이터베이스(Qdrant): 유사도 검색을 수행하기 위해 임베딩 벡터를 저장하고 검색합니다.
  • Qdrant: 벡터 데이터를 저장하고 유사도 검색을 지원하는 고성능 벡터 DB입니다.
  1. 임베딩 모델(Ollama): 원본 데이터에서 의미에 맞는 float 배열 값을 생성합니다.
  • Ollama: 텍스트, 이미지, 동영상 등의 데이터를 임베딩 벡터로 변환하는 모델입니다.
  1. 데이터 저장소: 벡터 DB에서 찾은 내용과 연결되는 원본 데이터를 보관합니다.
  • 관계형 DB 또는 NoSQL DB: 원본 데이터를 저장하는 데 사용됩니다.
  1. 프롬프트 설계: 언어 모델에 전달할 프롬프트를 설계합니다.
  • 언어 모델: RAG 시스템에서 생성된 결과를 바탕으로 답변을 생성합니다.

RAG의 장점:

  • 정확한 응답: 벡터 검색을 통해 유사한 맥락의 문서를 찾고, 이를 기반으로 언어 모델이 응답을 생성함으로써 더욱 정확한 답변을 제공합니다.
  • 효율성: 기존의 문서 인덱싱 방식보다 효율적으로 정보를 검색할 수 있습니다.
  • 확장성: 다양한 데이터 소스와의 통합이 용이하며, 임베딩 벡터를 통해 다양한 형태의 데이터를 처리할 수 있습니다.

RAG 시스템의 동작 과정:

  1. 질의 입력: 사용자가 질의를 입력합니다.
  2. 임베딩 생성: 질의를 임베딩 모델(Ollama)을 사용하여 벡터로 변환합니다.
  3. 유사도 검색: 생성된 임베딩 벡터를 벡터 DB(Qdrant)에서 유사한 벡터를 검색하여 관련 문서를 찾습니다.
  4. 데이터 추출: 벡터 DB에서 찾은 관련 문서의 원본 데이터를 관계형 DB 또는 NoSQL DB에서 추출합니다.
  5. 응답 생성: 추출된 원본 데이터를 바탕으로 언어 모델이 응답을 생성합니다.

기술의 발전과 함께 AI와 더불어 살아가는 시대에 인간이 어떤 방향을 추구해야 할지에 대한 고민이 요즘 제 관심사입니다.

참 좋은 세상이죠 ?

5개의 좋아요

Try #1:

  1. LLM 자체를 쓰는 것보다는 부하가 덜하지만, 결국 텍스트 임베딩 연산도 GPU가 있고 없고의 차이가 크다는 것을 알았습니다. 임베딩 연산 역시 CPU 연산만으로는 역시 꽤 느립니다.

  2. 첫 루프를 돌아봤는데, 검색 결과로 매칭된 문서들의 연관성이 많이 떨어져보이는 것 같습니다.

    1. RAG에서 또한 중요한 부분이 chunking이라는 것을 알 수 있었는데, 단순히 문단 단위로 나누어 embedding 하는 것으로는 충분하지 않고, 맥락 형성을 위해 일정한 토큰 갯수 유지 + 오버랩 지정이 필요하다는 것을 알게 되어서, 이 부분의 도움을 받기 위해 Semantic Kernel의 TextChunker 구현체를 사용해보기로 합니다.
3개의 좋아요

RAG 하는걸 옆에서 보면 기계적으로 잘라서는 도저히 성능이 안나오더라고요.

거의 노가다…수준

2개의 좋아요

Try #2:

  1. Ollama 모델 갤러리에 올라온 임베딩 전용 모델 중에 nomic-embed-text를 사용했었는데, 다시 보니 영어에만 특화된 모델이라 한국어 처리에는 문제가 있었습니다. Ollama에서 사용하려면 GGUF (llama.cpp용 포맷)로 가공된 임베딩 모델이 필요한데, 한국어에 대한 처리 능력이 있으면서 GGUF 형식으로 가공된 모델은 매우 숫자가 드물다는 것을 알게 되었습니다.

  2. 그래도 일단은 hf.co/KeyurRamoliya/multilingual-e5-large-GGUF 모델을 사용해보기로 결정했습니다. Ollama 공식 레지스트리에 없어도 허깅페이스에서 GGUF 모델을 pull 할 수 있어 테스트가 편리한 점은 매우 유용합니다.

2개의 좋아요
  1. 2년전 처음 저도 그런 시도를 했었는데 qdrant의 디폴트 임베딩 모델이 한글 성능이 정말 별로였었습니다.
  2. 문서 검색은 문서의 구조적 문제를 어떻게 잘 해결할지를 고민해야 합니다.
  3. 임베딩시 chunking 레벨 조정 잘하시면 문서 검색시 제법 좋아질 수 있습니다. 이를 위해서 정교한 semantic sentence spilter 구현이 필요합니다. 물론 도메인에 맞는 휴레스틱한 방법도 섞을 수 있고요.
  4. 유사도 검색결과 나온 임베딩 된 문서들이 정말 맞는 문제인지에 대해서 reranker model이라는 놈을 붙이면 좀더 정확도가 올라갑니다.
  5. 유사한 문서들이 다수 존재하고 질의가 복합 질의 일 경우가 생각외로 많아서 해당 경우도 나중에는 고려되어야 합니다.

이상 얘기하신거 비슷한거 해본 경험입니다.ㅎㅎ

3개의 좋아요

임베딩 모델은 짧은 문장의 경우에는 multilingual-e5-large로 충분하지만,
긴 문장은 bge-m3사용해보는 것도 나쁘지 않습니다.
검색된 결과를 다시 정렬할 때 Reranker Model까지 쓰면 성능이 더 좋아집니다.

데이터 청킹할 때는 여러 문장을 묶고 일부를 오버랩해서 저장하는 게 좋습니다
문장 분리는 한국어는 KSSKiwi (.NET Wrapper 있음),
영어는 PragmaticSegmenterNet (PragmaticSegmenter의 닷넷 재구현) 이나 BlingFire 같은 걸 쓰면 됩니다.
아니면 그냥 LLM한테 시키는 것도 방법입니다…

4개의 좋아요

코드 샘플을 공유합니다.

4개의 좋아요

먼가 되긴하는데 머가먼지 아직 잘 모르겠네요 재밋어보여요

2개의 좋아요

이렇게 검색되는 문서에서 일부 또는 전체를 발췌한 다음에 다음과 같은 형태로 LLM에 프롬프트로 던져주면 원하는 답을 이끌어낼 수 있다는 시나리오입니다.

이 상황에서 개인적으로 문서 검색을 위해 쌓아두는 벡터 DB만 디커플링해보고 싶었다는게 의도였습니다. ㅎㅎ

2개의 좋아요

공유해주신덕에 벡터디비를 처음써봤는데 해보니까 꼭 AI연동이 아니더라도 엄청 유용할거같네요 감사합니다.

프로젝트가 의존하는사항이랑 초기화 과정까지 Aspire로 통합하면 좋을거같아서 포크해봤어요.

naratteu/RagSample@e9d26a0

5개의 좋아요