homelab-codex-ws/jobs/documents-ingest/pyproject.toml

32 lines
778 B
TOML
Raw Normal View History

[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
[project]
name = "documents-ingest"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"asyncpg>=0.29",
"structlog>=24.1",
"aiohttp>=3.9",
"kb-mail",
"kb-retrieval",
feat(kb): faza 3 krok 2 — migracja 004 (document_summary) + pilot streszczeń A/B Migracja 004: document_summary (envelope_id, summary, tags JSONB, model, embedding VECTOR(1024) + HNSW cosine, embedding_model, UNIQUE(envelope_id, model) od razu — wzorzec 002/003). Zastosowana na żywej bazie kb-postgres@PIHA. Job documents-ingest-summarize: wejście = document_chunk.text WHERE excluded_reason IS NULL per koperta source='paperless' (duplikaty przez entities[duplicate_of] pomijane w całości), wymuszony JSON {summary, tags} przez --backend ollama|anthropic, słownik tagów kontrolowany (tags-vocab.yaml) z max 3 free-form, map-reduce dla dokumentów >200k znaków (grupy ~20 chunków), bilans + idempotencja + izolacja błędów per wiersz wg wzorców rodziny jobów. Osobny --embed-summaries (bge-m3, reużywa chunk_embed.embed_chunk). Bug znaleziony i naprawiony w trakcie pilota: brak options.num_ctx w wywołaniach Ollamy powodował, że gemma3:12b używał domyślnego runtime kontekstu (~2048 tok), nie zadeklarowanego 128k — dla 71/157 dokumentów (45%, >8k znaków) treść była cicho ucinana (zweryfikowane: prompt_eval_count=2051 dla dokumentu 93k znaków). Naprawa: compute_num_ctx() liczy num_ctx z długości promptu (~3 znaki/token, cap 131072). Cały tor lokalny przeliczony od zera po naprawie. Wynik pilota (186 dok. paperless, 3 duplikaty, 26 bez aktywnych chunków → 157 oczekiwanych na tor): tor lokalny (gemma3:12b) 155/157 (2 izolowane błędy JSON po retry: paperless:24, paperless:61), tor referencyjny (claude-haiku-4-5) 157/157, 0 błędów JSON, 0 tagów ucinanych — słownik przestrzegany w 100% przypadków. Oba komplety zembedowane (bge-m3). Znaleziony przy okazji: prompt do tagów wymagał dopracowania — pierwsza wersja pozwalała modelowi zwracać tagi po angielsku spoza słownika; wzmocniona instrukcja (słownik w pierwszej kolejności, "nigdy po angielsku") poprawiła zgodność w 2/3 przypadków testowych. Porównanie A/B (~15 dok.) i weryfikacja końcowa (bilans, sanity SQL, retrieval po summary) odłożone do następnej sesji. Testy: 157 (152 nowe/summarize.py + istniejące), mocki API/Ollama/DB, bilans, idempotencja, regresja num_ctx. Co najmniej 3 decyzje wymagały zatrzymania i potwierdzenia z Oskarem w sesji (sposób podania klucza API, wybór modelu lokalnego gemma3:12b, naprawa+przeliczenie całego toru lokalnego po odkryciu buga num_ctx) — udokumentowane w transkrypcie sesji. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 21:59:21 +02:00
"PyYAML>=6.0",
"anthropic>=0.40",
]
[project.scripts]
documents-ingest = "documents_ingest.extractor:main"
documents-ingest-paperless = "documents_ingest.paperless_adapter:main"
feat(documents-ingest): chunk + embed job (module 5 phase 2 step 6) Adds documents-ingest-embed: chunks source='paperless' envelope content (paragraph-preferring, ~600 tok/chunk, ~150 tok overlap, hard char-fallback for oversized paragraphs per plan §2 decision 3), embeds each chunk via Ollama (bge-m3, dim validated against document_chunk's VECTOR(1024) on every response) and inserts into document_chunk. Lives in documents-ingest per the plan's own recommendation (§6 step 6) rather than a new package — reuses the job family's existing idempotency/stats-balance/dry-run conventions (paperless_adapter.py, gmail-header-backfill). A 5-angle multi-agent code review of the initial implementation surfaced three real bugs, fixed here: hard_split() could infinite-loop if --chunk-overlap >= --chunk-size (now guarded in both hard_split() and main()); insert_chunk() wasn't error-isolated like embed_chunk(), so a DB write failure would crash the whole run instead of being counted and skipped; and ON CONFLICT DO NOTHING's outcome was discarded, so a silently skipped row (the known gap where document_chunk's UNIQUE constraint doesn't include `model`) would have been miscounted as a successful insert - now tracked separately as chunks_conflict_skipped and treated as a run failure. Smoke-tested and run to completion live on SOLARIA against the real Ollama instance and kb-postgres@PIHA: dry-run matched the known phase-2-step-5 figures exactly (186 fetched, 26 empty_content, 2684 chunks planned), a --limit 10 apply + idempotent re-run + DB/distance sanity checks all passed, and the full 186-document run inserted 2683/2684 chunks (1 isolated error - Ollama's runtime context window rejected one pathological dot-leader table-of-contents chunk that tokenized far more densely than estimated; documented as a known limitation, not fixed here given it's a single-chunk edge case). Timing: ~0.79s/chunk average on CPU (SOLARIA's Ollama runs GPU-less per the recent GPU-reservation-disabled fix), ~35 min wall-clock for the full pilot - the real input for scoping the later mail-corpus embedding phase (plan §7's GPU-based estimate doesn't hold here). pytest: 101 passed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 20:41:00 +02:00
documents-ingest-embed = "documents_ingest.chunk_embed:main"
feat(kb): faza 3 krok 2 — migracja 004 (document_summary) + pilot streszczeń A/B Migracja 004: document_summary (envelope_id, summary, tags JSONB, model, embedding VECTOR(1024) + HNSW cosine, embedding_model, UNIQUE(envelope_id, model) od razu — wzorzec 002/003). Zastosowana na żywej bazie kb-postgres@PIHA. Job documents-ingest-summarize: wejście = document_chunk.text WHERE excluded_reason IS NULL per koperta source='paperless' (duplikaty przez entities[duplicate_of] pomijane w całości), wymuszony JSON {summary, tags} przez --backend ollama|anthropic, słownik tagów kontrolowany (tags-vocab.yaml) z max 3 free-form, map-reduce dla dokumentów >200k znaków (grupy ~20 chunków), bilans + idempotencja + izolacja błędów per wiersz wg wzorców rodziny jobów. Osobny --embed-summaries (bge-m3, reużywa chunk_embed.embed_chunk). Bug znaleziony i naprawiony w trakcie pilota: brak options.num_ctx w wywołaniach Ollamy powodował, że gemma3:12b używał domyślnego runtime kontekstu (~2048 tok), nie zadeklarowanego 128k — dla 71/157 dokumentów (45%, >8k znaków) treść była cicho ucinana (zweryfikowane: prompt_eval_count=2051 dla dokumentu 93k znaków). Naprawa: compute_num_ctx() liczy num_ctx z długości promptu (~3 znaki/token, cap 131072). Cały tor lokalny przeliczony od zera po naprawie. Wynik pilota (186 dok. paperless, 3 duplikaty, 26 bez aktywnych chunków → 157 oczekiwanych na tor): tor lokalny (gemma3:12b) 155/157 (2 izolowane błędy JSON po retry: paperless:24, paperless:61), tor referencyjny (claude-haiku-4-5) 157/157, 0 błędów JSON, 0 tagów ucinanych — słownik przestrzegany w 100% przypadków. Oba komplety zembedowane (bge-m3). Znaleziony przy okazji: prompt do tagów wymagał dopracowania — pierwsza wersja pozwalała modelowi zwracać tagi po angielsku spoza słownika; wzmocniona instrukcja (słownik w pierwszej kolejności, "nigdy po angielsku") poprawiła zgodność w 2/3 przypadków testowych. Porównanie A/B (~15 dok.) i weryfikacja końcowa (bilans, sanity SQL, retrieval po summary) odłożone do następnej sesji. Testy: 157 (152 nowe/summarize.py + istniejące), mocki API/Ollama/DB, bilans, idempotencja, regresja num_ctx. Co najmniej 3 decyzje wymagały zatrzymania i potwierdzenia z Oskarem w sesji (sposób podania klucza API, wybór modelu lokalnego gemma3:12b, naprawa+przeliczenie całego toru lokalnego po odkryciu buga num_ctx) — udokumentowane w transkrypcie sesji. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 21:59:21 +02:00
documents-ingest-summarize = "documents_ingest.summarize:main"
feat(kb): faza 3 krok 5 — cykliczny ingest (systemd timer) + alerting documents-ingest-cyclic (jobs/documents-ingest/src/documents_ingest/cyclic_ingest.py): orkiestruje paperless_adapter -> chunk_embed -> summarize(--backend anthropic, claude-haiku-4-5) -> summarize(--embed-summaries) bez zmian w samych jobach. Ollama@SOLARIA (availability_target: medium) jest tolerowana offline: probe GET /api/tags przed obu etapami embed, brak -> pominięcie, nie fail (oba embed passy idempotentne, nadrobią się na kolejnym ticku). Czwarty etap (embed-summaries) dopisany ponad plan §7.1 (który wymieniał tylko 3 kroki) — bez niego nowe streszczenia miałyby embedding=NULL i byłyby niewidoczne dla cascade_query (bramka kroku 4, WHERE embedding IS NOT NULL); potwierdzone z Oskarem. Predykaty pass/fail każdego etapu 1:1 z exit-checkiem danego joba (chunks_errors, llm_errors, stats-balance itd.) — etapy izolowane, nie fail-fast (wcześniejszy fail nie blokuje kolejnych, tak jak joby izolują błędy per wiersz). Metryki .prom (atomowy zapis, last_success_timestamp trzymany z poprzedniego pliku przy failu) do /opt/homelab/state/node-exporter/kb-ingest.prom. 36 nowych testów (202/202 pakietu). systemd (jobs/documents-ingest/systemd/): pierwszy systemd-timer w repo — kb-ingest.timer (OnCalendar=*-*-* 03:30, Persistent=true, plan §7.1) + kb-ingest.service (host-level, User oskar, EnvironmentFile /opt/homelab/kb/.env) + kb-ingest-run.sh (log per-run do /opt/homelab/logs/kb-ingest/, konwencja repo). Instalacja i sekrety udokumentowane w README (Faza 3 krok 5) — instalacja na PIHA dopiero po merge. fleet-prometheus (rules/kb-ingest.yml): KbIngestStale (>172800s od last_success, critical) + KbEmbedBacklogGrowing (backlog>0 przez 72h, warning) — dostawa istniejącym torem brain-watchdog->Telegram, bez Alertmanagera (konwencja liveness.yml). node_exporter: owner_node vps -> per-host (service.yaml) + wpis + override (--collector.textfile.directory, bez nowego mountu — czyta przez istniejący /:/host:ro) + topology.yaml dla PIHA. Domyka pozycję z docs/backlog.md "stability-agent / node_exporter owner_node single, biegaja wielomiejscowo -> per-host" (połowę — node_exporter; stability-agent zostaje osobnym follow-upem) w ramach paczki B inwentaryzacji monitoringu dla PIHA. Test end-to-end na żywo na PIHA (2× --apply, po potwierdzeniu z Oskarem): pierwszy run złapał 5 dokumentów faktycznie nowych w Paperless (nieoczekiwane, niezwiązane z tym taskiem) -> 82 nowe chunki (2 ocr_junk), 5 nowych streszczeń, 5 embeddingów streszczeń, 0 błędów, metryki zapisane. Drugi run: pełna idempotencja, wszystko 0. ANTHROPIC_API_KEY dodany przez Oskara ręcznie do /opt/homelab/kb/.env (nigdy nie logowany/generowany). Co dalej: prawdziwa instalacja systemd (services.yaml już przygotowany, po merge), zdecydowanie czy stability-agent też idzie na per-host przy okazji. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-17 15:05:18 +02:00
documents-ingest-cyclic = "documents_ingest.cyclic_ingest:main"
[tool.setuptools.packages.find]
where = ["src"]
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]