RAG 문서 청킹 마스터하기: 효율적인 검색 시스템 구축을 위한 청킹 전략 가이드

AI 기술 자료6개월 전 업데이트 AI 공유 서클
8.1K 00

만약 RAG 앱이 원하는 결과를 제공하지 못하므로 청크 전략을 다시 검토해야 할 때입니다.더 나은 청킹은 더 정확한 검색을 의미하며, 궁극적으로 더 높은 품질의 응답으로 이어집니다.

그러나 청킹은 만능 접근 방식이 아니며 절대적으로 최적의 접근 방식은 없습니다. 프로젝트의 구체적인 요구 사항, 문서 특성, 예산 및 기타 요인에 따라 가장 적합한 전략을 고려하고 선택해야 합니다.

 

청킹의 품질이 RAG 응답 품질에 직접적인 영향을 미치는 이유는 무엇인가요?

이 글을 읽으셨다면 청킹과 RAG의 기본 개념에 대해 이해하셨을 것입니다. 간단히 요약하자면, RAG의 핵심 아이디어는 LLM을 만드는 것입니다. 주어진 상황에 맞는 정보를 바탕으로 질문에 답하세요.이는 LLM이 풍부한 지식 베이스를 보유하고 있지만 지식 업데이트에 지연이 있고 비공개 데이터에 직접 액세스할 수 없기 때문입니다. LLM은 풍부한 지식창고를 보유하고 있지만 지식 업데이트에 지연이 있고 비공개 데이터에 직접 액세스할 수 없기 때문입니다.

RAG는 관련 문서 조각(즉, 컨텍스트)을 프롬프트에 삽입하고 이러한 조각을 기반으로 답변을 생성하도록 LLM에 지시함으로써 LLM 자체의 단점을 보완합니다. 컨텍스트는 데이터베이스 쿼리, 인터넷 검색 또는 PDF 문서에서 추출하는 등 다양한 방법으로 얻을 수 있습니다.

효율적인 RAG 애플리케이션을 구축하는 데는 두 가지 주요 과제가 있습니다:

  1. LLM의 컨텍스트 창 제한GPT-2 및 GPT-3과 같은 초기 LLM은 컨텍스트 창이 작아 한 번에 처리할 수 있는 텍스트의 양이 제한적이었습니다. 지금은 더 큰 컨텍스트 창을 지원하는 모델이 있지만, 그렇다고 해서 전체 문서를 LLM에 밀어 넣을 수 있는 것은 아닙니다.
  2. 컨텍스트 노이즈 문제LLM의 문맥 창이 충분히 크더라도 제공된 문맥 정보에 질문과 관련이 없는 내용(노이즈)이 많이 포함되어 있으면 LLM의 이해와 판단에 영향을 미쳐 응답의 품질이 저하되거나 심지어 환각으로 이어질 수 있습니다.

이러한 문제를 해결하기 위해문서 청크이 기술이 탄생했습니다. 핵심 아이디어는 큰 문서를 의미적으로 일관된 작은 세그먼트(청크)로 분할한 다음, 검색 단계에서 가장 관련성이 높은 청크만 LLM에 컨텍스트로 제공하는 것입니다.

문서 청킹에는 단순한 문장 및 단락 청킹, 복잡한 시맨틱 청킹, 에이전트 청킹 등 다양한 방법이 있습니다. 적절한 청킹 전략을 선택하는 것은 RAG 시스템의 효율성과 최종 응답의 품질에 직접적인 영향을 미치기 때문에 중요합니다. 적절한 청킹 전략을 선택하는 것은 RAG 시스템의 검색 효율성과 최종 응답의 품질에 직접적인 영향을 미치므로 매우 중요합니다.

이 글에서는 보다 강력한 RAG 애플리케이션을 구축하는 데 도움이 되는 몇 가지 고급적이고 실용적인 문서 청킹 전략에 대해 자세히 살펴보겠습니다. 단순한 문장 및 단락 청킹은 건너뛰고 실제 RAG 애플리케이션에서 더 가치 있는 기술에 초점을 맞추겠습니다.

다음으로 제가 배우고 연습한 몇 가지 청크 전략에 대해 자세히 설명하겠습니다.

 

재귀적 문자 분할: 빠르고 경제적인 기본 접근 방식

재귀적 문자 분할은 가장 기본적인 방법이라고 생각할 수 있습니다. 실제로 가장 기본적인 방법이지만, 제 생각에는 여전히 가장 일반적으로 사용되고 비용 효율적인 청킹 기법 중 하나입니다. 이해하기 쉽고, 구현이 간단하며, 빠르고 저렴하기 때문에 특히 신속한 프로토타이핑과 비용에 민감한 프로젝트에 적합합니다.

재귀적 문자 분할의 핵심 아이디어는 다음과 같습니다.고정 크기 슬라이딩 창 사용를 클릭하고 창 간 겹침을 허용합니다. 미리 설정된 블록 크기와 겹치는 문자 수로 문서의 시작 위치에서 창을 계속 밀어서 텍스트 블록을 생성합니다.

다음 그림은 재귀적 문자 분할이 어떻게 작동하는지 보여줍니다:

精通 RAG 文档分块策略:构建高效检索系统的分块策略指南재귀적 문자 분할의 작동 원리 - Thuwarakesh가 그린 그림
블록 크기를 20자로 설정하고 겹치는 문자 수를 2로 설정한 그림의 예는 슬라이딩 창을 사용하여 텍스트 블록을 생성하는 방법을 보여줍니다.

재귀적 문자 분할의 장점은 단순성과 효율성입니다. 이를 통해 대용량 문서를 빠르게 처리하고 연례 보고서를 분 단위로 분류할 수 있습니다. Langchain에서 재귀적 문자 분할을 구현하는 것은 매우 간단합니다:

from langchain.text_splitter import RecursiveCharacterTextSplitter
text = """
Hydroponics is an intelligent way to grow veggies indoors or in small spaces. In hydroponics, plants are grown without soil, using only a substrate and nutrient solution. The global population is rising fast, and there needs to be more space to produce food for everyone. Besides, transporting food for long distances involves lots of issues. You can grow leafy greens, herbs, tomatoes, and cucumbers with hydroponics.
"""
rc_splits = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
chunk_size=20, chunk_overlap=2
).split_text(text)

슬라이딩 창 변형

실제로 슬라이딩 윈도우의 크기와 슬라이딩 스텝은 필요에 따라 다양하게 변경할 수 있습니다:

  • 문자 기반과 토큰 기반 슬라이딩 창위의 예는 문자 기반 슬라이딩 창입니다. 문자 기반 슬라이딩 창을 사용할 수도 있습니다. 토큰 슬라이딩 창을 사용하여 블록 크기가 LLM 처리와 더 일치하도록 보장합니다.Langchain의 리커시브 문자 텍스트 스플리터 문자 모드와 토큰 모드가 모두 지원됩니다.
  • 동적 창 크기:: 고정된 창 크기가 재귀적 문자 분할의 특징이지만, 특정 시나리오에서는 동적 창 크기 조정도 고려할 수 있습니다. 예를 들어 문장의 길이나 단락의 구조에 따라 창 크기를 적응적으로 조정하여 블록의 의미적 무결성을 보장할 수 있습니다.

제한 사항

재귀적 문자 분할은위치 기반 청킹 접근 방식. 이는 단순히 문서에서 위치적으로 인접한 텍스트는 의미적으로도 관련이 있다고 가정합니다. 그러나 이 가정은 많은 경우에 적용되지 않습니다.

생각하기: 위치 기반 청킹이 RAG의 성능을 저하시키는 이유는 무엇인가요? 시맨틱 청킹을 구현하고 더 나은 결과를 얻으려면 어떻게 해야 할까요?

예를 들어, 같은 장에서 작성자가 여러 가지 다른 개념을 먼저 논의한 후 최종적으로 관련시킬 수 있습니다. 재귀적 문자 분할만 사용하는 경우, 동일한 의미 단위로 분류되어야 하는 콘텐츠가 분리되거나 의미적으로 관련이 없는 콘텐츠가 결합되어 검색에 영향을 미칠 수 있습니다.

한계에도 불구하고 재귀적 문자 분할은 RAG를 시작하는 데 이상적입니다. 프로토타이핑 단계에서 또는 단순히 구조화된 문서에 만족스러운 결과를 제공하는 경우가 많습니다. 재귀적 문자 분할은 프로젝트에 높은 비용과 속도가 요구되는 경우에도 가치 있는 옵션입니다.

 

시맨틱 청킹: 텍스트의 의미를 이해하기 위한 청킹 접근 방식

시맨틱 청킹은 다음과 같은 고급 청킹 전략입니다.텍스트의 위치 정보에만 의존하는 대신 텍스트의 의미론적 의미를 더 깊이 이해할 수 있습니다. 핵심 아이디어는 문서의 의미가 크게 바뀔 때 문서를 세분화하여 각 청크가 가능한 한 하나의 주제를 중심으로 이루어지도록 하는 것입니다.

아래 그림은 시맨틱 청크의 작동 방식을 보여줍니다:

精通 RAG 文档分块策略:构建高效检索系统的分块策略指南시맨틱 청크 작업 - Thuwarakesh가 그린 작품
예를 들어 처음 두 문장은 수경 재배 농업에 대해 논의하고, 다음 두 문장은 글로벌 이슈로 이동했다가 다시 수경 재배로 돌아오는 식입니다. 시맨틱 청킹은 이러한 의미론적 주제 전환을 인식하고 주제 전환 시 세그먼트화할 수 있습니다.

재귀적 문자 분할과 달리, 시맨틱 청킹은 일반적으로 가변 길이의 블록을 생성합니다. 고정된 수의 문자나 토큰을 미리 설정하는 대신 시맨틱 무결성을 기반으로 블록의 경계를 결정합니다.

시맨틱 청킹을 위한 주요 단계

시맨틱 청크의 어려움은 다음과 같습니다.문장의 의미를 이해하도록 프로그래밍**했습니다. 이는 일반적으로 다음과 같은 도움을 받아 수행됩니다.모델 임베딩를 구현할 수 있습니다. OpenAI의 텍스트 임베딩-3-large를 사용하면 문장을 벡터 표현으로 변환할 수 있으며, 벡터는 문장의 의미 정보를 포착할 수 있습니다. 의미적으로 유사한 문장은 공간적으로도 더 가까운 벡터를 갖습니다. **

시맨틱 청킹의 일반적인 프로세스는 다음 5단계로 구성됩니다:

  • 초기 블록 구성하기: 처음에 문서를 문장 또는 단락으로 분할하고 인접한 문장 또는 단락을 초기 블록으로 결합합니다.
  • 블록 임베딩 생성임베딩 모델을 사용하여 각 초기 블록에 대한 벡터 임베딩을 생성합니다.
  • 블록 사이의 거리 계산인접한 블록 간의 의미적 거리를 계산합니다. 일반적으로 사용되는 거리 메트릭에는 코사인 거리 등이 있습니다. 거리가 클수록 의미적 차이가 커집니다.
  • 분할 지점 결정: 거리 임계값을 설정합니다. 인접한 블록 사이의 거리가 임계값을 초과하면 블록을 분리하여 새로운 의미 블록을 형성합니다. 임계값의 선택은 특정 문서와 실험 효과에 따라 조정해야 합니다.
  • 시각화(선택 사항)블록 사이의 거리를 시각화하면 청킹 효과를 보다 직관적으로 이해하고 임계값을 조정하는 데 도움이 됩니다.

다음 코드는 시맨틱 청킹을 구현하는 방법을 보여줍니다:

# Step 1 : Create initial chunks by combining concecutive sentences.
# ------------------------------------------------------------------
#Split the text into individual sentences.
sentences = re.split(r"(?<=[.?!])\s+", text)
initial_chunks = [
{"chunk": str(sentence), "index": i} for i, sentence in enumerate(sentences)
]
# Function to combine chunks with overlapping sentences
def combine_chunks(chunks):
for i in range(len(chunks)):
combined_chunk = ""
if i > 0:
combined_chunk += chunks[i - 1]["chunk"]
combined_chunk += chunks[i]["chunk"]
if i < len(chunks) - 1:
combined_chunk += chunks[i + 1]["chunk"]
chunks[i]["combined_chunk"] = combined_chunk
return chunks
# Combine chunks
combined_chunks = combine_chunks(initial_chunks)
# Step 2 : Create embeddings for the initial chunks.
# ------------------------------------------------------------------
# Embed the combined chunks
chunk_embeddings = embeddings.embed_documents(
[chunk["combined_chunk"] for chunk in combined_chunks]
# If you haven't created combined_chunk, use the following.
# [chunk["chunk"] for chunk in combined_chunks]
)
# Add embeddings to chunks
for i, chunk in enumerate(combined_chunks):
chunk["embedding"] = chunk_embeddings[i]
# Step 3 : Calculate distance between the chunks
# ------------------------------------------------------------------
def calculate_cosine_distances(chunks):
distances = []
for i in range(len(chunks) - 1):
current_embedding = chunks[i]["embedding"]
next_embedding = chunks[i + 1]["embedding"]
similarity = cosine_similarity([current_embedding], [next_embedding])[0][0]
distance = 1 - similarity
distances.append(distance)
chunks[i]["distance_to_next"] = distance
return distances
# Calculate cosine distances
distances = calculate_cosine_distances(combined_chunks)
# Step 4 : Find chunks with significant different to it's previous ones.
# ----------------------------------------------------------------------
import numpy as np
threshold_percentile = 90
threshold_value = np.percentile(cosine_distances, threshold_percentile)
crossing_points = [
i for i, distance in enumerate(distances) if distance > threshold_value
]
len(crossing_points)
# Step 5 (Optional) : Create a plot of chunk distances to get a better view
# -------------------------------------------------------------------------
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
def visualize_cosine_distances_with_thresholds_multicolored(
cosine_distances, threshold_percentile=90
):
# Calculate the threshold value based on the percentile
threshold_value = np.percentile(cosine_distances, threshold_percentile)
# Identify the points where the cosine distance crosses the threshold
crossing_points = [0]  # Start with the first segment beginning at index 0
crossing_points += [
i
for i, distance in enumerate(cosine_distances)
if distance > threshold_value
]
crossing_points.append(
len(cosine_distances)
)  # Ensure the last segment goes to the end
# Set up the plot
plt.figure(figsize=(14, 6))
sns.set(style="white")  # Change to white to turn off gridlines
# Plot the cosine distances
sns.lineplot(
x=range(len(cosine_distances)),
y=cosine_distances,
color="blue",
label="Cosine Distance",
)
# Plot the threshold line
plt.axhline(
y=threshold_value,
color="red",
linestyle="--",
label=f"{threshold_percentile}th Percentile Threshold",
)
# Highlight segments between threshold crossings with different colors
colors = sns.color_palette(
"hsv", len(crossing_points) - 1
)  # Use a color palette for segments
for i in range(len(crossing_points) - 1):
plt.axvspan(
crossing_points[i], crossing_points[i + 1], color=colors[i], alpha=0.3
)
# Add labels and title
plt.title(
"Cosine Distances Between Segments with Multicolored Threshold Highlighting"
)
plt.xlabel("Segment Index")
plt.ylabel("Cosine Distance")
plt.legend()
# Adjust the x-axis limits to remove extra space
plt.xlim(0, len(cosine_distances) - 1)
# Display the plot
plt.show()
return crossing_points
# Example usage with cosine_distances and threshold_percentile
crossing_poings = visualize_cosine_distances_with_thresholds_multicolored(
distances, threshold_percentile=bp_threashold
)

거리 시각화를 위한 세본 차트:

精通 RAG 文档分块策略:构建高效检索系统的分块策略指南시맨틱 청킹 시본 다이어그램을 설명하는 데 사용 - Thuwarakesh가 그렸습니다.
그림에서 블록 사이의 거리가 임계값 0.12를 초과하면 이전 블록이 하나의 큰 의미 블록으로 병합됩니다. 결국 다양한 길이의 시맨틱 블록 6개가 생성되었습니다.

시맨틱 청킹의 장점과 적용 가능한 시나리오

시맨틱 청킹의 장점은 다음과 같은 기능입니다.문서의 의미 구조를 더 잘 포착하고 의미적으로 관련된 텍스트 조각을 함께 집계하여 RAG 시스템의 검색 품질을 향상시킵니다. 다음 유형의 문서를 처리하는 데 더 적합합니다:

  • 복잡한 구조와 다양한 주제의 문서여러 개의 하위 주제를 포함하는 긴 보고서, 기술 문서, 책 등입니다.
  • 시맨틱 점프가 많은 문서작성자는 글을 쓸 때 생각이 이리저리 옮겨 다닐 수 있는데, 의미 덩어리를 사용하면 이러한 글쓰기 스타일을 더 잘 수용할 수 있습니다.

재귀적 문자 분할과 비교했을 때, 의미적 청크의계산 비용이 더 많이 들고** 속도가 느려집니다. 이는 주로 벡터 계산과 거리 메트릭을 포함해야 하기 때문입니다. 따라서 리소스가 제한된 시나리오에서는 장단점을 고려해야 합니다.

 

에이전트 청킹: 인간의 이해를 모방한 청킹 전략

에이전트 청킹은 지능형 청킹을 향한 한 걸음 더 나아간 것입니다. It사람이 문서를 읽고 이해하는 방식에서 착안한 LLM은 청크 작업을 지원하는 '지능형 에이전트'로 사용됩니다.

인간의 독서 습관은 완전히 선형적이지 않습니다.에이전트 청킹은 이러한 인간의 이해 과정을 모방하려고 시도합니다. 우리는 글을 읽을 때 주제에서 주제, 개념에서 개념으로 이동하며 머릿속으로 문서의 논리적 구조를 구축합니다. 에이전트 청킹은 이러한 인간의 이해 과정을 모방하려고 시도합니다.

앞의 두 가지 방법과 달리 에이전트 청킹은은 문서에서 의미적으로 유사한 콘텐츠가 연속적으로 나타난다고 가정하지 않습니다**. 흩어져 있지만 의미적으로 관련된 문서 조각을 모아 사람의 인식과 더 잘 어울리는 의미적 덩어리를 형성할 수 있습니다.

에이전트 청킹 워크플로

에이전트 청크의 핵심 아이디어는 다음과 같습니다.LLM이 사람처럼 문서를 '읽어' 문서의 핵심 개념과 주제를 파악하고 이를 기반으로 청킹을 수행하도록 합니다. 일반적인 에이전트 청킹 프로세스에는 다음이 포함됩니다:

  • 제안서 작성: 문서의 각 문장을 보다 독립적인 명제로 변환합니다. 예를 들어, 알 수 없는 대명사를 목적어로 대체하여 각 문장에 보다 완전한 의미적 의미를 부여할 수 있습니다.
  • 빌딩 블록 컨테이너: 의미적으로 관련된 명제에 대해 청크 컨테이너를 하나 이상 만듭니다. 각 청크 컨테이너에는 해당 컨테이너의 주제를 설명하는 제목과 요약이 있을 수 있습니다.
  • 에이전트 주도형 명제 할당LLM을 에이전트로 사용하여 명제를 하나씩 '읽고' 명제가 어느 블록 컨테이너에 속해야 하는지 결정합니다.
    • 에이전트가 해당 제안이 기존 블록 컨테이너 중 하나의 주제와 관련이 있다고 판단하면 해당 컨테이너에 추가됩니다.
    • 에이전트는 제안이 새로운 토픽을 제안한다고 생각하면 제안을 담을 새 블록 컨테이너를 생성합니다.
  • 컨테이너 재처리 차단블록 컨테이너의 사후 처리(예: 컨테이너 내의 명제를 기반으로 보다 정제된 블록 요약 및 제목 생성).

다음 코드는 에이전트 청킹의 구현을 보여줍니다:

from langchain import hub
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.pydantic_v1 import BaseModel, Field
# Step 1: Convert paragraphs to propositions.
# --------------------------------------------
# Load the propositioning prompt from langchain hub
obj = hub.pull("wfh/proposal-indexing")
# Pick the LLM
llm = ChatOpenAI(model="gpt-4o")
# A Pydantic model to extract sentences from the passage
class Sentences(BaseModel):
sentences: List[str]
extraction_llm = llm.with_structured_output(Sentences)
# Create the sentence extraction chain
extraction_chain = obj | extraction_llm
# NOTE: text is your actual document
paragraphs = text.split("\n\n")
propositions = []
for i, p in enumerate(paragraphs):
propositions = extraction_chain.invoke(p
propositions.extend(propositions)
# Step 2: Create a placeholder to store chunks
chunks = {}
# Step 3: Deine helper classes and functions for agentic chunking.
class ChunkMeta(BaseModel):
title: str = Field(description="The title of the chunk.")
summary: str = Field(description="The summary of the chunk.")
def create_new_chunk(chunk_id, proposition):
summary_llm = llm.with_structured_output(ChunkMeta)
summary_prompt_template = ChatPromptTemplate.from_messages(
[
(
"system",
"Generate a new summary and a title based on the propositions.",
),
(
"user",
"propositions:{propositions}",
),
]
)
summary_chain = summary_prompt_template | summary_llm
chunk_meta = summary_chain.invoke(
{
"propositions": [proposition],
}
)
chunks[chunk_id] = {
"summary": chunk_meta.summary,
"title": chunk_meta.title,
"propositions": [proposition],
}
def add_proposition(chunk_id, proposition):
summary_llm = llm.with_structured_output(ChunkMeta)
summary_prompt_template = ChatPromptTemplate.from_messages(
[
(
"system",
"If the current_summary and title is still valid for the propositions return them."
"If not generate a new summary and a title based on the propositions.",
),
(
"user",
"current_summary:{current_summary}\n\ncurrent_title:{current_title}\n\npropositions:{propositions}",
),
]
)
summary_chain = summary_prompt_template | summary_llm
chunk = chunks[chunk_id]
current_summary = chunk["summary"]
current_title = chunk["title"]
current_propositions = chunk["propositions"]
all_propositions = current_propositions + [proposition]
chunk_meta = summary_chain.invoke(
{
"current_summary": current_summary,
"current_title": current_title,
"propositions": all_propositions,
}
)
chunk["summary"] = chunk_meta.summary
chunk["title"] = chunk_meta.title
chunk["propositions"] = all_propositions
# Step 5: The main functino that creates chunks from propositions.
def find_chunk_and_push_proposition(proposition):
class ChunkID(BaseModel):
chunk_id: int = Field(description="The chunk id.")
allocation_llm = llm.with_structured_output(ChunkID)
allocation_prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"You have the chunk ids and the summaries"
"Find the chunk that best matches the proposition."
"If no chunk matches, return a new chunk id."
"Return only the chunk id.",
),
(
"user",
"proposition:{proposition}" "chunks_summaries:{chunks_summaries}",
),
]
)
allocation_chain = allocation_prompt | allocation_llm
chunks_summaries = {
chunk_id: chunk["summary"] for chunk_id, chunk in chunks.items()
}
best_chunk_id = allocation_chain.invoke(
{"proposition": proposition, "chunks_summaries": chunks_summaries}
).chunk_id
if best_chunk_id not in chunks:
best_chunk_id = create_new_chunk(best_chunk_id, proposition)
return
add_proposition(best_chunk_id, proposition)

명제화의 예

原始文本
===================
A crow sits near the pond. It's a white one.
命题化文本
==================
A crow sits near the pond. This crow is a white one.

에이전트 청킹의 장점과 과제

에이전트 청크가장 큰 장점은 유연성과 지능**입니다. 문서의 심층적인 의미 구조를 더 잘 이해하고 인간의 인지에 더 부합하는 의미 덩어리를 생성할 수 있으며 특히 다음과 같은 유형의 문서를 처리하는 데 능숙합니다**.

  • 비선형 구조 문서예를 들어, 생각의 전환이 많고 배경 지식이나 암시적인 정보가 많이 포함된 문서가 이에 해당합니다.
  • 단락과 섹션에 걸쳐 정보를 통합해야 하는 문서에이전트 청킹을 사용하면 문서의 여러 위치에 흩어져 있는 의미론적으로 관련된 조각을 집계할 수 있습니다.

하지만 에이전트 청킹도 몇 가지 문제에 직면해 있습니다:

  • 높은 비용에이전트 청킹을 사용하려면 LLM을 자주 호출해야 하므로 계산 및 시간 비용이 많이 듭니다.
  • 프로젝트 종속성 프롬프트에이전트 청킹의 효과는 프롬프트의 디자인에 따라 크게 달라집니다. 프롬프트가 효과적으로 청킹되도록 안내하려면 프롬프트를 신중하게 설계해야 합니다.
  • 결과의 불확실성LLM의 출력에 약간의 불확실성이 있어 청킹 결과가 불안정할 수 있습니다.

에이전트 청킹 적용 시나리오

에이전트 청크는 비용이 더 많이 들지만 RAG의 효율성이 중요한 일부 시나리오에서는 여전히 고려할 만한 옵션입니다. 예시:

  • 전문 분야의 지식 기반예를 들어 법률, 의학, 금융 등의 분야의 지식베이스는 매우 높은 검색 정확도와 회상률을 필요로 합니다.
  • 복잡한 질문과 답변 시스템추론과 정보 통합이 필요한 복잡한 질문을 처리해야 하는 질문 및 답변 시스템입니다.

심층 읽기:에이전트 청킹: AI 에이전트 기반 시맨틱 텍스트 청킹

 

다양한 문서 형식에 대한 청크 전략

이전 논의에서는 주로 일반 텍스트를 청크 처리하는 데 중점을 두었습니다. 하지만 실제 업무에서는 마크다운, HTML, PDF, 코드 등 다양한 문서 형식을 자주 접하게 됩니다. 다양한 형식의 경우, 문서의 구조적 정보를 최대한 활용하려면 보다 정교한 청킹 전략이 필요합니다.

마크다운 및 HTML 문서 청크

마크다운 및 HTML 문서에는 제목, 단락, 목록, 코드 블록 등과 같은 구조화된 태그 정보가 있습니다. 다음을 수행할 수 있습니다.보다 정확한 청킹을 위해 이러한 레이블을 청킹**의 기준으로 사용하세요. **

  • 제목별 차단: 각 제목과 그 아래의 콘텐츠를 별도의 블록으로 취급합니다. 이는 뚜렷한 섹션이 있는 명확한 구조의 문서에 적합합니다.
  • 청크: 각 단락을 청크로 취급합니다. 단락은 일반적으로 의미적으로 완전한 단위이며 기본 청크 단위로 적합합니다.
  • 청크: 제목과 단락을 결합하여 청크 분할합니다. 예를 들어, 먼저 문서를 1단계 제목으로 분할한 다음 각 1단계 제목 아래의 콘텐츠를 단락별로 분할합니다.

예: HTML 태그 기반 청킹(Python)

from bs4 import BeautifulSoup
html_text = """
<h1>Section 1</h1>
<p>This is the first paragraph of section 1.</p>
<p>This is the second paragraph of section 1.</p>
<h2>Subsection 1.1</h2>
<ul>
<li>List item 1</li>
<li>List item 2</li>
</ul>
"""
soup = BeautifulSoup(html_text, 'html.parser')
chunks = []
# 按 h1 标题分块
for h1_tag in soup.find_all('h1'):
chunk_text = h1_tag.text + "\n"
next_sibling = h1_tag.find_next_sibling()
while next_sibling and next_sibling.name not in ['h1', 'h2']: #  假设按 h1 和 h2 分级
chunk_text += str(next_sibling) + "\n" #  保留 HTML 标签,或 next_sibling.text  只保留文本
next_sibling = next_sibling.find_next_sibling()
chunks.append(chunk_text)
#  可以类似地处理 h2, p, ul, ol 等标签
print(chunks)

PDF 문서 청크

PDF는 기본적으로 텍스트 콘텐츠와 인쇄 정보가 혼합된 조판 형식이기 때문에 PDF 문서를 청크하는 작업은 비교적 복잡합니다. PDF를 문자나 줄 단위로 직접 분할하면 의미적 무결성이 손상될 수 있습니다.

PDF 청크의 주요 단계는 일반적으로 다음과 같습니다:

  • PDF 텍스트 추출PDF 구문 분석 라이브러리 사용(예: PyPDF2, pdfminer 또는 보다 전문화된 구조화되지 않은.io)를 사용하여 PDF 파일에서 텍스트 콘텐츠를 추출합니다.
  • 텍스트 정리 및 전처리노이즈 문자 제거, 줄 바꿈 처리, OCR 오류 수정 등을 수행할 수 있습니다.
  • 구조화된 정보 추출제목, 머리글 및 바닥글, 표, 목록 등과 같은 PDF에서 구조화된 정보를 추출해 보세요. 일부 고급 PDF 파싱 라이브러리(예: unstructured.io)는 구조화된 정보를 추출하는 데 도움을 줄 수 있습니다.
  • 청크 전략 옵션: 추출된 텍스트 콘텐츠와 구조 정보를 기반으로 적절한 청킹 전략(예: 시맨틱 청킹, 재귀적 문자 분할 등)을 선택합니다.

STH에 주목하세요.unstructured.io 는 PDF를 포함한 다양한 문서 형식을 처리하고 문서에서 구조화된 정보를 추출할 수 있는 강력한 도구로, PDF 청크 프로세스를 간소화합니다.

코드 문서 청크

코드 문서(예: Python, Java, C++ 코드 파일)를 청킹하려면 코드의 구문 구조와 논리적 단위를 고려해야 합니다. 단순히 줄이나 문자로 코드를 분할하면 코드의 무결성과 실행 가능성이 손상될 수 있습니다.

코드 문서를 청크화하는 일반적인 전략은 다음과 같습니다:

  • 기능/클래스별 청크: 각 함수 또는 클래스를 별도의 블록으로 취급합니다. 함수와 클래스는 일반적으로 코드의 논리적 단위입니다.
  • 코드 블록별 청크: 코드에서 논리 블록(예: 루프, 조건문, 시도 예외 블록 등)을 식별하여 각 블록을 청크로 취급합니다.
  • 코드 주석 청크와 결합코드 주석은 일반적으로 코드의 기능 및 논리에 대한 설명입니다. 코드 주석과 관련 코드 블록은 전체적으로 묶을 수 있습니다.

인공물다양한 프로그래밍 언어의 코드를 구문 분석하고 구문 구조에 따라 코드를 쉽게 청크할 수 있는 추상 구문 트리(AST)를 생성하는 트리시터와 같은 구문 분석 도구를 사용하면 코드의 구조적 분석 및 청크에 도움을 받을 수 있습니다.

 

올바른 청크 크기와 겹침 선택하기

청크 크기와 청크 오버랩은 청크 정책에서 RAG 시스템의 성능에 직접적인 영향을 미치는 두 가지 중요한 매개 변수입니다.

  • 청크 크기: 각 청크에 포함된 텍스트의 양입니다. 청크 크기가 너무 작으면 의미론적 정보가 불완전할 수 있고, 청크 크기가 너무 크면 노이즈가 발생하여 검색 정확도가 떨어질 수 있습니다.
  • 오버랩 크기인접한 블록 간에 겹치는 텍스트의 양입니다. 겹침의 목적은 문맥의 연속성을 보장하고 블록 경계에서 정보 손실을 방지하는 것입니다.

올바른 청크 크기와 겹침은 어떻게 선택하나요?

올바른 청크 크기와 중첩을 선택하는 데 절대적으로 최적의 답은 없으며 일반적으로문서 기능노래로 응답실험 효과를 확인합니다. 다음은 몇 가지 경험 법칙과 제안 사항입니다:

  • 휴리스틱 방법::
    • 문장/단락 길이 기준: 문서의 평균 문장 길이 또는 단락 길이를 청크 크기에 대한 기준으로 분석할 수 있습니다. 예를 들어 평균 문단 길이가 150 토큰인 경우 청크 크기를 150-200 토큰으로 설정할 수 있습니다.
    • LLM 컨텍스트 창을 고려하세요.블록 크기는 LLM의 컨텍스트 창 제한을 초과하지 않도록 너무 크지 않아야 합니다. 동시에 블록에 충분한 의미 정보를 포함할 수 있도록 너무 작아서는 안 됩니다.
  • 실험 및 평가:
    • 반복적 튜닝청크 크기와 겹침 매개변수의 초기 세트(예: 청크 크기는 500 토큰, 겹침은 50 토큰)를 설정하고 RAG 시스템을 구축한 후 평가합니다. 그런 다음 점차적으로 파라미터를 조정하고 검색 및 Q&A 효과의 변화를 관찰하여 최적의 파라미터 조합을 선택합니다.
    • 지표 평가검색을 위해 Recall@k, Precision@k, NDCG(정규화된 할인 누적 이득) 등과 같은 적절한 평가 지표를 사용해 RAG 시스템의 성능을 정량화합니다. 이러한 메트릭은 다양한 청킹 전략의 효과를 객관적으로 평가하는 데 도움이 될 수 있습니다.

랭체인의 평가 도구

Langchain은 RAG 시스템의 평가와 매개변수 조정을 지원할 수 있는 다양한 평가 도구를 제공합니다. 예를 들어, DatasetEvaluator 및 RetrievalQAChain과 같은 도구를 사용하면 다양한 청킹 전략, 검색 모델 및 LLM 모델의 조합에 대한 효과를 자동으로 평가하는 데 도움이 됩니다.

 

청크 전략의 효과 평가하기

올바른 청킹 전략을 선택한 후에는 그 효과를 어떻게 평가할 수 있을까요? "더 나은 청킹은 더 나은 검색을 의미한다"고 하지만 "더 나은"을 어떻게 정량화할 수 있을까요? 청킹 전략의 장점을 평가하기 위해서는 몇 가지 지표가 필요합니다.

다음은 청킹 전략이 RAG 시스템의 성능에 미치는 영향을 평가하는 데 도움이 되는 몇 가지 일반적인 평가 메트릭입니다:

  • 지표 검색:
    • 리콜(Recall@k)는 검색된 Top-k 결과 중 관련 문서(또는 청크)의 비율을 나타냅니다. 리콜이 높을수록 청크 전략에 의해 더 많은 관련 정보가 검색된다는 뜻입니다.
    • 정확도(정밀도@k)는 검색된 상위 k개의 결과 중 실제 관련성이 있는 문서(또는 청크)의 비율을 나타냅니다. 정확도가 높을수록 검색 결과의 품질이 높습니다.
    • NDCG(정규화된 할인 누적 이득)는 검색된 결과의 관련성 수준과 위치를 고려하는 보다 세분화된 순위 품질 평가 지표로, NDCG가 높을수록 검색 순위의 품질이 우수함을 나타냅니다.
  • Q&A 지표:
    • 답변 관련성연관성: LLM이 생성한 답변이 질문과 얼마나 연관성이 있는지 평가합니다. 답변 관련성이 높을수록 RAG 시스템이 검색된 정보를 기반으로 의미 있는 답변을 더 잘 생성할 수 있습니다.
      • 답변 정확도/충실성LLM이 생성한 답변이 검색된 문맥 정보에 충실한지 평가하여 '착각'과 부정확한 정보를 피합니다. 답변 정확도가 높을수록 청킹 전략이 신뢰할 수 있는 문맥을 제공하고 LLM이 더 신뢰할 수 있는 답변을 생성하도록 안내할 수 있음을 나타냅니다.
      • 유창하고 일관성 있는 답변주로 LLM 자신의 능력에 영향을 받지만, 좋은 청킹 전략은 간접적으로 답변의 유창성과 일관성을 향상시킬 수도 있습니다. 예를 들어 시맨틱 청킹은 보다 일관성 있는 문맥을 제공하고 LLM이 보다 자연스러운 언어를 생성하는 데 도움이 됩니다.

평가 도구 및 방법론

  • 수동 평가:: 가장 직접적이고 신뢰할 수 있는 방법입니다. 인간 평가자가 미리 정의된 평가 기준에 따라 RAG 시스템의 검색 결과와 Q&A 답변을 평가하도록 초대됩니다. 수동 평가의 단점은 비용과 시간이 많이 들고 주관적일 수 있다는 점입니다.
  • 자동화된 평가:: 예를 들어 자동화된 평가 지표 및 도구 사용:
    • 지표 검색예를 들어 리콜, 정확도, NDCG 등은 표준 정보 검색 평가 도구(예 rank_bm25sentence-transformers 등)를 사용하여 자동 계산을 수행합니다.
    • Q&A 지표일부 NLP 평가 지표(예: BLEU, ROUGE, METEOR, BERTScore 등)를 사용하여 답변 품질 평가에 도움을 받을 수 있습니다. 하지만 자동화된 Q&A 평가 지표에는 여전히 한계가 있으며 수동 평가를 완전히 대체할 수는 없다는 점에 유의해야 합니다.
    • 랭체인 평가 도구Langchain은 다음과 같은 다양한 통합 평가 도구를 제공합니다. DatasetEvaluator 노래로 응답 RetrievalQAChain이는 RAG 시스템의 자동화된 평가 프로세스를 간소화합니다.

평가 프로세스에 대한 권장 사항

  1. 평가 데이터 세트 구축:: 질문과 해당 표준 답변이 포함된 평가 데이터 세트를 준비합니다. 데이터 세트는 가능한 한 RAG 시스템에 대한 일반적인 애플리케이션 시나리오와 질문 유형을 포함해야 합니다.
  2. 평가 지표 선택:: 평가 목적에 따라 적절한 검색 및 질의응답 지표를 선택합니다. 수동 평가와 자동 평가를 동시에 사용할 수 있습니다.
  3. RAG 시스템 실행청킹 전략, 검색 모델, LLM 모델의 다양한 조합을 사용하여 평가 데이터 세트에서 RAG 시스템을 실행하여 평가 결과를 기록합니다.
  4. 분석 및 비교다양한 전략의 평가 지표를 비교하고 강점과 약점을 분석하여 최적의 전략 조합을 선택합니다.
  5. 반복적 최적화평가 결과를 바탕으로 청킹 전략, 검색 모델, LLM 모델의 파라미터를 지속적으로 조정하여 반복적으로 최적화함으로써 RAG 시스템의 성능을 개선합니다.

 

요약: 자신에게 가장 적합한 청크 전략을 선택하세요.

이 백서에서는 기본적인 재귀적 문자 분할부터 더 스마트한 시맨틱 청킹과 에이전트 청킹, 다양한 문서 형식에 대한 정교한 청킹 전략, 청크 크기와 중첩 매개변수를 선택하고 평가하는 방법까지 RAG 애플리케이션에서 중요한 문서 청킹 기술에 대한 심도 있는 논의를 제공합니다.

핵심 사항 검토

  • 청크 질량이 RAG 질량을 결정합니다.좋은 청킹 전략은 고성능 RAG 시스템 구축의 초석입니다.
  • 일률적인 청크 전략은 없습니다.: 청킹 전략은 저마다 장단점이 있으며 다양한 시나리오에 적합합니다. 프로젝트의 특정 요구사항, 문서 특성 및 리소스 제약 조건에 따라 가장 적합한 전략을 선택해야 합니다.
  • 재귀적 문자 분할프로토타입 제작 및 비용에 민감한 프로젝트에 간편하고 빠르며 경제적입니다.
  • 시맨틱 청크텍스트 의미론을 이해하고 의미적으로 일관된 청크를 생성하면 검색 품질이 향상되지만 계산 비용이 많이 듭니다.
  • 에이전트 청크인간의 이해를 시뮬레이션하고 더 스마트하고 유연하며 복잡한 문서를 처리할 수 있지만 비용이 많이 들고 프롬프트 엔지니어링이 복잡합니다.
  • 다양한 포맷을 위한 청크마크다운, HTML, PDF, 코드 등 다양한 형식의 경우 문서의 구조적 정보를 활용하려면 세분화된 청킹 전략이 필요합니다.
  • 청크 크기 및 겹침문서 특성 및 실험 결과에 따라 조정해야 하며, 절대적인 최적값은 없습니다.
  • 평가가 핵심:: 평가 지표와 수동 평가를 통해 청킹 전략의 효과를 정량화하고 반복적인 최적화를 수행합니다.

어떻게 선택하나요? 제 조언입니다.

  • 신속한 프로토타이핑:: 우선 시도재귀적 문자 분할RAG는 RAG의 프로토타입을 빠르게 구축하고 시스템의 타당성을 검증할 수 있는 방법입니다.
  • 더 높은 품질을 위한 노력RAG 품질에 대한 수요가 많고 컴퓨팅 리소스가 허용하는 경우 다음을 시도해 볼 수 있습니다.시맨틱 청크.
  • 복잡한 문서 처리:: 복잡한 구조와 의미 점프가 있는 문서의 경우.에이전트 청크선호되는 옵션일 수 있지만 비용과 효과를 신중하게 따져봐야 합니다.
  • 형식별마크다운, HTML, PDF, 코드 등과 같은 특정 형식의 문서로 작업하는 경우에는 반드시타겟 청크 전략또한 문서의 구조적 정보를 최대한 활용하는 것이 중요합니다.
  • 지속적인 반복 최적화:: 청킹 전략의 선택은 하루아침에 이루어지는 과정이 아닙니다. 실제 프로젝트에서는 끊임없이실험, 평가 및 반복적 최적화를 검색하여 자신에게 가장 적합한 청크 솔루션을 찾아보세요.

이 글이 RAG 청킹 기술을 더 깊이 이해하고, 실제 프로젝트에서 적절한 청킹 전략을 선택 및 적용하여 더욱 강력한 RAG 애플리케이션을 구축하는 데 도움이 되길 바랍니다!

© 저작권 정책

관련 문서

댓글 없음

댓글에 참여하려면 로그인해야 합니다!
지금 로그인
없음
댓글 없음...