Изучение трёх ключевых стратегий для создания эффективной генерации с дополненным извлечением (RAG)
Генерация с дополненным извлечением (RAG) — это полезная техника для использования ваших собственных данных в чат-боте на базе ИИ. В этой статье блога я расскажу о трёх ключевых стратегиях, которые помогут получить максимум от RAG:
Умное разбиение текста на фрагменты 📦:
- Первый шаг — разбить ваши текстовые данные на осмысленные, управляемые фрагменты. Этот шаг гарантирует, что ваша векторная база данных сможет быстро и точно извлекать наиболее релевантную информацию.
Итерации с разными моделями эмбеддингов 🔍:
- Итерации с моделью эмбеддингов крайне важны. Модель эмбеддингов определяет, как ваши данные представлены в виде векторов. Векторы — это lingua franca ИИ, улучшающая способность векторной базы данных извлекать нужные фрагменты информации.
Эксперименты с разными LLM или генеративными моделями 🧪:
- У каждого API языковой модели (LLM) разные стоимость, задержки и точность. Их тестирование позволяет выбрать ту, которая лучше всего подходит для вашей рабочей нагрузки.
Давайте углубимся и разберём, как работают эти стратегии и как с помощью оценок вы можете определить конфигурации с наилучшей производительностью для ваших реальных RAG-приложений! 🚀📚
Умное разбиение текста на фрагменты
Разбиение текста на фрагменты похоже на разрезание длинной истории на более мелкие, удобные части, чтобы компьютер мог легко находить и использовать самые важные фрагменты при ответах на вопросы или помощи с задачами.
Ниже я объясню несколько разных техник. Эти техники хорошо и подробно объяснены в этой оригинальной статье Грега Камрадта.
Рекурсивное разбиение текста по символам 🔄:
- Разбиение текста на фрагменты на основе количества символов гарантирует, что каждая часть будет управляемой и связной.
Разбиение текста от малого к большому 📏:
- Начало с более крупных фрагментов и постепенное разбиение их на более мелкие. Поиск выполняется по малым фрагментам, а извлечение — по большим.
Семантическое разбиение текста 🧠:
- Разделение текста на основе смысла, чтобы каждый фрагмент представлял завершённую идею или тему, обеспечивая сохранение контекста.
Эти методы помогут вам эффективно организовывать и извлекать текст для различных приложений. Углубитесь, чтобы изучить, как работает каждая техника!
Рекурсивное разбиение текста по символам 🔄
Начните с разбиения текста на фрагменты фиксированного размера с перекрытием фиксированного размера с помощью RecursiveCharacterTextSplitter из LangChain.
from langchain.text_splitter import RecursiveCharacterTextSplitter
CHUNK_SIZE = 512
chunk_overlap = np.round(CHUNK_SIZE * 0.10, 0)
print(f"chunk_size: {CHUNK_SIZE}, chunk_overlap: {chunk_overlap}")
# The splitter to use to create smaller (child) chunks.
child_text_splitter = RecursiveCharacterTextSplitter(
chunk_size=CHUNK_SIZE,
chunk_overlap=chunk_overlap,
length_function = len, # use built-in Python len function
separators = ["\n\n", "\n", " ", ". ", ""], # defaults
)
# Child docs directly from raw docs
sub_docs = child_text_splitter.split_documents(docs)
# Inspect chunk lengths
print(f"{len(docs)} docs split into {len(sub_docs)} child documents.")
plot_chunk_lengths(sub_docs, 'Recursive Character')
Изображение автора: график Matplotlib длин фрагментов RecursiveCharacterTextSplitter, полный код доступен на GitHub.
Разбиение текста от малого к большому 📏
Этот метод выполняет поиск с использованием небольших (дочерних) фрагментов, но извлекает большие (родительские) фрагменты текста. Используются два хранилища в памяти: 1) хранилище документов и 2) векторное хранилище. Приведенный ниже код использует LangChain MultiVectorRetriever.
from langchain_milvus import Milvus
from langchain.text_splitter import RecursiveCharacterTextSplitter
import uuid
from langchain.storage import InMemoryByteStore
from langchain.retrievers.multi_vector import MultiVectorRetriever
# Create doc storage for the parent documents
store = InMemoryByteStore()
id_key = "doc_id"
# Create vectorstore for vector index and retrieval.
COLLECTION_NAME = "MilvusDocs"
vectorstore = Milvus(
collection_name=COLLECTION_NAME,
embedding_function=embed_model,
connection_args={"uri": "./milvus_demo.db"},
auto_id=True,
# Set to True to drop the existing collection if it exists.
drop_old=True,
)
# The MultiVectorRetriever (empty to start)
retriever = MultiVectorRetriever(
vectorstore=vectorstore,
byte_store=store,
id_key=id_key,
)
PARENT_CHUNK_SIZE = 1586
# The splitter to use to create bigger (parent) chunks
parent_text_splitter = RecursiveCharacterTextSplitter(
chunk_size=PARENT_CHUNK_SIZE,
length_function = len, # use built-in Python len function
# separators=["\n\n"], # split at end of paragraphs
)
# Parent docs directly from raw docs
parent_docs = parent_text_splitter.split_documents(docs)
doc_ids = [str(uuid.uuid4()) for _ in parent_docs]
# Inspect chunk lengths
print(f"{len(docs)} docs split into {len(parent_docs)} parent documents.")
plot_chunk_lengths(parent_docs, 'Parent')
CHUNK_SIZE = 512
chunk_overlap = np.round(CHUNK_SIZE * 0.10, 0)
print(f"chunk_size: {CHUNK_SIZE}, chunk_overlap: {chunk_overlap}")
# The splitter to use to create smaller (child) chunks.
child_text_splitter = RecursiveCharacterTextSplitter(
chunk_size=CHUNK_SIZE,
chunk_overlap=chunk_overlap,
length_function = len, # use built-in Python len function
separators = ["\n\n", "\n", " ", ". ", ""], # defaults
)
# Child docs directly from parent docs
sub_docs = child_text_splitter.split_documents(parent_docs)
# Inspect chunk lengths
print(f"{len(docs)} docs split into {len(sub_docs)} child documents.")
plot_chunk_lengths(sub_docs, 'Small-to-big')
разбиение small-to-big.png
Изображение автора: график Matplotlib длин фрагментов small-to-big; полный код доступен на github.
Семантическое разбиение текста 🧠
Этот chunker работает, определяя, когда «разделять» предложения. Он выполняет эту задачу, вычисляя косинусные расстояния между соседними предложениями. Просматривая все эти косинусные расстояния, он ищет выбросы расстояний, превышающие некоторый порог. Эти выбросы расстояний определяют, когда фрагменты разделяются.
Существует несколько способов определить этот порог, которые управляются breakpoint_threshold_type kwarg.
from langchain_experimental.text_splitter import SemanticChunker
semantic_docs = []
for doc in docs:
# Extract and clean document content.
cleaned_content = clean_text(doc.page_content)
# Initialize the SemanticChunker with the embedding model.
text_splitter = SemanticChunker(embed_model)
semantic_list = text_splitter.create_documents([cleaned_content])
# Append the list of semantic chunks to semantic_docs.
semantic_docs.extend(semantic_list)
# Inspect chunk lengths
print(f"Created {len(semantic_docs)} semantic documents from {len(docs)}.")
plot_chunk_lengths(semantic_docs, 'Semantic')
Мы будем использовать документацию Milvus в качестве наших данных и Ragas в качестве метода оценки для вашего RAG. Прочитайте мой блог о том, как использовать RAGAS.
Результат был:
- Метод разбиения на фрагменты = Recursive Character Text Splitter с top_k=2 оказался лучшим.
Разные модели эмбеддингов
Зафиксировав метод разбиения на фрагменты как Recursive Character Text Splitter с top_k=2, я попробовал две разные модели эмбеддингов.
BAAI/bge-large-en-v1.5
Text-embedding-3-small с embedding-dim = 512
Используя документацию Milvus и метод оценки Ragas, результат был:
- Модель эмбеддингов = BAAI/bge-large-en-v1.5 оказалась лучшей.
Разные LLM
После фиксации метода разбиения на фрагменты как Recursive Character Text Splitter с top_k=2 и модели эмбеддингов как BAAI/bge-large-en-v1.5, я попробовал шесть разных API-эндпоинтов LLM.
Используя документацию Milvus и метод оценки Ragas, результат был:
- LLM = MistralAI mixtral_8x7b_instruct с использованием Anyscale Endpoints оказалась лучшей.
Заключение
Оценка конвейера RAG будет варьироваться в зависимости от ваших конкретных данных и варианта использования. Один ключевой вывод из личного опыта и литературы заключается в том, что наиболее значительные улучшения часто достигаются за счет уточнения ваших стратегий извлечения. 🛠️
Используя данные документации Milvus и оценку Ragas, в этом блоге было отмечено:
Улучшение на 35% за счет изменения стратегии разбиения на фрагменты 📦
Улучшение на 27% за счет изменения модели эмбеддингов 🔍
Улучшение на 6% за счет изменения модели LLM 🤖
Итерации по этим элементам могут помочь оптимизировать ваш конвейер RAG для лучших результатов!
Ссылки
Учебное пособие Greg Kamradt по 5 разным уровням разбиения текста: https://github.com/FullStackRetrieval-com/RetrievalTutorials/blob/main/tutorials/LevelsOfTextSplitting/5_Levels_Of_Text_Splitting.ipynb
LangChain Recursive Character Text Splitter: https://python.langchain.com/v0.2/docs/how_to/recursive_text_splitter/
LangChain Multivector Retriever: https://python.langchain.com/v0.2/docs/how_to/multi_vector/#smaller-chunks
LangChain Semantic Chunker: https://python.langchain.com/v0.2/docs/how_to/semantic-chunker/#standard-deviation
Как использовать RAGAS для оценки вашего конвейера RAG: https://medium.com/towards-data-science/rag-evaluation-using-ragas-4645a4c6c477
Читать далее

Vector Lakebase: End the AI Data Silo
Learn how Vector Lakebase unifies vector search, data lakes, and AI data operations so teams can serve RAG and agents without copy-and-sync pipelines.

Optimizing Embedding Model Selection with TDA Clustering: A Strategic Guide for Vector Databases
Discover how Topological Data Analysis (TDA) reveals hidden embedding model weaknesses and helps optimize vector database performance.

DeepSeek-VL2: Mixture-of-Experts Vision-Language Models for Advanced Multimodal Understanding
Explore DeepSeek-VL2, the open-source MoE vision-language model. Discover its architecture, efficient training pipeline, and top-tier performance.



