RAG 구조 심층 조사
TMT**Retrieval-Augmented Generation (RAG)**은 지식 기반 정보를 LLM에 제공하여 답변 정확도를 높이는 기법입니다. 고객지원 챗봇에 RAG를 적용하면 최신 지식과 문서를 활용해 사실적이고 신뢰도 높은 응답을 생성할 수 있습니다. RAG 파이프라인은 일반적으로 (1) 사용자의 질의를 임베딩하여 벡터 DB에서 유사한 문서를 검색하고 (2) 검색된 문서를 LLM에 컨텍스트로 주입한 뒤 답변 생성 단계를 거칩니다. 아래 그림은 이러한 RAG 시스템의 개요를 보여줍니다.
RAG 파이프라인 아키텍처: 사용자의 질의가 임베딩되어 벡터 공간에서 유사한 문서를 검색한 후, Claude 3.7 Sonnet과 같은 LLM이 검색된 문서를 컨텍스트로 받아 답변을 생성한다. 이 과정을 통해 LLM이 자체 지식 한계를 넘어 최신 정보에 접근하여 높은 정확도의 응답을 제공할 수 있다.
이번 섹션들에서는 정확도를 우선시하는 Claude 3.7 Sonnet 기반 RAG 시스템을 구축하기 위해 고려해야 할 세부 주제들을 다룹니다. 벡터 데이터베이스 선택부터 다양한 포맷의 데이터 처리, 임베딩 검색 전략, 검색 결과 정렬, LLM 프롬프트 구성, 그리고 HyDE나 Multi-hop RAG와 같은 변형 기법까지 차례로 살펴보겠습니다. 각 섹션에는 개발자가 직접 구현 가능한 구체적 예시, 비교표 및 프롬프트 샘플 등을 포함하여 설명합니다.
1. 정확도와 응답 속도의 균형: 주요 벡터 DB 비교
정확한 검색 결과를 제공하면서도 실시간 응답이 가능한 벡터 데이터베이스를 선택하는 것은 RAG 시스템의 성능에 핵심적입니다. 아래 표는 대표적인 벡터 DB(Pinecone, Weaviate, Qdrant, Milvus 등)의 특징을 요약 비교한 것입니다:
벡터 DB | 특징 요약 | 장점 | 단점 | 적합한 사용 사례 |
---|---|---|---|---|
Pinecone (클라우드 관리형) | 완전 관리형 서비스, 대규모 스케일 지원 | 고성능 ANN 인덱싱, 초당 수억개 벡터 검색 지원; 하이브리드 검색 지원 (벡터+키워드); 인프라 관리 불필요 (서버리스) | 비용 높음 (프리미엄 가격); 커스터마이징 제한 (클라우드 전용); 온프레미스 불가 (보안 제한) | 엔터프라이즈 대용량 서비스에 적합: 인프라 관리 리소스가 부족한 팀, 엄격한 SLA 필요한 고객-facing AI 앱 |
Weaviate (오픈소스/관리형) | GraphQL 기반 벡터 DB, 지식 그래프 통합 | 스키마 기반 지식 그래프로 개체 관계 표현; 다중 모달 지원 (텍스트, 이미지 임베딩 등); 상용 클라우드 및 오픈소스 모두 제공 | 학습 곡선 높음 (GraphQL, 스키마 설계 필요); 성능 튜닝 필요 (대규모시 리소스 소모) | 관계형 지식베이스에 적합: 데이터 간 복잡한 연관성을 유지해야 하는 KB, GraphQL로 유연한 질의가 필요한 경우 |
Qdrant (오픈소스 Rust) | 고성능 + 메타데이터 필터링 강점 | Rust로 구현되어 초고속 벡터 검색; 다양한 메타데이터 필터 기능 (조건부 검색 탁월); 분산 확장 및 ACID 트랜잭션 지원 (운영 신뢰성) | 생태계 규모 작음 (최근 등장); 세부 튜닝 필요 (최적 성능 위해 파라미터 조정) | 실시간 검색 엔진에 적합: 필터링 조건이 중요한 추천 시스템, 전자상거래 검색 등; 자체 서버에 배포하여 고성능/고신뢰 운영하려는 경우 |
Milvus (오픈소스 분산형) | 대용량 분산 벡터 DB (클라우드-네이티브) | 수십억 벡터 수용하는 확장성 (샤딩, 분산); 여러 인덱스 알고리즘 제공 (HNSW, IVF 등); 스냅샷, 백업 등 엔터프라이즈 기능 내장 | 운영 복잡도 높음 (쿠버네티스 등 분산 인프라 필요); 리소스 많이 사용 (대규모시 비용 상승) | 초대형 데이터셋에 적합: 대기업 수준 미션크리티컬 AI 서비스, 클라우드-네이티브 인프라에 통합하는 경우 (예: 대규모 문서 검색 플랫폼) |
표 1: 주요 벡터 데이터베이스의 특성 비교 및 권장 사용 사례. (★ 참고: Chroma나 FAISS와 같은 기타 벡터 스토어도 존재하나, 전자는 소규모 프로토타입에, 후자는 라이브러리 형태로 특수 고성능 용도에 적합합니다.)
위 비교에서 볼 수 있듯이, 정확도와 속도의 균형은 벡터 DB의 인덱싱 방식과 설정에 따라 크게 좌우됩니다. 예를 들어 대부분의 벡터 DB는 근사 근접 검색(ANN)을 사용하는데, 인덱스 파라미터(예: HNSW의 ef
나 M
)를 높이면 **검색 정확도(재현율)**는 상승하나 지연 시간이 늘어납니다. 정확도를 최우선한다면 각 DB에서 정밀 모드나 ef
파라미터를 높여 높은 recall로 검색하도록 튜닝할 수 있습니다. 다만 응답 시간을 위해 보통 상위 Top-K 결과만 사용하므로, 대용량 DB일수록 Pinecone이나 Milvus처럼 분산 및 GPU 가속을 지원하는 솔루션을 쓰는 것이 유리합니다. 엔터프라이즈 환경에서는 Pinecone처럼 관리형으로 일관된 성능을 내는 서비스를 고려하고, 자체 인프라를 선호하면 Qdrant이나 Milvus처럼 오픈소스를 활용해 세밀하게 튜닝하는 전략도 가능합니다.
✔️ 개발 Tip: 소규모 프로토타입 단계에서는 Chroma와 같이 설치가 간편한 경량 DB로 시작하고, 제품화 단계에서 데이터 양과 요구 성능에 맞춰 위 비교된 DB들로 교체 및 마이그레이션하는 접근도 실용적입니다. 벡터 DB별 Python 클라이언트들이 유사한 인터페이스를 제공하는 경우가 많아 교체 비용이 크지 않습니다.
2. 다양한 데이터 포맷의 정보 추출 및 벡터DB 저장 스키마
기업 내부에는 PDF 매뉴얼, PPT 발표자료, Excel 표, 이미지가 포함된 문서, Jira 기반 위키, Slack 대화 내용 등 형식이 서로 다른 지식 자산이 존재합니다. RAG 시스템의 정확도를 높이려면 이러한 다양한 포맷의 데이터를 효과적으로 텍스트 임베딩에 활용해야 합니다. 이를 위해 각 포맷별 최적의 정보 추출 방법과, 추출 결과를 벡터 DB에 저장하는 스키마 설계 전략을 살펴봅니다.
1) 데이터 추출 방법 (ETL 파이프라인):
-
PDF 문서: 텍스트 기반 PDF의 경우 PyMuPDF, pdfplumber, Adobe PDF Extract API 등으로 본문 텍스트와 구조(제목, 문단 등)를 추출합니다. 단순 텍스트 추출로는 표나 이미지 캡션 정보가 누락될 수 있으므로, 가능하다면 계층적 구조(예: PDF 아웃라인, 헤더)로 분할해 저장합니다. 스캔된 PDF나 이미지 포함 PDF의 경우, 우선 OCR(예: Tesseract)로 텍스트를 추출하고, 차트나 복잡한 레이아웃은 필요시 Vision 모델을 활용해 설명문을 얻습니다. 예컨대, 한 사용자는 PDF의 각 페이지를 이미지로 변환하여 멀티모달 LLM에 입력해 페이지를 통째로 텍스트로 기술하게 하는 방법(Hybrid OCR+LLM)을 사용하였는데, 일반적인 텍스트 파서가 레이아웃을 잘못 분할하는 문제를 보완해 표나 이미지 설명까지 포함한 정확한 텍스트를 얻을 수 있었다고 합니다. 이러한 접근은 비용이 높으므로, 일반 문서는 파서로 추출하고 복잡한 페이지만 LLM-OCR을 적용하는 하이브리드 전략이 권장됩니다.
-
PPT 프레젠테이션: PPT(X)는
python-pptx
등의 라이브러리나 Unstructured와 같은 도구로 슬라이드의 텍스트(제목, 항목)와 도형의 텍스트를 추출합니다. 슬라이드마다 시각적 레이아웃이 다르므로, 종종 PPT를 PDF로 변환한 뒤 앞의 PDF 방식을 적용하기도 합니다. 슬라이드 내 이미지나 차트는 파일로 추출해 OCR/캡션 생성하고, 원본 텍스트와 함께 슬라이드 순서대로 배열해 문맥을 보존합니다. 각 슬라이드별로 “슬라이드 제목 - 본문 내용 - (이미지 설명)” 형태로 하나의 문서로 간주해 처리하면 좋습니다. -
Excel 및 표 데이터: 셀 단위의 구조화 데이터는 직접 임베딩에 사용하기보다는 텍스트로 변환하거나 요약해서 사용하는 것이 효과적입니다. 예를 들어 Excel의 경우 Pandas 등을 이용해 CSV/JSON으로 변환한 후, 각 행을 읽기 쉬운 문장으로 서술하는 스크립트를 사용합니다 (예: "제품ID 123은 2023년 판매량 1,000대를 기록..."). 이렇게 하면 숫자와 문자열이 섞인 표도 검색 질의에 대응하는 의미 단위 텍스트로서 벡터화될 수 있습니다. 혹은 LangChain의 table QA 기능처럼 표를 직접 핸들링하는 방법도 있으나, 일반적인 벡터 검색에서는 텍스트로의 변환이 호환성이 높습니다. 표가 매우 크거나 복잡한 경우, 테이블 요약 임베딩 기법도 고려됩니다 – 테이블 내용을 자연어로 요약한 임베딩을 검색에 쓰고, 최종 답변 생성시에는 원본 테이블 데이터를 함께 제공하여 LLM이 세부 정보를 잃지 않도록 하는 방식입니다.
-
이미지가 포함된 문서: Word, HTML, Markdown 등 본문에 이미지가 포함된 문서는 Unstructured와 같은 도구를 활용하면 텍스트와 이미지 블록을 분리해 추출할 수 있습니다. Unstructured의 예시를 보면, PDF를 처리할 때 이미지 블록을 제거하고 레이아웃 모델(예: YOLOX)을 사용해 표/제목 등을 감지한 후 텍스트 블록으로 분할한다고 합니다. 추출된 이미지에 대해서는 CLIP 등 멀티모달 임베딩으로 이미지를 벡터화해 별도로 저장하거나, 이미지 캡션 생성 모델을 통해 텍스트 설명을 얻어 해당 설명을 텍스트로 임베딩하는 두 가지 접근이 있습니다. 이미지 자체를 검색해야 하는 시나리오(예: 제품 사진 찾기)가 아니라면, 후者인 이미지 내용 요약텍스트를 만들어 텍스트와 함께 저장하는 것이 통합 검색에 유리합니다. 또한 문서 내 이미지 캡션이나 주변 텍스트를 함께 저장하면 이미지 맥락 이해에 도움이 됩니다. 차트나 그래프처럼 시각화된 데이터는 가능하면 생성 시 사용된 원본 수치를 함께 수집해 두거나, Vision-LLM을 활용해 "그래프 요약 설명" 텍스트를 얻어 저장합니다. (금융 보고서 등에서는 차트에만 있는 정보가 중요할 수 있으므로, 해당 그래프의 의미를 별도 텍스트로 담아두어야 검색으로 표출됩니다.)
-
Jira 기반 내부 위키: Atlassian Confluence와 연계된 경우 API를 통해 페이지별 HTML이나 위키 마크업을 가져올 수 있습니다. 페이지 제목, 본문 텍스트, 표, 첨부 이미지의 텍스트 등을 추출합니다. 위키 문서는 보통 계층구조(스페이스 – 페이지 – 하위페이지)가 있으므로, 경로/폴더명을 메타데이터로 저장해 둡니다. 또한 각 페이지를 헤딩 단위로 나눠 섹션별로 chunking하는 것이 좋습니다 (예: 페이지 내 H2/H3 제목별로 분리). 이렇게 하면 검색 시 특정 섹션이 정확히 매칭되어 결과로 나오며, 응답 생성시에도 해당 섹션 주제만 집중할 수 있습니다. Jira 이슈 티켓 데이터도 유사하게 요약 가능하며, 이슈 키(ID), 상태, 설명, 댓글 등을 필드별로 조합해 하나의 텍스트로 만들고 저장합니다.
-
Slack 대화 내용: Slack API를 사용해 채널별 메시지를 수집한 후 벡터화할 수 있습니다. 중요한 것은 대화 특성을 살려 쓰레드 맥락을 보존하는 일입니다. 방법은 (a) 각 메시지를 개별 문서로 처리하되 쓰레드 ID를 메타데이터에 넣는 방식, (b) 쓰레드의 모든 발언을 모아 하나의 문서로 합치는 방식이 있습니다. 질의응답용 지식으로는 (b)가 맥락 이해에 유리하지만, 너무 긴 쓰레드는 잘게 나누어야 합니다. Slack 메시지에는 사용자명, 타임스탬프, 이모지 리액션 등도 있으나 검색 맥락에는 크게 필요 없으므로 본문 텍스트 위주로 저장합니다. 다만, 예를 들어 "2023/11/01에 John이 어떤 메시지를 남겼지?" 같은 질의를 대비하려면 날짜, 작성자 등을 메타데이터로 보존하고, 경우에 따라 필터 검색이나 re-rank에 활용해야 합니다. Slack 대화 로그를 RAG에 활용할 때 주의할 점은 권한 분리입니다 – 사내 기밀 채널의 내용까지 모델이 답변하지 않도록, 벡터 DB에 namespace를 사용자나 부서별로 분리하거나, 검색 시 메타데이터 필터로 사용자 권한에 해당하는 문서만 검색하는 구현이 필요합니다. (예: 사용자 A의 요청엔 A가 속한 채널의 대화만 검색) 이러한 권한 필터링을 위해 Slack의 채널 멤버십 정보를 ACL로 유지하거나, 검색 시 Slack API를 호출해 최종 검증하는 방법도 있습니다.
2) 벡터 DB 스키마 설계: 다양한 출처의 문서를 벡터로 저장할 때는 일관된 스키마로 메타데이터를 관리하는 것이 좋습니다. 일반적으로 각 **문서 조각(chunk)**을 다음과 같은 구조로 저장합니다:
-
id
: 문서 식별자 (고유 키) -
text
: 임베딩 대상이 되는 순수 텍스트 내용 (필드) -
embedding
: 텍스트의 벡터 표현 (별도 필드이지만, 대부분 벡터 DB에서 내부 관리) -
metadata
: JSON 형태로 부가정보 저장:source
: 출처 타입 (예:"pdf"
,"ppt"
,"slack"
,"wiki"
,"excel"
등)source_id
또는source_name
: 원본 파일명, 페이지 URL, 대화방 ID 등 (예: 파일 경로나 문서 ID)section_title
: (있다면) 섹션/슬라이드 제목, 위키 페이지 제목 등page_num
또는line_num
: 원본에서의 페이지 번호나 행 번호 (PDF 페이지, PPT 슬라이드 번호 등)- 그 외 필요한 필드:
author
(슬랙 메시지 작성자),timestamp
(메시지 시간) 등 도메인별 속성
예를 들어, PDF 문서라면 source="pdf"
와 page_num
메타데이터를 저장해 검색 결과를 원본 페이지와 연결할 수 있습니다. Slack 메시지는 source="slack"
와 channel
/thread_id
를 함께 저장해 쓰레드 단위 맥락을 사용할 수 있습니다. 이러한 메타데이터는 검색 후 후처리나 필터링에 활용할 수 있으므로 최대한 식별 정보를 담아두는 것이 좋습니다. 실제로 한 RAG 구현 사례에서는 “텍스트 청크를 벡터 DB에 저장하면서, 메타데이터로 출처 문서명과 페이지 번호 등을 함께 기록”했다고 합니다. 우리 역시 각 청크가 어느 문서의 몇번째 부분인지 추적 가능하도록 해야, 사용자가 답변의 근거를 요구할 때 해당 정보를 제공할 수 있습니다.
또한 다중 모달 데이터의 경우 스키마를 유연하게 설계합니다. 예를 들어 이미지 벡터와 텍스트 벡터를 같은 컬렉션에 넣되 source
나 modality
필드로 구분할 수 있습니다. 혹은 아예 별도 컬렉션으로 관리하고, 검색 단계에서 멀티 인덱스 질의를 통해 둘 다 조회할 수도 있습니다. 단, Claude 3.7 Sonnet 자체가 이미지 입력도 지원하므로, 가능하다면 이미지도 컨텍스트로 넣을 수 있게 이미지 경로/URL을 보존하거나 파일 ID를 메타데이터로 남겨둡니다.
✔️ 개발 Tip: JSON 등의 벡터 DB 업로드 스크립트를 작성해 두면 형식별 ETL을 유연하게 처리할 수 있습니다. 예컨대 {"id":..., "text":..., "metadata":{...}}
형식의 Python 딕셔너리를 각 문서 조각마다 만들고, 벡터 임베딩 모델을 통해 embedding
을 추가한 뒤, DB에 업서트(upsert)하는 식입니다. 또한 향후 문서 업데이트/삭제를 고려해 source_id
와 같은 필드로 문서 단위 필터링 삭제가 가능하도록 해두세요 (예: 특정 문서 개정판이 올라오면 해당 source_id
로 검색해 기존 벡터들을 일괄 삭제).
3. 사용자 질의 기반 벡터DB 검색 전략
사용자 질문을 벡터 DB에서 정확히 일치하는 문맥으로 찾기 위해서는 임베딩 모델 선정부터 검색 기법까지 신경써야 할 요소가 많습니다. 임베딩 품질이 곧 검색 정확도로 직결되므로, 도메인과 언어에 맞는 모델을 고르고, 청크 분할 방식, 하이브리드 검색, 필터링을 적절히 활용하는 전략이 중요합니다.
1) 임베딩 모델 선택: 임베딩 모델은 질의와 문서 조각 사이의 의미적 유사도를 잘 포착해야 합니다. 한국어 환경의 챗봇이라면 다국어 임베딩 모델이나 한국어에 특화된 모델을 선택하는 것이 좋습니다. 예를 들어 SBERT 계열의 sentence-transformers/xlm-r-...
모델이나, BGE 다국어 대형 모델, 혹은 OpenAI의 text-embedding-ada-002
(1536차원)처럼 한국어를 지원하는 상용 모델을 고려할 수 있습니다. 중요한 것은 코퍼스의 언어와 도메인입니다. 법률 문서같이 특수한 어휘가 많다면 도메인 특화 임베딩 모델로 파인튜닝하거나, 최소한 해당 도메인에서 좋은 성능을 보이는 모델을 써야 합니다. 예컨대 코드 스니펫이 섞인 문서를 검색해야 한다면 코드 임베딩 모델(OpenAI code embedding 등)과 일반 텍스트 모델을 병행하는 것도 방법입니다.
한편, 크로스 인코더 reranker 모델(BGE reranker 등)은 검색 후 순위 조정 단계에 사용되므로, 여기서는 우선 bi-encoder 형태의 임베딩 모델을 전제로 합니다. Claude 3.7 자체의 임베딩 기능이 있다면 활용할 수도 있지만, 일반적으로는 전용 임베딩 모델을 사용해 벡터 DB에 색인합니다. 또한 모델이 처리할 최대 토큰 길이도 고려하세요. BERT 기반 모델은 보통 512 토큰 제한이 있으므로 chunk 크기를 조절해야 하며, 1k 이상 문장을 임베딩하려면 Longer-form 모델(예: InstructorXL, OpenAI Ada) 등이 필요합니다.
2) 청크 분할 (Chunking) 및 슬라이딩 윈도우: 문서를 얼마나 큰 조각으로 쪼갤지에 따라 검색 결과의 정밀도와 재현율이 달라집니다. 일반적인 권장 범위는 128–512 tokens 정도이며, 질문의 성격에 따라 작게 또는 크게 조정합니다.
- 짧은 청크(예: 128–256토큰): 키워드 매칭이 중요한 팩트 질의에 유리합니다. 세밀하게 쪼개두면 특정 키워드가 포함된 작은 조각이 바로 검색되어, 불필요한 맥락 없이 정확한 부분을 제공할 수 있습니다. 예를 들어 고객 에러 로그처럼 국소적인 텍스트에 대한 질문에는 작은 청크가 적합합니다.
- 긴 청크(예: 256–512토큰): 문맥이 중요한 설명형 질문에 유리합니다. 다소 길게 묶어야 한 조각 안에 충분한 배경설명이 들어가서, 검색시 포괄적인 내용이 매칭될 수 있습니다. 예를 들어 기술 문서의 한 섹션 전체를 포함하면 요약/개념 질문에 답하는 데 도움이 됩니다.
청크 분할 시 문단/논리단위를 고려하는 것이 좋습니다. 문장을 무작정 글자수로 자르면 맥락이 끊길 위험이 있으므로, 가능하면 헤더나 구두점 등을 기준으로 자르고, 그래도 길면 슬라이딩 윈도우로 겹쳐 자릅니다. 일반적으로 청크간 10–20% 정도 중복(overlap)을 주어 문단이 잘리는 것을 보완합니다. 예를 들어 300토큰씩 자를 때 50토큰 overlap을 두면 앞 청크 끝과 다음 청크 시작 부분이 겹치므로, 중요한 연결 문장이 누락되지 않습니다. Overlap이 너무 크면 중복 저장으로 비효율적이니 수십 토큰 단위면 충분합니다.
예시: 800자 분량의 지침서를 200자 청크로 자르면 1–200, 151–350, 301–500... 식으로 겹쳐집니다. 이렇게 하면 300자 부근에 걸친 한 문단의 내용이 청크1에도 일부, 청크2에도 일부 포함되어 질문과 더 잘 매칭될 수 있습니다. Overlap을 주지 않으면 문단이 절반으로 끊겨 어느 쪽도 질문과 완전히 매치되지 않을 수 있습니다.
3) Hybrid 검색 (BM25 + 임베딩 결합): 하이브리드 검색은 **전통적인 키워드 검색(BM25)**과 벡터 유사도 검색을 결합해 정확도를 높이는 전략입니다. 임베딩은 강력한 의미 매칭을 해주지만, 가끔은 희귀 키워드나 숫자, 고유명사 등의 매칭에 약할 수 있습니다. 예를 들어 오류 코드 "ERR123"이 질의에 있고 문서에도 정확히 그 코드가 있으면, BM25는 이를 강하게 올려줄 수 있지만 임베딩은 이를 일반 어휘로 취급해 비슷하게 보지 않을 수도 있습니다. 이러한 경우 BM25 점수와 벡터 유사도 점수를 합산하거나 별도로 결과를 구한 후 병합하는 방법이 사용됩니다. 일부 벡터 DB는 내부적으로 hybrid 지원이 됩니다 (예: Weaviate는 쿼리에서 hybrid(k=...)
옵션을 제공하고, Pinecone도 metadata 필터 없이 sparse+dense 조합이 가능한 것으로 알려져 있습니다). 직접 구현한다면, Elasticsearch와 같은 엔진의 BM25 결과와 벡터 DB의 임베딩 결과를 가져와 랭킹 점수로 재정렬하거나, 교집합/합집합을 취해 상위 K를 고르는 방법이 있습니다.
하이브리드 검색의 한 가지 예로, dense 임베딩으로 100개를 미리 가져온 뒤 임베딩과 TF-IDF 점수의 가중 합으로 상위 10개를 최종 선택하는 방안이 있습니다. 또는 Dense 1단계 + Sparse 2단계 rerank도 생각해볼 수 있습니다 (반대로). 중요한 점은, 하이브리드를 쓰면 **정확도(precision)**가 올라갈 수 있으나 구현 복잡성이 증가하므로, 우선 임베딩 검색으로 만족할만한지 평가한 후 특정 케이스에서 누락되는 결과가 있다면 도입을 검토합니다. 특히 한국어의 조사/어미 등은 임베딩이 잘 처리하지만, 영문 약어, 코드, ID 등은 BM25가 유리할 수 있습니다.
4) 메타데이터 기반 필터링: 사용자의 쿼리에 따라 검색 범위를 미리 좁히는 필터를 적용하면 정확도가 올라갑니다. 예를 들어 “지난주 Slack 회의록에서 A 프로젝트 예산을 알아줘”라는 질문이라면, 벡터 검색 전에 source:"slack"
및 날짜 범위 필터를 거는 식입니다. 거의 모든 주요 벡터 DB가 메타데이터 조건으로 필터링을 지원합니다. Qdrant의 경우 boolean 조건들을 조합한 payload 필터 기능이 강력하고, Weaviate는 GraphQL 질의 내에 필터를 지정할 수 있으며, Pinecone도 QueryRequest에 filter
JSON을 넣어두면 server-side에서 선별합니다.
필터링은 정확도를 높이는 대신 재현율을 떨어뜨리므로, 남용하면 안 됩니다. 중요한 것은 쿼리 의도에 따라 동적으로 적용하는 것입니다. 이를 구현하려면 사용자의 질문을 분석해 필터 조건을 추출하는 로직이 필요합니다. 간단한 룰로 "질문에 'Slack' 단어가 있으면 Slack 문서로 제한"같은 것을 할 수도 있고, Advanced하게는 분류 모델/LLM을 써서 "이 질문은 내부위키 관련인지, 지원티켓 관련인지" 등을 판단해 대응 메타태그를 설정할 수 있습니다. Claude 3.7 같은 모델을 프롬프트로 "Query category"를 분류시켜 루팅하는 것도 생각해볼 수 있죠. 예컨대 system
메시지로 "사용자 질문이 언급하는 데이터 소스 유형을 Slack/Confluence/Etc 중 하나로 답하라" 식으로 물어 필터를 결정하는 자동화도 가능합니다.
또한 다중 출처를 한꺼번에 검색하면서 특정 출처를 우선순위 높이게 할 수도 있습니다. 예를 들어 최신 정보일수록 메타데이터에 date
가 크다면 score를 가중하거나, 신뢰도가 높은 문서에는 boost
필드를 둬서 rank에 영향을 주게 할 수 있습니다. 이러한 세밀한 조정은 Vector DB 단계보다는 다음 섹션의 reranking 단계 또는 LLM 단계에서 이뤄지지만, 필요하면 검색 쿼리단에서도 지원하는 DB가 있습니다.
✔️ 개발 Tip: 벡터 검색 호출시 top_k
를 넉넉히 잡아 초과 검색해두는 것이 좋습니다. 임베딩 검색 recall을 높이기 위해 상위 예: 20~50개 결과까지 받아 두고, 이후 단계(재정렬 및 최종 선택)에서 축소하는 것입니다. 이렇게 하면 혹시 4~5번째에 중요한 문서가 있었는데 top3까지만 잘라버려 놓치는 일을 줄일 수 있습니다. Claude 3.7 Sonnet이 200k 토큰까지 입력을 수용하므로 한번에 많은 문맥을 넣을 수 있지만, 너무 많은 문서를 그대로 넣으면 오히려 모델이 혼란을 느껴 답변 품질이 떨어질 수 있습니다. 다음 단계에서 설명할 reranker를 사용한다면 top_k=50까지 받아두고 rerank 후 5개만 투입…这样的 전략을 사용하게 됩니다.
4. 검색 결과 우선순위 정렬 (Ranking) 방법
벡터 DB에서 얻은 검색 결과들을 그대로 LLM에 넣기 전에, 질문에 가장 도움이 될 문맥을 선별하고 정렬하는 reranking 단계를 거치면 답변 정확도를 한층 높일 수 있습니다. 기본적으로 벡터 DB는 코사인 유사도나 내적 점수 순으로 결과를 반환하지만, 이 순위가 진짜 관련도와 완벽히 일치하지는 않을 수 있습니다. 임베딩 자체의 한계로 인해 실제 정답이 들어있는 문서가 top_k 밖으로 밀려나는 경우를 보완하려면, 2단계 랭킹 기법을 사용해야 합니다.
1) 코사인 유사도 기반 정렬: 가장 단순한 방법은 벡터 유사도 점수(코사인, dot 등) 순으로 상위 n개를 선택하는 것입니다. 예컨대 Qdrant나 Milvus에서 거리(Distance)가 낮은 순으로 top_k=5를 골라 그대로 사용합니다. 이 방법은 빠르지만, 임베딩 모델이 완벽하지 않은 경우 군더더기 문맥이 끼어들 수 있습니다. 특히 임베딩이 문서 전반 주제는 비슷하지만 세부 답이 없는 부분을 가져올 때, 진짜 답이 들어있는 약간 점수 낮은 chunk보다 상위에 올 수도 있습니다. 그럼에도 경량 시스템에서는 종종 이 방식만으로도 충분하며, 구현이 간단하다는 장점이 있습니다.
2) Cross-Encoder 기반 재랭킹 (reranker 모델): 리랭커는 질문+문서 조각을 한 쌍으로 넣어 해당 문서가 답변에 적합한지를 판단하는 모델입니다. BERT나 RoBERTa 기반의 Cross-Encoder 구조로, 입력으로 “[질문] [문서]”를 함께 넣어 relevance score를 출력합니다. 대표적인 공개 모델로 BAAI의 BGE Large reranker 등이 있으며, MS MARCO 데이터로 학습된 monoT5 계열이나 CofeVer 등이 존재합니다. Pinecone의 보고에 따르면, bi-encoder 임베딩 모델은 벡터 한 개에 문서 의미를 압축하기 때문에 정보가 손실되지만, reranker는 원문과 질의를 모두 보고 전체 Transformer 연산을 하므로 더 정확하다고 합니다. 특히 쿼리에 특화된 문맥 판단이 가능해 “문서에 언급은 돼있지만 질문에 답이 없는 경우”도 걸러낼 수 있습니다.
두단계 retrieval 구조에서는 보통 1단계로 빠른 벡터 검색 (수천~수백만 문서 -> 상위 50) 을 하고, 2단계로 reranker가 50개를 각각 점수 매겨 상위 5개를 선정하는 식으로 동작합니다. Reranker는 50개 문서 * 50개 BERT 추론 = 2500회 어텐션 계산에 해당하므로 비용이 크지만, 50개 중 5개만 남기고 LLM에 넘기면 LLM 입력 길이를 줄여 결과적으로 전체 성능을 높일 수 있습니다. 이러한 재정렬 기법은 오래전부터 검색 엔진에서 사용되어 왔고, RAG에서도 매우 효과적이어서 “embedding 모델만 쓸 때보다 훨씬 정확한 결과”를 얻을 수 있습니다.
실사용 팁: BGE reranker 같은 모델을 HuggingFace에서 불러와 곧바로 쓸 수 있습니다. 쿼리와 문서를
[CLS] query [SEP] document [SEP]
형태로 넣으면 시밀러리티 점수를 출력하며, 이 점수로 정렬합니다. 점수 계산 후 상위 k개를 선택해 Claude에게 넘기면 됩니다. 다만 cross-encoder는 GPU 자원이 없다면 지연이 커질 수 있으므로, 서비스 초기에 필요성을 판단해 도입하거나, 질문 유형에 따라 on/off하는 것도 고려합니다.
3) LLM 기반 reranking: 크로스 인코더 대신, Claude 3.7 자체를 reranker로 활용하는 방법도 있습니다. 예를 들어 Claude에게 "다음 문서들이 질문에 얼마나 도움이 되는지 1~5 점으로 평가하라"
라고 프롬프트하여 top10 문서를 입력하면, 각 문서의 관련도를 평가하게 할 수 있습니다. LLM은 맥락 이해력이 뛰어나 미세한 차이도 구분해줄 수 있지만, 비용상 효율이 떨어지고 프롬프트 설계에 따라 일관성이 좌우될 수 있습니다. 또한 많은 문서를 한 번에 평가하면 컨텍스트 길이 문제가 생기니, 한 문서씩 query와 함께 넣어 여러 번 호출해야 할 수도 있습니다. 따라서 전용 reranker 모델이 있는 경우 굳이 LLM을 재랭커로 쓰지는 않지만, 소규모 데이터에서는 시도해볼 수 있습니다.
4) 메타데이터 활용 및 기타 정렬: 때로는 관련도 외에 우선순위를 결정짓는 비즈니스 규칙을 적용하기도 합니다. 예컨대 FAQ봇이라면 공식 문서 > 비공식 발언 순으로 답변 신뢰도를 통제하고 싶을 수 있습니다. 이럴 때 각 문서에 신뢰 점수를 메타데이터로 주고, rerank시 이를 가산점으로 반영합니다. 구현 방법으로는 임베딩 점수를 조정할 때 메타데이터 값에 비례해 올리는 식으로 커스텀 rank 함수를 작성하거나, 1단계 검색 시 필터로 아예 제외하기도 합니다. 또한 여러 개의 문서를 넣을 공간이 충분해도, Claude가 앞부분 문서에 집중하는 경향이 있을 수 있으므로, 가장 관련 높은 문서를 맨 앞에 두는 편이 좋습니다. (일반적으로 컨텍스트 순서에 따라 답변 내용이 좌우될 수 있음)
5) 결과 다각화: 마지막으로, 가져갈 문맥들을 다양한 소스에서 고르게 선택하는 것도 고려됩니다. 만약 상위 5개가 모두 같은 문서의 연속된 청크라면, 사실 한두 청크로 충분할 내용을 중복 제공하는 셈일 수 있습니다. Re-rank 단계에서 상위권이 한 문서에 치우치면, 다음 문서로 한두 개 교체하는 방법도 있습니다. 이를 위해 일부 시스템은 문서별 상위 1개씩 뽑아서 모은 뒤 점수를 비교하는 등 문서당 1개 채택 전략을 쓰기도 합니다. 궁극적으로는 답변에 필요한 핵심 정보가 되는 문맥들을 골라야 하므로, 필요시 개발자가 룰 기반으로 결과를 튜닝하는 것도 가능합니다.
✔️ 개발 Tip: Retrieval Recall vs LLM Recall을 염두에 두세요. 벡터 DB에서 top50까지 받아 re-rank하면 retrieval 단계에서는 많은 관련 문서를 커버하지만, LLM에 최종 5개만 주므로 LLM이 놓칠 위험을 줄입니다. Claude 3.7처럼 컨텍스트가 크더라도 20개 문서를 다 주입하면 모델이 집중하지 못할 수 있습니다. 따라서 “많이 검색, 적게 제공” 원칙으로, 가능하면 reranker를 통해 꼭 필요한 K개만 선택하는 것을 권장합니다. 이는 특히 멀티홉 질문에서 중요합니다. (예: 두 문서의 정보를 비교해야 할 때, 관련없는 다른 문서 5개를 함께 주면 오히려 혼란을 줌)
5. Claude 3.7 Sonnet에 질의 맥락을 주입하는 프롬프트 구성 전략
이제 최종적으로 선정된 문맥을 Claude 3.7에게 전달하여 답변을 생성하는 단계입니다. 어떤 프롬프트 형식으로 문맥과 질문을 넣느냐에 따라, 모델이 주어진 정보를 얼마나 효과적으로 활용하는지가 달라집니다. 대표적인 기법으로 단순 연결, 구조화 프롬프트, 맵-리듀스, Tree-of-Thought, ReACT 스타일 등이 거론되는데, 각각 장단점이 있습니다. Claude 3.7 Sonnet의 특성과 200k에 이르는 거대한 컨텍스트 윈도우를 고려하여, 어떤 접근이 적합한지 살펴보겠습니다.
1) 단순 연결 (Naive concatenation): 가장 straightforward한 방법은, **[문맥] + [질문]
**을 하나의 프롬프트로 만들어 Claude에게 주는 것입니다. 예를 들어 시스템 메시지나 사용자 메시지에 아래처럼 넣습니다:
[시스템 프롬프트]
다음은 참고 문서와 사용자 질문입니다. 참고 문서를 활용해 질문에 답하세요.
[참고 문서]
1. 문서A 발췌: ... (내용) ...
2. 문서B 발췌: ... (내용) ...
3. 문서C 발췌: ... (내용) ...
[질문]
사용자: ... (질의 내용) ...
Claude는 주어진 참고 문서를 읽고 답변하게 됩니다. 장점: 구현이 쉽고 한 번의 호출로 끝납니다. 단점: 문서가 많거나 길면 모델이 어떤 정보를 어디서 찾을지 방향을 잃을 수 있습니다. 특히 컨텍스트 길이가 늘어날수록 LLM의 “망각 곡선” 문제가 있어 중간 부분 정보를 잘 활용 못할 수 있다는 연구가 있습니다. 그러므로 단순 연결을 할 때는 가장 관련도 높은 문서부터 배치하고, 각 문서 앞에 번호나 제목을 달아 구분해주는 것이 좋습니다. Claude는 긴 맥락도 처리 가능하지만, 여전히 앞부분에 편향되는 경향이 있을 수 있으므로, 순서와 포맷을 신경써야 합니다.
또한, 역할 지시자를 활용해 시스템 프롬프트로 “주어진 문서를 벗어난 상상은 하지 마라” 등의 지침을 넣어 **환각(hallucination)**을 억제해야 합니다. 예: 시스템: 너는 회사 지식베이스 전문가다. 아래 제공된 "참고 문서"의 정보만 바탕으로 질문에 답하고, 문서에 없는 내용은 '알 수 없다'고 답하라.
이런 구조화된 지침이 없으면, LLM이 문맥이 부족할 때 자체 상상을 채워 넣을 위험이 있습니다.
2) 구조화 프롬프트 (Structured prompting): 문맥과 질문을 단순 나열하기보다, 포맷을 명시적으로 구분해 주는 방식입니다. 예를 들어 <CONTEXT>...</CONTEXT>
태그로 문맥 부분을 감싸거나, JSON 형태로 입력하는 것입니다. Anthropic 권장 사항 중 하나는 XML 태그 등으로 구분자를 넣는 것으로, Claude는 이를 무시하지 않고 구조로 인식합니다. 예:
SYSTEM: 역할 지시...
USER:
<CONTEXT>
[문서1] ...내용...
[문서2] ...내용...
</CONTEXT>
질문: ...내용...
이처럼 <CONTEXT>
블록을 명시하면, Claude가 이 부분을 참고용 정보로 취급하고 답변에 활용하게 유도할 수 있습니다. 장점: 모델이 프롬프트의 역할별 부분을 혼동하지 않고 이해합니다. 단점: 반드시 필요한 것은 아니지만, 특정 형식을 강제하면 오히려 답변에 그 형식이 드러날 수 있어(예: 태그 채로 답을 한다든지) 프롬프트 튜닝이 필요할 수 있습니다. 대안으로, Markdown 헤더나 구분선(---
) 등을 써서 시각적으로만 구분하기도 합니다.
구조화의 또 다른 측면은, 문맥 각각에 출처 식별자를 붙이는 것입니다. 예를 들어 위에 [문서1]
같이 레이블을 달아두면 Claude가 답변시 "문서1에 따르면..." 식으로 출처를 분리해 언급할 수도 있습니다. 이는 사용자가 근거를 물을 때 도움이 되나, 모델이 반드시 그렇게 할지는 미지수입니다 (Claude에게 그런 스타일을 요구하는 추가 지침이 필요).
3) Map-Reduce 프롬프트 전략: 컨텍스트가 많을 때 유용한 방법으로, 여러 단계에 나눠 LLM을 호출하는 체인입니다. Map 단계: 각 문맥 조각에 대해 부분적인 답이나 요약을 생성하고, Reduce 단계: 이들을 종합하여 최종 답변을 만드는 것입니다. 예를 들어 10개의 문맥이 있다면 Claude에게 각 문맥과 질문을 함께 주고 "이 문맥에서 질문의 답 단서가 되는 내용을 찾아라"라고 물어, 10개의 중간 응답을 얻습니다. 그리고 Claude 혹은 다른 LLM 호출로 그 중간 결과들을 합쳐 최종 답변을 생성합니다. 장점: 한번에 긴 맥락을 넣지 않아도 되고, 병렬화할 수 있어 장문 요약 등에 강합니다. 단점: 여러번 LLM 호출로 지연이 늘고, 중간 요약 과정에서 정보 손실이 있을 수 있습니다.
Claude 3.7은 200k 토큰까지 가능해 웬만해선 단번에 처리가 되겠지만, 그 양을 모델이 다 이해한다는 보장과는 별개입니다. 따라서 Map-Reduce는 극단적으로 컨텍스트가 클 때 뿐 아니라, 질문이 복잡할 때도 도움이 됩니다. 가령 "다음 문서들을 검토하여 공통 결론을 도출하라" 같은 명령은, 각각의 문서를 요약(Map)하고, 종합(Reduce)하는 방식이 자연스럽습니다.
4) Tree-of-Thought (사고의 나무) 방식: Tree-of-Thought(TOT)는 모델이 답을 바로 내지 않고 여러 가설 경로를 탐색하여 최적 답을 찾는 아이디어입니다. RAG 맥락에서는, 모델이 문맥을 활용해 곧장 답하지 않고, 단계별 추론이나 질문 분해를 하는 것을 포함합니다. 예를 들어 "A의 B에 대한 입장은?"이라는 질문이 들어오면, Claude에게 곧장 답하게 하기보다, 먼저 "Step1: 질문을 분석해 서브질의 만들기", "Step2: 서브질의로 검색된 문맥 확인", "Step3: 종합" 이런 식의 내부 사고 과정을 유도할 수 있습니다. TOT은 주로 멀티홉 추론에 효과적이는데, Claude 3.7은 내부적으로 extended thinking이 향상된 모델로 알려져 이런 chain-of-thought을 잘 따를 수 있습니다. TOT 구현은 ReAct와 겹치는 부분이 많지만, 꼭 외부 툴 사용 없이 프롬프트 설계만으로도 가능하다는 점이 차이입니다.
예시 TOT 프롬프트:
시스템: 너는 분석적 비서야. 어려운 질문은 단계별로 생각해.
유저: "X와 Y의 차이점은 무엇이고, 각각 언제 사용하면 좋은가?"
어시스턴트 (생각):
1. 질문을 둘로 나눈다: [X와 Y의 차이점?] [X는 언제 쓰이고 Y는 언제 쓰이나?]
2. 각 부분에 답하기 위해 관련 문서를 찾는다... (생략)
이런 식으로 chain-of-thought을 유도하면 Claude가 자체적으로 필요한 정보를 찾는 방향으로 움직일 수 있지만, 강력한 제어가 어렵고 긴 답변이 생성되므로 실제 응답으로 채택되진 않고, 중간 thinking을 숨기거나 별도 토큰 처리해야 하는 복잡함이 있습니다. 요약하자면, TOT은 LLM의 자율 추론 능력을 믿고 맡기는 접근으로, RAG 파이프라인에서 자주 쓰이진 않지만 복잡 질의에서 모델이 실마리를 못 잡고 헤맨다면 시도해볼 수 있습니다.
5) ReACT 스타일 (자동 에이전트식): ReACT는 Reason + Act를 결합한 프레임워크로, 모델이 생각과 도구 사용 액션을 번갈아 수행하며 문제를 해결하는 패턴입니다. RAG에서 ReACT를 적용하면 모델이 스스로 벡터 검색이라는 도구를 호출하도록 만들 수 있습니다. 예를 들어 ReACT 프롬프트를 설계해 Claude에게 벡터 DB를 질의하는 함수를 허용하면, Claude는 답을 모를 때 직접 "Action: 검색('...')"
과 같이 행동을 결정하고, 그 결과(Observation: ...
)를 받아 reasoning을 이어갑니다. 이 방식은 마치 질의응답 Agent를 만드는 것으로, 인간 개입 없이도 다단계 검색을 수행할 수 있다는 장점이 있습니다.
실용 예로, 질문이 "우리 제품 X의 최신 사양과 관련된 Slack 논의 요약해줘"라면, Claude가 먼저 벡터 DB에 <벡터 검색: "제품 X 최신 사양 Slack">
같은 행동을 하고, 관련 Slack 메시지들을 observation으로 보고, 다시 그것을 토대로 최종 답변을 형성합니다. 장점: 필요한 만큼 여러 번 검색하여 multi-hop 질문도 풀 수 있고, 사람없이 자동으로 지식베이스에 질의하여 사고를 전개합니다. 단점: 프롬프트가 매우 복잡하고, 제대로 동작하도록 few-shot 예시를 많이 줘야 합니다. 또한 벡터 DB 검색 함수를 노출해야 하므로, 구현해야 할 코드가 늘어납니다. 실시간 API 호출이 여러 번 일어나 잠재적으로 지연이 커지는 것도 고려해야 합니다.
Claude 3.7 Sonnet은 “hybrid reasoning”을 지원하는 최초의 모델 중 하나로 알려져 ReAct 에이전트로의 활용 가능성이 높습니다. 따라서 장기적으로 자동 검색 에이전트 형태의 챗봇을 원한다면 ReACT 프롬프트를 연구해볼 만합니다. 다만, 정확도를 우선하는 지원 챗봇에서는 예측 불가능한 행동보다는 통제된 RAG가 안전하기에, 초기엔 ReACT보다는 정적인 프롬프트 + 필요한 경우 멀티턴을 권장합니다.
6) 기타 Prompt 전략 및 고려사항:
- 예시 제공 (few-shot): 모델이 참고 문서를 사용하는 답변에 익숙하지 않다면, 몇 가지 Q-A 예시를 보여줄 수 있습니다. 예: 문맥과 함께 "Q: ...? A: ...(문맥 내용 인용한 답)" 형식 시연. Claude에게 다량의 예시를 주기엔 토큰이 아깝지만, 1-2개 정도는 큰 부담 없습니다.
- 응답 형식 지시: 답변에 출처 표시나 인용을 요구할 것인지 결정합니다. 정확도를 높이려면 "답변 끝에 참고한 문서명이나 번호를 포함하라"고 지시할 수 있습니다. Claude가 이를 따를 수 있게 미리 문서에 번호를 붙였다면 효과가 있습니다.
- 긴 맥락 활용 팁: Anthropic 문서에 따르면, 모델에 장기 컨텍스트를 줄 때는 중요한 내용을 앞쪽에 두고, 섹션별로 요약 머리글을 넣어주는 것이 좋다고 합니다. 즉, 컨텍스트가 길 경우 우리가 미리 핵심만 압축하거나, 각 문서 발췌 앞에 한 줄 요약을 붙여 모델이 어디에 어떤 정보가 있을지 짐작하게 하면 더 잘 활용합니다.
- 시스템 vs 사용자 메시지: Claude는 system 메시지로 준 지시를 강하게 따르는 경향이 있습니다. 따라서 문맥+질의를 한꺼번에 사용자 메시지로 주기보다, 시스템에 역할과 제한을 걸고 사용자 메시지에 질문과 문맥을 넣는 구조가 효과적입니다. 시스템에는 "너는 지원봇", "문맥 외 모르는 건 모른다고 해" 등의 규칙을, 사용자 부분에 실제 컨텍스트와 질문을 넣으면, Claude 3.7은 긴 맥락도 비교적 일관되게 처리합니다.
✔️ 프롬프트 예시 (Structured Prompt):
시스템: |
너는 SaaS 플랫폼에 대한 지식베이스를 참조하여 질문에 답하는 고객지원 AI이다.
사용자의 질문에 대해 아래 '컨텍스트'에서 찾을 수 있는 정보만 활용하라.
모르는 내용이나 불확실한 내용은 추측하지 말고 "죄송합니다, 해당 정보를 찾을 수 없습니다."라고 답한다.
사용자: |
컨텍스트:
- 문서 제목: 제품 출시 노트 2023Q4
내용: "...."
- 문서 제목: Slack 대화 - 프로젝트 알파
내용: "...."
질문: 우리 제품의 2023Q4 신규 기능 중 알파 프로젝트와 관련된 내용은 무엇인가?
위 예시는 YAML-like 포맷으로 **문맥(Context)**을 구조화하고 질문을 구분하여 넣은 것입니다. Claude는 컨텍스트:
항목들을 참고자료로 인식하고 질문에 답하게 됩니다. 시스템 메시지에 출처 외 답변 금지 규칙을 명시했으므로, 정확도 향상에 큰 도움이 됩니다. 실제 답변에서 Claude가 "2023Q4 출시 노트에 따르면..." 식으로 컨텍스트를 활용해 답변할 가능성이 높아집니다.
6. 다양한 RAG 변형 기법 비교 및 Claude 3.7 Sonnet 적용 시 고려사항
마지막으로, 기본적인 RAG 구현을 넘어 성능을 높이기 위한 여러 변형 기법들을 알아보고, Claude 3.7에 적용할 때의 특징과 활용 시나리오를 살펴보겠습니다. 대표적으로 HyDE (Hypothetical Document Embeddings), Multi-hop RAG, Fusion-in-Decoder 등이 있는데, 각각 질문-문서 매칭이나 다중 문맥 추론 향상에 초점을 둔 기법들입니다.
🔹 Hypothetical Document Embeddings (HyDE)
HyDE는 사용자의 질문만으로 검색하기 어려울 때, 질문에 대한 가상의 정답 문서를 LLM으로 한 번 생성한 후 이를 임베딩하여 검색에 사용하는 기법입니다. 즉, “가상의 답변→벡터화→실제 문서 검색” 순서로 진행됩니다. 이렇게 하면 질문 자체의 표현이 아닌, 모델이 예상하는 답변의 표현으로 검색하므로 정확도가 올라갈 수 있습니다. 특히 질문이 막연하거나 짧은 경우 효과적입니다. 예를 들어 질문이 "애플 창업자가 해고된 연도는?"이라면, HyDE는 우선 LLM에게 이 질문에 대한 설명문을 생성시킵니다: “스티브 잡스는 1985년 9월 애플에서 해고되었다…”. 그런 다음 이 가상 문서를 임베딩하여 실제 KB에서 유사 문서를 찾으면, “1985년 잡스 퇴사” 관련 문서가 잘 매칭되는 식입니다.
- 장점: 임베딩 단계에서 질문과 문서 간 의미적 간극을 메워줍니다. LLM이 생성한 가짜 답은 실제 문서와 어휘적 유사도가 높아질 가능성이 높아, 검색 recall을 높여줍니다. 또한 HyDE로 얻은 문서는 실제 KB에 없던 내용이라도 임베딩만 사용하므로, 잘못된 정보가 그대로 답변에 쓰이지 않고 단지 검색 키워드로만 활용됩니다.
- 단점: 추가 LLM 호출이 필요하여 지연과 비용이 늘고, 프롬프트에 따라 엉뚱한 가상 문서를 생성하면 오히려 노이즈가 될 위험도 있습니다. Claude 3.7로 HyDE용 문서를 생성할 수도 있지만, 비용상 작은 LLM(예: 2~7B 로컬 모델)으로 생성 후 같은 임베딩 모델로 벡터화하는 방안도 고려됩니다. 다만 작은 모델이 부정확한 답을 생성하면 검색에 방해만 될 수 있으니, 검증 및 실험을 거쳐야 합니다.
Claude 3.7 적용: Claude는 긴 맥락도 스스로 처리 가능하므로, HyDE의 이점을 크게 체감 못할 수도 있습니다. 그러나 임베딩 단계에서는 Claude의 컨텍스트 크기가 상관없으므로, embedding 모델이 커버 못하는 어휘를 Claude가 생성해 채워준다고 생각하면 됩니다. 특히 드물거나 은어로 묻는 질문에 유효합니다. 예를 들어 슬랙 대화에서 나온 은어를 사용자가 질문하면 임베딩이 못 알아들을 수 있는데, Claude라면 그 의미를 풀어 일반 용어로 가짜 답을 써줄 수 있습니다. HyDE는 질의당 한 번 더 모델을 호출해야 하므로, 질문당 응답 시간이 수초 증가할 수 있습니다. 그래서 모든 질의에 적용하기보다는, 검색 결과가 없거나 부정확할 때 fallback으로 HyDE를 사용하는 전략이 추천됩니다. 즉: “1차 임베딩 검색 → 결과 부족하면 HyDE 생성 → 2차 검색” 흐름입니다.
활용 시나리오: 사내 KB가 방대하고 다양한 표현으로 질문이 들어올 때 HyDE를 도입하면 첫 검색 누락을 줄일 수 있습니다. 예를 들어 고객이 "이 제품 속도 빠르게 하는 법?" 이렇게 두루뭉술 묻는다면 Claude로 "가상 매뉴얼 단락"을 만들어보고 검색하면 적절한 FAQ를 찾기 쉬워집니다.
🔹 Multi-hop RAG (다중 도약형 질의응답)
Multi-hop RAG는 여러 문서를 거쳐야 답이 도출되는 질문에 대응하는 기법입니다. 일반적인 RAG는 한 번 검색으로 한 문서에서 답을 찾지만, 현실에는 “정보 A + 정보 B를 조합”해야 하는 질의가 많습니다. 예를 들어 “Google, Apple, Nvidia 중 2023 Q3 이익률이 가장 높은 회사는?” 질문은 각사의 2023 Q3 보고서를 찾아 그 수치를 비교해야 답이 나옵니다. Multi-hop RAG는 이러한 경우 질문을 분해하여 순차적으로 검색하고, 중간 결과를 결합합니다.
- 방식 1: 파이프라인 분할 – 애플리케이션 레벨에서 질문을 먼저 NLP로 분해합니다. 예컨대 위 질문을 "Google Q3 이익률?", "Apple Q3 이익률?", "Nvidia Q3 이익률?" 3개로 나누고, 각각 RAG 검색으로 답을 얻은 뒤 그를 다시 합쳐 최종 비교 답을 만듭니다. 이 접근은 특정 패턴(비교, 나열)에 유용하나, 임의의 복잡한 질문에 일반화하기는 어렵습니다.
- 방식 2: LLM Agent (ReAct) – 섹션5의 ReACT처럼 LLM에게 도구 사용 능력을 주어, 스스로 multi-hop 검색을 하게 합니다. 예를 들어 Claude에게 “먼저 Google 보고서를 찾는다…” 생각하게 하고 검색 액션을 연속 수행하도록 하면, 사람 개입 없이도 다중근거를 모읍니다. 이는 구현 난이도가 높지만, Claude 3.7의 고차원적 추론 능력을 십분 활용하는 방법입니다.
- 방식 3: Fusion 방식 활용 – 이후 설명할 Fusion-in-Decoder 모델을 쓰면, 애초에 여러 문서를 한꺼번에 처리하도록 학습되어 multi-hop 질문도 자연스럽게 해결합니다. 다만 Claude 3.7 같은 사전학습 모델은 명시적 multi-hop 최적화가 없으므로, 대신 여러 문서 투입 + chain-of-thought 유도로 대응해야 합니다.
Claude 3.7 적용: Claude는 200k 토큰 컨텍스트로 여러 문서를 한번에 넣을 수 있으므로, 겉보기에 Multi-hop 상황도 그냥 top10 문서를 모두 주고 답하게 할 수 있습니다. 그러나 이 경우 Claude가 각 문서를 연결 지어 추론해야 하므로, 프롬프트에서 이를 강조하는 게 필요합니다. 예: “여러 문서를 참고하여 답을 도출하라. 문서 간 정보가 상충하면 종합하라” 같은 지시를 줍니다. Claude 3.7은 긴 문맥에서 추론하는 능력이 향상되었다고는 하나, 여전히 명시적으로 비교/추론 단계를 텍스트화시키면 더 정확해질 수 있습니다. 따라서 multi-hop 질문이 들어오면, chain-of-thought을 허용(예: Assistant: 생각: ...
)했다가 최종 답만 내놓는 형태로 답하게 해볼 수 있습니다. 다만 일반 사용자 상대 챗봇에서는 이러한 과정을 숨기고 최종 답변만 줘야 하므로, 내부적으로만 활용하거나, 또는 개발단에서 multi-hop을 감지해 파이프라인적으로 처리하는 편이 안전합니다.
추천 시나리오: 질문이 여러 출처의 조합을 요구할 때 Multi-hop RAG를 고려하세요. 예컨대 “지난 분기 판매 1위 제품의 주요 개선점은?”이라면 판매 1위 제품을 알아내는 문서 -> 그 제품 개선점 문서를 이어봐야 합니다. 이런 질의가 빈번하면, Claude ReACT 에이전트를 만들어 자동화할 수 있습니다. 반면 질문 대부분이 단일 문서로 답변 가능하다면 굳이 복잡한 흐름을 추가할 필요는 없습니다.
🔹 Fusion-in-Decoder (FiD) 및 기타 Fusion 기법
**Fusion-in-Decoder (FiD)**는 retrieval과 generation 단계를 모델 내부에서 융합하는 아키텍처로, 예전 QA 모델 (T5 기반 등)에서 제안된 방식입니다. 전통 RAG에서는 벡터 검색 → 상위 문서들을 하나로 이어서 → seq2seq 디코더에 넣었다면, FiD는 각 문서를 인코더가 따로 인코딩하고, 디코더가 그것들을 참고하여 답을 만듭니다. 즉, 디코더 수준에서 정보를 융합하므로, 여러 문서의 관련 부분을 효과적으로 종합할 수 있습니다. 반면 **Fusion-in-Encoder (FiE)**는 질문+문서를 처음부터 concat하여 인코더에서 합쳐 처리하는 것으로, 간단하지만 유연성이 떨어집니다.
현재 우리가 Claude 3.7에 컨텍스트 넣는 것은 일종의 Fusion-in-Encoder 방식과 유사합니다 (모델 입력으로 이미 합쳐진 시퀀스를 주기 때문). FiD는 모델 구조 변경을 필요로 해서, Claude 같은 사전학습된 폐쇄형 모델에는 직접 적용 못합니다. 대신, 오픈소스 LLM을 파인튜닝한다면 FiD 방식을 선택할 수 있습니다. 예컨대 Llama2 70B를 FiD 스타일로 our corpus에 fine-tune하면, 답변 생성 시 벡터 검색한 여러 문서를 인코더에 병렬로 넣고 디코더에서 합성하도록 학습시킬 수 있습니다. 하지만 Claude 3.7은 API 제공형이므로 이런 fine-tuning은 불가하며, Prompt 수준에서 FiD 흉내만 낼 수 있습니다.
Claude 3.7 적용 Considerations: Claude는 100k+ 토큰을 넣을 수 있으니, 엄밀히 말하면 사실상 FiD가 필요 없지 않나? 생각할 수 있습니다. 그러나 컨텍스트 윈도우의 크기 = 모델이 활용하는 정보의 양은 아닙니다. Claude도 한 번에 많은 문서가 들어오면 중요하지 않은 부분까지 처리해야 해서 잡음에 약해질 수 있습니다. FiD가 뛰어난 점은, 모델이 여러 문서를 입력받도록 학습되었다는 것입니다. Claude는 그런 특수 훈련 없이 general하게 긴 문서를 읽도록 되어 있기에, 완벽한 FiD 효과를 기대하긴 어렵습니다. 대신, 개발 측면에서 Fusion-in-Prompt 전략을 고민해볼 수 있습니다. 예를 들어, 5개의 문서를 컨텍스트로 넣을 때 각 문서를 """문서 내용"""
로 따옴표 처리하거나 bullet로 나눠 표시하면, Claude가 구분해서 보는데 도움이 될 겁니다. 그리고 답변할 때 이들 문서를 다뤄야 함을 프롬프트에 분명히 지시하면, 디코딩하면서 각 문서를 적절히 참조하려 할 것입니다.
Fusion-in-Decoder의 강점은 특히 다중 문서에서 한 줄씩 모아서 답을 조합해야 할 때 나타납니다. Claude 같은 일반 LLM도 충분히 그런 작업을 할 수 있지만, 정확성 측면에서는 FiD로 학습된 T5 모델들이 SOTA를 찍곤 했습니다. 따라서, 정말 중요한 QA 정확도 평가에서 몇 점이라도 올리고 싶다면 오픈모델+FiD파인튜닝도 고려해볼 수 있습니다. 그러나 이는 Claude를 사용하는 전제와 어긋나므로, 현실적 방안은 아닙니다.
Fusion 관련 기타 기법:
- Fusion-in-Context: 여러 문서를 한꺼번에 넣는 지금 방식 자체를 가리키기도 합니다.
- Fusion-after-Answer: LLM이 각 문서별 답변을 내고, 별도 모듈이 그 답변들을 융합하는 방식도 생각해볼 수 있습니다 (일종의 post-processing).
- Knowledge distillation: 큰 FiD 모델이 낸 답변을 작은 RAG 모델이 배우게 하는 등도 연구되지만, Claude엔 해당없습니다.
요약: Claude 3.7을 사용할 때 Fusion류 기법은 Prompt 설계 영역에서 고려될 뿐, 모델 구조를 바꿀 수는 없습니다. 따라서 우리는 retrieval 단계에서 top-k 문서를 잘 고르는 것이 훨씬 중요합니다 (Fusion 기법은 기본적으로 top-k에 답이 있다는 전제하에 동작하는 것이므로). Claude에게 많은 문서를 잘 주는 건 좋지만, 너무 과하지 않게 주는 게 포인트입니다. Pinecone 측의 가이드도 “LLM 컨텍스트에 정보를 너무 많이 넣으면, 안 넣은 것만 못할 수 있다”는 것을 보여주고 있습니다.
🔹 종합 및 추천 시나리오
위의 각 변형 기법들을 적용할지 여부는 우리 시스템의 질문 특성과 자원 한계에 따라 다릅니다:
- HyDE: 질의가 짧거나 애매해 검색 누락이 잦으면 도입. Claude 3.7의 높은 언어능력을 활용해, 어려운 질의어를 평이한 서술로 풀어주는 용도로 추천. 다만 모든 질의에 쓰면 응답 지연이 2배 이상 될 수 있으니, 필요한 경우에만 fallback.
- Multi-hop/ReACT: 복잡한 질의 빈도가 높으면 고려. 특히 연속 질의 (대화 맥락에서 이어지는 추가 질문) 시 이전 답을 활용해 또 검색해야 하는 경우, Claude를 에이전트로 만들어 자동 추가 검색하게 하면 사용자 경험이 매끄럽습니다. 리스크는 존재하나, Anthropic의 Claude Code/Assistant 에이전트 예시들을 참고해 안전장치를 넣으면 활용 가능.
- Fusion-in-Decoder: Claude 사용 환경에서는 직접 적용 불가. 그러나 만약 성능 평가에서 GAP을 느낀다면, 별도로 FiD 기반 QA모델(예: Google T5 11B FiD)을 구축해 Claude 답변 전에 후보 답을 가져와 ensemble하는 식도 생각은 할 수 있습니다. 현실적으론, Claude 3.7이 충분히 강력하여 거기까지 할 필요는 드뭅니다.
마지막으로, Claude 3.7 Sonnet 자체의 특성을 고려한 팁:
- Claude 3.7 Sonnet은 100k→200k 토큰 업그레이드로 방대한 컨텍스트를 유지하며, 이미지 입력 지원도 됩니다. 따라서 문서와 함께 이미지까지 넣어 “이미지+텍스트 멀티모달 답변”도 이론상 가능합니다. 예컨대 제품 사진 이미지를 Claude에 주고, 벡터 DB에서 찾은 설명 텍스트도 주면, Claude가 둘 다 참고해 답변할 수 있습니다. 이런 응용은 HyDE나 Multi-hop과 별개로 새로운 정보 입력 채널을 열어주는 것이니, 활용도를 고민해볼 만합니다.
- Claude 3.7은 또한 Hybrid reasoning (추론과 계산 병행)에 특화되었다고 언급됩니다. 이는 RAG와 직접 관계는 없지만, 예를 들어 “수식을 계산하고 설명하라” 같은 요청에서 벡터 DB 지식을 참고해 추론하고, 필요시 tool도 쓰는 등 다양한 작업을 한 번에 수행할 수 있다는 의미입니다. 우리의 지원봇 시나리오에서, 필요한 경우 Python 계산기를 호출하는 에이전트 기능까지 넣어준다면 (예: 비용 계산 질문 등) 더욱 똑똑한 봇이 될 수 있습니다.
정리: HyDE, Multi-hop, Fusion-in-Decoder 등은 정확도를 극한으로 끌어올리기 위한 추가 옵션입니다. Claude 3.7 Sonnet을 기본으로 하는 한, 이러한 기법은 프롬프트 엔지니어링과 파이프라인 설계로 간접 활용하게 됩니다. 우선은 기본 RAG 세팅 (양질의 임베딩 + 최적 chunk + rerank + 컨텍스트 투입)으로 구현하고, 성능을 평가하면서 부족한 부분에 단계적으로 도입하는 걸 권장합니다. RAG의 세계에서는 작은 튜닝 하나가 전체 품질에 영향을 크게 주기도 하므로, 하나씩 실험하면서 우리의 도메인에 맞는 최적 조합을 찾아가는 것이 중요합니다.
끝으로, Claude 3.7 기반 고객지원 챗봇 개발시 지속적인 평가와 개선 루프를 만드는 것을 추천합니다. 초기에는 정확도를 높이기 위해 엄격하게 컨텍스트에 기반한 답변만 출력하도록 하다가, 사용자 피드백을 보며 필요한 추가 기능(HyDE나 multi-hop 등)을 붙여가는 방식이 현실적입니다. RAG 구성 요소들을 본 장에서 다룬 대로 잘 조합하면, 신뢰성 있는 한국어 지원 챗봇을 구현할 수 있을 것입니다.
참고 문헌 및 출처: 본 조사에서는 최신 벡터 DB 비교자료, 멀티모달 RAG 연구, Pinecone의 RAG 가이드 등 여러 출처를 인용하였으며, 각주에 표기된 링크를 통해 상세 내용을 확인할 수 있습니다. Claude 3.7 Sonnet의 사양은 Anthropic 공개 문서와 커뮤니티 논의를 참조하였고, 제시된 프롬프트 예시는 실제 구현을 돕기 위해 작성한 샘플입니다.