🧠 LLM 엔지니어링

LLM 문서에서 특정 키워드 추출하는 방법 02 (Shopwise 지식기반 예제)

개발자 린다씨 2025. 4. 25. 10:49
반응형

LLM에 넣기 좋은 문서를 만들기 위한 LangChain 문서 청크 분할 가이드 (chunk_size & overlap 이해하기)

# ✂️ 문서를 LLM 입력에 맞게 청크(chunk) 단위로 분할하는 단계
# - 긴 문서는 한 번에 LLM에 넣기 어렵기 때문에, 적절한 길이로 잘라야 함
# - LangChain의 CharacterTextSplitter를 사용해 '문자 기준'으로 분할함

text_splitter = CharacterTextSplitter(
    chunk_size=1000,        # 📏 한 청크당 최대 1,000자까지 포함
    chunk_overlap=200       # 🔁 청크 간에 200자씩 겹치도록 설정 (문맥 끊김 방지용)
)

# 🧩 실제 분할 실행: documents 리스트 내 각 문서를 위 설정대로 나눔
chunks = text_splitter.split_documents(documents)

💡 왜 chunk_overlap을 주는 걸까?

  • 단락이 청크 중간에 끊기면 의미 전달이 어려워집니다.
  • 예를 들어 청크 1 끝부분 내용이 청크 2의 앞부분에도 일부 포함되면
    • LLM이 더 자연스럽고 정확한 답변을 생성해요.

📌 결과 예시 (chunks [0])

Document(
    page_content="Shopwise는 2020년 설립된 전자상거래 플랫폼으로, 고객 중심 배송 시스템을 도입했습니다...",
    metadata={
        'source': 'knowledge-base/staff/jaeyoung.md',
        'doc_type': 'staff'
    }
)

 

⚠️ 메시지 의미:

Created a chunk of size 1088, which is longer than the specified 1000

 

이 메시지는 LangChain의 CharacterTextSplitter를 사용할 때 종종 나타나는 경고인데,

아주 자연스러운 현상이고, 오류는 아닙니다.

 

 chunk_size=1000으로 설정했지만, 분할된 결과 중 일부 청크가 

1,088자처럼 더 길어졌다는 뜻이에요.


🤔 왜 이런 일이 생길까?

LangChain의 CharacterTextSplitter는 단순히 1000자마다 무조건 자르지 않습니다.
청크를 나눌 때 문장이 갑자기 끊기지 않도록, 아래와 같은 기준을 사용합니다:

  • 기본적으로는 개행 문자(\n\n)나 구분자 기준으로 청크를 나누려고 시도함.
  • 만약 적절한 위치를 찾지 못하면, 조금 더 길어져서라도 자연스럽게 끊으려고 함
  • 그래서 결과 청크가 chunk_size보다 약간 클 수도 있음

✅ 해결책 (선택 사항)

문제가 아니므로 그냥 두어도 괜찮지만,
정말로 엄격하게 1000자 이하로 자르길 원한다면, 아래처럼 설정할 수 있습니다:

text_splitter = CharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    length_function=len,         # 길이 측정 기준
    keep_separator=False         # 분리자 포함 여부 (기본 True → False로 하면 좀 더 짧아짐)
)

 

혹은 더 정밀한 제어를 원한다면 RecursiveCharacterTextSplitter를 쓰는 방법도 있어요:

from langchain.text_splitter import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200
)

📦 문서 청크 수 확인하기

len(chunks)는 앞서 text_splitter.split_documents(documents)로 생성된 청크(Chunk)들의 총개수를 확인하는 코드입니다.

# 📦 전체 청크(문서 조각) 개수 확인
# - chunks는 text_splitter를 사용해 분할된 문서 단위 리스트
# - RAG 파이프라인에서 이 청크들을 벡터화(embedding)하고 검색에 활용함
# - 이 값을 통해 임베딩 작업 규모나 예상 처리량을 가늠할 수 있음

len(chunks)

💡 예시

len(chunks)  # 👉 132

 

→ 총 132개의 문서 청크가 생성되었으며,

이후 벡터 저장소(DB)에도 132개 벡터가 들어갈 예정이에요.

 


🔍 특정 문서 청크 미리 보기

# 🔍 7번째 문서 청크 확인 (인덱스는 0부터 시작하므로 6번이 실제 7번째)
# - 각 청크는 LangChain의 Document 객체로 구성됨
# - 이 객체는 다음과 같은 구조를 가짐:
#     - page_content: 잘려진 문서의 본문 내용
#     - metadata: 원래 문서의 경로, 유형(doc_type) 등의 부가 정보

chunks[6]

 

chunks [6]은 분할된 문서 청크 중 7번째 조각을 가져오는 코드입니다.

이걸 이해하면 RAG에서 실제로 LLM이 참고할 context가 어떤 형태인지 감 잡을 수 있어요.

💡 예시 출력 (실제 형태)

Document(
    page_content="Shopwise는 최근 빠른 결제 시스템을 도입하여 고객 편의성을 높였습니다...",
    metadata={
        'source': 'knowledge-base/products/payment.md',
        'doc_type': 'products'
    }
)

🗂️ 문서 유형(doc_type) 목록 확인

# 🗂️ 전체 청크에서 사용된 문서 유형(doc_type) 종류만 모아서 집합(set)으로 추출
# - 각 청크에는 'staff', 'products' 등 원래 폴더명을 기반으로 doc_type 메타데이터가 포함됨
# - set을 사용하면 중복 없이 고유한 유형만 추출됨
doc_types = set(chunk.metadata['doc_type'] for chunk in chunks)

# 📢 발견된 문서 유형을 쉼표로 구분해 출력
# - 어떤 종류의 문서가 knowledge base에 포함되어 있는지 확인 가능
print(f"Document types found: {', '.join(doc_types)}")

 

이 코드는 청크에 포함된 문서들의 유형(doc_type)이 몇 가지인지 확인하는 데 쓰입니다.

💡 예시 출력

Document types found: products, staff

이 출력은 현재 knowledge-base에

  • products 관련 문서
  • staff 관련 문서

가 있다는 걸 의미해요.

✅ 왜 중요해?

  • 벡터 검색 결과를 필터링할 때 활용 가능합니다.
  • 특정 유형 문서만 따로 관리하고 싶을 때도 유용해요.

🔎 특정 키워드가 포함된 청크 검색

# 🔍 전체 청크 중에서 'CEO'라는 단어가 포함된 문서만 찾아서 출력
# - chunk.page_content: 각 문서 청크의 본문 내용
# - 특정 키워드(예: 직책, 이름 등)가 들어간 문서를 필터링할 때 유용함
# - 실시간 검색/디버깅 용도로 자주 사용됨

for chunk in chunks:
    if 'CEO' in chunk.page_content:
        print(chunk)         # 해당 청크의 전체 정보 (본문 + 메타데이터)
        print("_________")   # 시각적으로 청크 구분을 위한 구분선

 

이 코드는 모든 청크를 순회하면서, 내용 안에 'CEO'라는 단어가 포함된 청크만 필터링해서 출력하는 작업입니다.

💡 예시 출력 형태

Document(
    page_content='Jaeyoung is the CEO of Shopwise...',
    metadata={'source': 'knowledge-base/staff/jaeyoung.md', 'doc_type': 'staff'}
)
_________

 

✨ 활용 아이디어

  • '배송', '결제 수단', '고객' 같은 키워드로도 필터링 가능
  • if keyword.lower() in chunk.page_content.lower()처럼 대소문자 무시하도록 개선 가능
  • 조건 추가해서 특정 doc_type만 검색할 수도 있어요.
반응형