Перейти к содержанию

Границы RAG и корпус учебной библиотеки

RAG связывает ответ на вопрос с найденными документами. Сначала система извлекает подходящие фрагменты, затем передаёт их в механизм подготовки ответа. Генерация и поиск остаются разными операциями: найденный источник ещё не означает, что сформулированный вывод следует из него. В этой серии построим библиотеку «Полка RAG» на PostgreSQL и постепенно разберём обе границы.

Первый урок задаёт корпус и договор результата. Нужны основы Python, JSON и HTTP; SQL пригодится в четвёртом уроке. Никаких внешних документов автоматически скачивать не будем: небольшие авторские материалы позволят увидеть, почему конкретная выдача получилась именно такой. После чтения у вас должен появиться сохранённый список источников и вопросов, с которыми можно сравнивать последующие изменения.

Вопрос, источник и утверждение

Представим посетителя, который спрашивает: «Как проверить, изменился ли документ?» В нашем корпусе есть заметка про ETag и условные HTTP-запросы. Поиск должен предложить эту заметку, хотя слово «изменился» может отсутствовать в её тексте. Векторное представление позволит исследовать сходство формулировок. Однако алгоритм не должен дописывать, что ETag обязательно совпадает с хешем файла: такой гарантии в исходнике нет.

Это различие удобно выразить через три объекта. Вопрос описывает потребность читателя. Фрагмент содержит ограниченный участок текста с адресом и заголовком. Ответ связывает утверждения с этими фрагментами. Если теряется один из объектов, становится трудно понять, что произошло: неправильно найден документ, неправильно прочитан источник или неправильно сформулирован вывод.

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

Договор маленькой библиотеки

Откройте состояние lesson-01 из архива серии. Файл corpus.json содержит два публичных документа и одну служебную заметку. Все тексты вымышлены для учебной задачи; это не выгрузка рабочего сайта и не статистика пользователей ProfessorWeb. Сохраняем короткие идентификаторы cache, urls и editor, чтобы сравнивать результаты между этапами.

{
  "fictional": true,
  "project": "Полка RAG",
  "questions": [
    "Как проверить, изменился ли документ?",
    "Как сохранить адрес статьи?"
  ],
  "result_fields": [
    "status",
    "sources",
    "space"
  ],
  "excluded": [
    "authentication",
    "production hosting",
    "automatic truth verification"
  ],
  "author_runs": false
}

Поле questions задаёт две отправные потребности. Список result_fields показывает, какие сведения приложение должно вернуть. status отличит отсутствие источников от успешного извлечения; sources даст проверяемую опору; space понадобится, когда появятся разные способы построения векторов. Поле excluded фиксирует возможности, которых у лаборатории пока нет.

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

Адрес документа в корпусе относится к будущему демонстрационному серверу. Он имеет вид /documents/cache.html и не является новым URL статьи ProfessorWeb. В самом сайте адрес этого урока остаётся независимым. Разделение предотвращает случайное смешение учебного приложения и оболочки курса: исходники лаборатории скачиваются отдельно и не подключаются к рабочему сайту.

Отбор документов и права читателя

Публичность задаём явным полем visibility. Значение public разрешает показывать материал читателю, editor обозначает служебный источник. На поздних этапах это поле войдёт в SQL-условие до формирования ответа. Скрыть приватную ссылку после ранжирования недостаточно: её текст уже мог попасть в контекст или повлиять на результат.

Пока роли не представляют реальные пользовательские сессии. Локальный оператор выбирает роль процесса, а веб-запрос не получает права назначать себе редакторский доступ. Для настоящего сервиса потребуются вход, проверенная идентичность и политика разрешений. Не следует переносить строку editor из демонстрационной конфигурации в доверенный параметр браузера.

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

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

Что будем считать результатом

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

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

При самостоятельной подготовке запишите для отправных вопросов нужные документы и нежелательные результаты. Не присваивайте приложению точность заранее. В авторских исходниках нет выполненных запусков, а приведённые реакции описывают ожидаемый договор. Замеры появятся только после отдельного выполнения в вашей среде.

Ещё одна полезная граница касается адресов исходников. В учебном корпусе мы сами назначаем короткие пути, потому что владеем всеми тремя документами. При подключении ProfessorWeb нельзя выводить новый адрес только из заголовка найденного текста: у старого урока уже есть постоянный URL. Карта документов должна связывать внутренний идентификатор с этим адресом явно. Тогда переименование темы или изменение заголовка не создаст новую ссылку без отдельного решения. При подготовке корпуса отмечайте также отсутствующие и закрытые страницы. Источник, который нельзя открыть, может оказаться непригодным доказательством для читателя, даже если его сохранённый текст хорошо отвечает на вопрос. Доступность материала проверяют как самостоятельное свойство корпуса.

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

Исходники и документация

Оглавление серии · Все состояния проекта. Для этого урока откройте папку lesson-01.

pgvector, транзакции Psycopg, Ollama Embed, структурированные ответы.

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