👇 이전의 포스팅은 아래에서 확인할 수 있습니다. 👇
금융 데이터 AI 챗봇 개발기: 1) 그래서 PDF를 어떻게 분석할건데? (기획부터 데이터 전처리까지)
출처: https://nanini.tistory.com/90 [데이터 개발 공부:티스토리]
이전 포스팅에 이어서
이제 RAG를 구축할 차례이다.
LangChain 공식문서에서 RAG는
두 가지 주요 구성 요속 있는데 1번 인덱싱 부분은 이전 포스팅에서 이미 완료한 상태였다.
I) 인덱싱 : 소스에서 데이터를 수집하고 인덱싱하는 파이프라인. 이는 일반적으로 오프라인에서 발생합니다.
- 로드 : 먼저 데이터를 로드해야 합니다. 이는 Document Loaders 로 수행됩니다 .
- 분할 : 텍스트 분할기는 큰 청크를 작은 청크로 나눕니다 Documents. 이는 데이터 인덱싱과 모델에 전달하는 데 모두 유용합니다. 큰 청크는 검색하기 어렵고 모델의 유한한 컨텍스트 창에 맞지 않기 때문입니다.
- 저장 : 나중에 검색할 수 있도록 분할을 저장하고 색인할 곳이 필요합니다. 이는 종종 VectorStore 및 Embeddings 모델을 사용하여 수행됩니다.
II) 검색 및 생성 : 런타임에 사용자 쿼리를 받아 인덱스에서 관련 데이터를 검색한 다음 이를 모델에 전달하는 실제 RAG 체인입니다.
- 검색 : 사용자 입력이 주어지면 관련 분할이 검색기를 사용하여 저장소에서 검색됩니다 .
- 생성 : ChatModel / LLM은 질문과 검색된 데이터를 모두 포함하는 프롬프트를 사용하여 답변을 생성합니다.
'데이터 수집부터 분할하고 임베딩해서 Chromadb로 저장'까지는 구현이 되었기 때문에
이어서는 위의 오른쪽 이미지에 해당하는 2번 검색 및 생성을 할 차례다.
Question
먼저 Question이 들어오면 Vectorstore에서 문서를 검색한 후에 검색된 문서를 LLM에 넣기 위해 하나의 컨텍스트로 결합했다.
이때, \n을 사용하여 문서 간 구분을 해주었고, 메타데이터는 sources라는 리스트에 저장되도록 한 후에
프롬프트에 포함되도록 해주었다.
RETRIEVE
Retriever는 키워드 기반의 BM25(spars retriever)와 임베딩 유사도(dense retriever)를 결합한 앙상블 검색기(Ensemble Retriever)를 사용했다.
이때, 임베딩 유사도에는 MMR(Maximum Marginal Relevance)을 사용하여 벡터 간 유사도로 관련 문서를 찾은 후에 MMR을 통해 다양한 문서가 선택되도록 했는데, 20개의 문서 중에서 lambda값을 0.7로 하여 최종적으로 8개의 문서를 선택하도록 했다.
아래 수식은 MMR의 기본 수식과 유사도인데 이해를 돕고자 들고왔다.
MMR = argmax[λ * sim1(Di, Q) - (1-λ) * max sim2(Di, Dj)]
# λ: 관련성과 다양성의 균형을 조정하는 파라미터 (코드에서는 0.7)
# sim1(Di, Q): 문서와 쿼리의 유사도
# sim2(Di, Dj): 이미 선택된 문서들과의 유사도
BM25는 키워드(단어) 기반의 검색 알고리즘으로써, 정확한 키워드 매칭이 강점을 가지고 있다. BM25에는 키워드와 매칭되는 상위 8개의 문서를 반환하도록 했다.
그리고 이 두 리트리버를 EnsembleRetriever로 결합했는데,
각각의 가중치는 0.8(MMR), 0.2로 두었는데, 의미적 유사 검색(Chroma)를 중요하게 하되 키워드 검색(BM25)는 보완하여
리트리버 성능을 높일 수 있도록 했다.
그리고 이렇게 나온 검색 결과에 대한 정제 작업을 진행했는데,
1) 새로운 메타데이터를 딕셔너리로 만들어서
프롬프트 내 출처 부분에 넣기 위해 필요한 메타데이터인 page, source, summary를 선택하고,
2) 문서 타입에 따라 text와 hypothetical_questions를 나누어서 처리했다.
초반 PDF 문서를 전처리할 때, text, image, table 요소마다 예상 질문으로 만들어놓았던 hypothetical_questions를 이용하여
RAG에서 더 정확하고 관련성이 높은 응답을 생성하기 위해 metadata type을 text와 hypothetical_questions를 나누어 처리했다.
PROMPT
프롬프트는 PDF에서 분석한 내용들이 최대한 정확하고 잘 검색되도록 하는 것이 중요했다.
먼저 '질문-답변' 으로 답변을 하도록 assistant의 역할을 지정하고, Direction을 통해 답변에 대한 구체적인 가이드를 제시했다.
retriever를 통해 검색된 문서의 내용들을 context로써 참고하도록 했고,
답변에 대한 예시를 만들어서 질문과 관련된 summary, table, chart와 관련한 설명이 있을 경우에 이를 포함하여 상세하게 답변하도록 구성했다.
그리고 검색 결과가 PDF의 어떤 문서의 어느 부분을 참조했는지 알려주기 위해 출처도 표기했는데,
위의 정제 작업에서 생성한 메타데이터 내의 source, summary, page를 사용하여 출처로 표기되도록 했다.
그리고 마지막으로
ChatGPT의 토큰 사용량을 줄이기 위해 모두 영어로 구성했다.
Chatgpt 사이트에서 각각 한/영으로 토큰을 비교해보았을 때, 확실히
영어로 된 토큰이 훨씬 적었다.
추가적으로
CoT(Chain-of-Thought )를 사용하여 모델이 더 체계적으로 생각하고 답변을 찾도록도 시도해보았으나
위의 basic한 프롬프트 대비 답변 구성은 비슷하지만 응답 속도가 약 1.5~2배 가까이 느렸다.
그리고 토큰 사용량도 조금이지만 증가함을 보였기 때문에
굳이 CoT를 사용할 이유가 없었다.
마찬가지로 Few-shot Learning도 고려해보았으나, 'markdown'으로 저장되어 있는 표 같은 형식을
프롬프트에 넣기에는 토큰소비가 너무 컸고,
답변의 포맷만 지정해주는 정도로도 퀄리티있는 답변이 나왔으므로
이 방법도 패스했다.
LLM
모델은 gpt-4o-mini를 사용했다.
이 부분도 gpt-4o를 쓸지, gpt-4o-mini를 사용할지 고민이 되었는데
동일한 프롬프트로 비교해보았을 때 4o답변의 퀄리티가 조금 떨어졌고(4o-mini에 비해 너무 엄격했다. 이 부분은 모델별로 특징이 다른 점에 기인한 것으로 보임), 토큰비용도 gpt-4o-mini 대비 너무 높은 편이라 mini를 선택했다.
할루시네이션을 방지하기 위해 temperature는 0으로 설정했는데,
위의 프롬프트에서도 DB에 존재하지 않는 답변은 '이 질문에 대한 답변을 제공할 수 있는 자료를 찾을 수 없습니다. 더 궁금한 점이 있다면 언제든 말씀해주세요.😊'라고 답변하도록 했다.
chain은 아래와 같은 형태로 구성해주었는데,
retrieve_and_fomart을 통해 관련 문서를 검색해서 관련된 컨텍스트와 사용자 질문을 결합한 후, 프롬프트를 통해 LLM에 전달하도록 했다.
이렇게 만들어진 LLM 모델을 통해 챗봇을 만들게 되었다.
그러나,
여기서 끝이 아니다.
사실 프로젝트 초반에 Agent 에 대한 기능이 언급되어서
Agent도 함께 개발해보았다.
단, 만약의 사태를 대비하기 위해 Chain도 구현하면서 Agent에도 도전해보기로 했다.
Agent 개발 도전
Agent가 언급된 이유는,
스스로 검색하고, 결정하여 행동하는 것 이외에도
잘만 쓰면 유용한 여러가지 강력한 도구들이 있다는 것이다.
이 중, 금융 레포트를 기반으로 하다보니 관련 산업 혹은 기업에 대한 질문이 들어왔을 때
실시간으로 뉴스 검색 기사도 제공해주면 경쟁력 있을 것이라 생각했다.
또, 답변 생성 시에 관련 자료를 엮어 새로운 표나 시각화 자료를 생성한다면 답변의 퀄리티가 많이 올라갈 것이라 생각했다.
그래서 우리가 목표한 Agent 구현은 다음과 같았다.
그러나...
아래의 한계 외에도 큰 단점이 존재했다.
일단
개발이 너무 어려웠다...
처음이다보니 이걸 담당한 팀원이 개발에만 3주가 소요되었다.
그리고
정말정말 느린 응답 시간...
혼자 생각하고, 결정하고, 행동을 하느라 질문이 하나 들어오면
답변이 굉장히 ...느리다.
또,
답변을 하기 위해 열심히 돌아가는 내용을 보면 내용은 진짜 풍부하고 장황하다.
'이자식 토큰 엄청 잡아먹네?'라는 생각이 드는데
나오는 답변의 퀄리티는 OMG였다.
이 외에도 도구를 적절히 사용하지 못하거나 하는 등의 자잘한 버그들도 있었지만 끝끝내 픽스하진 못했다.
결국 Agent와 Chain을 비교 평가 한 끝에
최종적으로 Chain을 사용하기로 결정했다.
그래도
꽤 도전적인 과정이었고,
Agent에 대해 잘 알게 된 계기였다.
참고로 이렇게 Agent를 한번 훑어보고나니
전세계적으로도 Agent 개발 소식이 귀에 엄청 잘 들리게 되었는데,
Agent 때문인지, 덕분인지는 모르겠지만
개발자의 채용컷이 훨씬 높아질 것이라는 뉴스를 보았다. (훌쩍)
이렇게 LLM 모델을 만들어보았고,
이어서
추천 시스템을 활용한 레포트 개인화 기능을 개발할 차례다!
다음 글에서..