3 мусорных документа (1.7%) отравили весь мой RAG
Строю локальную мультиагентную систему с RAG на ChromaDB. В какой-то момент модель начала нести чушь — вставлять в ответы куски маркдауна, генерить мусор вместо нормальных ответов.
Симптом
Спрашиваю: «Новый проект: цифровой двойник нефтеперерабатывающего завода. Как декомпозировать?»
В ответе — рандомные огрызки разметки типа "25*\n*Тип: Инфраструктура и системное администрирование*". Модель явно копировала что-то из контекста.
Копаю
Смотрю что RAG возвращает на этот запрос:
results = rag.query(query_texts=[query], n_results=3) for doc in results['documents'][0]: print(f"[{len(doc)} chars]: {doc[:50]}")
[56 chars]: 25*\n*Тип: Инфраструктура и системное администр... [15 chars]: и реализация*... [11 chars]: ров вместе...
Топ-3 — мусор, а не документы.
Причина
При загрузке маркдаун-файлов в ChromaDB чанкер резал по 800 символов механически — посередине заголовков, посередине предложений. В итоге появились микро-огрызки типа "ров вместе" (11 символов), которые стали отдельными документами.
Почему короткие чанки ломают RAG
Короткий текст → странный эмбеддинг. Вектор ни о чём, без смысла. И именно поэтому он оказывается «близок» к любому запросу случайным образом. Мой 11-символьный огрызок стабильно обгонял нормальные 800-символьные документы в similarity search.
Фикс
Нашёл все документы меньше 100 символов:
all_docs = rag.get(include=["documents"]) short = [(i, doc) for i, doc in enumerate(all_docs['documents']) if len(doc) < 100] print(f"Мусор: {len(short)}") # 3
Удалил:
ids_to_delete = [all_docs['ids'][i] for i, _ in short] rag.delete(ids=ids_to_delete) # Было: 176 docs → Стало: 173 docs
Галлюцинации прекратились сразу.
Вывод
Фильтруйте чанки по минимальной длине до загрузки в векторную БД:
MIN_CHUNK_LENGTH = 100 chunks = [c for c in chunks if len(c) >= MIN_CHUNK_LENGTH]
3 документа из 176 — это 1.7%. Процент не имеет значения. Если у мусора странный вектор — он всплывёт. Один плохой документ может отравить весь ваш RAG.
