Эксперименты с различными стратегиями разбиения на фрагменты через LangChain для LLM-приложений
Чанкинг — одна из самых сложных проблем при создании приложений rгенерации с дополненным извлечением (RAG). Чанкинг — это процесс разбиения предложений на более мелкие, управляемые части для последующей обработки. Хотя это звучит просто, дьявол кроется в деталях. Плохо выбранная стратегия чанкинга может привести к нерелевантным или неполным результатам, из-за чего AI-системе становится сложнее предоставлять точные ответы. Например, слишком маленькие фрагменты могут упускать контекст, а слишком большие фрагменты могут возвращать нерелевантную информацию.
В этом руководстве мы рассмотрим, как различные стратегии чанкинга влияют на производительность извлечения для одного и того же набора данных, в частности мы сосредоточимся на сценарии использования с чанкингом в Langchain. К концу этого руководства вы поймете, как размер фрагмента и перекрытие влияют на качество извлечения, и получите практические рекомендации по выбору правильных параметров для вашего конкретного сценария использования. Код для этого поста можно найти в этом GitHub Repo on LLM Experimentation.
Обзор LangChain и почему чанкинг важен
LangChain — это фреймворк для оркестрации Large Language Model со встроенными инструментами для загрузки документов и разбиения текста. Его гибкость делает его популярным выбором для создания RAG-приложений, где чанкинг играет критически важную роль в определении релевантности и полноты извлеченной информации.
Чанкинг включает выбор двух основных параметров: заданного фрагмента, размера и перекрытия.
Размеры фрагментов определяют количество символов или токенов в каждом фрагменте. Более крупные фрагменты захватывают больше контекста, но рискуют вернуть нерелевантную информацию, тогда как меньшие фрагменты обеспечивают точность, но могут потерять необходимый контекст. Перекрытие определяет, какой объем текста является общим для последовательных фрагментов. Оно помогает сохранять непрерывность, особенно для связанных идей в разных абзацах, но чрезмерное перекрытие увеличивает накладные расходы на обработку. Оптимизация этих параметров обеспечивает баланс между захватом контекста и сохранением фокуса при разбиении текста, что критически важно для точного извлечения.
Стратегии чанкинга имеют широкое применение за пределами рабочих процессов RAG. Они крайне важны в контекстно-осведомленных чатботах, которые полагаются на разбитый на фрагменты текст, чтобы давать краткие, но исчерпывающие ответы на запросы пользователей. Например, бот поддержки может использовать чанкинг, чтобы разбить руководства по устранению неполадок на практические шаги, не перегружая пользователя. Аналогично, при построении графов знаний чанкинг извлекает сущности и отношения из необработанного текста, помогая преобразовывать неструктурированные документы в структурированные данные. Эти примеры демонстрируют, как правильная стратегия чанкинга может улучшить последующие AI-задачи.
Понимание проблем плохого чанкинга
Плохо оптимизированная стратегия чанкинга может создавать значительные проблемы при извлечении. Действительно маленькие размеры фрагментов часто не содержат достаточного контекста, создавая фрагментированные или неполные результаты. Например, запрос к руководству по продукту может вернуть изолированные фрагменты вроде: «Шаг 1: Включите питание» — без сопутствующей информации о последующих шагах. И наоборот, когда ваше приложение разбивает текст на чрезмерно большие фрагменты, это может размыть конкретность извлеченной информации. Запрос семантического сходства, который ожидает краткие инструкции по устранению неполадок, может вернуть целый раздел, из-за чего пользователям будет сложнее определить релевантные детали.
Перекрытие добавляет еще один уровень сложности. Без перекрытия системы могут потерять непрерывность мысли между последовательными фрагментами, что критически важно для связанных повествований или процессов. Однако чрезмерное перекрытие может вносить избыточность, увеличивая затраты на хранение и обработку. Балансировка этих компромиссов жизненно важна, чтобы системы извлечения предоставляли точные, практически применимые ответы.
Импорты кода LangChain и настройка
Этот первый раздел посвящен импортам и другим инструментам настройки. Возможно, первое, что вы заметите в коде ниже, — это то, что там ОЧЕНЬ МНОГО импортов. Более часто используемые из них: os и dotenv, поэтому я не буду их рассматривать. Они просто используются для ваших переменных окружения. Давайте пошагово разберем разбиение текста в LangChain с Python и клиентом pymilvus.
Вверху вы увидите наши три импорта для загрузки документа. Во-первых, это NotionDirectoryLoader, который загружает директорию с markdown/Notion-документами. Затем у нас есть сплиттеры текста Markdown Header и Recursive Character. Они разбивают текст внутри markdown-документа на основе заголовков (сплиттер заголовков) или набора заранее выбранных разрывов по символам (рекурсивный сплиттер).
Далее у нас идут импорты ретривера. Milvus — это наша векторная база данных, OpenAIEmbeddings — наша модель эмбеддингов, а OpenAI — наша LLM. SelfQueryRetriever — это нативный ретривер LangChain, который позволяет векторной базе данных «запрашивать саму себя». Я подробнее писал об использовании LangChain для запросов к векторной базе данных в этой статье.
Наш последний импорт из LangChain — AttributeInfo, который, как можно догадаться, передает атрибут с информацией в self-query retriever. Наконец, я хочу затронуть импорты pymilvus. Они нужны исключительно для утилитарных целей; они не нужны нам для работы с векторной базой данных в LangChain. Я использую эти импорты, чтобы очистить базу данных в конце.
Последнее, что мы делаем перед написанием функции, — загружаем наши переменные окружения и объявляем несколько констант. Переменная headers_to_split_on важна — она перечисляет все заголовки, которые мы ожидаем увидеть и по которым хотим выполнять разбиение в markdown. path просто сообщает LangChain, где найти Notion-документы.
import os
from langchain.document_loaders import NotionDirectoryLoader
from langchain.text_splitter import MarkdownHeaderTextSplitter, RecursiveCharacterTextSplitter
from langchain.vectorstores import Milvus
from langchain.embeddings import OpenAIEmbeddings
from langchain.llms import OpenAI
from langchain.retrievers.self_query.base import SelfQueryRetriever
from langchain.chains.query_constructor.base import AttributeInfo
from pymilvus import connections, utility
from dotenv import load_dotenv
load_dotenv()
zilliz_uri = os.getenv("ZILLIZ_CLUSTER_01_URI")
zilliz_token = os.getenv("ZILLIZ_CLUSTER_01_TOKEN")
headers_to_split_on = [
("##", "Section"),
]
path='./notion_docs'
Создание функции для экспериментов с чанкингом
Создание функции для экспериментов — самая важная часть руководства. Как уже упоминалось, эта функция принимает несколько параметров для ингеста документов и экспериментов. Нам нужно указать путь к документам, заголовки, по которым выполнять разбиение (splitters), размер чанка, максимальное перекрытие размера чанка и то, хотим ли мы выполнять очистку, удаляя коллекции в конце. Удаление коллекции по умолчанию равно true
Если мы можем этого избежать, мы хотим создавать и удалять коллекции как можно реже, потому что у них есть накладные расходы, которых можно избежать. Вы можете увидеть, как скрипт меняется, пока я ищу хорошие обходные решения.
Эта функция очень похожа на ту, на которую мы ссылались выше, об использовании Notion с LangChain. Первый раздел загружает документ из пути с помощью Notion Directory Loader. Обратите внимание, что мы берем только html-контент для первой веб-страницы (и у нас всего одна страница).
Далее мы берём наши сплиттеры. Сначала мы используем markdown-сплиттер, чтобы разделить по заголовкам, которые мы передали выше. Затем мы используем наш рекурсивный метод сплиттера и разделяем на основе размера чанка и перекрытия.
Это всё разбиение, которое нам нужно. После завершения разбиения мы задаём имя коллекции и инициализируем экземпляр LangChain Milvus, используя переменные окружения по умолчанию, эмбеддинги OpenAI, сплиты и имя коллекции. Мы также создаём список полей метаданных через объект AttributeInfo, чтобы сообщить self-query retriever, что у нас есть «разделы».
После всей этой настройки мы получаем нашу LLM, а затем передаём её в python self-query retriever. Далее retriever делает свою магию, когда мы задаём ему вопрос о наших документах. Я также настроил его так, чтобы он сообщал нам, какую стратегию чанкинга мы тестируем. Наконец, при желании мы можем удалить коллекцию.
def test_langchain_chunking(docs_path, splitters, chunk_size, chunk_overlap, drop_collection=True):
path=docs_path
loader = NotionDirectoryLoader(path)
docs = loader.load()
md_file=docs[0].page_content
# Let's create groups based on the section headers in our page
markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=splitters)
md_header_splits = markdown_splitter.split_text(md_file)
# Define our text splitter
text_splitter = RecursiveCharacterTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap)
all_splits = text_splitter.split_documents(md_header_splits)
test_collection_name = f"EngineeringNotionDoc_{chunk_size}_{chunk_overlap}"
vectordb = Milvus.from_documents(documents=all_splits,
embedding=OpenAIEmbeddings(),
connection_args={"uri": zilliz_uri,
"token": zilliz_token},
collection_name=test_collection_name)
metadata_fields_info = [
AttributeInfo(
name="Section",
description="Part of the document that the text comes from",
type="string or list[string]"
),
]
document_content_description = "Major sections of the document"
llm = OpenAI(temperature=0)
retriever = SelfQueryRetriever.from_llm(llm, vectordb, document_content_description, metadata_fields_info, verbose=True)
res = retriever.get_relevant_documents("What makes a distinguished engineer?")
print(f"""Responses from chunking strategy:
{chunk_size}, {chunk_overlap}""")
for doc in res:
print(doc)
# this is just for rough cleanup, we can improve this
# lots of user considerations to understand for real experimentation use cases though
if drop_collection:
connections.connect(uri=zilliz_uri, token=zilliz_token)
utility.drop_collection(test_collection_name)
Тесты и результаты LangChain
Итак, теперь начинается самое интересное! Давайте посмотрим на тесты и результаты.
Код для тестирования чанков LangChain
Этот короткий блок кода ниже показывает, как мы можем запустить нашу функцию для экспериментов. Я добавил пять экспериментов. В этом руководстве тестируются стратегии чанкинга длиной от 32 до 512 с шагом по степеням 2 и с перекрытиями от 4 до 64, также по степеням 2. Для тестирования мы проходим циклом по списку кортежей и вызываем функцию, которую написали выше.
chunking_tests = [(32, 4), (64, 8), (128, 16), (256, 32), (512, 64)]
for test in chunking_tests:
test_langchain_chunking(path, headers_to_split_on, test[0], test[1])
Вот как выглядит весь вывод. Теперь давайте заглянем в отдельные результаты. Помните, что выбранный нами пример вопроса: "What makes a distinguished engineer?"
Длина 32, перекрытие 4
Итак, из этого мы ясно видим, что 32 — это слишком мало. Это предложение совершенно бесполезно. «Is a Distinguished Engineer» — это максимально возможное круговое рассуждение.
Длина 64, overlap 8
64 и 8 с самого начала не намного лучше. Однако это дает нам пример distinguished engineer. Werner Vogels, CTO of Amazon.
Длина 128, overlap 16
На 128 мы начинаем видеть более полные предложения. Меньше слов и ответов вроде «engineer.». Это неплохо: удается извлечь фрагмент о Werner Vogel и «Has achieved noteworthy technical, professional accomplishments while working as an engineer.» Последняя запись на самом деле из раздела principal engineer.
Один минус здесь в том, что мы уже видим появление примеров таких специальных символов, как \xa0 и \n. Это говорит нам, что, возможно, мы заходим слишком далеко с длиной чанка.
Длина 256, overlap 32
Я думаю, эта длина чанка определенно слишком большая. Она вытягивает нужные записи, но также вытягивает записи из «Fellow», «Principal Engineer» и «Senior Staff Engineer». Первая запись, правда, из Distinguished Engineer, и она охватывает три пункта по нему.
Длина чанка 512, overlap 64
Мы уже установили, что 256, вероятно, слишком много. Однако первая выдача для 512 на самом деле представляет собой весь раздел о distinguished engineers. Теперь у нас дилемма — хотим ли мы получать отдельные «строки» или «заметки» либо вытягивать целый раздел? Это зависит от вашего сценария использования.
Итоги экспериментов с разными стратегиями chunking
Отлично, итак, в этом руководстве с использованием langchain chunking python мы увидели пять разных стратегий text splitter с параметризованным подходом, который подсвечивает стратегии chunk size и chunks overlap. Одна из дилемм, которую мы увидели, просто попробовав эти 5 стратегий chunk, — это выбор между получением отдельных фрагментов информации и возвратом целого раздела в зависимости от размера чанка. Мы увидели, что 128 довольно хорошо подходит для получения отдельных «строк» или «заметок» о distinguished engineers, а 512 может вернуть весь наш раздел.
Однако 256 оказался не так хорош.
Эти три точки данных кое-что говорят нам о text splitter. Дело не только в том, что найти идеальный chunk size сложно. Это также знак, что при формировании размеров чанков нужно думать о том, что вы хотите получить в ответах.
Обратите внимание, что мы даже еще не дошли до тестирования разных overlap. После того как вы изучите и подберете хорошую стратегию chunk, проверка overlap — логичный следующий шаг. Может быть, мы рассмотрим это в будущем руководстве, возможно, с другой библиотекой. Следите за обновлениями!
Выводы из экспериментов
Эти эксперименты подчеркивают тонкую взаимосвязь между размером чанка и overlap. Меньшие чанки отлично подходят для задач, требующих точечной точности, тогда как более крупные чанки лучше подходят для вопросов, которым нужен обширный контекст. Overlap, в свою очередь, играет ключевую роль в балансировке этих целей.
Стоит подчеркнуть, что наблюдаемые здесь компромиссы не являются универсальными. Идеальные параметры chunking сильно зависят от вашего конкретного сценария использования. Приложения вроде conversational agents или инструментов быстрого поиска могут выиграть от меньших чанков для точных ответов. С другой стороны, исследовательские рабочие процессы, такие как суммаризация сложных юридических документов, требуют более крупных чанков, чтобы обеспечить полноценный контекст.
В реальных сценариях гибридный подход часто может обеспечить наилучшие результаты. Динамически настраивая размеры фрагментов в зависимости от запроса и намерения пользователя, разработчики могут найти баланс между эффективностью и качеством извлечения. Такие подходы могут включать добавление уровней интеллектуальной маршрутизации запросов или адаптивных конвейеров разбиения на фрагменты, адаптированных к потребностям пользователей.
Будущие соображения
Эксперименты, проведенные в этом руководстве, — это только начало. В будущих исследованиях можно рассмотреть иерархические стратегии разбиения на фрагменты, которые объединяют разные размеры фрагментов для многоуровневой сегментации. Кроме того, расширение до мультимодального извлечения данных, например объединение текстовых и графических эмбеддингов, открывает возможности для более сложных вариантов использования.
Также есть потенциал для систематических экспериментов с перекрытиями, чтобы лучше понять, как они влияют на извлечение в различных наборах данных. Интеграция других фреймворков наряду с LangChain может еще больше усовершенствовать подходы к разбиению на фрагменты и выявить новые методы оптимизации. Например, такие фреймворки, как Haystack, предлагают дополнительные возможности извлечения, которые могут дополнять рабочий процесс LangChain.
Наконец, включение обратной связи от пользователей в процесс извлечения может привести к адаптивным системам, которые со временем учатся и оптимизируют стратегии разбиения на фрагменты. Благодаря более тонко настроенным экспериментам можно создать надежные, ориентированные на пользователя конвейеры RAG.
Заключение
Разбиение на фрагменты — незаменимый компонент рабочих процессов извлечения в приложениях RAG. В этом руководстве была продемонстрирована важность тщательной настройки размера фрагмента и перекрытия, показано, как разные стратегии влияют на результаты извлечения.
Балансируя компромиссы между контекстом и точностью, разработчики могут оптимизировать свои системы для самых разных вариантов использования. Хотя в этом руководстве были рассмотрены базовые эксперименты, возможности для дальнейшего изучения огромны. Следите за новыми продвинутыми руководствами и материалами об оптимизации систем извлечения на базе ИИ.
Ссылки по разбиению на фрагменты
Разбиение на фрагменты, или стратегии text splitter, продолжают развиваться, поэтому мы начали создавать коллекцию этих различных стратегий, чтобы ознакомиться с ними и потенциально реализовать в вашем приложении. Наслаждайтесь!
Руководство по стратегиям разбиения на фрагменты для Retrieval Augmented Generation (RAG). В этом руководстве мы рассмотрели различные аспекты стратегий разбиения на фрагменты в системах Retrieval-Augmented Generation (RAG).
Руководство для начинающих по разбиению веб-сайта на фрагменты и эмбеддингам для ваших приложений RAG. В этой статье мы объясним, как извлечь контент с веб-сайта и использовать его в качестве контекста для LLM в приложении RAG. Однако, прежде чем сделать это, нам нужно понять основы веб-сайтов.
Исследование трех ключевых стратегий для создания эффективной Retrieval Augmented Generation (RAG). Retrieval Augmented Generation (RAG) — это полезный метод использования ваших собственных данных в чат-боте на базе ИИ. В этой публикации блога вы узнаете о трех ключевых стратегиях, которые помогут получить максимум от RAG.
Pandas DataFrame: разбиение на фрагменты и векторизация с Milvus. Если мы храним все данные, включая текст фрагмента и эмбеддинг, внутри Pandas DataFrame, мы можем легко интегрировать и импортировать их в векторную базу данных Milvus.
Читать далее

A Few Notes from Databricks Data + AI Summit 2026: Why the Data Layer Matters Again
James Luan shares notes from Databricks Data + AI Summit 2026 on why production AI is pushing the data layer back to the center of infrastructure.

Zilliz Cloud Audit Logs Goes GA: Security, Compliance, and Transparency at Scale
Zilliz Cloud Audit Logs are now GA, giving enterprises real-time visibility, compliance-ready trails, and stronger security across AWS, GCP, and Azure.

Vector Databases vs. Object-Relational Databases
Use a vector database for AI-powered similarity search; use an object-relational database for complex data modeling with both relational integrity and object-oriented features.



