Dokumentenverarbeitung (Job API)
Job-Lebenszyklus
JOB_STATUS_NEW → JOB_STATUS_FILE_PROCESSING → JOB_STATUS_OCR → JOB_STATUS_LLM → JOB_STATUS_COMPLETE | JOB_STATUS_PARTIAL | JOB_STATUS_FAILED
- JOB_STATUS_NEW — der Job wurde erstellt und in die Warteschlange gestellt.
- JOB_STATUS_FILE_PROCESSING — Dateien werden heruntergeladen, Archive entpackt und Formate in eine verarbeitbare Form normalisiert. Kann direkt in
JOB_STATUS_FAILEDübergehen, wenn alle Sources unerreichbar sind oder ein Datei-Limit überschritten wird (Grund imerror-Feld des Jobs). - JOB_STATUS_OCR — Texterkennung für jede Datei.
- JOB_STATUS_LLM — der erkannte Text wird mit Ihren Prompts an das Modell geschickt.
- JOB_STATUS_COMPLETE — keine Fehler in der OCR- oder LLM-Stufe.
- JOB_STATUS_PARTIAL — mindestens eine erfolgreiche Modellantwort (LLM), aber auch mindestens ein Fehler in der OCR- oder LLM-Stufe (prüfe die Fehler auf Dateiebene im Ergebnis), oder eine Antwort, die nicht
responseSchemaentspricht (siehe „Strukturierte Ausgabe”). - JOB_STATUS_FAILED — Fehler haben verhindert, dass irgendeine Datei eine erfolgreiche Modellantwort erreicht: entweder ein Fehler beim Herunterladen/Entpacken der Dateien (Grund im Job-
error-Feld;ocr[]/llm[]sind leer) oder keine Datei erreichte ein erfolgreiches Ergebnis in der OCR- oder LLM-Stufe.
Die Verarbeitung ist asynchron: Fragen Sie den Job-Status über GET /v1/jobs/{id} ab, bis der Job einen terminalen Status erreicht.
Dateien hochladen
Sie können die Source eines Jobs auf zwei Arten angeben:
- Öffentliche URL — übergeben Sie die URL beim Erstellen des Jobs in
sourceUrls. ChunkChef lädt die Datei herunter (Download-Timeout: 30 s, bis zu 3 Weiterleitungen). - Direkter Upload — laden Sie die Datei hoch und verwenden Sie dann die zurückgegebene URL in
sourceUrls:POST /v1/jobs/upload(multipart/form-data) — lädt eine einzelne Datei über HTTP hoch. Das ist ein eigenständiger HTTP-Endpunkt (kein grpc-gateway). Die Antwort ist JSON in snake_case:{"url": "…", "name": "…", "size_bytes": 12345}. Der Fehler-Body dieses Endpunkts ist{"code": <int>, "message": "…"}, ohnedetails-Array. Das Größenlimit wird per Konfiguration gesetzt (grpc.maxRecvMsgBytes; 20 MiB im aktuellen Deployment), nicht durch einen Code-Standardwert.- gRPC-Methode
Upload(Client-Streaming) — ein Streaming-Upload (Limit pro Nachricht: 20 MiB).
Prompts und Datenextraktion
Die Datenextraktion wird durch Text-Prompts gesteuert, nicht durch ein Schema. Im Feld prompts übergeben Sie ein Array von Anweisungen. In der LLM-Stufe wird der erkannte Text jeder Datei genommen, bei Bedarf in Chunks aufgeteilt und zusammen mit Ihrem Prompt an das Modell geschickt. Die Antwort des Modells wird als Text je Datei zurückgegeben (und je Chunk, falls die Datei aufgeteilt wurde). Optional können Sie responseSchema — ein JSON Schema — angeben, damit ChunkChef die Form der Antwort nach dem Aufruf validiert; siehe „Strukturierte Ausgabe” weiter unten.
Die Aufteilung in Chunks ist strukturbewusst: Der Text wird entlang der Strukturgrenzen seines Formats geschnitten (Markdown, HTML, XML oder plain) — Tabellen werden nicht mitten in einer Zeile zerrissen (und passt eine Tabelle nicht als Ganzes, wird ihre Kopfzeile in jedem Chunk wiederholt), und der Kontext der Abschnittsüberschriften bleibt erhalten. Die Chunk-Größe in Tokens wird durch neural.chunkBudgetTokens gesetzt (siehe „Modell anbinden”); ist sie nicht gesetzt, gilt der konservative Standardwert des Dienstes. Jeder Chunk ist ein separater Modellaufruf mit einer vollständigen Kopie des Prompts und einer eigenen llm[]-Zeile (chunkIndex / chunkTotal). Das Zusammensetzen der Antwort aus den Chunks in der Reihenfolge von chunkIndex erfolgt standardmäßig auf Ihrer Seite. Optional kann ChunkChef dieses Zusammensetzen für Sie übernehmen: Setzen Sie merge.enabled, damit der Dienst die Antworten je Chunk zu einem Gesamtergebnis für das ganze Dokument zusammenführt — siehe „Zusammenführen von Chunk-Ergebnissen” weiter unten.
Um strukturierte Daten zu erhalten, fragen Sie sie direkt im Prompt an — zum Beispiel: „Gib das Ergebnis als JSON mit den folgenden Feldern zurück: …”. Ihr Prompt und das von Ihnen gewählte Modell bestimmen die Gültigkeit und Form des JSON; ChunkChef erzwingt und validiert kein Schema, es sei denn, Sie aktivieren responseSchema (siehe „Strukturierte Ausgabe” weiter unten).
Prompt-Tipps:
- listen Sie die benötigten Felder explizit und eindeutig auf;
- geben Sie das Ausgabeformat direkt im Prompt-Text an;
- behalten Sie das Größenlimit für Prompts im Blick — 64 KiB (siehe „Limits”).
Hinweis. Die Platzhalter <…> in den folgenden Vorlagen markieren Stellen, die Sie selbst ausfüllen müssen: ChunkChef ersetzt sie nicht automatisch — der Prompt wird genau so an das Modell geschickt, wie er geschrieben ist. Ihr Prompt und das von Ihnen gewählte Modell bestimmen die Ausgabestruktur; es gibt keine serverseitige Schemavalidierung, es sei denn, Sie setzen responseSchema (siehe „Strukturierte Ausgabe”).
Die Prompt-Qualität bestimmt Ihre Ergebnisse. Wie gut die Extraktion funktioniert, hängt ebenso sehr von Ihrem Prompt ab wie vom gewählten Modell — oft sogar mehr. Ein vager Prompt liefert vage Ausgaben selbst auf einem Spitzenmodell, während ein präziser, gut strukturierter Prompt selbst von kleineren, günstigeren Modellen zuverlässige Ergebnisse erzielt. Behandeln Sie die folgende Vorlage als Ausgangspunkt, nicht als fertigen Prompt: Nehmen Sie sie, beschreiben Sie Ihren Dokumenttyp, Ihre Aufgabe und die genaue Ausgabe, die Sie brauchen, und lassen Sie dann ein leistungsfähiges Modell daraus einen auf Ihren Fall zugeschnittenen Prompt erstellen — wobei diese Struktur beibehalten und die Regeln, Sonderfälle und die Ausgabevalidierung für Ihre Daten geschärft werden. Der Meta-Prompt dafür steht am Ende dieses Abschnitts.
Beispiel: Felder aus Rechnung / Bestellung / Beleg extrahieren
Text von prompts[0]:
TASK
Extract structured fields from a single procurement/accounting document and return them strictly as JSON.
IMPORTANT
Your answer must contain ONLY JSON. Do not add any comments, explanations, or surrounding text before or after the JSON.
1. INPUT
The recognized text of a single document follows the "---" marker below (the OCR output is HTML). It is the only source. The document may be an <type: invoice / purchase order / receipt> and may contain stamps, signatures, and multi-row line-item tables. The text may be truncated or split into chunks — work only with the text you are given and never assume content you cannot see.
2. OUTPUT JSON FORMAT
Return a single JSON object matching this schema (the inline comments are explanatory — do not include them in the output):
{
"doc_type": "string", // one of: invoice | purchase_order | receipt | unknown
"number": "string",
"date": "string", // ISO 8601: YYYY-MM-DD
"supplier": {
"name": "string",
"tax_id": "string" // e.g. US EIN or EU VAT ID, as printed
},
"items": [
{ "name": "string", "qty": number, "price": number, "amount": number }
],
"total": number,
"currency": "string" // ISO 4217, e.g. USD, EUR
}
3. EXTRACTION RULES
- doc_type: classify from the title, headers, and content. If it is none of the listed types, set "unknown" and still fill any fields you can.
- number / date: the document's own number and issue date. Convert the date to ISO 8601 (YYYY-MM-DD).
- supplier: the selling/issuing party, not the buyer. tax_id: the supplier's tax identifier, exactly as printed.
- items: one object per line item, in document order. Keep "name" exactly as written, including specifications and units that identify the item.
- qty / price / amount / total: return as JSON numbers — strip thousands separators and currency symbols, use a dot as the decimal separator ("1,200.50" -> 1200.5).
- currency: ISO 4217 code. If only a symbol is present, map it ("$" -> "USD", "€" -> "EUR"). If it cannot be determined, use null.
4. PROCESSING REQUIREMENTS
- Use ONLY the provided document text. Do not add external knowledge or infer values that are not present.
- Do NOT guess, complete, or reformat values beyond the normalization explicitly required above.
- Field not found -> null for scalars (including supplier sub-fields), [] for "items". Never drop a schema key.
- Analyze the entire document, including tables and appendices. A reference to an external attachment is not a line item.
- Be literal and deterministic: the same input must always produce the same output.
5. RESPONSE FORMAT
- Return ONLY the valid JSON object described above.
- No markdown, no code fences, no text before or after the JSON.
REMEMBER
Your answer must start with "{" and end with "}". Nothing else. If the document is not one of the expected types, return the schema with "doc_type": "unknown" and whatever fields you could extract.
JSON-Envelope (primär — auf dem verifizierten Xiaomi mimo-v2-flash):
{
"sourceUrls": ["<YOUR_FILE_URL>"],
"title": "Invoice <number>",
"prompts": ["<THE ENTIRE TEMPLATE ABOVE, AS A SINGLE STRING>"],
"ocr": { "provider": "NEURAL_CLIENT_TYPE_MISTRAL", "model": "mistral-ocr-latest", "providerKey": "<YOUR_KEY>" },
"neural": {
"type": "NEURAL_CLIENT_TYPE_XIAOMI",
"model": "mimo-v2-flash",
"apiKey": "<YOUR_PROVIDER_KEY>",
"reasoningEffort": "low"
}
}
Header: Authorization: Bearer <YOUR_CHUNKCHEF_KEY>.
| Anfragefeld | Was es ist | „Variable” |
|---|---|---|
Authorization | Ihr ChunkChef API-Schlüssel | API-Zugriffsschlüssel |
sourceUrls[] | Datei-URLs | Dokument-Links |
prompts[0] | die gesamte Vorlage als ein einzelner String | strukturierter Prompt |
neural.type / neural.model | Provider und Modell | Modell |
neural.apiKey | Provider-Schlüssel (BYOK) | Modell-Schlüssel |
neural.reasoningEffort | optional minimal/low/medium/high | Reasoning-Tiefe |
ocr.provider | OCR-Provider-Enum (z. B. NEURAL_CLIENT_TYPE_MISTRAL) | OCR-Provider |
ocr.model | OCR-Modellbezeichner; erforderlich | OCR-Modell |
ocr.providerKey | Provider-Schlüssel für OCR (BYOK); nur als Eingabe entgegengenommen, niemals zurückgegeben | OCR-Schlüssel |
extractionMode | Textextraktionsstrategie: EXTRACTION_MODE_HYBRID (Standard, „Optimierte Hybrid-Extraktion”) lässt den Konverter je Datei entscheiden, ob direkt Text extrahiert oder OCR verwendet wird; EXTRACTION_MODE_OCR_ALWAYS erzwingt neuronale OCR für jede Datei; nicht gesetzt / EXTRACTION_MODE_UNSPECIFIED erbt Ihren Konto-Standard | Optional |
responseSchema | Optionales JSON Schema (Roh-String), das die JSON-Antwort des Modells validiert; in sich geschlossen, kompatibel mit der strict-Teilmenge (siehe „Strukturierte Ausgabe”) | Optional |
Weitere Beispiele (kurz). Gleiche Mechanik — nur der Prompt-Text und die erwartete Antwortform in llm[].content unterscheiden sich:
- Klassifikation. Prompt: „Bestimme den Dokumenttyp: Rechnung / Vertrag / Beleg / Brief / Sonstiges. Gib ein einzelnes Wort aus der Liste zurück, ohne Erklärung.” Antwort: ein einzelnes Wort (z. B.
contract). - Vertragskonditionen. Prompt: „Extrahiere: Parteien, Gegenstand, Betrag, Laufzeit und Kündigungsbedingungen. Gib JSON gemäß dem Schema {parties[], subject, amount, term, termination} zurück. Feld nicht gefunden → null.” Antwort: JSON gemäß dem Schema.
- Zusammenfassung. Prompt: „Fasse das Dokument in 3–5 Sätzen zusammen. Keine Aufzählungslisten.” Antwort: Fließtext.
Eigenen Prompt erstellen (Meta-Prompt)
Der schnellste Weg zu einem hochwertigen Prompt ist, ein leistungsfähiges Modell ihn für Sie schreiben zu lassen. Geben Sie ihm den folgenden Meta-Prompt: Fügen Sie unser Beispiel als Referenzstruktur ein, die benötigte Ausgabeform (JSON/CSV/Markdown) sowie eine Beschreibung Ihres Kontexts und Ihrer Aufgabe — und Sie bekommen einen einsatzbereiten ChunkChef-Prompt zurück. Füllen Sie die eingeklammerten Blöcke aus; den Rest erledigt das Modell.
You are a senior prompt engineer. Build a production-grade extraction prompt that will be sent to a document-processing model through the ChunkChef API. The model receives the OCR'd text (HTML) of a single document and must return data in a strict, machine-parseable format.
WHAT I'M GIVING YOU
1) REFERENCE PROMPT — the structure and style to follow. Preserve its section anatomy.
<<<REFERENCE_PROMPT
[paste the ChunkChef example prompt here]
REFERENCE_PROMPT
2) TARGET OUTPUT — the exact shape I need back: a JSON schema/sample, CSV columns, or Markdown layout.
<<<TARGET_OUTPUT
[paste your desired JSON / CSV / Markdown here]
TARGET_OUTPUT
3) DOMAIN & CONTEXT — what these documents are, where they come from, and their quirks (languages, layouts, stamps, tables, common OCR errors).
<<<CONTEXT
[describe your documents and domain]
CONTEXT
4) TASK — exactly what to extract or produce, plus the business rules, definitions, and edge cases that matter.
<<<TASK
[describe the task and rules]
TASK
5) OUTPUT FORMAT — one of: JSON | CSV | Markdown. Default: JSON.
<<<FORMAT
JSON
FORMAT
HOW TO BUILD THE PROMPT
1. Study the domain and task deeply before writing. Infer the edge cases a careful human reviewer would catch — ambiguous fields, duplicates, ranges, units, missing data, multi-row tables, appendices — and address each one explicitly.
2. Keep the REFERENCE PROMPT's anatomy: a one-line TASK, an IMPORTANT "only the target format" rule, then numbered sections (INPUT, OUTPUT FORMAT, EXTRACTION/PROCESSING RULES field by field, PROCESSING REQUIREMENTS, RESPONSE FORMAT), and a final REMEMBER reinforcement.
3. Make the output contract unambiguous for the chosen format:
- JSON: give the full schema with types and nullability, mark required vs optional keys, forbid any text/markdown/code fences outside the JSON, and require the answer to start with "{" (or "[") and end with "}" (or "]").
- CSV: fix the exact column order and header row, the delimiter, the quoting/escaping rule, and how empty values are written; one record per row, no prose.
- Markdown: fix the exact headings/table columns and forbid any content outside that layout.
4. Pin the data discipline: use only the provided document text; never invent, guess, or reformat beyond the normalization you explicitly define; specify number, date, and unit normalization; define how "not found" is represented (null / empty / skipped) and how duplicates are handled; preserve source values verbatim where identity matters.
5. Account for ChunkChef specifics: the model sees one document's OCR'd HTML, possibly truncated or split into chunks; do not rely on any temperature setting — enforce determinism through wording ("be literal and deterministic"); the prompt is sent verbatim, so resolve every "<placeholder>" yourself.
6. Self-check before finishing: re-read the TARGET OUTPUT and confirm the prompt forces exactly that shape, that every field has a rule, and that a small, cheap model could follow it without guessing.
OUTPUT
Return ONLY the finished prompt, ready to paste into ChunkChef's "prompts" array — no explanation, no preamble, no code fences.
Modell anbinden (BYOK)
Die LLM-Stufe läuft mit Ihrem Provider-Schlüssel. Die Konfiguration wird beim Erstellen eines Jobs im neural-Objekt übergeben:
| Feld | Erforderlich | Beschreibung |
|---|---|---|
type | ja | Provider (siehe Liste unten) |
model | ja | Modellbezeichner; wird unverändert an den Provider übergeben |
apiKey | ja | Ihr Provider-Schlüssel; nur als Eingabe entgegengenommen, niemals in Antworten zurückgegeben |
reasoningEffort | nein | Hinweis zur Reasoning-Tiefe; erlaubte Werte: minimal, low, medium, high (leer = aus). Ein ungültiger Wert → 400-Fehler. Ob er beachtet wird, hängt vom Provider/Modell ab. |
chunkBudgetTokens | nein | Token-Budget für einen einzelnen Modellaufruf: deckt sowohl den Prompt als auch den Dokumenttext in einem Chunk ab. 0/nicht gesetzt → der konservative Standardwert des Dienstes. Bereich: 16000–2000000; ein Wert außerhalb des Bereichs → 400. Setzen Sie ihn auf das Kontextfenster Ihres Modells — nur Sie kennen es. Die Antwort gibt immer das tatsächlich wirksame Budget zurück (auch wenn Sie sich auf den Standardwert verlassen): Der Dienst reserviert einen kleinen Spielraum für Chat-Template-Tokens, sodass der zurückgegebene Wert etwas niedriger ist als der von Ihnen gesetzte. |
Unterstützte Provider:
| Provider | neural.type-Wert |
|---|---|
| OpenAI | NEURAL_CLIENT_TYPE_OPENAI |
| Anthropic (Claude) | NEURAL_CLIENT_TYPE_CLAUDE |
| xAI (Grok) | NEURAL_CLIENT_TYPE_GROK |
| Together | NEURAL_CLIENT_TYPE_TOGETHER |
| DeepSeek | NEURAL_CLIENT_TYPE_DEEPSEEK |
| Xiaomi | NEURAL_CLIENT_TYPE_XIAOMI |
| Mistral | NEURAL_CLIENT_TYPE_MISTRAL |
| OpenRouter | NEURAL_CLIENT_TYPE_OPENROUTER |
Über NEURAL_CLIENT_TYPE_OPENROUTER erhalten Sie Modelle vieler Anbieter, die keine direkte Integration haben.
Sie zahlen den Provider direkt zu dessen Tarif — ChunkChef erhebt keinen Aufschlag auf Tokens oder OCR.
Extraktionsmodus (Optimierte Hybrid-Extraktion)
Standardmäßig (EXTRACTION_MODE_HYBRID) entscheidet der Konverter je Datei, ob Text direkt extrahiert oder OCR verwendet wird — die Strategie „Optimierte Hybrid-Extraktion”. Setzen Sie extractionMode bei einem Job auf EXTRACTION_MODE_OCR_ALWAYS, um neuronale OCR für jede Datei zu erzwingen, auch wenn diese bereits eine extrahierbare Textebene hat. Hinweis: Dadurch wird jede Datei zu einem OCR-Aufruf beim Provider — das erhöht die Kosten gegenüber dem Hybrid-Modus. Der extractionMode eines einzelnen Jobs überschreibt den Konto-Standard, den Sie im Dashboard unter Einstellungen festlegen.
Strukturierte Ausgabe (responseSchema)
Übergeben Sie bei der Job-Erstellung optional responseSchema — ein JSON Schema als Roh-JSON-String —, damit ChunkChef die Form der JSON-Antwort des Modells validiert, zusätzlich zu (nicht anstelle von) der Beschreibung dieser Form in Ihrem Prompt.
Das Schema muss in sich geschlossen sein: Es sind nur interne #/$defs-Referenzen erlaubt — keine externen $ref und kein entferntes $schema/$id. Limits: höchstens 128 KiB, Verschachtelungstiefe 64, insgesamt 10000 Knoten. Ein ungültiges oder die Limits überschreitendes Schema wird bei der Job-Erstellung mit 400 abgelehnt, bevor irgendeine Datei verarbeitet wird. Bei OpenAI und Grok laufen strukturierte Ausgaben nativ und erfordern ChunkChefs strict-Teilmenge: jede Eigenschaft in required aufgeführt (Optionalität über type: [..., "null"] ausdrücken, nicht durch Weglassen) und additionalProperties: false bei jedem Objekt. Schreiben Sie Ihr Schema in dieser strict-Teilmenge, wird es portabel — dasselbe Schema funktioniert unverändert bei allen unterstützten Providern. Ein Schema, das ChunkChefs Prüfungen bei der Erstellung besteht, aber nicht mit der strict-Teilmenge für OpenAI/Grok kompatibel ist, schlägt bei der Erstellung nicht fehl und durchläuft nie den schemaValid/schemaErrors-Ablauf — stattdessen endet die entsprechende llm[]-Zeile als JOB_LLM_STATUS_FAILED mit einem Provider-Fehler, da die Einhaltung der strict-Teilmenge vom Provider durchgesetzt wird, nicht von ChunkChefs Erstellungsprüfung.
Zwei Felder in jeder llm[]-Zeile melden das Ergebnis (siehe „API-Referenz”):
schemaValid— ob dercontentdieser ZeileresponseSchemaentspricht. Nur vorhanden, wenn die Datei nicht in Chunks aufgeteilt wurde (chunkTotal= 1); bei Dateien mit mehreren Chunks fehlt es — dasselbe Schema wird trotzdem mit jedem Chunk-Aufruf gesendet, aber Verdikte pro Chunk werden noch nicht zu einem Verdikt für das Gesamtdokument zusammengeführt.schemaErrors— die Konformitätsverstöße, wennschemaValidfalseist (bis zu 10, je bis zu 512 Bytes).
Entspricht die Antwort auch nur einer Zeile nicht, endet der Job als JOB_STATUS_PARTIAL statt JOB_STATUS_COMPLETE — content wird trotzdem unverändert zurückgegeben, nur markiert. Bei Providern, für die ChunkChef strukturierte Ausgaben emuliert (DeepSeek, Xiaomi), erhält eine nicht konforme erste Antwort einen automatischen Korrekturversuch, bevor sie bewertet wird.
Zusammenführen von Chunk-Ergebnissen (merge)
Wenn ein Dokument in Chunks aufgeteilt wird (siehe „Prompts und Datenextraktion” oben), erhält jeder Chunk einen eigenen Modellaufruf und eine eigene llm[]-Zeile — diese Teile zu einer einzigen Antwort zusammenzuführen liegt standardmäßig bei Ihnen. Aktivieren Sie merge bei der Job-Erstellung, und ChunkChef übernimmt diese Zusammenführung für Sie, serverseitig: Das Ergebnis erhält ein merged[]-Array mit einer Antwort für das gesamte Dokument (oder die gesamte Datei), zusammengesetzt aus den Antworten je Chunk.
Wann Sie es aktivieren sollten. Wenn Sie strukturiertes JSON aus einem Dokument extrahieren, das lang genug ist, um in mehrere Chunks aufgeteilt zu werden — etwa eine mehrseitige Rechnung, deren Positionen sich über Seite 1 und Seite 2 verteilen —, erspart Ihnen merge das Schreiben des Zusammensetz-Codes: ChunkChef vereinigt die JSON-Felder, verkettet und dedupliziert Arrays und löst jedes Feld auf, das zwischen Chunks voneinander abweicht.
Aktivieren:
{ "merge": { "enabled": true } }
Alle übrigen merge.*-Felder sind optional und haben sichere Standardwerte (die vollständige Liste finden Sie unter „Merge-Optionen” in der API-Referenz).
Beispiel: eine zweiseitige Rechnung
Stellen Sie sich eine 2-seitige Rechnung vor, per OCR in einen Text erkannt, der lang genug ist, um in 2 Chunks aufgeteilt zu werden — Seite 1 landet in Chunk 0, Seite 2 in Chunk 1. Ihr Prompt fragt nach JSON: { "supplier": "...", "items": [...], "total": "..." }. Ohne merge erhalten Sie zwei separate llm[]-Antworten, eine je Chunk, und führen sie selbst zusammen. Mit aktiviertem merge kombiniert ChunkChef sie zu einem einzigen JSON-Objekt: Die items-Arrays beider Chunks werden zu einem einzigen verkettet, und supplier/total werden aus dem Chunk übernommen, der sie tatsächlich enthält. Melden Chunk 0 und Chunk 1 für dasselbe Dokument einen unterschiedlichen total (etwa weil die OCR eine Zwischensummenzeile verdoppelt hat oder das Modell eine Zahl falsch gelesen hat), ist das ein echter Konflikt — siehe „Konfliktbehandlung bei widersprüchlichen Werten” weiter unten.
Umfang: eine Antwort je Datei oder eine für den ganzen Job (merge.scope)
merge.scope steuert, wie viel in einen einzigen merged[]-Eintrag zusammengeführt wird:
MERGE_SCOPE_FILE(Standard) — Chunks werden innerhalb jeder Datei separat zusammengeführt: einmerged[]-Eintrag je Datei, mitmerged[].filegleich der URL dieser Datei.MERGE_SCOPE_JOB— Chunks werden über alle Dateien des Jobs hinweg zu einer einzigen Antwort zusammengeführt: einmerged[]-Eintrag, mit leerem ("")merged[].file. Verwenden Sie diesen Modus, wenn die hochgeladenen Dateien Seiten oder Teile eines einzigen logischen Dokuments sind (z. B. eine Bestellung, aufgeteilt auf mehrere Quelldateien) und Sie ein einziges kombiniertes Ergebnis statt eines je Datei möchten.
Konfliktbehandlung bei widersprüchlichen Werten (merge.conflictPolicy)
Beim Zusammenführen von JSON können zwei Chunks bei demselben Feld voneinander abweichen — der eine meldet total: 1500, der andere total: 1520 für dieselbe Rechnung. merge.conflictPolicy entscheidet, welcher Wert gewinnt:
MERGE_CONFLICT_POLICY_FIRST_NON_NULL(Standard) — der erste nicht-null-Wert in Chunk-Reihenfolge gewinnt.MERGE_CONFLICT_POLICY_MAJORITY— der am häufigsten vorkommende Wert gewinnt; bei weniger als 3 Chunks oder wenn kein Wert eine strikte Mehrheit hat, fällt ChunkChef aufFIRST_NON_NULLzurück.
In beiden Fällen wird jede solche Abweichung in merged[].conflicts[] erfasst — das field, die konkurrierenden values und aus welcher Datei/welchem Chunk jeder Wert stammt —, sodass ein abweichender total nicht stillschweigend übertüncht wird. Prüfen Sie conflicts[], wenn Sie wissen müssen, ob dem gewonnenen Wert zu trauen ist oder ob ihn sich jemand manuell ansehen sollte.
Zwei Arten der Deduplizierung
Merge entfernt doppelten Inhalt auf zwei unterschiedliche Arten:
- Technische Deduplizierung — immer aktiv, keine Konfiguration nötig. Chunks derselben Datei überlappen sich an ihrer Grenze meist ein wenig (dieselbe Tabellenzeile erscheint am Ende eines Chunks und am Anfang des nächsten); merge erkennt und entfernt diese Überlappung automatisch. Das ist das sichere Standardverhalten — es rührt nie Inhalt an, der über diese Grenzüberlappung hinausgeht.
- Inhaltliche Deduplizierung — optional, über
merge.dedupeBy. Beiscope=jobkann derselbe logische Datensatz legitim in mehr als einer Datei vorkommen (z. B. eine Position, die sich in zwei zusammengehörigen Dokumenten wiederholt), und diese Wiederholung kann durchaus beabsichtigt sein — deshalb rührt ChunkChef sie nicht an, sofern Sie nicht ausdrücklich danach fragen. Geben Sie inmerge.dedupeBydie JSON-Feldnamen an, die einen eindeutigen Datensatz identifizieren (z. B.["invoiceNumber", "lineNo"]), und ChunkChef fasst Datensätze zusammen, die bei diesen Feldern übereinstimmen, wobei das erste Vorkommen erhalten bleibt. Lassen Siemerge.dedupeByleer — den Standard —, um nur die technische Deduplizierung beizubehalten, sonst nichts.
merge.dedupeBy funktioniert nur mit JSON-Ausgabe und setzt voraus, dass responseSchema gesetzt ist — die Feldnamen werden als einfache Schlüssel im geparsten JSON-Objekt nachgeschlagen. Höchstens 32 Feldnamen; mehr werden bei der Job-Erstellung mit 400 abgelehnt.
Beide Arten der Entfernung werden im Ergebnis getrennt gezählt, damit Sie sie unterscheiden können:
merged[].dedupeRemovedTechnical— Datensätze, die durch die automatische Grenzüberlappungs-Deduplizierung entfernt wurden.merged[].dedupeRemovedContent— Datensätze, die durch IhrededupeBy-Felder entfernt wurden.
Das Ergebnis lesen: merged[] vs. rohes llm[]
Wenn merge aktiviert ist, erhält das Ergebnis merged[] zusätzlich zum bestehenden llm[]:
merged[]— die serverseitig zusammengeführte Antwort für das gesamte Dokument (oder die Datei). Lesen Sie dies als Ihr primäres Ergebnis.llm[]— unverändert: weiterhin eine Zeile je Chunk. Es bleibt verfügbar, damit Sie genau prüfen können, was der Modellaufruf jedes Chunks zurückgegeben hat — nützlich beim Debuggen eines Konflikts.
Haben Sie responseSchema gesetzt, wird das zusammengeführte JSON-Objekt dagegen validiert, und merged[].schemaValid meldet das Ergebnis — genauso, wie es llm[].schemaValid heute für eine Datei mit einem einzigen Chunk tut.
Wenn ein Chunk nicht zusammengeführt werden konnte (merged[].mergeIncomplete)
Das Zusammenführen erfolgt best-effort: Lässt sich die Antwort eines Chunks nicht parsen, oder verteilt sich eine Dokumentstruktur so über Chunks, dass merge sie nicht sicher kombinieren kann, wird der Rohinhalt dieses Chunks trotzdem einbezogen — angehängt, nicht zusammengeführt —, und merged[].mergeIncomplete wird auf true gesetzt, wobei die betroffenen Chunks in merged[].incompleteChunks aufgeführt werden. Der Rest der Zusammenführung wird trotzdem normal abgeschlossen; prüfen Sie dieses Flag, wenn Sie wissen müssen, ob das zusammengeführte Ergebnis vollständig sauber oder nur teilweise zusammengesetzt ist.
Konsens-Durchläufe (consensus)
Bei einem Schema-Job (responseSchema gesetzt) können Sie ChunkChef denselben Chunk mehrfach durch das Modell laufen lassen und Feld für Feld über die Antwort abstimmen lassen, statt einem einzelnen Durchlauf zu vertrauen. Aktivieren Sie dies mit consensus.mode bei der Job-Erstellung: ChunkChef führt den Chunk k-mal aus, nimmt für jedes Feld den Mehrheitswert (relative Mehrheit) und meldet sowohl die konsentierte Antwort als auch jedes Feld, bei dem das Modell geschwankt hat — ein Vertrauenssignal zusätzlich zu Ihrem einzelnen BYOK-Aufruf, zum k-fachen Token-Preis auf Ihrem eigenen Schlüssel. Da die Durchläufe sequenziell erfolgen, vervielfacht der Konsens auch die Verarbeitungszeit — ein Job kann bis zu k-mal länger dauern.
Setzt responseSchema voraus. Der Konsens braucht ein Schema, um zu wissen, was ein „Feld” ist — ohne eines gibt es nichts, worüber abgestimmt werden könnte. consensus.mode ohne responseSchema wird bei der Job-Erstellung mit 400 abgelehnt.
Aktivieren:
{ "responseSchema": "...", "consensus": { "mode": "CONSENSUS_MODE_THREE_RUNS" } }
Modi
Der Modusname trägt den Token-Multiplikator in sich — eine separate „Anzahl Durchläufe” müssen Sie nicht setzen:
| Modus | Durchläufe | Hinweise |
|---|---|---|
CONSENSUS_MODE_TWO_RUNS | 2 | am günstigsten — eine Instabilitäts-Probe, keine verlässliche Abstimmung (siehe Einschränkungen unten) |
CONSENSUS_MODE_THREE_RUNS | 3 | empfohlener Standard — eine echte Mehrheitsabstimmung |
CONSENSUS_MODE_FIVE_RUNS | 5 | maximale Prüftiefe, höchste Kosten |
Wird consensus weggelassen (oder CONSENSUS_MODE_UNSPECIFIED gesetzt), ist es deaktiviert — das Verhalten ist byteidentisch zu einem Job ohne Konsens.
Das Ergebnis lesen: minAgreement und disagreements
Jede llm[]-Zeile erhält ein consensus-Objekt (die vollständige Feldliste finden Sie in der „API-Referenz”). Zwei Werte sollten Sie zuerst lesen:
minAgreement— die niedrigste Feld-Übereinstimmung in dieser Zeile, als Bruchteil (1.0= jedes abgestimmte Feld stimmte in jedem Durchlauf überein). Der Wert0ist ein reservierter Sentinel-Wert, der bedeutet, dass keine echte Abstimmung stattgefunden hat (weniger als 2 abstimmungsfähige Durchläufe, oder die Abstimmung ist degradiert) — lesen Sie ihn als „Konsens für diesen Chunk nicht verfügbar”, nicht als „0 % Übereinstimmung”.disagreements[]— ein Eintrag je Feld (oder Array), bei dem die Durchläufe nicht vollständig übereinstimmten, mit der niedrigsten Übereinstimmung zuerst. Jeder Eintrag listet die konkurrierendenvariants[]auf: den Wert, wie viele Durchläufe ihn geliefert haben, und ob erincludedist — die Abstimmung gewonnen hat und in der konsentierten Antwort vorhanden ist. Eine Variante kannincluded: truehaben, selbst wenn ihr Wertnullist: Ein gewinnendesnullbedeutet, dass der Schlüssel des Felds im konsentierten JSON weggelassen wird, nicht dass ein literalesnullerscheint.
Ehrliche Einschränkungen
CONSENSUS_MODE_TWO_RUNSist eine Probe, keine Abstimmung. Beik=2teilt sich eine skalare Abweichung immer 1-zu-1 (Übereinstimmung0.5), und bei Arrays wird nie etwas gefiltert — jedes Element, das einer der beiden Durchläufe gemeldet hat, landet in der konsentierten Antwort. Zwei Durchläufe zeigen Ihnen dass das Modell geschwankt hat; erst ab drei Durchläufen wird ein Minderheitswert tatsächlich herausgefiltert.- Die Mehrheitsabstimmung kann eine Entität verwerfen, deren Existenz jeder Durchlauf bestätigt hat. Ist ein Array-Element in jedem Durchlauf vorhanden, aber mit einer abweichenden Zelle (z. B. eine Position, die jeder Durchlauf extrahiert hat, bei der aber ein Durchlauf eine einzelne Zahl falsch gelesen hat), zählt die Version jedes Durchlaufs als eigenständiger kanonischer Wert — bei
k≥3erhält jede davon 1 von k, unterhalb der Mehrheitsschwelle, und das Element kann aus dem konsentierten Array entfernt werden, obwohl jeder Durchlauf zustimmte, dass es dazugehört.disagreements[]zeigt trotzdem jede Variante, sodass der Rohbefund erhalten bleibt, selbst wenn die konsentierte Antwort ihn nicht enthält — prüfen Sie das, wenn ein Array kürzer aussieht als erwartet. - Eine überdimensionierte Antwort gilt als nicht abstimmungsfähig, nicht als Fehler. Die Antwort eines Durchlaufs, die als valides JSON geparst wird, aber die internen Parse-Limits von ChunkChef überschreitet, wird von der Abstimmung ausgeschlossen; die Abstimmung läuft über die verbleibenden Durchläufe weiter — dieselbe Behandlung wie bei einem Durchlauf, der Nicht-JSON-Text zurückgibt.
- Konsens und
mergesetzen sich aus zwei unabhängigen Abstimmungen zusammen. Sind beide aktiviert, wird zunächst intern je Chunk abgestimmt (kDurchläufe → eine konsentierte Chunk-Antwort), und dann, fallsmerge.conflictPolicyaufMERGE_CONFLICT_POLICY_MAJORITYsteht, stimmt der Merge-Schritt erneut ab — über die konsentierten Antworten der Chunks hinweg. Zwischen den beiden Abstimmungen wird nichts übertragen — ein Feld, das der Chunk-Konsens bereits aufgelöst hat, wird dem Merge als feststehender Wert präsentiert, wie die Antwort jedes anderen Chunks.
Idempotente Wiederholungen
Um die Job-Erstellung sicher zu wiederholen, generieren Sie einen idempotencyKey (eine UUID) und verwenden Sie ihn
über die Wiederholungen derselben Anfrage hinweg:
POST /v1/jobs
{ "sourceUrls": ["..."], "ocr": { ... }, "neural": { ... }, "idempotencyKey": "3f1c…" }
Eine Wiederholung mit demselben Schlüssel und identischen Parametern gibt den ursprünglichen Job zurück;
das Ändern eines beliebigen Parameters unter demselben Schlüssel gibt 400 zurück. Verwenden Sie einen neuen Schlüssel für einen
wirklich neuen Job.
Webhooks
Webhooks ersparen Ihnen das Polling: Der Dienst sendet ein POST an Ihren Endpunkt, sobald der Job abgeschlossen ist. Das ist völlig optional — wenn Sie die Felder nicht setzen, ändert sich das API-Verhalten nicht.
Einrichtung
Übergeben Sie beim Erstellen eines Jobs (POST /v1/jobs) eines oder beide optionalen Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
webhookUrl | string | Absolute URL Ihres Endpunkts (http:// oder https://). Wird nur als Eingabe entgegengenommen — in Antworten niemals zurückgegeben. |
webhookSecret | string | Optionaler Secret für die HMAC-Signatur. Wird nur als Eingabe entgegengenommen — in Antworten niemals zurückgegeben; verschlüsselt gespeichert und zusammen mit dem Job gelöscht (siehe „Sicherheit und Daten”). |
Beispiel:
{
"sourceUrls": ["https://example.com/invoice.pdf"],
"prompts": ["Extract the total."],
"ocr": { "provider": "NEURAL_CLIENT_TYPE_MISTRAL", "model": "mistral-ocr-latest", "providerKey": "..." },
"neural": { "type": "NEURAL_CLIENT_TYPE_XIAOMI", "model": "mimo-v2-flash", "apiKey": "..." },
"webhookUrl": "https://your-service.example.com/hooks/ChunkChef",
"webhookSecret": "my-secret-value"
}
Wann der Webhook auslöst
Einmal pro Job — beim ersten Übergang in einen terminalen Status (JOB_STATUS_COMPLETE, JOB_STATUS_PARTIAL oder JOB_STATUS_FAILED). Die Zustellung selbst ist at-least-once (bei Fehlern sind Duplikate möglich); siehe „Zustellungsgarantien”. Der Webhook trägt nicht das Verarbeitungsergebnis; er signalisiert nur die Fertigstellung. Das vollständige Ergebnis holen Sie wie gewohnt mit GET /v1/jobs/{id}/result.
Anfrage-Body
Der Dienst sendet ein POST an Ihre webhookUrl mit Content-Type: application/json und einem JSON-Body:
{
"job_id": "15b07304-...",
"account_id": "a1b2c3d4-...",
"status": "complete",
"finished_at": "2026-06-24T12:34:56Z"
}
| Feld | Typ | Beschreibung |
|---|---|---|
job_id | string | Job-UUID |
account_id | string (Konto-Identifikator) | Konto-Identifikator |
status | string | Einer von: complete, partial, failed |
finished_at | string | Abschlusszeit des Jobs im RFC3339-Format (UTC) |
Signaturprüfung
Wenn webhookSecret gesetzt ist, enthält jede Anfrage zwei zusätzliche Header:
| Header | Beispielwert | Beschreibung |
|---|---|---|
X-Chunkchef-Timestamp | 1750765200 | Unix-Zeit der Zustellung (Sekunden) |
X-Chunkchef-Signature | sha256=a3f4... | HMAC-SHA256-Signatur |
Signaturalgorithmus:
signature = "sha256=" + hex( hmac_sha256(secret, "<timestamp>.<body>") )
wobei <timestamp> die String-Form der Unix-Zeit aus X-Chunkchef-Timestamp ist, <body> der rohe Anfrage-Body (Bytes wie empfangen) und . das Trennzeichen. hex ist kleingeschrieben; secret wird als UTF-8-Bytes verwendet.
So prüfen Sie auf Ihrer Seite:
- Extrahieren Sie den Wert von
X-Chunkchef-Timestamp. - Berechnen Sie
hmac_sha256(secret, "<X-Chunkchef-Timestamp value from step 1>.<raw request body>"), kodieren Sie als kleingeschriebenes hex und stellen Siesha256=voran. - Vergleichen Sie das Ergebnis mit
X-Chunkchef-Signatureüber einen konstant-zeitigen Vergleich (hmac.Equal/crypto/subtle.ConstantTimeCompareoder gleichwertig). - Weisen Sie die Anfrage ab, wenn der Zeitstempel zu alt ist (empfohlene Toleranz: 5 Minuten).
Wenn webhookSecret nicht gesetzt ist, werden die Header X-Chunkchef-Timestamp und X-Chunkchef-Signature nicht gesendet.
Wiederholungsrichtlinie
Wenn Ihr Endpunkt nicht erreichbar ist oder einen Fehler zurückgibt, wiederholt der Dienst die Zustellung nach folgendem Zeitplan:
| Versuch | Verzögerung bis zum nächsten |
|---|---|
| 1 → 2 | 1 Minute |
| 2 → 3 | 5 Minuten |
| 3 → 4 | 15 Minuten |
| 4 → 5 | 30 Minuten |
| 5 → 6 | 30 Minuten |
| 6 | — (letzter; danach wird die Zustellung als fehlgeschlagen markiert) |
Insgesamt: bis zu 6 Versuche.
Dauerhafte Fehler (4xx außer 408/429, unbrauchbare URL, SSRF-Blockierung) werden nicht wiederholt: Die Zustellung wird sofort als fehlgeschlagen markiert. Eine 2xx-Antwort gilt als Erfolg.
Zustellungsgarantien
Die Zustellung ist at-least-once: In den meisten Fällen erhält Ihr Endpunkt genau einen Aufruf, aber Wiederholungen können bei Fehlern zu doppelter Zustellung führen. Deduplizieren Sie Events auf Ihrer Seite anhand von job_id.