При разработке ИИ-агента для базы знаний, мне казалось, что задача почти типовая: складываем информацию в Qdrant, находим информацию через векторный поиск и поиск в интернете и формируем красивый ответ через LLM. На деле ИИ-агент начал уверенно галлюцинировать, приносить не только нерелевантные ответы, но и тупить с короткими пользовательскими запросами. И самое неприятное, при всем при этом метрика «похожести» similarity выглядела достаточно высокой, что сильно вводило в заблуждение.
После серии экспериментов и улучшений пришло понимание, что промышленный ИИ-агент — это не столько про LLM, сколько про качество извлечения информации и гибкую оркестрацию компонентов ИИ-агента. Что в итоге полностью поменяло архитектуру моей системы.
В статье расскажу, как я пришла от «просто добавь LLM» к промышленному графовому ИИ-агенту на LangGraph + Qdrant.
Многие обучающие материалы в интернете создают впечатление, что для создания ИИ-агента достаточно подключить LLM к векторной базе данных. Однако на практике такой подход оказался слишком поверхностным. Я сделала ИИ-агента для базы знаний и довольно быстро столкнулась с типичными проблемами:
-
пользовательский запрос в его первичной формулировке плохо ищется по базе, особенно с учетом специфической терминологии и сленга;
-
векторный поиск ставит высокие метрики совсем не подходящим ответам;
-
много шумных ответов из-за нерелевантной сегментации вопроса;
-
на таких неполных и шумных данных LLM фантазирует, что совсем не подходит для продакшена.
Когда геометрия подводит
Здесь всплывает ключевая проблема, которую не стоит недооценивать при проектировании ИИ-агента: разница между человеческой «инклюзивной семантикой» и векторной «геометрией близости формулировок». Например, вопрос «Какое время торгов и расчетов в будни?» может привести к совершенно неожиданным ответам про регламент расчетов или формирование отчетов. На уровне метрики similarity всё выглядит корректно, тексты действительно «похожи». Но с точки зрения пользователя ответ часто оказывается неполным или вообще нерелевантным.
Проблема становится еще более очевидной при сравнении рейтинга выдачи по этой метрике, например, расширенная формулировка «Какое время торгов и расчетов в будни, выходные и праздничные дни?» получила в выборке только 11 место.
Так происходит, потому что векторное представление текста работает не как логический фильтр, а как геометрическая функция: каждый вопрос превращается в точку в многомерном пространстве смыслов так, что семантически близкие вопросы оказываются близко друг к другу. Однако модель не способна определить, является ли один запрос подмножеством другого, и не может оценить, достаточно ли найденного контекста для ответа. Также следует заметить, что в повседневной речи человек часто использует неформальные выражения, профессиональный сленг, сокращения и опускает важные детали вопроса, подразумевая их как само собой разумеющиеся.
В результате один и тот же смысл может быть передан множеством разных способов. Это приводит к тому, что система по-разному интерпретирует одинаковые по содержанию вопросы, и, соответственно, ухудшается стабильность слоя retrieval. Поэтому каждую формулировку следует привести к стандартной, канонической форме, устранить вариативность языка и выделить основные сущности и ограничения перед подачей в наши модели.
Но дело тут не только в нормализации формулировок. Все несколько глубже: система возвращает не ответы на заданные вопросы, а похожие по смыслу фрагменты, а LLM, получая такой контекст, дальше уверенно достраивает недостающую информацию, что приводит к классическим галлюцинациям на неполных данных. А значит нужно достроить ограничения вопроса, сделав его более емким и полным и как-то «сузить» смысловое поле поиска.
Привести вопрос к более полному и однозначному виду можно через предварительный процесс смыслового достраивания (semantic completion), когда недостающая информация восполняется на основе общей семантической информации. Вопрос из моего примера представляет собой короткий набор смыслов «время, торги, расчеты, будни», который заставляет модель искать все ответы, связанные с терминами «регламент», «клиринг», «отчетность». На самом же деле пользователь имеет в виду более конкретную информацию о графике работы платформы.
Слишком короткий вопрос порождает слишком широкое семантическое поле, из-за чего в top-k попадают нерелевантные ответы, что ухудшает качество слоя поиска и извлечения информации из базы данных (далее – слоя retrieval). Практическое решение этой проблемы, которое дало заметный прирост качества — это использование техники расширения запроса (query expansion) перед векторным поиском, когда исходный запрос расширяется дополнительными словами, связанными терминами, синонимами, уточнениями или альтернативными формулировками. В моем примере вопрос может превратиться в «расписание сессий и расчетов в будние дни, часы работы платформы, время начала и окончания сессии, расписание расчетов».
Итого, дополнительный слой предобработки пользовательского вопроса, включающий нормализацию запроса, его смысловое достраивание и расширение при разработке систем становится критически важным наряду с требованиями устойчивости к шуму и обеспечения безопасности.
Relevance vs Completeness: разделяем понятия
После тюнинга векторного поиска слоем предобработки казалось, что основная часть системы уже работает: ИИ-агент стабильно выдает хорошую метрику похожести действительно «достаточно близким» ответам. Но на практике быстро выяснилось: высокое качество векторного поиска с технологическими улучшениями не гарантирует финальную релевантность ответа в целом.
Например, на мой вопрос выше модель выдавала следующий топ-3 ответов про уточнения по расписанию в летнее время (score: 0.92), про порядок расчетов в нестандартные дни (score: 0.88) и реально полезный ответ про расписание торгов (score: 0.71), который оказался ниже из-за шумных, но семантически близких результатов. Ну и ответ LLM на основе этой выборки был очень близким, но размытым и с второстепенными уточнениями, хотя точный ответ лежал в третьем ответе.
Стало очевидно, нужен дополнительный слой, который разделяет релевантность ответа, насколько найденный текст соответствует вопросу, и его полноту, хватает ли информации для ответа. Итогом стал слой постобработки ответа, который фактически превратил поиск в многошаговый фильтр качества.
Одним из ключевых механизмов стало повторное ранжирование (rerank), которое заново переоценивает найденные фрагменты с учетом совместного взаимодействия с конкретным вопросом, выталкивая наверх действительно релевантные ответы.
Новый порядок топ-3 ответов выглядел так: ответ про расписание торгов (score: 0.94), порядок расчетов в нестандартные дни (score: 0.45, оценен ниже как шумный) и регламент торгов (score: 0.32)
Затем в игру вступила лексическая фильтрация (lexical filtering), которая отсеивает шум через простое совпадение ключевых слов, работая как безопасный и надежный фильтр.
Венчает этот слой метрика уверенности (confidence), которая оценивает достаточность и надежность найденного контекста для генерации. Если уверенность низкая, контекст противоречивый, информации недостаточно, тогда вместо того, чтобы рисковать и галлюцинировать система делает что-то более безопасное: уходит в веб-поиск, переключает на человека или же честно говорит о том, что нет точной информации по этому вопросу.
На практике эта комбинация резко сократила количество «уверенно неправильных» ответов. Но важно другое: система перестала быть линейной цепочкой «нашёл → ответил», и начала работать как фильтрующий контур: сначала проверяется качество контекста, и только затем LLM допускается к генерации.
Итоговый пайплайн промышленного ИИ-агента
После всех экспериментов со слоями предобработки, retrieval-стратегиями и постфильтрацией стало очевидно: «идеального» ответа не существует. Любая добавленная логика улучшает один сценарий, но усложняет другой. А значит система ветвится бесконечно, усложняя и нагромождая пайплайн. И тут мы подошли к развязке нашего сюжета: необходимости управлять самим процессом получения ответа.
Изначально агент был построен как классический линейный пайплайн: пользовательский запрос, слой векторного поиска, формирование контекста, генерация LLM, ответ пользователю. Такой линейный алгоритм выглядит аккуратно и предсказуемо, но в реальности такая схема быстро перестает работать, потому что появляются условия, которые ломают линейность:
-
Итеративность улучшений. Запрос может проходить несколько циклов: предобработка, повторный retrieval, rerank, повторная проверка релевантности и полноты контекста, и важно не просто улучшать результат, а понимать, когда уже пора остановиться.
-
Fallback во внешний мир. Если внутреннего контекста недостаточно для уверенного ответа, то иногда системе стоит выходить за пределы базы знаний, через веб-поиск или внешние источники, а иногда признать, что ответа нет и стоит «позвать оператора/сохранить вопрос для последующей обработки человеком».
-
Выявление кейсов, когда отсутствует необходимость в LLM. Например, в базе знаний есть прямой ответ и любой синтез ухудшит результат.
Линейная архитектура плохо масштабируется под такую динамику. Она предполагает фиксированную последовательность шагов, тогда как промышленный ИИ-агент должен
-
возвращаться на предыдущие этапы,
-
пропускать узлы,
-
выбирать разные ветки исполнения,
-
и динамически адаптировать маршрут в зависимости от состояния контекста.
Поэтому я перешла на graph-based orchestration через LangGraph. Это стало не просто рефакторингом, а радикальной сменой архитектуры системы: вместо линейного процесса появился управляемый граф состояний. Каждый узел графа отвечает за отдельное действие, а переходы между ними зависят от данных, а не от фиксированного сценария.
В итоге агент был разбит на набор нод (узлов графа):
-
normalize — нормализация и семантическое расширение запроса,
-
analyze — определение intent и типа задачи,
-
plan — выбор стратегии обработки запроса,
-
route — маршрутизация (RAG / web / direct answer),
-
rag — слой retrieval + rerank + filtering,
-
synthesis — формирование финального ответа.
При этом важный момент: часть переходов между нодами пришлось несколько раз перерабатывать. Причина простая – реальные пользовательские запросы почти никогда не укладываются в заранее заданную схему маршрутизации.
Таким образом, ключевое изменение здесь не в самих этапах, а в их свойствах: каждый шаг теперь стал независим, повторяемым и условным. Graph-orchestration подход фактически объединяет retrieval слои, метрики и логику принятия решений в единую систему управления потоком. Да, сложность архитектуры выросла, но flow стал значительно более управляемым и нелинейным, где каждый этап может повторяться, пропускаться или маршрутизироваться в зависимости от состояния запроса и качества контекста.
В итоге у меня получился адаптивный ИИ-агент с query expansion, rerank-слоем, confidence-проверками, fallback-маршрутизацией и графовой оркестрацией исполнения.
Дальнейшие планы
Интересный эффект, который проявился уже в продакшене: иногда лучший ответ — это вообще отсутствие LLM. Если пользовательский запрос напрямую совпадает с фрагментом базы знаний, система находит точное совпадение, проходит confidence-проверку и возвращает результат напрямую, без генерации, без синтеза, без дополнений от LLM. На практике это оказалось: быстрее, дешевле и стабильнее, чем любой LLM-пайплайн даже с идеальным контекстом.
После всех итераций стало очевидно, что самым сложным оказались не embeddings, не LLM, и даже не оркестрация, как таковая. Самое сложное — quality retrieval. Проблема в том, что метрики «похожести» вопроса у ИИ-агента и реальная релевантность ответа для человека — это разные измерения. Модель работает в геометрии векторного пространства, тогда как человек оценивает результат инклюзивно: учитывает контекст, полноту, наличие ключевых ограничений, соответствие намерениям и смысловую достаточность.
Дополнительная сложность – семантический шум, недостаточность контекста. Векторный поиск неизбежно подтягивает соседние по смыслу области, и высокая близость ответов не гарантирует наличие корректного результата. Отсюда возникает ключевая проблема продакшен ИИ-агентов: система должна оценивать не только похожесть, но и достаточность ответа.
Следующий этап развития системы связан с усилением retrieval-слоя. В первую очередь я планирую переход к гибридному поиску, где BM25 дополняет векторный поиск и компенсирует слабые места семантических эмбеддингов. Параллельно думаю над расширением evaluation dataset, чтобы системно улучшать качество retrieval и формировать более устойчивую картину типовых ошибок.
Отдельное направление — логирование. Важно не просто фиксировать факт ошибки, но понимать ее структуру: на каком этапе произошла деградация, какой слой ее внес, какого рода несоответствие возникло. На основе таких данных можно построить полноценный feedback loop, который будет использовать реальные пользовательские запросы для улучшения системы. Также планируется доработка rerank-модели, калибровка confidence-метрик и более строгая проверка достаточности контекста перед генерацией.
В моем кейсе после всех итераций стало очевидно: ИИ-агент — это не про LLM как центральный элемент. Это система, где ключевую роль играют quality retrieval, decision logic и orchestration подход. В этой архитектуре LLM выступает скорее как synthesis layer, чем как центральный механизм принятия решений.
Если все упростить до сути, то современный промышленный ИИ-агент, это гибридная система оркестрации: retrieval как механизм извлечения фактов, graph как механизм управления потоком, эвристики как слой принятия решений и LLM как исполнитель, подключаемый по необходимости.
Cегодня вокруг ИИ-агентов много хайпа — от «революции» до «пузыря». Но если убрать шум, остаётся довольно прагматичная инженерная реальность: это новый класс систем, в которых вместо ручной логики появляется управляемая LLM-оркестрация. И, возможно, главный сдвиг здесь не в том, что модели стали умнее, а в том, что мы наконец начали строить вокруг них системы, которые работают предсказуемо в продакшене.
ссылка на оригинал статьи https://habr.com/ru/articles/1038190/