단독 개발 두 번째 프로젝트 · 코딩테스트 과제 2025.08

PDF 가이드 챗봇

매뉴얼 멀티모달 RAG

PDF 매뉴얼에 자연어로 물으면, 설명과 함께 그 페이지의 도해를 같이 보여주고 참조 페이지까지 알려주는 검색 챗봇입니다.

PythonOpenAI GPT-4oLangChainLanceDB OllamaStreamlitPyMuPDF
이 프로젝트의 출발점

과제는 "PDF 읽고 챗봇"이었습니다.
만든 것은 그것이 아닙니다.

"도어 잠금 방법을 알려주세요"라는 질문에 "설정 메뉴에서 잠금 아이콘을 누르세요"라고 텍스트로만 답하면, 그 아이콘이 어느 것인지 모르는 사용자에게는 답이 되지 않습니다. 매뉴얼은 설명과 도해가 짝을 이루는 문서입니다. 텍스트만 검색하는 챗봇은 문서의 절반을 버리는 것이라고 판단해, 이미지를 함께 다루는 구조로 범위를 넓혔습니다.

아래 모든 결정은 이 판단에서 파생됩니다.

01

이미지를 따로 임베딩하지 않았다

대안

이미지를 CLIP 등으로 임베딩해 별도의 벡터 검색을 만드는 방법.

선택

이미지 파일명에서 페이지 번호를 파싱해 페이지별로 묶고, 그 정보를 텍스트 청크의 메타데이터에 병합.

근거

매뉴얼은 "설명 바로 옆에 그림"인 구조라 페이지가 곧 의미 단위입니다. 이미지 임베딩보다 비용과 지연이 낮고, 텍스트 검색만 정확하면 이미지는 자동으로 따라옵니다.

인지한 한계

"이 아이콘이 뭐야" 같은 이미지 자체가 질의인 경우는 이 방식으로 못 잡습니다. 그래서 03의 이미지 분석을 별도로 두었습니다.

02

최선이 없으면 차선, 단 사용자에게 알린다

개발 중 버전에서, 데이터 구축 단계가 여러 번이라 환경마다 테이블 상태가 달랐습니다. 테이블명을 하나로 고정하면 그것이 없을 때 전체가 죽습니다. 3단계로 내려가되 지금 어느 품질의 결과를 보고 있는지를 화면에 표시하도록 했습니다.

tesla_manual_with_images — 이미지 포함 (최선)
tesla_manual_with_images_text — 이미지 텍스트
tesla_manual_bge — 텍스트만, 이미지 제한적
없음 — 명시적 실패
03

이미지 분석을 자동에서 버튼으로

문제

검색된 이미지를 전부 LLaVA로 분석하면 답변까지 수십 초가 걸렸습니다.

선택과 근거

기본은 이미지만 표시하고, 필요할 때 버튼으로 분석을 실행하도록 바꿨습니다. 대부분의 질문은 이미지를 보기만 해도 해결되고, 분석이 필요한 것은 아이콘·경고 표시처럼 소수입니다. 다수의 응답 속도를 소수의 정밀도보다 앞에 두었습니다.

04

바꿀 수 있게 하되 기존 모델을 기본값으로

임베딩 모델을 BGE-M3(로컬)와 OpenAI 중에 고를 수 있게 하되, 기본값은 BGE-M3로 두었습니다. 임베딩 모델을 바꾸면 이미 저장된 벡터와 호환되지 않기 때문입니다. 기존 테이블이 BGE-M3로 만들어졌으므로 기본값을 바꾸면 검색이 조용히 망가집니다. 선택지는 열되 기본은 안전한 쪽으로 두었습니다.

부수적으로 로컬 임베딩이 기본이라 API 비용 없이 개발과 시연이 가능했습니다. 답변 생성 모델도 llava:13b부터 qwen2.5:0.5b까지 크기별로 고를 수 있게 해, 사양이 낮은 환경에서도 시스템 자체는 동작하도록 했습니다.

05

출처는 첫 단계의 설계다

답변에 참조 페이지가 표시되도록 프롬프트에 요구했습니다. 다만 "(있는 경우)"라는 단서를 붙여, 페이지 정보가 없을 때 지어내지 않도록 했습니다.

이 원칙은 문서를 자르는 단계에서부터 준비됩니다. 청크마다 page(페이지)와 title(섹션 제목)을 함께 실어 둡니다. 출처 표기는 마지막에 붙이는 기능이 아니라 파이프라인 첫 단계의 설계입니다.


목차 113개 → 섹션 제목

목차를 정규식으로 뽑아 모든 페이지에 "열기 및 닫기" 같은 섹션 제목을 붙였습니다.

청크 700 · 겹침 100

매뉴얼 한 항목이 끊기지 않을 정도의 크기로 잘랐습니다.

06

다시 만든다면

경로 하드코딩

다른 환경에서 그대로 실행되지 않습니다. 설정 파일로 분리했어야 합니다.

파일명 파싱 의존

이미지 파일명의 구분자에 의존해, 명명 규칙이 바뀌면 깨집니다.

검색 품질 미측정

눈으로만 확인했습니다. 정답 세트를 만들어 수치로 평가했어야 합니다.