Wymagania
- Działający ekosystem z Szybkiego startu (CS na
:3032, semantic-search na:3037). - Zainstalowany Document Processor (aplikacja desktopowa Tauri; instalatory dla Linuksa —
.deb/ AppImage — na innych platformach build ze źródeł). - 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 katalogu pod katalogiem danych
aplikacji, wyznaczanym przez resolver ścieżek Tauri — na Linuksie
~/.local/share/com.buildonai.document-processor/przetworzone/<document-id>/,
na innych platformach w ich odpowiedniku. Kształt:
# Document Processor zapisuje jeden katalog per sparsowany dokument,
# pod katalogiem danych aplikacji (pokazany Linux):
~/.local/share/com.buildonai.document-processor/przetworzone/
└── <document-id>/
├── document.md # markdown czytelny dla człowieka
├── document.json # metadane + full_text + images[] z kontekstem
├── images/
│ ├── img_001.png # wyekstrahowane obrazy
│ └── thumb_img_001.png
└── original.pdf # kopia pliku wejściowego
Przekazujesz katalog dalej. Plik document.json to
koń pociągowy — metadane dokumentu, pełny wyekstrahowany tekst
i rekordy kontekstu per obraz. Gotowego pliku z chunkami nie ma;
podzielenie full_text na jednostki wyszukiwalne to
zadanie skryptu ingestującego.
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.
import json, sys, requests
from pathlib import Path
CS = "http://127.0.0.1:3032"
DATA = Path.home() / ".local/share/com.buildonai.document-processor/przetworzone"
# Katalog z argv albo wpisz id ręcznie: zajrzyj ls-em do przetworzone/
DOC = Path(sys.argv[1]) if len(sys.argv) > 1 else DATA / "<document-id>"
doc = json.loads((DOC / "document.json").read_text())
# document.json zawiera full_text, nie gotowe chunki — dzielenie na
# kawałki to zadanie skryptu. Podział po akapitach to dobry punkt wyjścia.
chunks = [c.strip() for c in (doc.get("full_text") or "").split("\n\n") if c.strip()]
# 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).
for chunk in chunks:
requests.post(f"{CS}/api/memory/training", json={
"agent": "doc-pipeline",
"type": "explanation",
"goal": f"ingest:{doc['filename']}",
"instruction": "search-retrievable chunk",
"input": doc.get("doc_type") or "",
"output": chunk,
"tags": [doc.get("doc_type") or "document", "doc:" + doc["id"]],
}).raise_for_status()
print(f"Ingested {doc['filename']} — {len(chunks)} 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:
# 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:" + doc["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).
Zautomatyzuj połowę ingestową
Jedna rzecz wymaga jasności: Document Processor nie obserwuje
folderów w tle. Jego „watch folder" to tylko zapamiętany katalog
do ręcznego ponownego skanu — parsowanie rusza, gdy upuścisz plik
albo klikniesz „Skanuj ponownie". Zautomatyzować da się natomiast
przekazanie dalej: każde parsowanie tworzy nowy katalog w
przetworzone/, więc watcher po stronie wyjścia
zamienia „sparsowałem plik" w „chunki są wyszukiwalne" bez
dodatkowej pracy w UI:
# Document Processor sam NIE obserwuje folderów — parsowanie rusza,
# gdy upuścisz plik na okno albo klikniesz "Skanuj ponownie". Ale każde
# parsowanie tworzy nowy katalog w przetworzone/, więc można obserwować
# stronę WYJŚCIOWĄ i automatycznie ingestować wszystko, co sparsujesz:
DATA=~/.local/share/com.buildonai.document-processor/przetworzone
inotifywait -m -e create "$DATA" | while read dir _ name; do
# document.json powstaje pod koniec parsowania — poczekaj na niego
for i in $(seq 1 30); do
if [ -f "$dir$name/document.json" ]; then
python ingest.py "$dir$name"; break
fi
sleep 1
done
done
Owiń 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 też do
korpusu — samo parsowanie pozostaje czynnością ręczną.
Dalsze kroki
- Napisz własnego agenta →, który przeszukuje korpus i odpowiada na pytania o niego.
- Włącz podpisane żądania → przed wystawieniem korpusu wielu agentom.
- Strona produktu Document Processor → dla wnętrzności parsera (ekstrakcja obrazów z kontekstem, klasyfikator, wyszukiwanie hybrydowe).