PRZEWODNIK · PIPELINE

Parsuj dokumenty na hoście, szukaj ich jak pamięci.

Document Processor zamienia PDF / DOCX / TXT w chunki strukturalne plus obrazy z kontekstem. Consciousness Server trzyma chunki jako rekordy treningowe. semantic-search odczytuje je po znaczeniu. Nic nie wychodzi z maszyny. Ten przewodnik spina trzy klocki end-to-end.

Wymagania

  • Działający ekosystem z Szybkiego startu (CS na :3032, semantic-search na :3037).
  • Zainstalowany Document Processor (aplikacja Tauri, dostępna na Linux / macOS / Windows).
  • Sparsowany jeden dokument. Przeciągnij PDF na okno Document Processora; poczekaj na zielony check.

Co wytwarza Document Processor

Każdy sparsowany dokument ląduje w ~/.local/share/document-processor/exports/<data>_<slug>/ (ścieżki Linuksa; macOS używa ~/Library/Application Support/document-processor/; Windows używa %APPDATA%\\document-processor\\). Kształt:

filesystem
# Document Processor zapisuje drzewo per dokument:
~/.local/share/document-processor/exports/
└── 2026-04-27_supplier-contract/
    ├── document.json        # metadane (kind, title, daty, strony)
    ├── chunks.jsonl         # chunki semantyczne, jeden JSON na linię
    ├── images/              # ekstraktowane obrazy z opisem kontekstowym
    │   ├── fig-001.png
    │   └── fig-001.txt      # "Figure 1: revenue breakdown 2025..."
    └── full-text.txt        # surowy tekst (fallback)

Przekazujesz katalog dalej. Plik chunks.jsonl to koń pociągowy — jedna jednostka wyszukiwalna na linię, z zachowanymi nagłówkami sekcji.

Wgranie do Consciousness Server

Każdy chunk staje się rekordem treningowym. Pole type jest wymagane — dla prozy użyj explanation, dla treści typu klauzula / sekcja strukturalna użyj architecture. Otaguj każdy rekord identyfikatorem dokumentu, żeby później móc filtrować search.

ingest.py
import json, requests
from pathlib import Path

CS = "http://127.0.0.1:3032"
EXPORT = Path.home() / ".local/share/document-processor/exports/2026-04-27_supplier-contract"

meta = json.loads((EXPORT / "document.json").read_text())

# Każdy chunk staje się rekordem treningowym. Pole "type" jest WYMAGANE.
# Dla treści dokumentowych użyj "explanation" (samowystarczalny chunk
# znaczeniowy) lub "architecture" (sekcja strukturalna, np. klauzula umowy).
with (EXPORT / "chunks.jsonl").open() as f:
    for line in f:
        chunk = json.loads(line)
        requests.post(f"{CS}/api/memory/training", json={
            "agent": "doc-pipeline",
            "type": "explanation",
            "goal": f"ingest:{meta['title']}",
            "instruction": "search-retrievable chunk",
            "input": chunk["heading"] or "",
            "output": chunk["text"],
            "tags": [meta["kind"], "doc:" + meta["id"]],
        }).raise_for_status()

print(f"Ingested {meta['title']} — {meta['chunk_count']} chunks indexed.")

CS embeduje każdy rekord do ChromaDB przez Ollamę na hoście. Indeks rośnie liniowo z liczbą chunków; embeddingi to ~1.5 KB każdy, więc korpus 10 000 chunków to ~15 MB plus narzut ChromaDB. Wszystko na lokalnym dysku.

Wyszukiwanie po sensie, nie po nazwie pliku

Po wgraniu agent znajduje właściwą klauzulę bez wiedzy, w jakim pliku ona była:

search.py
# Teraz agent znajdzie tę umowę po sensie, nie po nazwie pliku.
hits = requests.post("http://127.0.0.1:3037/api/search", json={
    "query": "what penalty applies if delivery slips by 30 days",
    "limit": 5,
    "filters": {"tags": ["doc:" + meta["id"]]},
}).json()

for h in hits["results"]:
    print(f"score={h['score']:.2f}  {h['snippet'][:120]}")

filters.tags zawęża search do jednego dokumentu; usuń filter, żeby zapytać po całym korpusie. score to cosine similarity (0..1).

Zrób z tego ciągły pipeline

Document Processor to aplikacja desktopowa — każdy sparsowany dokument pojawia się jako nowy katalog w exports/. Jednolinijkowy directory-watcher zamienia "sparsowałem plik" w "chunki są wyszukiwalne" bez żadnej pracy w UI:

watch.sh
# Daemon obserwujący katalog zamienia exports/ z Document Processora w
# ciągły feed ingestowy. systemd-path lub inotify — dowolnie. Wersja inotify:
inotifywait -m -e create -r ~/.local/share/document-processor/exports/ |
while read dir _ name; do
    if [ -f "$dir$name/document.json" ]; then
        python ingest.py "$dir$name"
    fi
done

Owinij to w jednostkę systemd usera (~/.config/systemd/user/doc-ingest.service), żeby przeżyła reboot. Z punktu widzenia operatora: przeciągnięcie PDF na Document Processor wrzuca go do korpusu.

Dalsze kroki