AI 챗봇을 만드는 토이프로젝트가 시작되었다.
팀원분들을 잘 만난 것 같았다. 각자가 누가 시키지도 않았는데 알아서 다한다. 이번에도 나만 잘하면 된다. RAG의 첫 단계인 문서로드 단계를 한 2일 안에 끝날 줄 알았는데, 지금 5일째다.. 그 이유가 있긴 한데, 그것은 아래에서 정리하면서 적기로 하겠다.
AI 챗봇을 설계하면서 시스템에 포함될 요소들이 명확히 정의되지 않아 혼란이 있었다. 특히, 사용자 질문에 대해 문서 기반 답변을 제공하기 위해 어떤 컴포넌트가 필요한지, 각 요소들이 어떤 순서로 작동해야 하는지 흐름을 정립하는 것이 중요했다.
처음에는 단순히 문서를 불러와 임베딩하고 검색하는 정도로 생각했지만, 이 과정을 문서 전처리 → 청크 분할 → 임베딩 → 벡터 저장소 구축 → 검색 → 응답 생성으로 세분화해야 정확한 데이터 흐름을 구성할 수 있었다.
벡터 저장소로 사용되는 FAISS나 Chroma가 많이 쓰이지만, ElasticSearch를 써보기로 하였다.
Word2Vec과 같은 모델의 역할이 현재 설계에는 부적합하다는 판단도 내릴 수 있었다. 사전학습된 단어 임베딩이 아니라, 청크 단위로 처리되는 문맥 임베딩(OpenAI Embedding 등)이 더 적절했다.
PDF에서 텍스트를 추출해 처리하는 과정에서도 몇 가지 예상치 못한 문제가 발생했다. 특히, 도표가 섞여 있는 교재 PDF 파일에서는 텍스트 정합도가 떨어졌고, 추출된 텍스트의 구조적 일관성 부족이 주요 원인이었다.
처음에 pdfplumber, unstructured 등을 사용했지만 텍스트 손실이나 순서 왜곡이 심하게 나타났다. 이후 PyMuPDF 기반 로더로 전환하면서 텍스트 순서와 분리 기준이 안정되었고, 청크 분할의 정확도도 크게 향상되었다.
텍스트 분할 과정에서 CharacterTextSplitter를 단순히 사용하는 대신, 청크의 의미 단위를 유지할 수 있는 분할 방식(SemanticChunker) 적용 여부도 검토했다. 이는 이후 응답 정합도에 큰 영향을 미쳤다.
시스템 설정 과정에서는 .env 파일이 로드되지 않는 이슈가 발생했는데, 이는 단순한 모듈 누락이 아니라 환경 구성이 꼬인 상태였다. python-dotenv는 설치되어 있었지만, 실제 코드 실행 환경과 모듈 설치 위치가 불일치했다. 이는 Jupyter Notebook 환경에서 자주 발생하는 문제 중 하나였다.
해당 문제는 sys.executable을 통해 현재 파이썬 인터프리터 경로를 확인하고, !pip show로 설치 위치를 확인하면서 가상환경이 정상적으로 활성화되어 있지 않다는 점을 발견해 해결할 수 있었다.
또한 load_dotenv()의 반환값이 False였던 원인은 경로 문제였다. 상대 경로가 잘못 지정되어 있었고, 절대 경로로 수정하거나 os.path.exists()로 경로 유효성을 확인하는 방식으로 안정성을 확보했다.
문서 임베딩 과정에서는 동일한 청크가 반복 임베딩되며 API 호출 비용이 증가하는 문제가 발생했다. 이를 방지하기 위해 캐시 전략을 도입할 필요가 있었다.
처음에는 단순히 벡터 DB에만 임베딩 결과를 저장하는 구조였지만, 실행 시마다 동일한 청크에 대해 반복 호출이 발생하면서 불필요한 리소스 낭비가 발생했다.
이를 해결하기 위해 CacheBackedEmbeddings 전략을 검토했고, 기존 임베딩 결과를 빠르게 탐색 가능한 캐시 메커니즘을 앞단에 삽입함으로써 중복 호출을 제거할 수 있었다.
이 캐시는 메모리 기반 또는 로컬 파일(JSON/SQLite 등) 기반으로 구현될 수 있으며, 실험 환경에서는 로컬 캐시 파일을 통해 정확한 벡터 재사용이 가능하도록 설계했다.
문서 내 특정 개념이 정의된 페이지를 추적하고, 이를 기반으로 챗봇이 “이 개념은 ○○페이지에 정의되어 있어요”라고 안내하도록 만들고자 했을 때도 고민이 있었다.
페이지 정보나 섹션 번호 같은 메타데이터를 각 청크에 하드코딩 형태로 부여했지만, 이 정보가 LLM 응답 시 실질적으로 활용될 수 있는지 확신이 없었다.
실험 결과, Document 객체의 metadata 필드는 LangChain QA 체인에서 적극적으로 활용 가능했고, 답변의 출처 제공이나 색인 기능에 유용하게 활용되었다.
따라서 이후 설계에서는 "page": 123, "section": "4.3.1" 등의 필드를 자동화된 방식으로 주입해, LLM이 응답 내에 출처 정보를 포함하도록 유도하는 방향으로 발전시킬 수 있었다.
기초부터 꼼꼼하게 구조 설계하시는 모습이 너무 멋지십니다!!
완전 자극 받고 가요~ 프로젝트 끝까지 화이팅입니다