13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] LlamaIndex

  • [AI] LlamaIndex 임베딩 오류: 임베딩 검색 시스템 구축과 해결책

    [AI] LlamaIndex 임베딩 오류: 임베딩 검색 시스템 구축과 해결책

    LlamaIndex 임베딩 오류: 임베딩 검색 시스템 구축과 해결

    llamaindex 임베딩 오류는 LlamaIndex 기반 검색 시스템을 처음 붙일 때 가장 자주 만나는 문제입니다. 문서는 분명 들어갔는데 검색이 안 되거나, 임베딩은 생성됐는데 결과가 엉뚱하게 나오고, 벡터 저장소 쪽에서 차원 불일치 같은 에러가 터지기도 하거든요. 저도 처음엔 코드 몇 줄이면 끝날 줄 알았는데, 막상 해보니 임베딩 검색은 데이터 전처리, 청킹(chunking, 문서를 잘게 나누는 방식), 임베딩 모델 선택, 저장소 구성이 다 맞물려 있더라고요.

    특히 RAG 시스템(Retrieval-Augmented Generation, 검색 기반 생성)을 만들다 보면 더 민감합니다. 검색 정확도가 조금만 흔들려도 답변 품질이 바로 떨어지거든요. 그래서 이번 글에서는 제가 홈랩과 사내 테스트 환경에서 반복적으로 부딪혔던 문제를 기준으로, LLM 애플리케이션에서 LlamaIndex 기반 임베딩 검색 시스템을 어떻게 구성하고 어디서 흔히 틀리는지, 또 어떻게 바로잡으면 되는지 정리해보겠습니다.

    llamaindex 임베딩 오류를 이해하기 위한 임베딩 검색 전체 아키텍처 이미지

    LlamaIndex, 임베딩 모델, 벡터 저장소, 질의 흐름을 한눈에 보여주는 개요 이미지입니다.

    1. 왜 llamaindex 임베딩 오류가 자꾸 생길까요

    쉽게 말해 LlamaIndex는 문서를 읽고, 적절히 쪼개고, 임베딩(Embedding, 텍스트를 숫자 벡터로 바꾸는 과정)으로 바꿔서, 나중에 질문이 들어왔을 때 비슷한 내용을 찾아주는 연결 허브 역할을 합니다. 문제는 여기서 어느 한 단계만 어긋나도 결과가 바로 이상해진다는 점입니다.

    • 문서 청킹이 너무 크면 검색 단위가 뭉개집니다.
    • 임베딩 모델이 바뀌었는데 기존 벡터를 재생성하지 않으면 차원 문제가 납니다.
    • 메타데이터(metadata, 문서 부가정보) 필터 조건이 잘못되면 검색 결과가 비어버립니다.
    • 벡터 저장소를 재사용하면서 이전 인덱스와 섞이면 디버깅이 정말 힘들어집니다.

    제가 직접 해보니 대부분의 문제는 라이브러리 자체보다도 데이터 수명주기를 헐겁게 관리해서 생기더라고요. 코드만 보는 게 아니라, 어떤 문서가 어떤 모델로 임베딩됐는지까지 같이 관리해야 합니다.

    2. 핵심 개념 먼저 정리해보겠습니다

    2-1. 임베딩 검색이란?

    임베딩 검색은 키워드 일치만 보는 방식이 아니라, 문장의 의미적 유사성까지 반영해서 비슷한 내용을 찾는 방식입니다. 예를 들어 사용자가 “로그가 너무 많이 쌓여서 디스크가 가득 찼다”고 물었는데, 문서에는 “스토리지 사용량이 급증했다”고 적혀 있어도 비슷한 것으로 잡아낼 수 있거든요.

    2-2. LlamaIndex는 어디에 쓰이나요?

    LlamaIndex는 문서 로딩, 노드 분할, 인덱싱, 검색, 질의 처리 같은 흐름을 묶어줍니다. 쉽게 말해 검색 파이프라인 조립기에 가깝습니다. 직접 구현해도 되지만, 실무에서는 이런 조립 계층이 있으면 훨씬 빨라요. 대신 내부 구조를 모르고 쓰면 장애 지점도 함께 숨겨집니다. 저도 처음엔 “왜 검색은 되는데 답이 안 맞지?” 하고 한참 봤었습니다.

    2-3. 벡터 데이터베이스는 왜 필요할까요?

    문서 수가 적으면 메모리에서도 할 수 있지만, 실제 운영에서는 벡터 저장소가 필요합니다. 이유는 단순합니다. 빠르게 찾고, 다시 불러오고, 메타데이터로 필터링해야 하거든요. Chroma 같은 벡터 데이터베이스가 자주 쓰이고, FAISS 같은 벡터 인덱스 라이브러리도 로컬 검색 테스트에 많이 활용됩니다.

    구성 요소 역할 문제 생기는 지점
    LlamaIndex 문서 처리와 검색 흐름 관리 설정 누락, 인덱스 재사용 실수
    Embedding Model 텍스트를 벡터로 변환 차원 변경, 언어 적합성 문제
    Vector Store 벡터 저장 및 유사도 검색 기존 데이터 충돌, 영속성 경로 꼬임
    Chunking 문서 분할 검색 정확도 저하

    3. 실전 구현: 가장 단순한 임베딩 검색 시스템부터

    여기서는 너무 복잡하게 가지 않고, 로컬에서 재현 가능한 흐름으로 설명드리겠습니다. 실제로 써보면 처음부터 외부 서비스까지 얹는 순간 문제 원인을 분리하기가 어렵더라고요. 그래서 문서 디렉터리 + LlamaIndex + 로컬 임베딩 모델 + Chroma 조합으로 시작하는 걸 추천합니다.

    3-1. 기본 설치

    python -m venv .venv
    source .venv/bin/activate
    pip install llama-index llama-index-embeddings-huggingface llama-index-vector-stores-chroma chromadb sentence-transformers

    여기서는 설치 패키지를 분리해서 적는 게 안전합니다. 최근 LlamaIndex는 코어 패키지와 통합 패키지가 나뉘는 경우가 있어서, 예전처럼 몇 개만 설치하면 import 단계에서 바로 막히기도 하거든요. 저도 이 부분을 대충 넘겼다가 반나절을 날린 적이 있더라고요.

    3-2. 테스트용 문서 준비

    문서가 실제로 로드되는지 확인하려면 아주 짧은 샘플부터 넣어보는 게 좋습니다. 처음부터 큰 문서 묶음으로 가면, 인덱싱 문제인지 검색 문제인지 분리가 잘 안 됩니다.

    mkdir -p data
    cat > data/ops-note.txt <<'EOF'
    장애 대응 시에는 로그 보관 주기와 디스크 사용량을 함께 확인한다.
    임베딩 검색 품질은 문서 청킹 방식과 메타데이터 구성에 큰 영향을 받는다.
    RAG 시스템에서는 검색 정확도가 곧 응답 품질로 이어진다.
    EOF

    3-3. 인덱스 생성 코드

    아래 예시는 임베딩 모델을 명시적으로 고정하고, Chroma 영속 경로를 따로 두는 가장 단순한 구성입니다. llamaindex 임베딩 오류를 줄이려면 이 두 가지부터 분명하게 잡는 게 좋습니다.

    from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
    from llama_index.core.storage.storage_context import StorageContext
    from llama_index.embeddings.huggingface import HuggingFaceEmbedding
    from llama_index.vector_stores.chroma import ChromaVectorStore
    import chromadb
    
    # 임베딩 모델을 명시적으로 고정합니다.
    Settings.embed_model = HuggingFaceEmbedding(
        model_name="sentence-transformers/all-MiniLM-L6-v2"
    )
    
    # 문서를 읽어옵니다.
    documents = SimpleDirectoryReader("data").load_data()
    
    # Chroma 영속 저장 경로와 컬렉션을 분리합니다.
    client = chromadb.PersistentClient(path="./chroma_db")
    collection = client.get_or_create_collection(name="ops_notes")
    vector_store = ChromaVectorStore(chroma_collection=collection)
    storage_context = StorageContext.from_defaults(vector_store=vector_store)
    
    # 인덱스를 생성합니다.
    index = VectorStoreIndex.from_documents(
        documents,
        storage_context=storage_context,
    )
    
    query_engine = index.as_query_engine(similarity_top_k=2)
    response = query_engine.query("디스크 사용량 확인과 관련된 운영 팁을 알려줘")
    print(response)

    이 예제의 핵심은 두 가지입니다. 첫째, 임베딩 모델을 코드에서 명시적으로 고정했다는 점입니다. 둘째, 저장 경로와 컬렉션 이름을 눈에 보이게 분리했다는 점이죠. 저는 이 두 개만 해도 디버깅 난이도가 꽤 줄었습니다.

    llamaindex 임베딩 오류 방지를 위한 설정과 벡터 저장소 연결 흐름 이미지

    임베딩 모델 설정, 문서 로딩, Chroma 컬렉션 연결 순서를 보여주는 구성 이미지입니다.

    4. 청킹과 메타데이터가 임베딩 검색 품질을 좌우합니다

    많이들 임베딩 모델만 바꾸면 검색 성능이 올라갈 거라고 기대하시는데, 실제로는 청킹이 더 크게 작용하는 경우가 많습니다. 특히 한국어 문서는 문단 길이와 문장 연결이 꽤 중요하거든요.

    4-1. 청킹이 너무 크면 생기는 문제

    • 질문과 직접 관련 없는 문장이 한 덩어리에 섞입니다.
    • 검색은 됐는데 답변이 핵심을 못 집습니다.
    • 후처리 단계에서 불필요한 토큰 사용량이 늘어납니다.

    4-2. 청킹이 너무 작으면 생기는 문제

    • 문맥이 잘려서 문서 의미가 흐려집니다.
    • 질문과 부분 일치는 되는데 설명이 빈약합니다.
    • 검색 결과가 산만해집니다.

    제가 해보니 운영 매뉴얼, 장애 기록, 위키 문서는 한 문단 단위보다 조금 더 크게 가져가는 쪽이 안정적인 경우가 많았습니다. 반대로 FAQ나 짧은 규정 문서는 더 잘게 나누는 게 낫더라고요. 정답이 하나 있는 게 아니라, 문서 유형에 따라 청킹 전략을 달리해야 한다가 더 정확합니다.

    4-3. 메타데이터는 꼭 넣으세요

    메타데이터를 넣어두면 나중에 운영 문서만 검색하거나, 개발 환경 문서를 제외하는 식의 필터링이 쉬워집니다. RAG 시스템에서는 이 차이가 생각보다 큽니다.

    from llama_index.core import Document
    
    sample_doc = Document(
        text="배치 작업 실패 시에는 작업 로그와 스케줄러 상태를 함께 점검한다.",
        metadata={
            "source": "runbook",
            "team": "platform",
            "env": "prod"
        }
    )

    부서별 문서가 섞이는 환경에서는 검색 자체는 잘되는데 답변 출처가 엉키는 일이 자주 생깁니다. 이럴 때 메타데이터가 없으면 원인 찾기가 꽤 답답하더라고요.

    5. ⚠️ 흔한 llamaindex 임베딩 오류와 해결책

    이제 본론입니다. 제가 실제로 가장 자주 봤던 llamaindex 임베딩 오류 패턴을 정리해보겠습니다. 여기서 중요한 건 에러 메시지 하나만 보지 말고, 현재 인덱스가 어떤 모델과 어떤 데이터로 생성됐는지까지 같이 확인하는 겁니다.

    5-1. 차원 불일치(dimension mismatch)

    대표적인 증상은 이렇습니다. 기존에 저장된 벡터와 지금 쓰는 임베딩 모델의 출력 차원이 다를 때 발생합니다. 예를 들어 다른 임베딩 모델로 바꿨는데 예전 Chroma 컬렉션을 그대로 재사용하면 바로 꼬입니다.

    ValueError: embedding dimension does not match collection dimensionality

    해결 방법

    1. 현재 사용하는 임베딩 모델을 고정합니다.
    2. 기존 벡터 저장소를 비우거나 새 컬렉션을 생성합니다.
    3. 전체 문서를 다시 임베딩합니다.

    이 오류는 정말 자주 나옵니다. 저도 모델만 바꿔놓고 왜 검색이 안 되지 싶어서 한참 봤는데, 결국 저장소를 새로 안 만들었던 경우가 많았습니다.

    5-2. 검색 결과가 비어 있는 문제

    에러가 아니라 더 답답한 케이스죠. 실행은 되는데 아무것도 안 나옵니다. 이때는 보통 아래 네 가지를 먼저 봅니다.

    • 문서가 실제로 인덱싱됐는지
    • 질문 언어와 문서 언어가 너무 다른지
    • 메타데이터 필터가 과하게 걸렸는지
    • 청킹이 너무 작거나 너무 커서 검색 품질이 깨졌는지

    점검 코드 예시

    documents = SimpleDirectoryReader("data").load_data()
    print(f"loaded docs: {len(documents)}")
    
    query_engine = index.as_query_engine(similarity_top_k=3)
    response = query_engine.query("로그 보관 주기 관련 내용을 찾아줘")
    print(response)

    여기서 문서 개수부터 먼저 보세요. 너무 당연한 얘기 같아도, 파일 경로를 잘못 잡아서 빈 디렉터리를 읽는 경우가 생각보다 많습니다.

    5-3. 한글 검색 품질이 기대보다 낮은 문제

    한국어 문서를 다루는데 영어 중심 임베딩 모델을 쓰면 결과가 애매해질 수 있습니다. 간단한 테스트는 가능해도, 실제 업무 문서에서는 한글 표현의 뉘앙스가 꽤 중요하거든요. 이런 경우엔 문서 샘플을 뽑아서 직접 질의해보고, 필요하면 다국어 또는 한국어 성능이 검증된 임베딩 모델을 따로 비교해보는 편이 낫습니다.

    여기서 조심할 점은 모델 이름만 보고 무조건 더 좋을 거라고 기대하지 않는 겁니다. 제가 직접 해보니 같은 모델이라도 문서 정리 상태와 청킹 방식이 더 크게 작용하는 경우가 많았습니다.

    5-4. 저장은 됐는데 재시작 후 인덱스가 사라진 문제

    이건 영속성(persistence, 재실행 후에도 데이터 유지) 경로 설정을 놓쳤을 때 자주 생깁니다. 메모리 기반으로만 테스트하면 처음엔 잘 되는데, 프로세스를 다시 띄우는 순간 전부 사라지거든요.

    client = chromadb.PersistentClient(path="./chroma_db")

    해결 포인트

    • 로컬 테스트라도 영속 저장 경로를 명시합니다.
    • 컨테이너 환경이면 볼륨 마운트도 같이 확인합니다.
    • 개발 환경과 운영 환경의 경로를 분리합니다.

    5-5. 라이브러리 import 오류

    LlamaIndex는 버전에 따라 통합 패키지 구조가 나뉘어 있어서, 예전 예제를 그대로 붙여넣으면 import 오류가 날 수 있습니다. 특히 Hugging Face 임베딩이나 Chroma 연동은 별도 통합 패키지를 함께 설치해야 하는 경우가 있습니다. 이럴 땐 블로그 글 한 편만 믿지 말고, 현재 설치한 패키지 기준으로 import 경로와 설치 목록을 다시 확인하는 습관이 중요합니다.

    llamaindex 임베딩 오류 유형과 해결 흐름을 보여주는 트러블슈팅 이미지

    차원 불일치, 빈 검색 결과, 한글 검색 품질 저하, 영속성 문제를 분류한 트러블슈팅 이미지입니다.

    6. 운영 관점에서 꼭 넣어야 할 점검 항목

    개발 환경에서는 돌아가는데 운영에서 흔들리는 경우가 있습니다. 특히 LLM 애플리케이션은 처음엔 데모처럼 보여도, 문서가 늘어나면 금방 운영 이슈가 드러납니다.

    1. 인덱싱 로그를 남기세요. 몇 개 문서가 들어갔는지 모르면 장애 때 답이 없습니다.
    2. 임베딩 모델 이름을 설정 파일이나 환경 변수에 명시하세요.
    3. 컬렉션 이름 규칙을 정하세요. 예: 서비스명-환경-모델명.
    4. 재색인(reindex) 절차를 문서화하세요.
    5. 샘플 질의 테스트를 CI나 배포 체크리스트에 넣으세요.

    특히 마지막이 중요합니다. 검색 시스템은 애플리케이션이 죽지 않아도 품질이 망가질 수 있거든요. 그래서 저는 최소한 “예상 답이 나와야 하는 질문” 몇 개를 고정해서 배포 후 확인합니다. 이거 해두면 진짜 편하더라고요.

    7. 검증: 임베딩 검색 결과를 어떻게 확인하면 좋을까요

    단순히 답변 문장만 보는 건 부족합니다. 검색 시스템은 무엇이 검색됐는지, 왜 그 결과가 선택됐는지를 같이 봐야 합니다.

    7-1. 샘플 질의 만들기

    처음부터 평가 자동화를 크게 만들 필요는 없습니다. 아래처럼 짧은 질문 몇 개만 고정해도 품질 변화를 꽤 빨리 잡아낼 수 있습니다.

    test_queries = [
        "디스크 사용량 점검 방법은?",
        "RAG 시스템에서 검색 정확도가 중요한 이유는?",
        "로그 보관 주기와 관련된 운영 팁은?"
    ]
    
    for q in test_queries:
        result = query_engine.query(q)
        print("Q:", q)
        print("A:", result)
        print("-" * 40)

    이런 식으로 돌려보면 적어도 문서 내용이 질문과 연결되는지 빠르게 감이 옵니다. 저는 여기서 안 맞으면 모델 바꾸기 전에 먼저 문서와 청킹부터 다시 봅니다.

    7-2. 기대 결과 체크리스트

    • 질문과 관련된 문서가 실제로 검색되는가
    • 서로 다른 질문에 같은 답만 반복되지 않는가
    • 메타데이터 필터 적용 시 결과가 합리적인가
    • 문서 추가 후 재검색 결과가 자연스러운가

    이 검증 과정을 거치면 단순히 “돌아간다” 수준이 아니라 실제 서비스 가능한지 판단할 수 있습니다. 결국 임베딩 검색은 정확도와 재현성이 핵심이니까요.

    llamaindex 임베딩 오류 점검을 위한 검색 결과 검증 대시보드 이미지

    샘플 질의별 검색 결과와 검증 체크리스트를 시각적으로 보여주는 결과 이미지입니다.

    8. 정리: 제가 다시 구축한다면 이렇게 하겠습니다

    정리해보면 llamaindex 임베딩 오류는 대부분 신기한 버그라기보다 기본기 문제에 가까웠습니다. 모델이 바뀌었는데 저장소를 재사용했다든지, 문서가 제대로 안 들어갔다든지, 청킹이 엉망이었다든지 하는 식이죠. 저도 처음엔 라이브러리 탓을 많이 했는데, 실제로 써보니까 시스템 설계와 운영 습관이 훨씬 중요했습니다.

    • 임베딩 모델은 명시적으로 고정합니다.
    • 벡터 저장소는 환경별로 분리합니다.
    • 문서 청킹 전략을 문서 유형별로 다르게 가져갑니다.
    • 샘플 질의로 배포 후 검증합니다.
    • 재색인 절차를 표준화합니다.

    혹시 지금 검색은 되는데 결과가 영 이상하신가요? 그럼 모델 교체부터 하지 마시고, 먼저 문서 구조와 인덱스 상태를 보시는 걸 추천드립니다. 여기서 갈리는 경우가 정말 많더라고요. RAG 시스템 품질은 생성 모델보다 검색 품질에서 더 크게 갈리는 때가 많습니다.

    다음 글에서는 메타데이터 필터링을 더 적극적으로 써서, 팀별 문서 분리 검색과 운영 문서 우선 검색을 어떻게 구성하는지 다뤄볼 예정입니다. 이전 글에서 다룬 로그 수집 파이프라인 최적화 내용도 함께 보면 흐름이 더 잘 보이실 겁니다.

    LlamaIndex 임베딩 검색 시스템 구축 시 꼭 확인해야 할 체크리스트를 요약한 이미지입니다.

    9. FAQ: 짧지만 자주 받는 질문

    Q1. 검색이 되긴 되는데 정확도가 낮습니다

    대부분은 모델보다 청킹과 문서 정리가 먼저입니다. 문서 중복, 너무 긴 단락, 불필요한 머리말이 섞이면 품질이 떨어집니다.

    Q2. 벡터 데이터베이스는 꼭 써야 하나요?

    작은 테스트는 메모리 기반으로도 가능하지만, 운영 환경이나 문서량 증가를 생각하면 Chroma 같은 벡터 저장소를 일찍 붙이는 편이 관리가 수월합니다.

    Q3. 한글 문서도 바로 잘 되나요?

    가능은 하지만 문서 성격과 모델 특성에 따라 차이가 있습니다. 샘플 질의를 직접 만들어 검증하는 과정은 꼭 필요합니다.

    Q4. 가장 먼저 확인할 한 가지는 뭔가요?

    지금 쓰는 임베딩 모델과 기존 인덱스가 서로 맞는지부터 보시면 됩니다. 차원 불일치나 품질 저하의 시작점이 여기인 경우가 많습니다.

  • [AI] LlamaIndex RAG 시스템 구축 실패 사례: 흔한 문제와 디버깅 전략

    [AI] LlamaIndex RAG 시스템 구축 실패 사례: 흔한 문제와 디버깅 전략

    [AI/LLM] LlamaIndex RAG 시스템 구축 실패 사례: 흔한 문제와 디버깅 전략

    LlamaIndex RAG 시스템을 처음 붙일 때 많은 분들이 비슷한 지점에서 막히시더라고요. 문서는 잘 넣은 것 같은데 답이 엉뚱하게 나오고, 로그를 봐도 어디서 망가졌는지 감이 안 오고, 심지어 AI 에이전트(Agent) 문제인 줄 알았는데 알고 보니 검색 단계가 이미 틀어져 있던 경우도 많습니다. 저도 홈랩에서 이것저것 붙여 보면서 삽질 좀 했습니다 ㅎㅎ 처음엔 모델 탓인가 싶었는데, 실제로는 데이터 적재(Ingestion, 문서 수집 및 변환), 청킹(Chunking, 문서 분할), 검색(Retrieval, 관련 문맥 찾기), 응답 합성(Response Synthesis, 답변 생성) 중 하나가 어긋난 경우가 대부분이었습니다.

    이번 글은 제품 홍보나 멋진 데모가 아니라, LlamaIndex RAG를 구성하다가 망했던 패턴을 정리한 실패 사례 중심 글입니다. 특히 RAG 시스템 문제, LlamaIndex 오류, RAG 디버깅, AI 에이전트 실패를 한 번에 점검할 수 있는 흐름으로 정리해 보겠습니다. 혹시 “문서는 넣었는데 왜 이렇게 못 찾지?” 같은 경험 있으신가요? 그거 생각보다 정상입니다. 중요한 건 감으로 고치는 게 아니라, 단계별로 끊어서 확인하는 겁니다.

    LlamaIndex RAG 전체 아키텍처를 보여주는 홈랩 다이어그램

    문서 수집부터 청킹, 벡터 저장, 검색, 응답 생성까지 흐름을 한눈에 보는 개요 이미지입니다.

    LlamaIndex RAG를 쉽게 말하면 어디서 망가질까요?

    쉽게 말해 RAG(Retrieval-Augmented Generation, 검색 증강 생성)는 “모델이 원래 알고 있는 것”에만 기대지 않고, 질문이 들어오면 관련 문서를 먼저 찾아서 그 문맥을 바탕으로 답하게 만드는 구조입니다. LlamaIndex는 이 과정을 구성하기 쉽게 만들어 주는 프레임워크 쪽에 가깝고요.

    제가 직접 해보니, 실패 지점은 생각보다 단순했습니다.

    • 문서 적재 단계: 파일은 읽었는데 내용이 이상하게 잘렸습니다.
    • 인덱싱(Indexing, 검색용 구조화): 벡터 저장은 됐는데 실제 검색 품질이 낮았습니다.
    • 리트리버(Retriever, 관련 문맥 검색기): 질문과 맞는 조각을 못 가져왔습니다.
    • 응답 합성: 검색은 맞았는데 모델이 답변을 엉성하게 만들었습니다.
    • 영속화(Persistence, 디스크 저장): 재시작 후 이전 상태가 사라지거나 오래된 인덱스를 계속 읽었습니다.

    여기서 중요한 포인트! LlamaIndex RAG는 한 덩어리처럼 보이지만, 실제 디버깅은 단계별 분리 진단이 핵심입니다. 검색이 틀렸는데 프롬프트만 계속 고치면 시간만 날립니다.

    실패를 줄이는 기본 설계: 먼저 관측 가능한 구조로 만드세요

    저는 예전엔 일단 돌아가게만 만들고 나중에 로그를 붙였었는데요, 그 방식은 RAG에서 진짜 비효율적이더라고요. 처음부터 “문서가 어떻게 잘렸는지”, “질문에 대해 어떤 노드(Node, 검색 가능한 문서 조각)가 선택됐는지”, “최종 답변이 어떤 근거를 썼는지”가 보여야 합니다.

    구간 자주 보이는 증상 실제 원인 후보 우선 확인할 것
    문서 로딩 파일은 읽히는데 답변이 비어 있음 본문 추출 실패, 인코딩 문제, 예상과 다른 폴더 로드된 문서 수와 본문 샘플
    청킹 검색 결과가 너무 짧거나 문맥이 끊김 chunk_size 과소, overlap 부족 분할된 노드 샘플
    검색 관련 없는 문서가 자주 나옴 질문-문서 표현 불일치, 메타데이터 필터 오류 retriever 결과 목록
    응답 생성 찾아놓고도 엉뚱한 답변 프롬프트 제약, 컨텍스트 과다/과소 source nodes와 최종 응답 비교
    저장/재로딩 재실행 후 결과가 달라짐 인메모리 상태 의존, persist 누락 저장 경로와 로딩 경로

    LlamaIndex RAG 실전 구현: 최소 구성부터 시작하기

    처음부터 벡터 DB(Vector Database, 벡터 저장소)까지 크게 벌이지 말고, 최소한의 로컬 흐름으로 시작하는 걸 추천드립니다. 이유는 간단합니다. 실패 지점을 줄여야 하거든요.

    1. 문서를 읽는다.
    2. 인덱스를 만든다.
    3. 리트리버와 쿼리 엔진을 분리해서 테스트한다.
    4. 결과가 괜찮으면 그다음에 영속화와 외부 저장소를 붙인다.

    1. 설치와 기본 실행

    python -m venv .venv
    source .venv/bin/activate
    pip install llama-index
    

    여기서는 버전 핀을 일부러 박지 않았습니다. 글의 목적이 특정 릴리스 사용기가 아니라, 디버깅 흐름 자체에 있기 때문입니다.

    2. 가장 단순한 인덱스 생성

    from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
    
    # data/ 폴더 아래 문서를 읽습니다.
    documents = SimpleDirectoryReader("data").load_data()
    
    # 가장 단순한 형태의 인덱스를 만듭니다.
    index = VectorStoreIndex.from_documents(documents)
    
    # 검색과 응답 생성을 분리해서 확인할 수 있도록 둘 다 만듭니다.
    retriever = index.as_retriever()
    query_engine = index.as_query_engine()
    
    question = "장애 대응 절차를 요약해줘"
    retrieved_nodes = retriever.retrieve(question)
    response = query_engine.query(question)
    
    print("[RETRIEVED NODES]")
    for i, node in enumerate(retrieved_nodes, start=1):
        print(f"--- node {i} ---")
        print(node.text[:300])
    
    print("[FINAL RESPONSE]")
    print(response)
    

    이 코드에서 핵심은 retriever와 query_engine을 따로 본다는 점입니다. 처음엔 이게 뭔가 싶었는데, 실제로 써보니까 이 분리가 디버깅 시간을 확 줄여주더라고요.

    3. 청킹을 직접 통제하는 적재 파이프라인

    from llama_index.core import Document
    from llama_index.core.ingestion import IngestionPipeline
    from llama_index.core.node_parser import SentenceSplitter
    from llama_index.core.extractors import TitleExtractor
    
    sample_docs = [
        Document(text="장애 조치 문서 예시입니다. 원인 분석, 임시 조치, 영구 조치가 포함됩니다."),
    ]
    
    pipeline = IngestionPipeline(
        transformations=[
            SentenceSplitter(chunk_size=256, chunk_overlap=32),
            TitleExtractor(),
        ]
    )
    
    nodes = pipeline.run(documents=sample_docs)
    
    for i, node in enumerate(nodes, start=1):
        print(f"NODE {i}")
        print(node.text)
        print(node.metadata)
    

    청킹은 진짜 중요합니다. 저는 처음에 chunk_size를 너무 작게 잡아서 문서 문맥이 다 잘려 나갔었는데, 검색 결과는 그럴듯하게 보여도 실제 답변은 자꾸 뜬구름 잡더라고요.

    LlamaIndex RAG 문서 청킹과 리트리버 동작을 설명하는 이미지

    문서가 여러 조각으로 분할되고 질문에 따라 관련 노드가 선택되는 구조를 설명하는 이미지입니다.

    4. 캐시와 저장 경로를 명시하기

    from llama_index.core import StorageContext, load_index_from_storage
    
    # 인덱스를 저장합니다.
    index.storage_context.persist(persist_dir="./storage")
    
    # 저장된 인덱스를 다시 로드합니다.
    storage_context = StorageContext.from_defaults(persist_dir="./storage")
    loaded_index = load_index_from_storage(storage_context)
    
    loaded_query_engine = loaded_index.as_query_engine()
    print(loaded_query_engine.query("장애 대응 절차를 요약해줘"))
    

    여기서 많이 터집니다. LlamaIndex는 기본적으로 인메모리(in-memory, 메모리 상주) 상태를 많이 쓰기 때문에, 재시작 후에도 같은 동작을 기대한다면 저장과 로딩 경로를 명확히 관리해야 합니다.

    ⚠️ 흔한 LlamaIndex 오류와 RAG 디버깅 전략

    이제부터는 제가 실제로 자주 밟았던 실패 패턴입니다. 증상만 보면 비슷한데 원인은 꽤 다릅니다.

    문제 1. 문서는 읽었는데 답변이 너무 일반적입니다

    이 경우 많은 분이 모델 성능부터 의심하시는데요, 사실은 검색 단계에서 충분한 근거가 안 들어간 경우가 많습니다.

    • 문서가 너무 잘게 쪼개져 핵심 문맥이 끊겼는지 봅니다.
    • 질문과 문서 표현이 다르면 검색 품질이 확 떨어집니다.
    • 리트리버가 뽑은 노드 본문을 직접 읽어 봅니다.

    제가 직접 해보니, 최종 답변보다 retrieved_nodes 출력을 먼저 보는 습관이 정말 중요했습니다. 답이 이상하면 먼저 “뭘 찾았는가”를 봐야 합니다.

    문제 2. 재색인했는데 결과가 안 바뀝니다

    이건 캐시나 저장 디렉터리 때문에 생기는 경우가 많습니다. 특히 테스트하면서 같은 경로를 계속 재사용하면 오래된 데이터가 남아 있을 수 있습니다.

    rm -rf ./storage
    rm -rf ./pipeline_storage
    

    물론 운영 환경에서는 이렇게 단순 삭제하면 안 됩니다. 다만 로컬 검증 단계에서는 “정말 새로 인덱싱된 게 맞나?”를 확인하는 용도로 한 번쯤 초기화가 필요하더라고요.

    문제 3. 검색은 맞는데 최종 답변이 엉뚱합니다

    이건 응답 합성 단계 문제일 가능성이 높습니다. 쉽게 말해 모델이 가져온 컨텍스트를 잘 못 쓰는 거죠.

    • 너무 많은 컨텍스트를 넣어 핵심이 묻히지 않았는지 봅니다.
    • 프롬프트에서 근거 기반 답변을 강하게 요구하는지 봅니다.
    • 답변과 함께 source nodes를 출력해 비교합니다.

    근데 여기서 중요한 포인트가 하나 있습니다. 검색 품질이 80인데 생성 품질이 20이면 프롬프트 손봐야 하고, 검색 품질이 20인데 생성만 만지면 계속 헛바퀴 돕니다.

    문제 4. AI 에이전트 실패처럼 보이는데 사실 RAG 실패입니다

    도구 호출(Tool Calling, 외부 기능 호출)이나 에이전트 라우팅이 문제인 줄 알았는데, 막상 까보면 검색된 문맥이 비어 있는 경우가 있습니다. 저도 처음엔 에이전트 흐름도를 한참 봤었는데요, 결국 원인은 리트리버였습니다. 그래서 저는 에이전트를 붙이기 전에 항상 아래 순서로 검증합니다.

    1. retriever.retrieve() 결과를 먼저 확인
    2. query_engine.query() 결과와 비교
    3. 그 다음에만 agent/tool 레이어 확인

    문제 5. 운영 환경에서만 이상합니다

    개발 환경에서는 한글 문서가 잘 검색됐는데 운영에서만 품질이 달라지는 경우도 있었습니다. 이런 건 대개 데이터셋 차이, 적재 타이밍 차이, 저장소 경로 차이, 혹은 문서 전처리 차이로 이어집니다. 이럴 때는 “운영 모델이 다르다” 같은 큰 가설보다, 실제로 어떤 문서가 들어갔는지를 먼저 비교하는 게 낫습니다.

    검증 방법: 정답률보다 먼저 파이프라인을 눈으로 확인하세요

    저는 RAG를 검증할 때 처음부터 화려한 평가 지표를 붙이지 않습니다. 물론 평가(Evaluation, 성능 측정)는 중요하지만, 초기에는 육안 검증이 훨씬 빠릅니다.

    1. 질문 10개를 만든다.
    2. 각 질문마다 검색된 노드 상위 결과를 저장한다.
    3. 최종 답변과 근거 문장을 같이 본다.
    4. 틀린 답변은 검색 실패인지 생성 실패인지 분류한다.
    test_questions = [
        "장애 보고 절차는 어떻게 되나요?",
        "임시 조치와 영구 조치의 차이는 무엇인가요?",
        "점검 창구는 어디인가요?",
    ]
    
    for question in test_questions:
        nodes = retriever.retrieve(question)
        answer = query_engine.query(question)
    
        print("=" * 40)
        print("Q:", question)
        print("[TOP NODES]")
        for node in nodes[:3]:
            print(node.text[:200])
        print("[ANSWER]")
        print(answer)
    

    실제로 써보니까, 이 정도만 해도 어디가 문제인지 금방 보입니다. 드디어 됐다! 싶은 순간도 보통 여기서 오더라고요.

    LlamaIndex RAG 검증 과정에서 검색 노드와 답변을 비교하는 대시보드 이미지

    질문, 검색된 문서 조각, 최종 응답을 나란히 놓고 비교하는 검증 화면 예시입니다.

    실패를 줄이는 운영 체크리스트

    LlamaIndex RAG를 조금 오래 운영하다 보면, 결국 반복 확인 항목이 정해집니다. 저는 아래 체크리스트를 배포 전 마지막 관문처럼 씁니다.

    • 문서 수와 예상 파일 수가 맞는가
    • 청크 샘플을 3개 이상 직접 읽어 봤는가
    • 검색 결과 상위 노드가 질문 의도와 맞는가
    • 재시작 후에도 같은 인덱스를 로드하는가
    • 캐시와 저장 디렉터리 충돌이 없는가
    • 에이전트 문제로 보기 전에 검색 결과를 확인했는가
    실패 유형 대응 우선순위 빠른 처방
    답변이 너무 일반적 높음 청킹과 검색 결과부터 확인
    재색인 후 결과 동일 높음 persist 경로와 캐시 정리 확인
    운영에서만 품질 저하 중간 실제 적재 문서와 경로 비교
    AI 에이전트 실패처럼 보임 높음 agent 이전에 retriever 단독 테스트

    자주 묻는 질문: RAG 시스템 문제를 어디서부터 봐야 하나요?

    Q1. LlamaIndex 오류가 나지 않아도 시스템이 틀릴 수 있나요?

    네, 그게 더 흔합니다. 에러 없이도 잘못된 문맥을 검색하면 결과는 충분히 틀릴 수 있습니다. 그래서 RAG 디버깅은 예외 메시지보다 중간 산출물 확인이 더 중요합니다.

    Q2. 청킹만 잘하면 해결되나요?

    아닙니다. 청킹은 출발점일 뿐이고, 메타데이터, 저장 경로, 검색기 설정, 응답 합성까지 같이 봐야 합니다.

    Q3. AI 에이전트 실패와 RAG 실패는 어떻게 구분하나요?

    에이전트를 떼고 retriever와 query_engine을 각각 테스트해 보시면 됩니다. 에이전트를 제거했는데도 답이 틀리면 대개 RAG 쪽입니다.

    마무리: 화려한 튜닝보다 관측 가능한 구조가 먼저입니다

    이번 글에서 말씀드리고 싶었던 건 딱 하나입니다. LlamaIndex RAG는 잘 만들면 강력하지만, 실패했을 때는 “어디서 틀어졌는지 보이게” 설계해야 한다는 점입니다. 저도 처음엔 모델만 계속 바꿔 봤었는데, 결국 해결은 대부분 로그, 노드 출력, 저장 경로 확인에서 나왔습니다. 사실 이런 건 멋이 없거든요. 근데 운영에서는 이런 기본기가 제일 셉니다.

    다음 글에서는 검색 품질이 낮을 때 메타데이터 필터링과 질문 정규화 전략을 어떻게 붙이는지 다뤄볼 예정입니다. 이전 글 참고 형식으로 이어서 보시면 흐름 잡기 편하실 겁니다.

    LlamaIndex RAG 디버깅 체크리스트와 실패 유형 요약 이미지

    실패 유형별 점검 순서와 대응 우선순위를 한 장으로 정리한 요약 이미지입니다.

    참고한 공식 문서