Диагностика¶
Начинайте с симптома и проверяйте слои слева направо. Не удаляйте volumes до фиксации logs и состояния БД — иначе исчезнет причина.
Универсальный снимок¶
docker compose --profile ai ps
docker compose --profile ai logs --tail 100 postgres nats migrate crawler indexer api ollama model-init
Invoke-WebRequest http://localhost:8000/health/live -UseBasicParsing
Invoke-WebRequest http://localhost:8000/health/ready -UseBasicParsing
Дерево решений¶
flowchart TD
START["Что не работает?"] --> UP{"API :8000 отвечает?"}
UP -->|"нет"| PS["compose ps + api/migrate logs"]
UP -->|"да"| READY{"ready=200?"}
READY -->|"нет, postgres"| DB["pg_isready + migration + DSN"]
READY -->|"нет, ai"| AI["AI_MODE + Ollama models + /api/tags"]
READY -->|"да"| DATA{"Есть ready tenders/chunks?"}
DATA -->|"нет tenders"| CR["crawler logs + source API + cursor"]
DATA -->|"pending/failed"| IX["NATS consumer + indexer logs"]
DATA -->|"ready"| HTTP{"HTTP status запроса"}
HTTP -->|"401/403"| KEY["X-API-Key secret/enabled"]
HTTP -->|"429"| RATE["UTC window / Retry-After"]
HTTP -->|"200 empty"| REL["query/model/threshold"]
.env не принимается¶
docker compose config --quiet
docker compose run --rm api python -c "from tender_lens.config import Settings; print(Settings())"
Частые причины:
.envотсутствует рядом сdocker-compose.yml;EMBEDDING_DIMENSIONSне 1024;- число записано с лишним текстом;
- URL не соответствует ожидаемому runtime hostname (
postgres,nats,ollamaвнутри Compose,localhostс host).
.env.example проверяется отдельным unit test; если его чистая копия не загружается, это regression.
api не стартует¶
- Проверьте
migrate— он долженExited (0). - Проверьте port collision:
Get-NetTCPConnection -LocalPort 8000. - Посмотрите первую exception в
docker compose logs api, а не только последнюю restart line. - Проверьте, что image содержит
src/tender_lens/web/index.html.
ready.ai=false¶
Fake¶
AI_MODE=fake не требует Ollama. Если readiness false, убедитесь, что Compose environment действительно передал fake:
docker compose exec api python -c "from tender_lens.config import get_settings; print(get_settings().ai_mode)"
Live¶
docker compose exec ollama ollama list
docker compose logs --tail 100 model-init ollama
docker compose exec api python -c "import urllib.request; print(urllib.request.urlopen('http://ollama:11434/api/tags').status)"
Если model-init ещё скачивает model, readiness временно 503 — это правильно. После загрузки перезапустите indexer/API, если они были запущены до Ollama и находятся в старом crash loop.
Crawler получает 403/429/5xx¶
- 429/5xx ретраятся с
Retry-After/backoff; - Contracts Finder 403 может быть временным и получает отдельный cooldown;
- TED 403 не считается обычным временным response;
- после
HTTP_MAX_ATTEMPTSsource cycle завершается ошибкой, второй source всё равно запускается.
Не уменьшайте delay и не добавляйте произвольный host в allowlist только для «прохождения». Сначала подтвердите официальный endpoint/attachment link.
Host не разрешён политикой crawler¶
Это security decision, а не network bug. Сравните hostname URL с _source_hosts(). Добавлять host можно только если он:
- присутствует в живом payload официального API;
- принадлежит официальному владельцу/поставщику документов;
- покрыт unit test;
- не расширяет правило до wildcard/произвольного external host.
Tender есть, но pending¶
docker compose logs --tail 100 crawler indexer nats
Invoke-RestMethod http://localhost:8222/jsz?streams=true&consumers=true
Возможности:
- publish не прошёл — следующий crawler cycle вызовет
republish_pending; - NATS consumer отсутствует — indexer не подключился;
- сообщение ack-pending — indexer ещё работает или завис;
- model недоступна — временная ошибка получает NAK.
Tender failed¶
docker compose exec postgres psql -U tender_lens -d tender_lens -c `
"SELECT id, external_id, left(last_error, 300) FROM tenders WHERE index_status='failed' ORDER BY updated_at DESC LIMIT 20;"
Исправьте первопричину и повторно опубликуйте pending/failed запись через новый crawl/изменение. Для одноразовой диагностики не меняйте status напрямую: ручная SQL-правка обходит сервисные инварианты.
Search пуст при ready data¶
Проверьте:
docker compose exec postgres psql -U tender_lens -d tender_lens -c "SELECT count(*) FROM chunks;"
docker compose exec api python -c "from tender_lens.config import get_settings; print(get_settings().min_relevance_score, get_settings().embedding_model)"
Причины: query не связан с corpus, threshold слишком высок, индекс построен другой embedding model/режимом или живые документы не содержат ожидаемого текста. После смены embedding model нужна полная reindex, а не смешивание vectors.
Ask отвечает «Недостаточно данных»¶
Это не ошибка, если sources=[]. Такой ответ означает, что retrieval threshold защитил модель от выдумывания. Сначала выполните /search тем же query и изучите corpus/score.
401/403 и «что за ключ»¶
401 api_key_required— header отсутствует;401 api_key_invalid— передан не secrettl_…, key не найден;403 api_key_disabled— key создан, но отключён.
Создайте новый secret через CLI. Нельзя извлечь старый открытый key из DB: хранится только необратимый SHA-256 hash.
Integration test и event loop¶
Integration fixtures создают/закрывают async engine внутри loop конкретного test. Если появляется Future attached to a different loop, проверьте, что engine/session factory не стали session-scoped globals и что pytest-asyncio config не переопределён локально.
Документация не собирается¶
python scripts/generate_code_reference.py
python -m mkdocs build --strict
python scripts/check_docs.py
reference drift— generator изменил output; commit-ните его;- missing nav file — путь в
mkdocs.ymlне существует; - invalid source link — диапазон строк вышел за файл;
- Mermaid syntax — проверьте diagram block и quotes в labels.