LangChain vs LlamaIndex: Выбираем инструмент для работы с LLM

AI и LLM
Схема гибридной архитектуры: движок запросов LlamaIndex подключён как инструмент к агенту на LangGraph

TL;DR LangChain и LlamaIndex сейчас занимают разные ниши и редко конкурируют. LangChain 1.x после перехода на LangGraph — это слой оркестрации и агентов. LlamaIndex — слой данных: загрузка, чанкинг, индексация, стратегии извлечения для RAG. Если ваша задача — «LLM должна отвечать по нашим документам» — начинайте с LlamaIndex. Если LLM должна выполнять многошаговые действия с инструментами — LangChain/LangGraph. Если и то, и другое — оборачивайте query engine LlamaIndex в инструмент агента. Ниже — актуальный код, границы применимости и то, где обычно ломаются версии.

Что изменилось: LangChain 1.x и LangGraph

Самое важное, что нужно понимать про экосистему в 2026 году: LangChain перестал быть монолитом. Старый код с initialize_agent и AgentType — мёртв: эти API удалены при переходе на 1.x. Если у вас в репозитории есть агент на таком стеке — он не заработает на актуальных версиях, и это не вопрос «поправить импорт».

Актуальная раскладка:

  • langchain-core — абстракции: сообщения, промпты, runnable-интерфейс, декоратор @tool.
  • langchain-openai, langchain-anthropic и другие пакеты-провайдеры — конкретные модели через ChatOpenAI, ChatAnthropic и т.д.
  • langgraph — агенты: либо готовый create_react_agent из langgraph.prebuilt, либо собственный граф состояний, когда нужен контроль над ветвлением, циклами и human-in-the-loop.

LangChain стал тоньше и честнее: он больше не притворяется, что агент — это «одна функция с магией внутри». Агент — это граф, и LangGraph делает этот граф наблюдаемым: вы видите каждый шаг, можете поставить точку прерывания, сохранить состояние (checkpointers) и продолжить выполнение. Для продакшн-систем, где агент крутится в цикле «модель → инструмент → модель», это принципиальная разница в отладке. Мои мысли о том, когда агентный подход вообще оправдан, а когда это переусложнение, я собрал в статье AI-агенты: хайп или реальный инструмент.

Актуальный каркас агента

from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent


@tool
def get_exchange_rate(currency: str) -> str:
    """Возвращает текущий курс валюты к рублю. Принимает код, например USD."""
    # Здесь — ваш вызов внешнего API
    return f"Курс {currency}: данные из API"


llm = ChatOpenAI(model="gpt-4.1-mini", temperature=0)

agent = create_react_agent(llm, tools=[get_exchange_rate])

result = agent.invoke(
    {"messages": [("user", "Какой сейчас курс доллара?")]}
)
print(result["messages"][-1].content)

Обратите внимание на три вещи. Во-первых, инструмент описывается декоратором @tool, и его docstring — это реально описание для модели: пишите его осмысленно, от него зависит, когда агент решит вызвать инструмент. Во-вторых, ввод и вывод агента — это список сообщений, а не одна строка, как было у старого agent.run(). В-третьих, create_react_agent возвращает скомпилированный граф, и вы можете использовать agent.stream() вместо invoke(), чтобы получать промежуточные шаги по мере выполнения — без этого отлаживать агента почти невозможно.

Что изменилось: LlamaIndex после реструктуризации

У LlamaIndex своя история боли: пакет распилили на части. from llama_index import ... и ServiceContext — в прошлом. Актуальная структура: ядро в llama-index-core (llama_index.core), а интеграции — отдельными пакетами: llama-index-embeddings-openai, llama-index-vector-stores-qdrant, llama-index-readers-file и так далее. Глобальная конфигурация — через объект Settings, а не через ServiceContext.

Сильная сторона LlamaIndex не изменилась: он лучше всех в экосистеме закрывает путь «куча документов → индекс → качественные ответы». И дело не только в базовом векторном поиске:

  • Стратегии чанкинга. SentenceSplitter режет по границам предложений с перекрытием, SemanticSplitterNodeParser — по смысловой плотности. Выбор сплиттера влияет на качество RAG сильнее, чем выбор самой векторной БД.
  • Стратегии запросов. SubQuestionQueryEngine раскладывает сложный вопрос на подвопросы по разным индексам. response_mode="tree_summarize" древовидно суммаризирует найденные чанки — полезно, когда релевантных фрагментов много и они не влезают в один промпт. Гибридный поиск (векторный плюс ключевые слова) доступен через ретриверы конкретных векторных хранилищ.
  • Интеграции с данными. Десятки ридеров (PDF, Notion, Confluence, S3, SQL) и десятки коннекторов к векторным БД.

Практический пайплайн «от документов до продакшна» с оценкой качества я разбирал отдельно: Как построить RAG-пайплайн для корпоративных документов. Здесь — минимальный актуальный пример:

from llama_index.core import (
    Settings,
    SimpleDirectoryReader,
    VectorStoreIndex,
)
from llama_index.core.node_parser import SentenceSplitter
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI

Settings.llm = OpenAI(model="gpt-4.1-mini")
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")
Settings.node_parser = SentenceSplitter(chunk_size=512, chunk_overlap=50)

documents = SimpleDirectoryReader("data").load_data()
index = VectorStoreIndex.from_documents(documents)

query_engine = index.as_query_engine(
    similarity_top_k=5,
    response_mode="compact",
)

response = query_engine.query("Как устроен процесс возврата средств по договору?")
print(response)

Конфигурация через Settings — первая типовая грабля: если вы создали VectorStoreIndex с одной моделью эмбеддингов, а потом поменяли Settings.embed_model и загружаете старый индекс — поиск молча деградирует, потому что векторы лежат в разных пространствах. Модель эмбеддингов — это часть контракта индекса, фиксируйте её вместе с данными. Вторая грабля — размер чанка: 512 токенов с перекрытием 50 — разумная стартовая точка для документации, но для юридических текстов с длинными перекрёстными ссылками или для таблиц это нужно подбирать на своих данных. Подробнее про выбор хранилища под эти векторы — в сравнении Qdrant, Chroma и Pinecone.

Гибрид: LlamaIndex как инструмент агента на LangGraph

Связка, которая закрывает большинство реальных задач, выглядит так: LlamaIndex отвечает за данные и извлечение, LangGraph — за логику агента. Query engine оборачивается в инструмент, и агент вызывает его только когда вопрос реально про ваши документы, а остальное решает другими инструментами — калькулятором, API, базой.

from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent

# query_engine — из предыдущего примера на LlamaIndex


@tool
def search_knowledge_base(query: str) -> str:
    """Ищет ответ по внутренней базе знаний: документы, инструкции, договоры.
    Использовать для вопросов о продуктах, процессах и регламентах компании."""
    response = query_engine.query(query)
    return str(response)


llm = ChatOpenAI(model="gpt-4.1-mini", temperature=0)
agent = create_react_agent(llm, tools=[search_knowledge_base])

result = agent.invoke(
    {"messages": [("user", "Как оформить возврат и сколько он занимает по регламенту?")]}
)

Здесь docstring инструмента несёт двойную нагрузку: он одновременно и описание, и маршрутизатор. Формулировка «использовать для вопросов о…» — это то, как вы управляете тем, когда агент пойдёт в базу знаний, а когда ответит из контекста или вызовет другой инструмент. При сомнениях между несколькими похожими инструментами пишите в docstring явные антипримеры («не использовать для расчётов»).

Если в агенте нужна ещё и долговременная память между сессиями, это отдельный слой — в LangGraph она строится на checkpointers и store. Тема объёмная, я вынес её в статью про память AI-систем.

Про боль: версии и отладка

Обозначу две проблемы, которые сжигают больше всего времени, потому что в статьях про «выбор фреймворка» о них обычно молчат.

Зависимости. Оба фреймворка развиваются быстро, и минорные версии бывают ломающими. Классический сценарий: langchain-core подтянулся новее, а langgraph в вашем lock-файле ещё старый — и create_react_agent падает на несовместимости типов внутри. У LlamaIndex то же самое между llama-index-core и пакетами интеграций. Правила простые: фиксируйте точные версии всего связанного семейства пакетов в requirements.txt или lock-файле (uv, poetry — неважно, важен lock), обновляйте их одним траншем и прогоняйте после обновления хотя бы смоук-тест на агента и на индекс. Чтение changelog перед апдейтом мейджорной версии — не парanoia, обе команды публикуют миграционные гайды: docs.langchain.com и docs.llamaindex.ai.

Отладка середины цепочки. Когда RAG отвечает чепухой, причина почти всегда в одном из трёх мест: плохие чанки, нерелевантный retrieval или кривой промпт. Без трейсинга вы гадаете. Практический минимум: в LangGraph смотрите шаги через stream() и подключайте LangSmith для трейсинга вызовов; в LlamaIndex включайте callback-хендлеры и логируйте, какие именно чанки ушли в контекст (у ответа есть response.source_nodes — смотрите их в первую очередь). Правило, которое экономит часы: сначала смотрите на retrieval без LLM вообще — если по запросу поднимаются нерелевантные фрагменты, чините сплиттер и similarity_top_k, а не промпт.

Сравнительная таблица

ХарактеристикаLangChain 1.x + LangGraphLlamaIndex
Основной фокусОркестрация, агенты как графы, инструментыЗагрузка данных, индексы, стратегии извлечения
Актуальный API агентовlanggraph.prebuilt.create_react_agent или собственный графЕсть свои агентные обёртки, но сила не в этом
Работа с даннымиБазовые загрузчики и ретриверы через интеграцииГлубокая специализация: сплиттеры, sub-question, tree summarize, гибридный поиск
Управление состояниемCheckpointers, прерывания, стриминг шаговНе является фокусом
Структура пакетовlangchain-core + пакеты провайдеровllama-index-core + пакеты интеграций
Когда братьLLM принимает решения и вызывает инструментыLLM отвечает по вашим документам

Моя рекомендация

Прямо, с условиями:

  1. Задача — ответы по корпоративным документам, базе знаний, инструкциям? Берите LlamaIndex и не добавляйте LangChain, пока не появится потребность в действиях. Один фреймворк в проекте всегда дешевле двух в поддержке.
  2. LLM должна выполнять многошаговые сценарии с внешними инструментами, ветвлением, подтверждениями от человека? Берите LangChain/LangGraph. Именно LangGraph: если агент сложнее двух инструментов, пишите собственный граф — на create_react_agent вы быстро упрётесь в отсутствие контроля над потоком.
  3. Нужно и то, и другое? Гибрид: LlamaIndex за данные, LangGraph за логику, связка через @tool, как в примере выше. Это мой вариант по умолчанию для ассистентов в корпоративной среде.
  4. Прототип на выходных? Начните вообще без фреймворков: прямой вызов API модели плюс ручной retrieval из тридцати строк кода покроет больше сценариев, чем кажется. Фреймворк подключайте, когда появляется вторая интеграция или второй агент — не раньше.

FAQ

А что делать со старым кодом на initialize_agent и AgentType?

Мигрировать на LangGraph: этот API удалён в LangChain 1.x и не чинится обновлением импортов. Простой ReAct-агент переезжает на create_react_agent за вечер; если у вас был сложный кастомный агент с ручной логикой — переписывайте его сразу как граф состояний, переносить один в один обычно дороже, чем переосмыслить.

Где граница между ретривером LangChain и LlamaIndex, если у обоих есть RAG?

Формально RAG можно собрать и на чистом LangChain — там есть загрузчики и векторные хранилища. Практически: как только вам нужны нестандартный чанкинг, sub-question-запросы, суммаризация по дереву или гибридный поиск, вы либо берёте LlamaIndex, либо пишете это руками. Если ваш RAG — «split, embed, top-k» и больше ничего, разницы почти нет, выбирайте по тому, что уже стоит в проекте.

Можно ли использовать другие LLM, не OpenAI?

Да, обе экосистемы работают с Anthropic, локальными моделями и open-source эндпоинтами через пакеты-провайдеры. В примерах выше заменяются класс модели и пакет эмбеддингов, остальное не меняется. Одно условие: для агентов модель должна поддерживать function calling, иначе связка с инструментами не заведётся или будет работать через ненадёжный парсинг текста.

Как понять, что RAG работает плохо, до того как пожалуются пользователи?

Соберите тестовый набор вопросов с эталонными ответами (хватит 30–50 реальных вопросов) и прогоняйте его при каждом изменении сплиттера, модели эмбеддингов или промпта. Смотрите на два сигнала: попадает ли нужный фрагмент в retrieved-чанки (метрика retrieval) и совпадает ли финальный ответ с эталоном по смыслу (это можно оценивать LLM-судьёй). Без такого набора любая оптимизация RAG — это гадание.

Оба фреймворка в одном проекте — это нормально или перебор?

Нормально, если у каждого своя зона ответственности: LlamaIndex владеет данными и индексами, LangGraph — логикой агента. Перебор — это когда они дублируют друг друга, например, вы тянете загрузчики документов из обоих. И помните про цену: два семейства пакетов — двойная поверхность для проблем с версиями, lock-файл обязателен.

Нужна помощь с выбором стека или переносом агента на актуальный API? Напишите мне — обсудим ваш проект.

Обсудить проект

Есть идея или задача? Давайте обсудим, как можно её реализовать с помощью современных AI-технологий.

Написать мне
Вернуться к блогу