LangChain 리트리버, 뭘 보고 골라야 실패하지 않을까?
결론부터: 어떤 벡터 DB나 search_type이 "맞는 선택"인지는 제품명이 아니라 동일한 평가 세트에서 검색 재현율(Recall@k), 근거성(groundedness), p95 지연시간, 질의당 비용, 권한 필터 정확도를 함께 비교해야 나온다. LangChain 공식 문서도 리트리버를 "문자열 질의를 받아 Document 목록을 반환하는 인터페이스"로만 정의하고, 보편적인 최적 k나 임계값은 제시하지 않는다출처. 기본값을 그대로 운영에 올리는 것이 가장 흔한 함정이다.
이 글의 핵심
as_retriever()의 기본k,search_type은 벤치마크 없이 운영에 그대로 쓰면 안 된다.- 검색 품질(Recall@k, MRR)과 답변 품질(정확성, groundedness)은 다른 지표이며 따로 측정해야 한다.
similarity_score_threshold는 벡터 저장소를 바꾸면 같은 숫자를 재사용할 수 없다.- 권한 필터는 검색 "후"가 아니라 검색 "단계"에 적용해야 정보 노출 위험이 없다.
- 비용은 임베딩·재색인·저장·조회·LLM 토큰을 구간별로 나눠 "질의 1,000건" 단위로 기록해야 비교 가능하다.
저희 쪽 실험에서도 처음엔 vector_store.as_retriever(search_type="similarity", search_kwargs={"k": 4})를 그대로 붙이고 넘어갔다. 공식 예제가 k=1로 단일 청크를 시연하는 걸 봤으면서도출처 "일단 4개면 충분하겠지"로 넘긴 게 문제였다. 결과적으로 질문 난이도에 따라 정답 청크가 5~8번째에 있는 경우를 전부 놓쳤고, 프롬프트 토큰은 불필요하게 늘어 LLM 비용만 올라갔다.
k 값은 몇으로 맞춰야 할까?
정답은 "질문 세트로 직접 재봐야 안다"이다. LangChain 공식 문서도 k의 적정값이 문서 구조, 청크 길이, LLM 컨텍스트 예산에 따라 달라진다고 명시하며, k=1, 3, 5, 8처럼 후보를 고정해 평가하는 방식을 제안한다출처. 리트리버는 invoke뿐 아니라 batch도 지원하므로, 동일 질문 묶음을 여러 k에 반복 적용해 Recall@k·MRR을 한 번에 비교할 수 있다출처.
실무에서 자주 생기는 실수는 k를 올릴수록 안전하다고 가정하는 것이다. k가 커지면 정답 청크를 놓칠 확률은 줄지만, 프롬프트에 들어가는 토큰 수와 LLM 입력 비용이 같이 늘고, 관련 없는 청크가 섞여 근거성이 오히려 떨어지는 경우도 있었다. 저희는 k=1,3,5,8 네 조건을 고정 질문 30개에 돌려 Recall@k와 답변 정확성을 따로 기록했는데, k=5에서 Recall이 포화되고 k=8은 토큰만 늘어난 사례가 있었다. 이 결과는 문서 세트마다 다를 수 있으므로 숫자 자체보다 "고정 후보군 + 배치 평가" 절차를 재사용하는 편이 안전하다.
similarity 대신 mmr을 써야 하는 순간은 언제일까?
같은 내용의 청크가 중복으로 반환되거나, 서로 다른 근거를 함께 확보해야 하는 질의라면 search_type="mmr"을 비교 후보로 올려야 한다. PGVector 공식 통합 예제는 as_retriever(search_type="mmr", search_kwargs={"k": 1}) 형태를 사용하며, VectorStoreRetriever는 similarity, mmr, similarity_score_threshold 세 가지 검색 유형을 지원한다출처.
실제로는 "MMR이 더 다양한 결과를 준다"는 기대만으로 전환하면 함정에 빠진다. MMR은 다양성을 늘리는 대신 질의와 가장 가까운 청크 하나만 필요한 단순 사실 질문에서는 오히려 관련성이 떨어지는 청크를 섞어 넣을 수 있다. 저희는 "정의를 묻는 질문"과 "여러 조건을 비교하는 질문"을 나눠 두 그룹으로 평가했는데, 비교형 질문에서만 MMR이 Recall을 개선했고 정의형 질문에서는 similarity가 더 안정적이었다. 즉 질의 유형별로 검색 유형을 바꿔야 할 수도 있다는 뜻이며, 이는 전체 설정을 하나로 고정하면 놓치는 부분이다.
검색이 잘 됐는데 답변이 틀리는 이유는 뭘까?
검색 품질과 답변 품질을 같은 점수로 합쳐서 보기 때문이다. 리트리버는 검색 결과의 품질만 책임지고 답변 생성은 별도 체인에서 처리하므로, "답변이 자연스럽다"는 평가와 "정답 근거가 검색됐다"는 평가는 분리해야 한다출처. LangChain 공식 평가 튜토리얼도 답변 정확성, 답변 관련성, 근거성, 검색 품질을 서로 다른 측정 대상으로 제시한다출처.
저희가 겪은 실패 케이스는 이렇다. Recall@k는 높게 나왔는데(정답 청크가 분명히 k=5 안에 들어 있었는데) 최종 답변에는 검색 결과에 없는 내용이 섞여 있었다. 원인은 프롬프트가 "CONTEXT만 근거로 답변"을 강제하지 않았기 때문이었다. 이후 시스템 프롬프트에 "제공된 CONTEXT만 근거로 답변한다", "근거가 없으면 '문서에서 확인할 수 없습니다'라고 답한다", "source와 page를 표시한다"는 세 조건을 명시한 뒤에야 근거성 점수가 올라갔다출처. 검색이 맞았다고 답변까지 맞는 게 아니라는 걸, 지표를 따로 기록하고 나서야 확인할 수 있었다.
유사도 임계값 숫자, 벡터 DB를 바꿔도 그대로 써도 될까?
쓰면 안 된다. 이게 가장 자주 발생하는 오해다. similarity_score_threshold는 관련성이 낮은 문서를 억지로 반환하지 않도록 막아주는 기능이지만, 임계값의 의미와 스케일은 벡터 저장소와 거리 함수 구현에 따라 달라진다출처. 코사인 거리와 유클리드 거리, 그리고 벡터 저장소별 정규화 방식이 다르면 "0.7"이라는 숫자가 어떤 저장소에서는 느슨한 기준이고 다른 저장소에서는 거의 모든 결과를 걸러내는 기준이 될 수 있다.
저희도 한 번은 테스트용 벡터 저장소에서 튜닝한 임계값을 그대로 운영용 PGVector 인스턴스로 옮긴 적이 있는데, 거의 모든 질의가 "근거 없음"으로 처리되는 상황이 발생했다. 원인은 거리 함수 설정이 달랐던 것이었고, 결국 저장소를 바꿀 때마다 임계값을 재보정하는 절차를 별도 체크리스트에 추가했다. PGVector 같은 PostgreSQL 기반 선택지는 기존 운영 역량과 메타데이터 관리 체계를 재사용할 수 있다는 장점이 있지만출처, 이런 장점이 임계값 재사용 가능성을 의미하지는 않는다.
운영에서 비용은 어디서 새어 나갈까?
비용은 한 항목이 아니라 임베딩 생성, 재색인, 벡터 DB 저장·조회, reranker 호출, LLM 입력 토큰 다섯 구간으로 나눠 측정해야 샌다. 공개 자료에는 LangChain 리트리버 특정 구성의 2025~2026년 실측 비용표가 없으므로, 각 팀이 "질의 1,000건" 또는 "문서 10만 청크 색인" 단위로 직접 기록하는 수밖에 없다. 운영 환경에서는 검색 지연시간, 임베딩 생성 시간, 벡터 DB 조회 시간, LLM 호출 시간을 분리해 측정해야 하며, VectorStoreRetriever는 동기·비동기 호출을 모두 지원하므로 이 네 구간을 각각 트레이싱하는 것이 가능하다출처.
메타데이터 필터링(source, page, tenant_id, access_level 등)도 비용과 직결된다. 권한 필터는 검색 결과를 받은 뒤 걸러내는 방식이 아니라 검색 단계에서 적용해야 하는데, 먼저 전체 문서를 검색하고 나중에 결과를 삭제하면 접근 권한이 없는 문서의 존재 여부나 내용이 중간 계층에 노출될 위험이 있다출처. 이 설계를 잘못 하면 추가 보안 검토·재색인 비용이 발생하므로, 2026년 기준으로 다중 테넌트 환경을 다룬다면 필터 적용 위치를 초기 설계 단계에서 고정해두는 편이 재작업 비용을 줄인다.
핵심 정리
- 리트리버 선택은 제품명 비교가 아니라 동일 평가 세트에서 Recall@k·MRR·groundedness·p95 지연시간·질의당 비용을 함께 재는 문제다.
k와search_type은 기본값을 그대로 쓰지 말고, 질문 유형별로 고정 후보군(k=1,3,5,8, similarity/mmr)을 배치 평가해야 한다.- 검색 품질과 답변 품질은 다른 지표이며, "CONTEXT만 근거"로 강제하는 프롬프트 조건 없이는 groundedness가 떨어질 수 있다.
similarity_score_threshold숫자는 벡터 저장소·거리 함수가 바뀌면 재보정이 필요하다.- 권한 필터는 검색 단계에서 적용해야 하며, 비용은 임베딩·저장·조회·reranker·LLM 토큰 구간별로 분리해 기록해야 비교 가능하다.