Перейти к содержимому
24 мая 2025 г.·7 мин чтения

Ссылки на источники в ответе ассистента: как их строить

Ссылки на источники в ответе ассистента помогают проверить выводы. Разберем, как собирать цитаты по документу, а не по случайным кускам текста.

Ссылки на источники в ответе ассистента: как их строить

В чем проблема случайных цитат

Когда ассистент пишет "по данным документа" и рядом вставляет кусок текста без ясной связи с выводом, это не выглядит как доказательство. Пользователь видит совпадение слов, но не понимает, почему ответ следует именно из этого места. Поэтому случайно выбранные цитаты быстро теряют смысл.

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

Особенно хорошо это видно на договорах. В тексте может быть сказано: оплата идет в течение 10 дней после приемки товара. Ассистент находит похожую фразу про 10 дней после выставления счета и делает вывод, что срок одинаковый. Разница кажется мелкой, но для бухгалтера, юриста или закупщика это уже другой ответ.

Даже правильный общий вывод не всегда спасает доверие. Если пользователь открывает цитату и не видит там прямой опоры под конкретную фразу ответа, он начинает сомневаться во всем остальном. Модель могла угадать верно, но пользователь этого не знает. Ему нужен не удачный результат, а проверяемый путь от документа к выводу.

Слабую цитату обычно выдают простые признаки. Фрагмент говорит о похожей теме, но не о том утверждении, которое сделал ассистент. Цитату вырвали без соседнего условия, исключения или даты. В ответе есть точный вывод, а в источнике только косвенный намек. Или ассистент показывает сразу несколько сниппетов и никак не связывает их с тезисами.

Длинный список фрагментов тоже редко помогает. Когда пользователь получает пять кусков текста без объяснения, он сам должен собрать логику, проверить пересечения и решить, какой абзац к чему относится. Это уже ручная проверка документа, только в неудобной форме.

В RAG с цитированием ошибка часто не в поиске, а в последнем шаге. Система находит что-то близкое, а потом строит ответ шире, чем позволяет найденный текст. Если человеку нужно перечитать весь документ, чтобы проверить одну фразу ассистента, цитирование не сработало.

Как выглядит нормальная опора на документ

Нормальная опора на документ устроена просто: ассистент делает тезис, рядом показывает короткую цитату и сразу указывает точное место в тексте. Пользователь не должен гадать, откуда взялся ответ. Он должен за несколько секунд открыть нужную страницу, найти абзац и проверить смысл.

Хорошая ссылка подтверждает не весь ответ целиком, а каждый важный тезис по отдельности. Если ассистент пишет, что срок оплаты - 30 календарных дней, ссылка должна вести именно к пункту про срок оплаты, а не на любую страницу договора, где встречается слово "оплата".

Рабочая схема обычно выглядит так: один тезис - один источник, рядом с тезисом - короткий фрагмент на одну-две строки, а в ссылке или подписи - страница, раздел, пункт или строка таблицы. Если в ответе две разные мысли, у них должны быть две разные опоры.

Это особенно важно для таблиц. Если ассистент отвечает про лимит, цену или дату, пользователь должен видеть не просто номер страницы, а строку или заголовок столбца. Иначе поиск по документу превращается в ручной квест, а доверие к ответу падает.

Плохой вариант выглядит так: внизу ответа лежат три длинных куска текста с разных страниц, но непонятно, какой из них что доказывает. Формально цитаты есть, пользы почти нет. Человек все равно перечитывает документ сам, потому что не видит связи между тезисом и источником.

Хороший вариант короче и честнее. Например: "Штраф за просрочку составляет 0,1% в день". И сразу ниже: "п. 6.4, стр. 12: 'Поставщик уплачивает пеню в размере 0,1% от суммы просроченного обязательства за каждый день просрочки'". Здесь легко проверить и число, и контекст.

Для проверки ответов LLM это важнее, чем кажется. Даже сильная модель может правильно пересказать общий смысл, но ошибиться в числе, сроке или исключении мелким шрифтом. Короткая цитата рядом с тезисом быстро ловит такие промахи.

RAG с цитированием работает лучше, когда система не тащит в ответ все найденные куски подряд. Ей нужна дисциплина: сначала тезис, потом конкретная опора. Если опора не помещается в одну ясную ссылку с коротким фрагментом, значит ответ еще не собран.

Что считать источником для цитаты

Источник для цитаты - это не весь документ и не случайный кусок текста, который поиск вытащил первым. Источник должен быть таким фрагментом, который можно показать человеку отдельно, и он поймет смысл без догадок. Если ассистент ссылается на половину страницы без границ, пользователь видит не опору, а шум.

Лучше считать отдельными источниками разные типы фрагментов: абзац, строку таблицы, сноску, приложение, блок с определением, пункт договора. Страница тоже нужна, но только как адрес, а не как смысловая единица. На одной странице часто живут сразу несколько мыслей, и ссылка на всю страницу делает цитату размытой.

У каждого фрагмента должен быть стабильный идентификатор. Не временный номер из индекса и не позиция в выдаче, а понятный ID, который не меняется после переиндексации. Например: doc_17:p_12:sec_4.3:para_2 или contract_A:appendix_1:table_3:row_5. Тогда ответ, лог и ручная проверка будут ссылаться на один и тот же кусок.

Есть и простое правило: не рвите смысловую пару. Если в одном месте дано определение, а в следующем предложении идет ограничение, храните их вместе. Если в таблице есть ставка, а под таблицей написано исключение, связывайте эти части явно. Иначе модель возьмет удобную цифру, пропустит оговорку и ответит уверенно, но неверно.

Обычно для каждого источника достаточно хранить сам текст фрагмента, номер страницы или листа, заголовок раздела и соседний контекст до и после него. Последний пункт нужен чаще, чем кажется. Он помогает понять, к чему относится местоимение, что именно ограничивает условие и не продолжается ли мысль в следующем абзаце. Особенно это заметно в договорах, регламентах и PDF с таблицами.

Если документ длинный, полезно делать два слоя источников. Первый слой - маленький фрагмент для точной цитаты. Второй - родительский блок, например весь пункт договора или вся таблица. Тогда ассистент может показать короткую цитату, но проверить себя по более широкому отрывку. Такой подход заметно снижает число ответов, где цитата формально есть, а смысла в ней нет.

Как настроить поиск по документу

Если вы хотите, чтобы ссылки на источники выглядели честно, начинать нужно не с генерации ответа, а с поиска. Плохой поиск почти всегда дает плохую цитату: модель находит случайный абзац с похожими словами и делает вид, что этого достаточно.

Документ лучше индексировать не как сплошной текст, а как структуру. Заголовки, подпункты и основной текст стоит хранить раздельно. Тогда поиск понимает не только слова, но и место мысли в документе. Фраза из раздела "Ответственность сторон" и такая же фраза из приложения - это не одно и то же.

Чанки не должны обрывать смысл на полуслове. Если пункт договора продолжается в соседнем блоке, система должна подтянуть и его. Иначе ассистент процитирует первую половину правила, а исключение останется за кадром. На практике это решают просто: кроме найденного фрагмента, добавляют соседний кусок сверху или снизу, если у них один раздел и общая нумерация.

Что хранить рядом с фрагментом

Без метаданных поиск быстро начинает путать похожие куски. Для каждого блока полезно хранить идентификатор документа, его версию, путь по заголовкам и номер страницы или диапазон строк.

Эти поля не дают смешивать старое и новое. Если договор обновили вчера, ассистент не должен брать цитату из прошлой редакции только потому, что формулировка кажется знакомой. Версии лучше разводить жестко: отдельный version_id, отдельная дата, явный статус "актуальная" или "архив".

Отдельная история - таблицы. Не превращайте их в набор строк без связей между колонками. Когда цена, срок и условие оплаты лежат в разных кусках текста, модель начинает склеивать данные как попало. Храните таблицу как таблицу: с названием, колонками, строками и привязкой к разделу. Тогда ассистент сможет сослаться не на случайный обрывок, а на конкретную строку.

Для RAG с цитированием обычно лучше работает смешанный поиск. Сначала он ищет по словам, потом по смыслу, а затем ранжирует результаты с учетом заголовка, версии и соседних блоков. Это не самый эффектный кусок архитектуры, но именно он дает цитаты, которые можно проверить за минуту.

Как собрать ответ шаг за шагом

Соберите честный прогон
Подайте один набор контрольных вопросов и сравните ответы без привязки к одному вендору.

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

Возьмем вопрос: "Когда поставщик может поднять цену и какой штраф за срыв срока?" Здесь уже два разных утверждения. Первое касается условий изменения цены, второе - санкций за просрочку. Искать их одним запросом неудобно: хороший поиск почти всегда вытаскивает один кусок сильнее другого.

Сначала разложите вопрос на короткие проверяемые тезисы. Не "объясни условия договора", а "можно ли менять цену", "кто должен уведомить", "какой размер штрафа", "за какой период считают просрочку".

Затем под каждый тезис ищите свой фрагмент. Если тезис про штраф, не давайте поиску тянуть абзацы про оплату или приемку. Четыре точных поиска обычно лучше одного широкого.

Потом склейте соседние куски, если смысл размазан по нескольким абзацам. Часто правило лежит в одном пункте, а исключение или срок уведомления - в следующем. Если отправить модели только один кусок, она достроит недостающее сама.

Перед генерацией соберите для каждого тезиса маленький пакет опоры: цитата, номер пункта, страница, соседний контекст. После этого разрешайте модели писать только то, что есть в этом пакете. Если опоры нет, модель должна прямо сказать, что документ не дает ответа.

Ссылку лучше ставить сразу после утверждения или короткого блока, а не в конце всего ответа. Пользователь должен сразу видеть, на что опирается каждая мысль: "Поставщик может изменить цену только после письменного согласования сторон [п. 4.2, стр. 7]".

Такой порядок заметно снижает число красивых, но ложных ответов. Для RAG с цитированием это почти обязательное правило: сначала тезис, потом опора, потом формулировка. Если дать модели писать свободно, она быстро превращает документ в пересказ с догадками.

На практике помогает и простой запрет: один тезис без найденного фрагмента - один явный пропуск в ответе. Это выглядит строже, зато пользователь понимает границы системы и меньше спорит с цитатами.

Пример на договоре поставки

Менеджер по закупкам задает ассистенту простой вопрос: "Есть ли штраф за просрочку поставки?" На таком кейсе хорошо видно, зачем вообще нужны цитаты. Если поиск работает слабо, система вытаскивает два случайных куска: один про ответственность сторон, второй про форс-мажор. Формально цитаты есть, а ясности нет.

Нормальный ответ строится иначе. Система находит основной пункт о штрафе и соседний пункт с исключениями. Допустим, в договоре пункт 8.4 на странице 12 говорит: "Поставщик уплачивает пеню 0,1% от стоимости непоставленного в срок товара за каждый день просрочки, но не более 10% от общей стоимости такой партии". А пункт 8.6 на странице 13 уточняет: штраф не начисляют, если покупатель сам задержал приемку, не передал обязательные данные или наступил форс-мажор.

Тогда ассистент отвечает по существу: штраф есть, ставка - 0,1% в день, предел - не более 10% стоимости партии, а исключения - задержка со стороны покупателя и форс-мажор. Источники при этом указаны отдельно: п. 8.4, стр. 12 и п. 8.6, стр. 13.

Такой ответ уже можно проверить за несколько секунд. Пользователь видит сумму, условие применения и границы, а не просто фразу "ответственность предусмотрена договором".

Есть и еще один важный момент. По клику система должна открывать не весь PDF, а нужный абзац на нужной странице. Если человек нажимает на ссылку к п. 8.4, он сразу попадает на страницу 12, где подсвечена строка со ставкой 0,1%. Если он открывает исключение, система ведет его к абзацу на странице 13. На бумаге это кажется мелочью. На деле именно такие детали решают, поверит ли пользователь ответу.

Для этого мало хранить текст чанка. Система должна сохранять координаты фрагмента: страницу, номер пункта, границы абзаца и соседние предложения. Тогда цитата выглядит как опора на документ. Человек видит, что ассистент нашел нужное место в договоре, а не собрал ответ из случайных фрагментов.

Где система ошибается чаще всего

Проверяйте ответы по логам
Разбирайте спорные ответы по аудит-логам, когда цитата не совпала с выводом.

Частая ошибка - ссылка не на само правило, а на заголовок раздела. Пользователь спрашивает про срок оплаты, а в ответе стоит цитата из строки вроде "Порядок расчетов". Это не опора на документ. Нужен фрагмент, где есть точный срок, условие, исключение или формула расчета.

Много проблем возникает на стыке страниц и чанков. Поиск находит похожий кусок с соседней страницы, потому что там повторяются те же слова: "поставка", "акт", "срок", "стороны". В итоге ответ выглядит правдоподобно, но цитата указывает на место рядом, а не туда, где написано нужное правило. Такое часто бывает в договорах, где похожие формулировки идут подряд в нескольких разделах.

Плохо работает и схема, где одна ссылка прикрывает сразу несколько выводов. Например, ассистент пишет, что товар нужно оплатить за 10 дней, просрочка дает штраф 0,1%, а расторгнуть договор можно только после уведомления. Если рядом стоит одна общая цитата, проверить такой ответ почти нельзя. Пользователь не понимает, какой вывод из какого места взят. Лучше привязывать цитату к одному утверждению или к двум соседним фразам из одного абзаца.

С версиями документов ошибка еще опаснее. В индекс легко попадает черновик, старая редакция приложения или договор без последней правки. Модель отвечает уверенно, потому что текст действительно нашла, но это уже не тот документ, по которому нужно давать ответ. Если в хранилище лежат "договор_final", "договор_final2" и "договор_signed", без метаданных система почти наверняка ошибется.

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

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

Если такие ошибки не ловить, ссылки на источники превращаются в декор. Снаружи все выглядит аккуратно, но пользователь проверяет цитату и не находит там опоры для вывода.

Быстрые проверки перед выдачей ответа

Работайте через один вход
Используйте один эндпоинт для OpenAI, Anthropic, Google, DeepSeek, xAI и других провайдеров.

Перед отправкой ответа полезно сделать короткий ручной или автоматический прогон. Он занимает секунды, но спасает от самой неприятной ситуации: ассистент звучит уверенно, а опора на документ распадается при первом же просмотре.

У каждого вывода должен быть свой источник. Не общий комментарий в духе "документ подтверждает", а понятная привязка: пункт, абзац, таблица или страница. Если ассистент пишет, что срок оплаты - 10 рабочих дней, пользователь должен сразу видеть, откуда это взялось.

На практике хватает нескольких проверок. Сверьте каждое конкретное утверждение с одной цитатой рядом с ним. Проверьте числа, даты, имена компаний и номера пунктов символ в символ. Убедитесь, что цитата открывает нужное место сразу, без долгого поиска по файлу. И отдельно посмотрите, на какую версию файла ссылается система, если документ обновляли.

Особенно часто ломаются числа. Модель легко перепутает "10 календарных дней" и "10 рабочих дней", "до 5%" и "5%". Для договора, тарифа или регламента это уже другой смысл. Поэтому суммы, ставки, сроки и ФИО лучше прогонять отдельной проверкой, даже если остальной текст выглядит правдоподобно.

С версией файла тоже много промахов. В базе может лежать черновик, а пользователь смотрит на подписанный вариант. Тогда формальные ссылки есть, но пользы от них мало. Система должна хранить идентификатор версии документа и подтягивать цитату только из актуального файла, который участвует в ответе.

Хорошая цитата открывается в один клик и показывает ровно тот фрагмент, на который опирается ответ. Не весь PDF на 80 страниц и не набор соседних кусков текста. Пользователь не должен сам угадывать, где именно модель нашла основание для вывода.

Если опора слабая, лучше сказать об этом прямо. Например: "Я нашел похожий фрагмент, но он не подтверждает срок однозначно" или "В документе есть упоминание штрафа, но не видно, к какому случаю он относится". Такая честность полезнее, чем гладкий ответ с сомнительной цитатой.

Простой тест звучит так: может ли человек за 5-10 секунд проверить ответ и согласиться с ним без повторного чтения всего документа. Если нет, сборку ответа стоит доработать.

Что делать дальше в продакшене

В продакшене цитаты ломаются не из-за одной большой ошибки, а из-за мелочей, которые никто не отслеживает. Сегодня ответ выглядит убедительно, а через неделю модель ссылается на другой фрагмент, потому что поменялась версия модели, индекс или порядок чанков.

Поэтому храните след каждого ответа. Для каждого запроса полезно логировать сам вопрос, найденные фрагменты, их позиции в документе, итоговый промпт, версию модели и текст ответа. Без этого вы не поймете, сломался поиск, генерация или постобработка цитат.

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

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

Сравнивать модели лучше на одном и том же пайплайне. Один и тот же ретривер, одинаковые чанки, одинаковые правила вставки цитат. Иначе вы сравниваете не модели, а весь стек сразу. На практике одна модель пишет чище, а другая точнее привязывает вывод к документу. Для задач с цитированием точность ссылки важнее красоты текста.

Когда команда доходит до регулярных тестов, быстро встает еще один практический вопрос: как переключать модели и не переписывать клиентский код. Здесь удобен единый OpenAI-совместимый шлюз. Например, AI Router на airouter.kz позволяет менять base_url и работать через один эндпоинт с разными моделями, сохраняя привычные SDK и промпты. А аудит-логи помогают разбирать спорные ответы и внутренние проверки.

Хороший признак зрелой системы простой: любой неудачный ответ можно разобрать по шагам за несколько минут. Если для этого нужно поднимать три сервиса и гадать, какая модель отвечала, система еще не готова к серьезной нагрузке.