MAATRIX / Блог / AnythingLLM не индексирует документы: причины и решение

AnythingLLM не индексирует документы: причины и решение

AnythingLLM не индексирует документы: причины и решение

MAATRIX

Файл загрузился, в списке он есть, а на вопрос по нему чат отвечает, что ничего не нашёл. Жалоба «anythingllm не видит документы» почти никогда не означает поломку: между загрузкой и ответом с цитатой лежит конвейер из пяти шагов, и рвётся он чаще всего на втором — там, где от пользователя ждут нажатия кнопки, о существовании которой он не догадывается.

Обсудить статью, задать вопрос или начать новую тему

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

Перейти в сообщество →

Пять шагов от файла до ответа — и где рвётся конвейер

AnythingLLM не «читает файлы» в момент вопроса — он заранее строит по ним индекс. Путь документа состоит из пяти независимых операций, и любая может отработать вхолостую.

  1. Загрузка. Сервер на порту 3001 передаёт файл коллектору на 8888.
  2. Разбор. Коллектор вытаскивает текст в storage/documents/ отдельным JSON.
  3. Привязка. Документ появляется в общем списке «My Documents» и не принадлежит ни одному пространству.
  4. Эмбеддинг. Текст режется на чанки, они превращаются в векторы и пишутся в LanceDB.
  5. Поиск. На вопрос считается вектор запроса, ближайшие чанки уходят в промпт.

Главное: шаги 2 и 4 — разные операции, между ними стоит ручное действие пользователя. Файл в списке доказывает одно — разбор прошёл.

Что вы наблюдаетеГде оборвалосьКуда смотреть
Файл в «My Documents», чат его не знаетшаг 3кнопка Save and Embed
Документ в пространстве, ответы пустыешаг 4эмбеддер, лимит чанка, размерность
В карточке документа 0 wordsшаг 2скан без текстового слоя, кодировка
Цитаты приходят, но не тешаг 5порог сходства, число сниппетов, язык

Первую развилку дают три команды:

docker logs --tail 300 anythingllm 2>&1 | grep -iE 'collector|embed|chunk|failed|error'
sudo find /opt/anythingllm/storage/documents -name '*.json' | wc -l
sudo du -sh /opt/anythingllm/storage/{documents,vector-cache,lancedb,models}

Каталог documents растёт на шаге 2, vector-cache и lancedb — только на шаге 4. Если documents весит 40 МБ, а lancedb — 20 КБ, диагноз готов: разбор прошёл, эмбеддинга не было.

Документ загружен, но не «вшит» в рабочее пространство

Причина четырёх обращений из пяти — не техническая, а интерфейсная. Окно документов состоит из двух колонок: слева My Documents — общее хранилище всех разобранных файлов сервера, справа документы этого пространства. Перетаскивание кладёт файл в левую колонку.

Чтобы документ участвовал в ответах, нужны два действия подряд: пометить его галочкой и нажать Move to Workspace, а затем — обязательно — появившуюся внизу кнопку Save and Embed. Расчёт векторов запускает вторая, и она легко теряется в списке: закрыли окно крестиком — изменения отменены молча.

Два следствия. Документы не глобальные: файл, вшитый в пространство «Договоры», не виден пространству «Регламенты», хотя в «My Documents» лежит в одном экземпляре. И загрузка через API не привязывает файл никуда: /api/v1/document/upload только разбирает документ — главная ошибка при наполнении базы скриптом.

ALLM=http://127.0.0.1:3001
curl -s -X POST $ALLM/api/v1/document/upload -H "Authorization: Bearer $ALLM_KEY" \
     -F "file=@/root/docs/reglament.pdf" | jq -r '.documents[].location'
curl -s -X POST $ALLM/api/v1/workspace/kb/update-embeddings \
     -H "Authorization: Bearer $ALLM_KEY" -H 'Content-Type: application/json' \
     -d '{"adds":["custom-documents/reglament.pdf-6f2c….json"],"deletes":[]}'

Значение location из первого ответа подставляется во второй дословно, вместе с хэшем в имени; ключ берётся в разделе Developer API.

Проверять привязку надёжнее в базе. Таблица workspace_documents хранит связь документа с пространством, document_vectors — записанные векторы. Работайте с копией:

sudo cp /opt/anythingllm/storage/anythingllm.db /tmp/allm.db
sqlite3 /tmp/allm.db "SELECT w.name, COUNT(wd.id) AS docs FROM workspaces w
  LEFT JOIN workspace_documents wd ON wd.workspaceId = w.id GROUP BY w.id;"
sqlite3 /tmp/allm.db "SELECT wd.filename, COUNT(dv.id) AS vectors
  FROM workspace_documents wd LEFT JOIN document_vectors dv ON dv.docId = wd.docId
  GROUP BY wd.id ORDER BY vectors ASC LIMIT 20;"

docs = 0 — документ до пространства не доехал. docs > 0, а vectors = 0 — привязка есть, эмбеддинг провалился. Векторы есть, ответов нет — дело в поиске.

Развернуть за пару минут

Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть AnythingLLM

Коллектор разобрал файл не так, как вы думаете

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

sudo jq -r '[.title, .wordCount, .token_count_estimate] | @tsv' \
     /opt/anythingllm/storage/documents/custom-documents/*.json | sort -k2 -n | head

Ноль слов на шестидесятистраничном PDF — это скан. Своего OCR у AnythingLLM нет: файлы из сканера для него пустые. Лечится до загрузки:

sudo apt-get install -y ocrmypdf tesseract-ocr-rus poppler-utils
pdftotext -layout skan.pdf - | wc -c          # около нуля — текстового слоя нет
ocrmypdf -l rus+eng --skip-text skan.pdf skan-ocr.pdf

Остальные причины пустого разбора:

  • Формат. Старый бинарный .doc (не .docx), .pages, .numbers и архивы не разбираются: libreoffice --headless --convert-to docx *.doc. PDF с паролем останавливает парсер даже при пустом пароле — снимайте защиту qpdf --decrypt.
  • Кодировка. Текст и CSV из российских учётных систем часто в windows-1251. Коллектор читает их как UTF-8, в чанки уезжает мусор. Проверка — file -i notes.txt, лечение — iconv -f cp1251 -t utf-8 notes.txt -o notes-utf8.txt.
  • Вёрстка. Текст идёт потоком: двухколоночный PDF даёт перемешанные строки, таблица теряет структуру — выгружайте её отдельно в CSV.
  • Ссылки на сайты. Сбор страниц по URL поднимает headless-Chromium внутри коллектора, и без --cap-add SYS_ADMIN он падает с No usable sandbox!, тогда как файлы грузятся: «PDF индексируются, а сайты нет». Отдельная беда — Cloudflare и SPA: в индекс уходит текст капчи или пустой каркас.

Крупные файлы отваливаются ещё на прокси: client_max_body_size 1m рубит PDF молча — конфиг в статье про ошибки AnythingLLM на сервере.

Векторы не посчитались: чанки, лимиты эмбеддера и кэш

Если document_vectors пуст, а документ привязан, ищите в логе строку вида Could not embed document chunks! This document will not be recorded.: эмбеддер вернул ошибку, приложение откатило запись.

Эмбеддер не скачался. Режим native тянет ONNX-сборку all-MiniLM-L6-v2 с huggingface.co в storage/models/ при первом использовании; с обрезанным исходящим доступом загрузка виснет без сообщения. Проверка — sudo du -sh /opt/anythingllm/storage/models/: там сотни мегабайт, а не пустой каталог.

Чанк длиннее окна. Размер куска задаёт настройка Max embedding chunk length. У all-MiniLM-L6-v2 окно 512 токенов, и лишнее отбрасывается; у OpenAI превышение приходит явной ошибкой:

This model's maximum context length is 8192 tokens, however you requested
9411 tokens (9411 in your prompt). Please reduce the length of the prompt.

Рабочие значения — 500–1000 для встроенного эмбеддера, до 8000 для text-embedding-3-small; chunk overlap обязан быть меньше длины чанка.

Провайдер и модель. Индексация книги шлёт тысячи чанков подряд, OpenAI отвечает Rate limit reached for text-embedding-3-small ... on tokens per min, и документ пишется частично — грузите партиями. Эмбеддер в Ollama ставится отдельно от чат-модели (ollama pull nomic-embed-text), иначе получите model "nomic-embed-text" not found, try pulling it first.

Размерность не совпала. Самое коварное — смена эмбеддера на живой базе. Встроенный даёт 384 измерения, text-embedding-3-small — 1536, nomic-embed-text — 768. Chroma скажет прямо: Embedding dimension 384 does not match collection dimensionality 1536, LanceDB промолчит и перестанет находить старое.

И ловушка, на которой теряют вечер: AnythingLLM кэширует векторы в storage/vector-cache/ по хэшу файла. Меняете эмбеддер, удаляете документ, заливаете заново — а приложение достаёт из кэша старые. Поэтому чистка кэша обязательна: sudo rm -f /opt/anythingllm/storage/vector-cache/*.json.

Векторы есть, а чат отвечает «ничего не найдено»

Конвейер отработал, вопрос к поиску. Начните с режима чата в настройках пространства. В режиме Query модель отвечает строго по найденным фрагментам и при пустой выдаче возвращает шаблон There is no relevant information in this workspace to answer your query. В режиме Chat та же ситуация даёт ответ из общих знаний модели и выглядит как «документ проигнорирован» — для отладки Query удобнее.

Порог сходства. Параметр Document similarity threshold принимает значения от «No restriction» до «High». Высокий порог отсекает всё, что не совпало с запросом почти дословно, и на русской базе режет много. Для отладки поставьте «No restriction»: появились цитаты — дело в пороге.

Число сниппетов. Max Context Snippets по умолчанию равно 4. PDF на 200 страниц при чанке в 1000 знаков даёт порядка полутора тысяч фрагментов, а в промпт попадут четыре — вопрос «перечисли все сроки» так не решается. Поднимите до 6–12, если окно позволяет.

Контекстное окно. Сниппетов подняли до 12, а Ollama по умолчанию режет контекст до нескольких тысяч токенов: лишнее отбрасывается, и модель отвечает так, будто документов не видела.

ollama show qwen2.5:7b-instruct-q4_K_M --modelfile | grep -i num_ctx
printf 'FROM qwen2.5:7b-instruct-q4_K_M\nPARAMETER num_ctx 8192\n' > Modelfile
ollama create qwen-ctx8k -f Modelfile

Язык эмбеддера. Про это молчат инструкции, а по русскоязычным базам бьёт сильнее всего: встроенный all-MiniLM-L6-v2 обучен на английском. Русские документы он векторизует, но качество поиска падает, особенно если вопрос сформулирован иначе, чем текст. Берите многоязычную модель — bge-m3 (1024 измерения), multilingual-e5-large или text-embedding-3-large.

Честное ограничение. Векторный поиск не умеет считать («сколько всего договоров») и находить по точному идентификатору («что в пункте 5.3.2»). Обход один — закрепить документ булавкой: текст уйдёт в промпт целиком, минуя поиск, но это годится для одного-двух небольших файлов. Подбор компонентов — в материале про векторную базу для RAG.

Порядок восстановления: переиндексация без потери базы

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

  1. Бэкап. SQLite не любит копирование на ходу: docker compose stop && sudo tar -czf /root/allm-$(date +%F).tgz -C /opt/anythingllm storage && docker compose start.
  2. Зафиксируйте эмбеддер и проверьте ответ: curl -s http://127.0.0.1:11434/api/embeddings -d '{"model":"bge-m3","prompt":"тест"}' | head -c 200. Пусто — дальше бессмысленно.
  3. Выставьте нарезку заранее: чанк под окно модели, перекрытие 10–20% от длины.
  4. Удалите документы из пространства — снятие галочек и Save and Embed; чистятся и workspace_documents, и document_vectors.
  5. Почистите vector-cache, иначе пересчёт вернёт старые векторы.
  6. Вшивайте партиями по 5–10 файлов под docker logs -f anythingllm: так проще поймать файл, на котором всё встало.
  7. Проверьте тем же SQL-запросом: у каждого документа ненулевое число векторов.

Две вещи проверьте заранее. Диск: документ хранится трижды — исходник, разобранный текст, векторы, — и ENOSPC: no space left on device посреди индексации оставит базу в половинчатом состоянии. Память: на тесной машине процесс уходит по OOM, это видно в docker inspect anythingllm --format '{{.State.OOMKilled}}'. Расчёт есть в статье про то, сколько RAM нужно для AnythingLLM.

Какой сервер под AnythingLLM брать в MAATRIX

Требования к железу задаёт индексация, а не чат: разбор PDF, расчёт эмбеддингов и запись в LanceDB идут в одном контейнере.

Честный минимум: 2 vCPU, 4 ГБ RAM, 40 ГБ NVMe. Документация называет 2 ГБ, но это граница для интерфейса поверх внешнего API: как только идут документы, к Node-серверу добавляются парсер и эмбеддер, и первая же партия сканов укладывает такую машину.

Комфортный вариант: 4 vCPU, 8 ГБ RAM, 80–120 ГБ NVMe. Разница видна не в скорости ответа, а в скорости наполнения: расчёт эмбеддингов на CPU хорошо параллелится. Восьми гигабайт хватает на несколько пространств, переиндексацию на живой системе и внешнюю векторную базу (Qdrant, Chroma) рядом. Диск считайте по правилу тройного хранения: 10 ГБ исходников займут в storage около 25–30 ГБ. Локальный инференс добавит веса — 7B в Q4 это ~4,5 ГБ, — поэтому генерацию разумнее отдать Ollama на отдельной машине.

Локация — Лондон (UK). Причина связана с темой статьи напрямую: встроенный эмбеддер идёт за ONNX-моделью на huggingface.co, а эмбеддеры OpenAI отвечают не на все адреса. С российского IP индексация встаёт на первом же документе и выглядит как поломка приложения. С британской площадки оба доступны напрямую, пинг до Европы низкий, а база рабочих документов лежит в понятном европейцам правовом поле. США (Нью-Йорк) берите под OpenAI, Россию — под 152-ФЗ, но тогда эмбеддер держите локальным.

Приложение из каталога apps.maatrix.io разворачивается автоматически при заказе сервера — команды вводить не нужно, работает и на Ubuntu, и на Debian. Адрес панели и ключи появятся в личном кабинете, в разделе «Доступ»: останется войти, выбрать эмбеддер (многоязычный, если база на русском) и залить первую партию документов. Оплата — картами российских банков, по СБП, криптой или токеном MAAT. Как подбирается площадка — в обзоре VPS в Великобритании для нейросетей.

Развернуть за пару минут

Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть AnythingLLM

Обсудить статью, задать вопрос или начать новую тему

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

Перейти в сообщество →

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

Загрузил PDF, он виден в списке, но чат про него не знает.

Документ остался в «My Documents» и не привязан к пространству. Отметьте файл, нажмите Move to Workspace, а затем обязательно Save and Embed — векторы считает вторая кнопка. Проверка — запрос к workspace_documents в anythingllm.db.

Документ вшит, векторы посчитаны, а ответы всё равно «не нахожу».

По очереди: режим чата на Query, порог сходства на «No restriction», сниппетов с 4 до 8. Не помогло — смотрите wordCount в JSON внутри storage/documents/: у скана без OCR там ноль, вшита была пустота.

Сменил эмбеддер, перезалил документы — поиск не работает.

Старые векторы достались из кэша: AnythingLLM хранит их в storage/vector-cache/ и переиспользует по хэшу файла. Удалите документы из пространства, очистите vector-cache/*.json, только потом вшивайте заново.

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.