Гайд · TNWS AI

Как избежать дублей документов в Haystack через DocumentWriter и DuplicatePolicy

Haystack 3.1Python#Haystack#RAG#Python
4 мин—

Повторная индексация без случайных дублей: стабильные ID, режимы OVERWRITE, SKIP и FAIL и проверка числа записей.

Применимость и задача

Поисковый запрос: «как обновить документы Haystack DocumentWriter DuplicatePolicy». Материал сверён с официальной документацией Haystack 3.1 19 сентября 2026 года. Примеры рассчитаны на Python и пакет haystack-ai; для векторного примера дополнительно нужен пакет sentence-transformers-haystack. Демонстрационное InMemoryDocumentStore не рекомендовано разработчиком для production. Интерфейс командной строки ниже — Python API, а не названия пунктов графического меню.

DocumentWriter записывает Document в document_store и возвращает documents_written. DuplicatePolicy имеет режимы NONE, OVERWRITE, SKIP и FAIL. Поведение NONE зависит от настроек хранилища; OVERWRITE заменяет документ с тем же ID, SKIP игнорирует повтор, FAIL поднимает ошибку.

Нумерованный порядок работы

  1. Назначьте каждому логическому документу стабильный ID из учётной системы, не формируйте случайный UUID при каждом прогоне.
  2. Сохраните начальную версию и зафиксируйте число записей после первого вызова.
  3. Повторите с тем же ID и новым содержимым в безопасном тестовом store, применив OVERWRITE.
  4. Проверьте, что доступна только новая редакция и общее число документов не выросло.
  5. Прежде чем менять production, отдельно прогоните SKIP и FAIL, чтобы выбрать требуемую семантику.

Готовый шаблон

from haystack import Document
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.writers import DocumentWriter
from haystack.document_stores.types import DuplicatePolicy

store = InMemoryDocumentStore()
writer = DocumentWriter(document_store=store, policy=DuplicatePolicy.OVERWRITE)
first = writer.run(documents=[Document(id="policy-R7", content="Срок 14 дней")])
second = writer.run(documents=[Document(id="policy-R7", content="Срок 10 дней")])
print(first["documents_written"], second["documents_written"],
      store.count_documents())

Пример предназначен для контролируемого теста на своих данных. Установите зависимости в отдельном окружении и зафиксируйте версии; при использовании локальной embedding-модели отдельно проверьте её доступность и разрешённость для вашего проекта. Код был проверен на синтаксис Python, но его вызовы Haystack в данном материале не выдаются за выполненный runtime-тест.

Вход и ожидаемый результат

Вход: документ policy-R7 со сроком 14 дней, затем тот же ID со сроком 10 дней. Ожидаемое число документов в тестовом хранилище — один, а поиск по актуальной версии должен показывать 10 дней. Число documents_written в ответе компонента следует проверять отдельно: оно описывает операцию записи, не истинность правового правила.

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

Разбор ограничений и ошибок

Проблема повторной индексации редко сводится к чистому дублированию строк. Если ID собирается из содержания документа, изменение одного символа рождает новый ID, и старые противоречащие версии останутся в поиске. Напротив, если одинаковым ID назвать разные страницы одного договора, OVERWRITE перезапишет одну другой. Определите единицу идентичности заранее: целый документ, версия, страница или чанк. Для чанков нужен стабильный составной ключ из идентификатора исходника и идентификатора фрагмента, плюс осмысленная процедура удаления старых чанков при изменении границ. NONE не означает универсальное разрешение конфликтов: документация явно передаёт решение настройкам хранилища. FAIL удобен для теста загрузки с ожиданием отсутствия дублей; SKIP — для неизменяемых источников; OVERWRITE — для исправлений при стабильной структуре. Это выбор сценария, а не рейтинг лучшего режима.

Проверочный сценарий на реальном корпусе

Создайте тест на исчезновение фрагмента: старая версия имела три чанка, новая — два. Простое OVERWRITE обновит два знакомых ID, но третий старый может остаться, если приложение не удалит его явно. План миграции должен учитывать удаление остатка и атомарность переключения версии, зависящую от конкретного store. Для документов с персональными данными отдельно проверьте физическое удаление согласно вашей политике хранения. В memory-store легко проверить счётчик, но его нельзя принимать за гарантию поведения любой внешней интеграции. До повторной массовой загрузки снимите список ID и прогоните на выборке один запрос к старому и новому правилу. Если оба ответа доступны как актуальные, публикацию поискового индекса надо остановить и выяснить, где смешались версии.

Критерии приёмки

  • Стабильный ID сохраняется между запусками.
  • Повторный OVERWRITE не добавляет второй документ.
  • Устаревшие фрагменты удалены отдельно, если их число уменьшилось.

Не принимайте демонстрационный вывод за статистику качества. Для рабочего приложения повторите эти проверки на обезличенном наборе документов, измерьте результат после изменения версии корпуса и запретите доступ к источникам вне полномочий пользователя. Корректный ответ HTTP вашего сервиса не означает корректный ответ RAG-системы; последнее устанавливается сопоставлением факта с разрешённым источником.

Типичные ошибки: чего не делать

  • Не полагаться на NONE без проверки настроек store.
  • Не считать overwrite удалением всех старых чанков.
  • Не использовать одинаковый ID у разных документов.

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

FAQ

Что делает SKIP?

Пропускает документ, чей ID уже присутствует.

Что делает FAIL?

Выдаёт ошибку при повторном ID.

Можно ли использовать in-memory store в production?

Документация рекомендует его для экспериментов, не production.

Официальные источники

Частые вопросы

Что делает SKIP?

Пропускает документ, чей ID уже присутствует.

Что делает FAIL?

Выдаёт ошибку при повторном ID.

Можно ли использовать in-memory store в production?

Документация рекомендует его для экспериментов, не production.

Читайте также

Комментарии

Пока тихо. Скажите первое слово