4주차 — LLM RAG 고급 검색 기법 (Multi-Vector · BM25 · Dense · Ensemble)
고급 검색 기법 전부를 "멀티벡터 스토어" 패턴 하나로 꿰고, 책의 인메모리 장난감 예제를 pgvector·Chroma 기반 실무형으로 재구축해 — 질의 확장(Multi-Query·HyDE), 문서 확장(Parent Document), 수동 BM25(SQL), HNSW 밀집 검색, RRF 앙상블까지 — 하이브리드 검색 파이프라인을 완성하라.
0. 사전 필수 용어 (선행지식)
- 임베딩(Embedding) — 텍스트를 고정 길이 숫자 벡터로 바꾼 것. 비유하면 문장에 "의미 좌표"를 찍는 일이다. 같은 손실(loss)로 학습된 모델이 만든 벡터끼리만 거리 비교가 가능하다. §3-E와 §6에서 본질을 다시 정의한다.
- RAG(Retrieval-Augmented Generation, 검색 증강 생성) — LLM이 답하기 전에 관련 문서를 검색해 프롬프트에 넣어 주는 구조. 비유하면 "오픈북 시험"이다. 본 강의 전체의 전제다.
- 청킹(Chunking) — 긴 문서를 검색 단위로 자르는 것. 비유하면 책을 카드 뭉치로 재단하는 일. 자를 크기가 검색 정밀도와 컨텍스트 풍부함을 좌우한다. §3-B와 §4 Step 2의 핵심 변수다.
- 리트리버(Retriever) — "질문 → 관련 문서 리스트"를 돌려주는 검색 컴포넌트 인터페이스. 비유하면 사서(司書)다. 이번 주의 모든 기법은 결국 리트리버 형태로 조립된다(§3~§5 전체).
- TF-IDF — 단어가 문서에 자주 나올수록(TF), 그리고 그 단어가 전체 문서에서 희귀할수록(IDF) 중요하다고 보는 고전 검색 지표. §3-F에서 구축하는 BM25의 뼈대다.
선행지식이 부족하면 AI 엔지니어링 EP17 (컨텍스트 증강과 RAG) 학습자료를 먼저 보고, PostgreSQL 기초는 공식 문서(https://www.postgresql.org/docs/)를 참조하라.
1. 주제 정의
이 강의(뉴런데브 RAG 실전 4주차)는 RAG 검색 고도화 기법을 "멀티벡터 스토어(Multi-Vector Store)" 패턴 하나로 통일해 가르친다. 하나의 원본 정보에 대해 벡터를 여러 개 잡아 검색 품질을 끌어올리는 접근이며, 두 갈래로 나뉜다.
- 질의 확장(Query Expansion) — 질문 쪽을 여러 개로: Multi-Query, HyDE
- 문서 확장(Document Expansion) — 답변이 될 문서 쪽을 여러 개로: Parent Document(이중 청킹)
실습은 노트북 6개로 진행한다.
| 노트북 | 주제 |
|---|---|
| practice4-1 | Parent Document Retriever (부모 1000 · 자식 200 이중 청킹) |
| practice4-2 | Multi-Query Retriever (LLM 질의 확장) |
| practice4-3 | HyDE (가상 문서 임베딩) |
| practice4-4 | BM25 — PostgreSQL 수동 구축 (mecab-ko · tsvector · 트리거) |
| practice4-5 | 밀집 검색 (Dense Retrieval) — pgvector HNSW 인덱싱 |
| practice4-6 | EnsembleRetriever — BM25 + Dense 를 RRF 로 결합 |
⚠️ 주의: 강의의 출발 선언 — "책에 있는 내용을 그냥 따라 하면 장난감이라서 실무에 전혀 쓸 수가 없다." 책의 FAISS·BM25Retriever·InMemoryStore 는 모두 인메모리다. 실무 데이터는 RAM 에 올릴 수 있는 크기가 아니므로, 이번 주 전체가 인메모리 스토어를 배제하고 pgvector(PostgreSQL) 기반으로 재구축하는 과정이다.
2. 풀려는 문제
- 문제 1 — 질문 품질 의존: 사용자의 질문은 대체로 형편없다. 단일 질문 벡터 하나로 검색하면 그 품질에 검색이 통째로 종속된다. → 질의 확장.
- 문제 2 — 청크 크기 딜레마: 작게 자르면 검색은 정확하지만 LLM 에 줄 컨텍스트가 빈약하고, 크게 자르면 그 반대다. → 부모-자식 이중 청킹.
- 문제 3 — 의미 검색의 키워드 취약성: 임베딩 유사도는 고유명사·수치("발행주식의 총수") 같은 정확 일치에 약하다. → 희소(BM25) + 밀집(Dense) 하이브리드.
- 문제 4 — 인메모리의 실무 부적합: 프로세스 재시작이면 증발하고, 다른 프로세스에서 공유할 수 없고, 억 단위 레코드면 애초에 메모리에 안 올라간다. → PostgreSQL 영속화.
- 문제 5 — 점수 체계 이질성: BM25 점수(13점대)와 코사인 유사도(0~1)는 스케일이 달라 직접 합산할 수 없다. → RRF 순위 융합.
3. 핵심 개념·구조
3-A. 멀티벡터 스토어 패턴 (큰 그림)
오늘 배우는 기법 대부분이 이 패턴의 변주다. 벡터를 여러 개 더 잡아 검색 기능을 향상시킨다 — 질문을 확장하든, 문서를 확장하든, 양쪽 다든.
3-B. Parent Document Retriever (문서 확장)
원본 문서를 자식 청크(200자) 로 잘게 잘라 임베딩·검색하고, 검색된 자식과 연결된 부모 청크(1000자) 를 최종 컨텍스트로 제공한다. 정확한 검색(자식)과 풍부한 컨텍스트(부모)를 동시에 얻는다.
💡 실무 노하우: 책은 부모 문서를 InMemoryStore(RAM)에 넣지만, 강의는 부모도 Chroma 컬렉션에 key-value 로 저장한다. 실무 데이터 크기와 서버 안정성 때문이다. 부모-자식 연결은 메타데이터로 유지한다. 또한 청크 분할은 고정 길이보다 시멘틱 분할기로 부모 안의 서브 청크를 나누면 부모-자식 관계가 더 유의미해진다(비용은 커진다).
3-C. Multi-Query Retriever (질의 확장)
사용자의 단일 질문을 LLM 이 여러 개(시연에서는 6개)의 다양한 질문으로 확장해 각각 검색하고 결과를 합친다. "사용자의 형편없는 질문을 LLM 이 좋은 질문들로 바꿔 검색하겠다"는 발상. LangChain 에 질의 확장 프롬프트까지 내장돼 있어 MultiQueryRetriever.from_llm(retriever=…, llm=…) 한 줄이면 끝난다.
3-D. HyDE (Hypothetical Document Embeddings)
질문으로 검색하는 대신, 질문에 대한 가상의 답변 문서를 LLM 으로 먼저 생성한 뒤 그 가상 문서로 유사도 검색을 수행하는 질의 변형 기법. 질문 임베딩과 답변(문서) 임베딩은 공간이 달라 유사도가 안 잡히던 구식 임베딩 시대의 처방이다.
⚠️ 주의: HyDE 의 전제는 "LLM 이 그럴듯한 답을 지어낼 수 있다"이다. LLM 이 답을 만들어낼 수 없는 도메인(사내 고유 데이터 등)이면 가상 문서 자체가 무의미해져 기법이 통하지 않는다. 또 질의마다 LLM 호출이 한 번 더 붙는다.
3-E. 희소 벡터 vs 밀집 벡터 — 정확한 정의 (강의 최중요 개념)
"0이 많으면 희소, 꽉 차 있으면 밀집"은 부정확하다.
- 희소 벡터(Sparse Vector) = 각 차원이 완전히 분리된 독립 의미를 갖는 벡터. 사람이 차원을 설계(엔지니어링)할 수 있다. 값이 0으로 비는 것은 현실 데이터를 전부 확보할 수 없어서 유도되는 현상이지 정의가 아니다.
- 밀집 벡터(Dense Vector) = 역전파(Backpropagation) 학습의 결과로만 만들어지는 벡터. 여러 차원이 뒤엉켜 복합 의미를 나타내며, 사람이 공학적으로 제작하는 것은 불가능하다. 희소 벡터의 "반대 개념"이 아니다.
- 임베딩(Embedding) = 벡터의 부분집합. 같은 손실로 학습된 벡터들끼리만 유사도 비교가 가능하다. 서로 다른 임베딩 모델이 만든 벡터는 비교할 수 없다.
3-F. BM25 를 PostgreSQL 에 수동 구축
PostgreSQL 에는 BM25 가 내장돼 있지 않다. 핵심은 TF·IDF 이므로 이를 테이블로 직접 관리한다.
- 형태소 분석: 기본 pgvector 이미지는 한국어 형태소 분석이 안 된다. mecab-ko(엔진) + 사전 + textsearch_ko 확장을 설치한 이미지를 써서
to_tsvector('korean', content)로 토큰화한다. - tsvector: 콘텐츠의 텍스트를 토큰 분해해 토큰별 등장 횟수를 담은 타입. 문서의 길이는 텍스트 길이가 아니라 토큰 수다(BM25 의 doc_len·avgdl 도 토큰 수 기준).
- 순색인(forward index): (문서, 토큰) 조합별 등장 횟수(TF) 테이블 — "이 문서에 어떤 토큰이 몇 번" 즉시 조회.
- 역색인(inverted index): 토큰별 등장 문서 수(DF) 테이블 — "이 토큰이 몇 개 문서에 등장" 즉시 조회(IDF 용).
- 통계 테이블: doc_count 와 total_len 을 유지 — 평균 문서 길이(avgdl)를 매번 전체 재계산하지 않고 합과 개수로 증분 관리.
- 트리거: INSERT/UPDATE/DELETE 때마다 세 테이블을 증분 갱신. 삭제면 카운트 하나 깎고 그 문서 길이만큼 빼고, 삽입이면 반대로. 검색 시점이 아니라 미리 만들어 둔다.
✅ 확인: 이 구조의 장점 — BM25 전체가 애플리케이션 서버 없이 온리 쿼리(SQL 만)로 동작한다. 적재 후 CTE(WITH) 쿼리 하나로 BM25 점수를 계산해 랭킹한다.
3-G. 밀집 검색 — pgvector HNSW
책의 OpenAIEmbeddings(text-embedding-3-large) + FAISS.from_documents() 인메모리 방식 대신, 로컬 임베딩 모델로 벡터를 만들어 pgvector 에 진짜 ANN(근사 최근접 이웃) 인덱스인 HNSW 를 구축한다. 인덱스와 벡터가 PostgreSQL 서버에 영구 저장되므로 어떤 프로세스가 접속하든 동일한 인덱스를 그대로 쓴다.
⚠️ 주의 — 차원 제한: pgvector 의 ANN 인덱스(HNSW/IVFFlat)는 vector 타입 기준 2000차원 상한(절반 정밀도 halfvec 타입은 4000차원)이다. 로컬 임베딩 모델(embedding-8b, Qwen3 계열)은 4096차원을 그대로 내놓으므로 인덱스 생성이 실패한다. → MRL(Matryoshka Representation Learning) 학습을 한 임베딩 모델은 벡터 앞부분만 잘라 차원 축소해도 의미가 보존된다. MRL 미지원 모델은 다른 차원 축소 방법이 필요하다.
- 인덱스 전략: 코사인 거리(cosine distance) 기반 HNSW,
m=16(노드당 연결 수),ef_construction=64(구축 시 탐색 폭). - 코사인 유사도는 크기를 배제하고 각도로 의미 유사를 재는 척도다. 1에 가까울수록 유사. 음수 값은 "반대 의미"가 아니므로 그냥 버린다 — 임베딩 공간에서 부정(negation)은 그렇게 표현되지 않는다.
3-H. Ensemble Retriever — RRF 순위 융합
BM25 점수(13점대)와 코사인 유사도(0~1)는 아예 다른 차원의 값이라 직접 합칠 수 없다. 그래서 점수를 버리고 등수만 남긴다 — RRF(Reciprocal Rank Fusion). 각 리트리버의 결과를 순위 역수로 환산해 가중 합산하므로 상위권 가중이 세고 하위권은 자연 감쇠한다. LangChain EnsembleRetriever 의 기본 융합 방식이 RRF 다.
4. 실습·구현 가이드 (Step 1·2·3·4)
Step 1 — 인프라: Docker 로 pgvector + 한국어 형태소 분석 + pgAdmin
pgvector 확장이 설치된 공식 배포 이미지(PostgreSQL 17 기반)에서 시작해 mecab-ko(형태소 분석 엔진) + 사전 + textsearch_ko 확장을 추가로 설치한 이미지를 컨테이너로 띄운다. pgAdmin(dpage/pgadmin4 이미지)도 같이 잡아 두면 테이블·데이터를 GUI 로 확인할 수 있다.
# 접속 설정 (노트북 공통)
PG_HOST = "pgvector"; PG_PORT = 5432
PG_DSN = f"host={PG_HOST} port={PG_PORT} dbname={PG_DB} user={PG_USER} password={PG_PASSWORD}"
임베딩·LLM 은 LM Studio 로컬 서버(OpenAI 호환 API)로 서빙한다. 임베딩 모델은 Qwen3 계열 8B(4096차원)를 사용한다.
embeddings = OpenAIEmbeddings(
model=EMBED_MODEL, base_url=LMSTUDIO_BASE_URL, api_key=LMSTUDIO_API_KEY,
check_embedding_ctx_length=False, # tiktoken 대신 원문 문자열 전송 — LM Studio 호환
chunk_size=8, # 배치 크기: 로컬 서버가 한 번에 받는 텍스트가 많으면 연결이 끊기므로 작게
)
💡 실무 노하우: 임베딩 서버는 요청 병렬도를 낮게(예: 4 이하) 잡아야 로컬 서버가 버틴다. 노트북을 여러 번 재실행할 것이므로 기존 컬렉션·테이블을 제거하고 초기화하는 코드를 각 노트북 앞에 넣어 멱등하게 만든다.
Step 2 — 문서 확장: Parent Document Retriever (practice4-1)
from langchain_text_splitters import RecursiveCharacterTextSplitter
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=1000) # 부모: 컨텍스트 제공
child_splitter = RecursiveCharacterTextSplitter(chunk_size=200) # 자식: 검색 단위
자식 벡터스토어(Chroma)와 부모 문서 저장소(역시 Chroma 컬렉션, key-value)를 만들고, 두 스플리터와 함께 ParentDocumentRetriever 를 생성해 문서를 추가한다. 통합 리트리버에 질의하면 부모 문서가, 벡터스토어에 직접 질의하면 자식 청크가 나오는 차이를 확인한다.
Step 3 — 질의 확장: Multi-Query · HyDE (practice4-2 · 4-3)
retriever = MultiQueryRetriever.from_llm(
retriever=vectorstore.as_retriever(), llm=llm) # 프롬프트 내장 — 지정만 하면 끝
로깅을 설정하면 LLM 이 생성한 하위 질의들을 콘솔에서 확인할 수 있다(시연 데이터: 분할 문서 219개). HyDE 는 가상 문서를 생성하는 프롬프트로 먼저 한 바퀴 돌린 뒤 그 출력을 임베딩해 검색한다 — LangChain 에 HyDE 체인이 내장돼 있다.
📚 참고: langchain 1.x 에서
ParentDocumentRetriever·MultiQueryRetriever·RetrievalQA의 위치가langchain_classic으로 이동했다. 구버전과 신버전 임포트를 폴백으로 함께 두면 버전이 달라도 동작한다. 내부 메서드는 동일하다.
Step 4 — 하이브리드: 수동 BM25 + HNSW + 앙상블 (practice4-4 · 4-5 · 4-6)
문서 적재 — 투자설명서.pdf 를 로드해 분할한다(분할 문서 수: 522).
from langchain_community.document_loaders import PyPDFLoader
docs = PyPDFLoader("투자설명서.pdf").load()
splits = RecursiveCharacterTextSplitter(chunk_size=2000, chunk_overlap=200).split_documents(docs)
BM25(희소) — 순색인 테이블과 트리거를 DDL 로 구성한다.
-- 순색인: (문서, 토큰) 조합별 등장 횟수(TF)
CREATE TABLE IF NOT EXISTS practice4_bm25_term_freq (
doc_id INTEGER NOT NULL REFERENCES practice4_bm25_docs(id) ON DELETE CASCADE,
token TEXT NOT NULL,
tf INTEGER NOT NULL,
PRIMARY KEY (doc_id, token)
);
CREATE INDEX IF NOT EXISTS idx_practice4_bm25_term_freq_token
ON practice4_bm25_term_freq (token);
-- 문서 삽입 시 형태소 분석과 토큰 수(doc_len) 계산을 자동화하는 트리거
CREATE OR REPLACE FUNCTION practice4_bm25_docs_before() RETURNS TRIGGER AS $$
BEGIN
NEW.tsv := to_tsvector('korean', NEW.content); -- mecab-ko 형태소 분석
SELECT COALESCE(SUM(cardinality(positions)), 0) INTO NEW.doc_len
FROM unnest(NEW.tsv) AS u(lexeme, positions, weights); -- 문서 길이 = 토큰 수
RETURN NEW;
END $$;
tsv 필드에 GIN 인덱스를 걸고, 역색인(DF)·통계(doc_count·total_len) 테이블에도 증분 트리거를 건다. 검색은 CTE 쿼리로 BM25 공식을 그대로 계산한다 — SUM( idf * (tf*(k1+1)) / (tf + k1*(1 - b + b*doc_len/avgdl)) ). 시연 데이터에서 상위 5건이 랭킹되고 1등 문서(id 48)가 13점대로 나온다.
밀집(Dense) — langchain_postgres v2 API 로 벡터 테이블과 HNSW 인덱스를 만든다.
from langchain_postgres import PGEngine, PGVectorStore
from langchain_postgres.v2.indexes import HNSWIndex, DistanceStrategy
hnsw_index = HNSWIndex(name="practice4_dense_docs_hnsw",
distance_strategy=DistanceStrategy.COSINE_DISTANCE, m=16, ef_construction=64)
vectorstore.apply_vector_index(hnsw_index)
overwrite_existing=True 로 테이블을 매번 새로 만들면 몇 번을 재실행해도 안전하다. 4096차원 그대로 HNSW 를 걸면 실패하는 것을 먼저 확인하고(예상된 결과), MRL 절단 후 인덱스를 생성한다.
앙상블 — 두 리트리버를 LangChain 리트리버로 래핑해 결합한다. 수동 BM25 는 BaseRetriever 를 상속해 "쿼리 → SQL 검색 → Document 리스트" 로 동작을 바꿔 주기만 하면 된다.
dense_retriever = vectorstore.as_retriever(search_kwargs={"k": 2})
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, dense_retriever], weights=[0.5, 0.5])
마지막으로 RetrievalQA 체인에 앙상블 리트리버를 넣어 최종 답변 생성까지 확인한다.
5. 사례·적용
- 정확 일치 질의: "발행주식의 총수"를 묻자 밀집 검색 쪽이 정답 문단("발행주식의 총수 13,602,977")을 정확히 찾았고, RRF 순위 융합 덕에 이 문단이 앙상블 결과에도 살아남아 최종 답변에 그대로 반영됐다.
- BM25 단독의 빈틈: 같은 코퍼스에서 BM25 단독으로는 키워드가 문서에 안 들어 있으면 못 찾는 사례를 확인 — 하이브리드가 필요한 이유의 실증.
- 서비스 이식: 파이썬으로 개념·구조를 한 번 이해해 두면, 고성능이 필요할 때 이 코드를 베이스로 다른 언어(Rust·TypeScript)로 옮기는 작업은 AI 코딩 도구에 시키면 된다. 라이브러리가 파이썬에만 있으면 그 기능만 파이썬으로 감싸 Docker 에 올려 API 처럼 쓴다.
- 리랭커 예고: 임베딩 0.6B 모델은 리랭커 0.6B 와 세트로 배포된다. 양자화(Q4 등)하면 두 모델 합쳐 수 GB 수준이라 로컬 GPU 에 동시 적재해 크로스 인코더 앞 단계로 쓸 수 있다.
💡 실무 노하우: 검색 품질 문제를 만나면 "에이전틱 루프로 풀 것인가, 멀티벡터 스토어로 풀 것인가"를 먼저 결정하라. LLM 호출 비용에 민감한 서비스라면 DB 레코드를 늘리는 멀티벡터 쪽이 대체로 싸다(§6·§7).
6. 핵심 원리
- 연산은 언제나 메모리와 교환된다. 사인값을 CPU 로 계산하는 대신 모든 라디안의 사인값을 미리 메모리에 잡아 두면 조회만으로 끝나듯이 — 검색 품질도 같다. 에이전틱 루프는 LLM 이 만족할 때까지 질의를 바꿔 재검색하는 "연산 지불" 노선이고, 멀티벡터 스토어는 레코드 100개면 될 DB 에 2,000개를 넣는 "메모리 지불" 노선이다. 임베딩 유사도 비교는 LLM 연산보다 훨씬 싼 연산이므로 DB 를 불리는 쪽이 대체로 경제적이다.
- 임베딩은 같은 손실로 학습된 벡터끼리만 비교 가능하다. 임베딩이 유사도 검색이 되는 이유는 신경망이 같은 손실로 학습됐기 때문이다. 다른 모델의 임베딩과는 비교 자체가 성립하지 않는다.
- 모든 신경망 학습은 긍정 학습이다 — 오답 공간은 우리가 넣어야 한다. 모델은 정답만 배웠지 오답 공간(무한히 크다)을 배운 적이 없다. 프롬프트가 뜻대로 안 되는 주원인이므로 부정 조건·실패 조건·종료 조건을 반드시 명시해야 모델이 바르게 동작한다.
- 미리 계산해 두는 색인 설계. BM25 의 TF·DF·avgdl 을 질의 시점에 계산하지 않고 트리거로 증분 유지한다 — "합과 개수"만 들고 있으면 평균은 언제든 싸게 나온다.
7. 한계·트레이드오프
💡 트레이드오프 요약: 이번 주 기법 전부가 "품질 ↔ 비용(연산·메모리·복잡도)" 교환이다. 무엇을 지불하는지 명확히 알고 선택하라.
- LangChain 은 프로토타이핑용. 생태계·내장 구현이 압도적이라 입문·검증에는 최선이지만, 동시성이 약해 실서비스에 그대로 올리기 어렵다. 강의의 권장 — LangChain 으로 쉽게 짜서 개념을 확정하고, 서비스 전환 시 고성능 스택으로 이식하라.
- HyDE 는 조건부. instruction 을 이해하는 LLM 기반 임베딩 모델(강의 사용: Qwen3 계열)이 주류가 되면서 질문↔답변 공간 문제가 완화됐고, 질의당 LLM 1회 추가 비용이 붙으며, LLM 이 답을 못 지어내는 도메인이면 실패한다.
- HNSW 는 근사(approximate)다. 정확도 손실이 있으나 "합의 볼 수 있는" 수준의 휴리스틱 인덱스다. IVF·PQ(벡터 양자화) 등 대안 인덱스도 pgvector 가 지원한다.
- RRF 는 점수 크기 정보를 버린다. 등수만 남기므로 한쪽 리트리버가 압도적으로 확신한 결과도 순위로만 반영된다. 정규화 후 가중합하는 Convex Combination(CC) 계열이 대안으로 존재한다.
- ts_rank_cd 의 함정: 내장 전문검색 랭킹은 사실상 AND 결합이라, 토큰이 많은 문장형 질의(RAG 의 기본형)에서는 전 토큰 포함 문서가 드물어 랭킹이 무너진다. 짧은 키워드 질의(2~3 단어)에서만 쓸 만하다.
- 코사인 음수 값은 버려라. 임베딩에서 음수 유사도는 "부정"이 아니므로 의미 해석하지 말 것.
8. 다른 접근과 비교
| 축 | 책(교재) 방식 | 강의 방식 | 차이의 이유 |
|---|---|---|---|
| 벡터 저장 | FAISS (인메모리) | pgvector + HNSW (영속) | 실무 데이터 크기·프로세스 공유·재시작 안정성 |
| 부모 문서 저장 | InMemoryStore (RAM) | Chroma 컬렉션 (key-value) | 동일 |
| BM25 | BM25Retriever (인메모리) | PostgreSQL 수동 구축 (SQL 온리) | 인메모리 배제 + 온리 쿼리로 앙상블 |
| 임베딩 | OpenAIEmbeddings(text-embedding-3-large) | LM Studio 로컬 서빙 (Qwen3 계열 8B) | API 비용·로컬 통제 |
- ts_rank_cd vs 수동 BM25: §7 참조 — AND 결합 vs 시그마 가중합.
- 에이전틱 루프 vs 멀티벡터 스토어: 연산 지불 vs 메모리 지불(§6). 둘 다 유효하며 비용 구조로 선택.
- 전용 검색엔진(예: Elasticsearch) vs PostgreSQL 단일화: 강의는 기존 업무 DB 와의 통합·운영 단순성을 들어 PostgreSQL(pgvector) 단일 스택을 택했다. mecab-ko 확장으로 한국어 형태소까지 커버되므로 별도 검색엔진 없이 하이브리드 검색이 성립한다.
- 다음 주 예고 — GraphRAG: 그래프 형태로 구축된 데이터베이스의 그래프 구조 자체를 임베딩해, 질문과 가까운 그래프를 찾아 그 내용을 컨텍스트로 쓰는 방식. 순수 임베딩 검색과의 차이를 다룬다.
📚 참고 (Tier 1 오픈소스): pgvector https://github.com/pgvector/pgvector · LangChain https://github.com/langchain-ai/langchain · ChromaDB https://github.com/chroma-core/chroma · mecab-ko 기반 한국어 전문검색 확장 textsearch_ko
9. 메타인지 자기평가
- Multi-Query·HyDE·Parent Document 를 "질의 확장 / 문서 확장" 두 갈래에 정확히 배치할 수 있는가?
- 희소 벡터를 "0이 많은 벡터"가 아니라 "차원이 분리된 벡터"로, 밀집 벡터를 "역전파 학습의 결과"로 설명할 수 있는가? 값이 비는 현상이 왜 정의가 아니라 결과인지 말할 수 있는가?
- PostgreSQL 에서 BM25 를 구현하려면 어떤 테이블 3종(순색인·역색인·통계)이 필요하고, 트리거가 각각 무엇을 증분 유지하는지 그릴 수 있는가?
- ts_rank_cd 가 RAG 문장형 질의에 부적합한 이유를 "AND vs 시그마"로 설명할 수 있는가?
- pgvector HNSW 의 2000차원 제한에 4096차원 임베딩을 맞추는 방법(MRL 절단)과 그 전제 조건을 아는가?
- BM25 점수와 코사인 유사도를 왜 직접 합산할 수 없고, RRF 가 무엇을 버리고 무엇을 얻는지 평가할 수 있는가?
- 검색 품질 개선을 "연산 ↔ 메모리 교환"으로 환원해, 에이전틱 루프와 멀티벡터 스토어 중 하나를 비용 근거로 고를 수 있는가?
- 프롬프트에 부정·실패·종료 조건을 넣어야 하는 이유를 "긍정 학습과 오답 공간"으로 설명할 수 있는가?
클릭하거나 Space를 눌러 뒤집기