RAG Project

[RAG Project] GlobalMacro QA chatbot - Data Preprocessing - md 파일 (2)

hibyeys 2024. 9. 10. 17:47

2024.09.09 - [RAG Project] - [RAG Project] GlobalMacro QA chatbot - 데이터 크롤링(1)

 

[RAG Project] GlobalMacro QA chatbot - 데이터 크롤링(1)

2024.08.20 - [RAG Project] - [RAG Project] GlobalMacro QA chatbot - 개요(0) [RAG Project] GlobalMacro QA chatbot - 개요(0)Preview필자는 원래 LLM에 크게 관심 없었다. (하지만 chatGPT는 유료결제해서 열심히 갈궜다!)아무래

hibyeys.tistory.com

지난글에서 크롤링을 수행하였고 그 다음으로 크롤링 한 RAW 데이터를 Processing하는 과정을 거쳤다.

 

데이터 유형

수집한 데이터 유형은 크게 3가지로 나눠 볼 수 있을 것 같다.

  1. MD (마크다운) 파일
  2. Audio 파일
  3. 표 형식의 각종 지표 (csv or xlsx)

각각의 데이터 별로 처리 방식이 조금씩 다르긴 하지만 기본적으로 문서를 분할해서 아래와 같이 Document 형식으로 바꿔줘야한다.

Page_content = 텍스트 내용 ❘ Metadata = 텍스트와 관련된 정보 (ex. title, datetime, keyword ...)

 

1. MD (마크다운) 파일 처리

1.1 Chunking

보통 RAG를 하기위해서 문서를 어떻게 chunking 할까에 대한 고민을 많이 한다.

문서 내에서 주제가 전환되는 시점에 문서를 분할해 주어야 나중에 분할된 문서가 Retrieve 되었을 때 context를 제대로 전달할 수 있기 때문이다. 그런 측면에서 마크다운으로 된 문서는 대부분 (# 제목, ## 소제목, ### 하위제목 ...) 이런식으로 구조화되어있어서 의미적으로 문서를 분할하기 편하다. 

from langchain_text_splitters import MarkdownHeaderTextSplitter

markdown_document = "# Title\n\n## 1. SubTitle\n\nHi \n\n this \n\n### 1-1. Sub-SubTitle \n\n love \n\n## 2. yotube\n\n peace"

headers_to_split_on = [("#", "Header 1",), ("##", "Header 2",),("###", "Header 3",)]


markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
md_header_splits = markdown_splitter.split_text(markdown_document)

for header in md_header_splits:
    print(f"{header.page_content}")
    print(f"{header.metadata}", end="\n=====================\n")

MarkdownHeaderTextSplitter 를 사용하여 분할한 예시

이번에 수집한 칼럼들은 구조가 잘 정리된 것도 있었고 sub-title의 의미로 ## (header) 등을 썼다기 보다는 강조의 의미로 header를 사용하는 것도 있었다. 결론부터 말하자면 문제가되는 것들을 전부 직접 수정했다. 그렇게 한 이유는 데이터 수가 엄청 많은 수준이 아니었고 이왕이면 칼럼을 읽으면서 도메인 지식을 쌓자는 생각이 있었다. 또한 강조의 의미로 사용했다하더라도 header를 사용한 곳에는 주제를 대표하는 곳에 사용되었기 때문에 header의 크기를 수정해주면서 split 하기 편했기 때문이다.

 

처음에는 여러 chunk stretegy를 적용해 보았다.

ChracterTextsplitter 와 TokenTextsplitter 를 파라미터를 조정하며 사용해보았지만 정성적 평가를 하였을 때 잘 구조화된 문서를 분할 하는 것보다 퀄리티가 그닥 좋아보이지 않았다. (사실 나쁘지 않은 정도 였지만 초반이라 의욕이 앞섰던 것 같다..^^)

 

(Chunk Stretegy 는 https://www.mongodb.com/developer/products/atlas/choosing-chunking-strategy-rag/, https://www.sagacify.com/news/a-guide-to-chunking-strategies-for-retrieval-augmented-generation-rag을 참고하였다.)

 

A Guide to Chunking Strategies for Retrieval Augmented Generation (RAG) — Sagacify

Introduction Retrieval Augmented Generation (RAG) has been a hot topic in understanding, interpreting, and generating text with AI for the last few months. It's like a wonderful union of retrieval-based and generative models, creating a playground for rese

www.sagacify.com

 

How to Choose the Right Chunking Strategy for Your LLM Application | MongoDB

In this tutorial, we explore and evaluate different chunking strategies to choose the right one for a sample RAG application.

www.mongodb.com

1.2 MetaData Tagger

metadata는 Ducument 객체의 한 축을 담당하고 있고 검색 및 생성에 중요한 역할한다.

metadata는 다음과 같은 방식으로 활용될 수 있다 :

  1. 검색 필터링: metadata를 사용하여 특정 조건에 맞는 문서만 검색할 수 있습니다. 예를 들어, 날짜, 저자, 카테고리 등의 metadata를 기반으로 검색 범위를 좁힐 수 있습니다.
  2. 정보 컨텍스트 제공: 생성 모델에 metadata 정보를 함께 제공하여 더 정확하고 맥락에 맞는 응답을 생성할 수 있습니다.
  3. 검색 결과 순위 조정: metadata를 사용하여 검색 결과의 순위를 조정할 수 있습니다. 예를 들어, 최신 문서에 더 높은 가중치를 부여할 수 있습니다.
  4. 응답 생성 시 추가 정보 제공: 생성된 응답에 metadata 정보를 포함시켜 사용자에게 더 풍부한 정보를 제공할 수 있습니다.

openai에서는 LLM을 이용해 자동으로 metadata를 생성하는 함수를 제공한다. LangChain에도 integration 이 되어있기때문에 손쉽게 사용이 가능하다.

먼저 pydantic을 이용해서 아래와 같이 Property를 선언해준뒤 (자신이 넣고자하는 key를 알아서 custom 하면된다)

생성할 metadata = keyword, datetime, events

 

아래와 같이 create_metada_tagger 함수에 Property와 LLM을 전달해주면 된다.

또한 중요한점은 각각의 key에 대한 prompt를 잘 작성해줘야한다는 것이다. (자세한건 Github Repo를 참고해 주세요)

metadata tag 를 하는 함수
Prompt 예시 (keyword)

1.3 Tips

· MetaDataTagger는 전달된 Document 객체의 metadata를 참고해서 생성하지 않는다.

금융 칼럼은 글을 쓴 시점이나 이야기하고 있는 시대의 시점이 중요하므로 datetime을 metadata로 받으려고 했다.

내가 수집한 자료는 주로 제목에 칼럼을 쓴 시점을 적어놔서 MarkdownHeaderSplitter 로 분할시 metadata에 제목이 할당된다.

처음에는 멋도모르고 프롬프트가 안먹힌다고 생각하고 계속 프롬프트만 수정했다가, metadata를 합쳐서 넣어보니 잘 생성되었다.

(그래서 위의 코드처럼 page_content 와 metadata를 합친 merged_content로 Document를 다시 만들어 전달했다.)

 

· 구조화된 Prompt Engineering 을 하자!

위의 언급한 문제를 해결하면서 프롬프트를 계속 수정했는데 프롬프트를 구조화 할 수록 더 좋은 결과값을 받아볼 수 있었다.

  1. **keyword** 와 같이 마크다운 문법으로 인덱싱 해서 구조화
  2. 1-shot 프롬프팅
  3. Structured output format