Grenzen
Was kaimarkit nicht kann und woran eine Umwandlung scheitert.
Fünf Werte begrenzen einen Aufruf
Alle fünf kommen aus der Umgebung. docs/admin/konfiguration.md beschreibt sie
im Einzelnen, docker/.env.example nennt die Standardwerte.
| Variable | Standard | Was sie begrenzt |
|---|---|---|
KAIMARKIT_MAX_FILE_SIZE_MB |
50 | Größe einer einzelnen Datei |
KAIMARKIT_MAX_FILES |
20 | Dateien je Stapelaufruf |
KAIMARKIT_MAX_CONCURRENT |
2 | gleichzeitige Umwandlungen |
KAIMARKIT_CONVERSION_TIMEOUT |
600 | Sekunden je Datei |
KAIMARKIT_URL_TIMEOUT |
30 | Sekunden je Abruf über /api/convert/url |
Die Größe prüft der Dienst schon beim Empfang. Überschreitet die Datei das Limit,
bricht er ab und antwortet mit 413 und file_too_large. Den Rest des Uploads liest
er nicht mehr ein — eine Prüfung danach käme zu spät, dann läge die Datei bereits
vollständig im Speicher.
Solange eine Umwandlung läuft, liegt die Datei in einer temporären Datei. Danach löscht der Dienst sie, auch wenn die Engine gescheitert ist. Gespeichert wird nichts.
Die Zeitgrenze beendet den Wartevorgang, nicht die Engine
Dauert eine Umwandlung länger als KAIMARKIT_CONVERSION_TIMEOUT, antwortet der
Dienst mit 504 und conversion_timeout. Die Engine arbeitet im Hintergrund weiter,
bis sie von selbst fertig ist, und verbraucht so lange Rechenzeit. Häufen sich die
Zeitüberschreitungen, sammeln sich diese Läufe an und der Dienst wird langsam;
dann hilft nur ein Neustart des Containers.
Eine Ausnahme ist Pandoc: Es läuft als eigener Prozess, und den beendet der
Dienst nach KAIMARKIT_PANDOC_TIMEOUT tatsächlich.
Wer regelmäßig an die Zeitgrenze stößt, setzt sie besser hoch, statt es mehrfach zu versuchen: Jeder Versuch legt einen weiteren Lauf obendrauf.
Gescannte Seiten ohne OCR bleiben leer
Ein PDF aus dem Scanner enthält Bilder und keinen Text. MarkItDown liest darin
nichts, weil es nichts zu lesen findet, und Pandoc kommt für PDF ohnehin nicht in
Frage. Das ist kein Fehler: Die Umwandlung gelingt, das Markdown bleibt leer, und
die Antwort nennt den Grund in warnings.
Text aus solchen Dateien holt allein Docling, und nur mit eingeschalteter
Texterkennung. KAIMARKIT_OCR_ENABLED setzt den Standard, das Feld ocr einer
einzelnen Anfrage überschreibt ihn:
Zwei Dinge kosten das. Die Texterkennung ist um ein Vielfaches langsamer als das
Lesen einer Textebene, und ihre Sprachen müssen stimmen: KAIMARKIT_OCR_LANGS steht
auf de,en, ein französisches Dokument braucht dort seinen eigenen Eintrag, fr.
Läuft der Aufruf mit engine=auto und ist Docling noch nicht bereit, nimmt der
Dienst für das PDF die nächste Engine der Liste — und die findet dann eben keinen
Text. Wer sicher OCR will, nennt Docling ausdrücklich und wartet.
OCR greift nur in PDF und Bilddateien
Wer ein Word-Dokument mit einem abfotografierten Absatz hochlädt, bekommt diesen
Absatz nicht — auch mit ocr=true nicht. Die Texterkennung erreicht zwei Arten von
Dateien: PDF und die Bildformate .png, .jpg, .jpeg und .tiff. In .docx,
.pptx, .xlsx, .html und .epub bleibt sie aus.
Der Grund liegt in Docling. Ein PDF läuft dort durch eine eigene Pipeline, und nur
diese baut das OCR-Modell; jedes andere Format geht an die einfache Pipeline, deren
Optionen do_ocr gar nicht kennen. Es gibt also keinen Schalter, den man umlegen
könnte: Weder das Feld ocr einer Anfrage noch KAIMARKIT_OCR_ENABLED wirkt auf
diese Formate.
In PDF reicht die Texterkennung dafür bis in eingebettete Bilder hinein. Ein Absatz,
der nur als Bild in der Seite steht, erscheint mit ocr=true im Markdown und fehlt
mit ocr=false. Gemessen im Container-Abbild mit docling 2.124.0; eine spätere
Version kann das ändern.
Der Umweg führt über PDF: das Dokument aus Word, LibreOffice oder dem Programm, aus
dem es stammt, als PDF speichern und dieses PDF mit engine=docling und ocr=true
abgeben.
Bilder werden nicht beschrieben
Ein Bild im Dokument erscheint im Markdown als Platzhalter oder als Alt-Text, nie als Beschreibung seines Inhalts. Der Dienst schickt bewusst nichts an ein Sprachmodell — er soll den Kontext zeigen, den man einem Modell gibt, und ihn nicht selbst erzeugen. Ein Diagramm, dessen Aussage nur im Bild steht, geht dabei verloren.
Bei einem PDF durch MarkItDown bleibt nicht einmal die Stelle übrig. Die Engine
liest dort nur die Textebene und lässt jedes Bild ersatzlos weg; im Markdown steht
danach weder Platzhalter noch Alt-Text. Der Dienst warnt deshalb bei jedem PDF, das
durch MarkItDown läuft. Docling setzt stattdessen <!-- image --> ein und zählt die
ersetzten Bilder.
Jeder Worker hält eigene Docling-Modelle
Die Modelle liegen im Speicher des Prozesses, nicht daneben. Zwei Uvicorn-Worker
halten sie deshalb zweimal, mit jeweils rund 2 GB. KAIMARKIT_WORKERS erst
hochsetzen, wenn genug RAM da ist, und KAIMARKIT_MEM_LIMIT mit anheben — sonst
schießt der Kernel den Container beim ersten großen PDF ab. Dass es das war, zeigt
docker inspect als OOMKilled.
Gleichzeitige Anfragen sind der falsche Grund für mehr Worker. Wie viele
Umwandlungen nebeneinander laufen, regelt KAIMARKIT_MAX_CONCURRENT innerhalb eines
Prozesses; das kostet keinen zweiten Satz Modelle.
Wartezeit kostet auch der erste Aufruf. Docling beginnt seine Modelle zu laden,
sobald der Dienst hochfährt, und lädt sie im Hintergrund; jeder Neustart fängt damit
von vorn an. Bis das fertig ist, meldet die Auskunft warming und engine=auto
nimmt für ein PDF MarkItDown.
Webseiten: nur öffentlich, kein JavaScript
POST /api/convert/url holt genau eine Seite, und der Dienst prüft vorher, wohin
er greift. Erlaubt sind http und https; jede andere Adresse endet mit 400 und
invalid_url. Den Hostnamen löst er auf und prüft jede zurückgegebene Adresse:
Loopback, private Netze, Link-local und alles andere, was nicht öffentlich
erreichbar ist, weist er ab. Ein Name, der auf eine öffentliche und eine private
Adresse zeigt, kommt ebenfalls nicht durch. http://127.0.0.1/ etwa endet mit 400
und invalid_url, und http://localhost:8000/ genauso — der Name schützt nicht
davor, weil der Dienst ihn auflöst, bevor er verbindet. Der Grund steht im Betrieb: Der Dienst
teilt sich sein Docker-Netz mit Traefik und Authelia, und ein ungeprüfter Abruf
wäre ein Sprungbrett dorthin — bis zu 169.254.169.254, wo Cloud-Anbieter ihre
Metadaten anbieten.
Einer Weiterleitung folgt der Dienst höchstens fünfmal, und er prüft das Ziel vor
jedem Sprung erneut. Wer öfter weiterleitet, bekommt invalid_url.
Die geholte Antwort unterliegt derselben Größenprüfung wie ein Upload: Über
KAIMARKIT_MAX_FILE_SIZE_MB bricht der Dienst mitten im Empfang ab und antwortet
mit 413. Für den Abruf selbst gilt KAIMARKIT_URL_TIMEOUT — 30 Sekunden im
Standard, alle Weiterleitungen eingeschlossen; danach 504. Die Umwandlung danach
zählt wieder gegen KAIMARKIT_CONVERSION_TIMEOUT. Auch der Abruf belegt einen der
KAIMARKIT_MAX_CONCURRENT Plätze: Wer zwanzig Adressen auf einmal schickt, öffnet
nicht zwanzig Verbindungen zugleich.
Zwei Arten von Seiten liefern trotzdem nichts Brauchbares:
- Seiten hinter einer Anmeldung. Der Dienst schickt keine Cookies, keine Kopfzeilen und keine Zugangsdaten mit. Er sieht, was ein fremder Browser ohne Sitzung sieht — meist die Anmeldeseite, und die wandelt er dann auch.
- Seiten, die ihren Inhalt erst im Browser aufbauen. Es läuft kein JavaScript. Was der Server als HTML ausliefert, ist die Seite. Bei einer Anwendung, die ihren Text erst nachlädt, bleibt ein fast leeres Markdown übrig.
Die geholte Datei liegt wie ein Upload in einem eigenen temporären Verzeichnis, das nach der Antwort verschwindet — auch dann, wenn die Engine gescheitert ist.
Was der Dienst gar nicht tut
- Nichts aufheben. Es gibt keine Historie und keinen Zwischenspeicher. Wer ein Ergebnis behalten will, lädt es herunter.
- Niemanden erkennen. Die API kennt keine Anmeldung und keine Kennungen. Eine Anmeldung kommt von außen davor, siehe Authelia.
- Nichts nachladen. Alle Modelle stecken im Abbild. Aus dem Netz holt der
Dienst zur Laufzeit nur, was ein Aufruf von
/api/convert/urlverlangt: die eine Seite, von einer öffentlichen Adresse, und nichts von sich aus. - Nichts zurückschreiben. Der Weg führt nur in eine Richtung: nach Markdown. Aus Markdown wieder ein PDF zu machen, ist nicht Aufgabe dieses Dienstes.