Zum Inhalt springen

Den Agenten die Daten schreiben lassen, nicht das HTML

Ein KI-Agent, der das HTML eines Dokuments schreibt, schreibt jedes Mal ein neues Dokument. Einer, der eine geprüfte Vorlage füllt, schreibt dasselbe. Was die Vorlage dem Modell noch falsch zu machen lässt, und wie man es kostenlos prüft, bevor etwas gedruckt wird.

Es gibt zwei Wege, einen KI-Agenten eine Rechnung erstellen zu lassen. Man bittet ihn um die Rechnung: Er schreibt HTML, etwas druckt es, ein PDF kommt heraus. Oder man gibt ihm eine Rechnungsvorlage, die schon jemand geprüft hat, und bittet ihn nur um die Fakten — wer, was, wie viel, zu welchem Satz — als JSON.

Der erste Weg funktioniert in der Demo. Der zweite übersteht auch den zweiten Durchlauf.

Ein Dokument verspricht, gleich auszusehen

Ein Modell schreibt, indem es Stichproben zieht. Bittet man es zweimal um „eine Rechnung über diese zwei Positionen“, bindet nichts die zweite Antwort an die erste: die Reihenfolge der Spalten, der Wortlaut der Umsatzsteuerzeile, ob die Zahlungsbedingungen ein Fälligkeitsdatum nennen, ob die Pflichtangaben im Fuß überhaupt dastehen. Jeder Durchlauf ist ein neuer Entwurf, den niemand geprüft hat. Für eine Geburtstagskarte ist das charmant. Für eine Rechnung, die in Deutschland eine Liste von Pflichtangaben tragen muss, heißt es, jede einzelne zu prüfen, die der Agent verschickt — und dann hat der Agent niemandem Arbeit erspart.

Eine Vorlage dreht das um. Layout, Pflichtangaben, Rechnung und Zahlungscode werden einmal geschrieben, von einem Menschen angesehen, versioniert und veröffentlicht. Was sich von Durchlauf zu Durchlauf ändert, sind die Daten, und Daten sind genau das, was ein Modell gut kann: den Kundennamen aus einer E-Mail ziehen, die Mengen aus einer Tabelle, den Satz aus einem Vertrag.

Die Form, die funktioniert, hat vier Teile, und unser MCP-Server hat für jeden ein Tool:

Schritt Tool Kosten
Die Vorlage finden list_templates keine
Erfahren, was sie erwartet get_template_schema — das JSON Schema der Daten und die Beispieldaten, mit denen sie gebaut wurde keine
Die Daten prüfen validate_template keine: Die API prüft eine gespeicherte Vorlage, ohne sie zu rendern; Ad-hoc-HTML wird im MCP-Server selbst geprüft
Rendern render keine mit einem Test-Key; mit einem Live-Key eine Einheit je angefangene fünf PDF-Seiten

Test-Keys (ff_test_…) rendern dieselben Vorlagen durch dieselbe Pipeline und werden nie gezählt; ihre Ergebnisse bleiben 24 Stunden erhalten und tragen im Free-Plan ein Wasserzeichen.

Wo die Daten trotzdem schiefgehen

Dass das Modell nur die Daten schreibt, macht die Daten noch nicht richtig. Also haben wir eine kurze Rechnungsvorlage genommen, validate_template mit den Daten gefüttert, die ein Agent typischerweise liefert, und jede Fassung gerendert. Die Vorlage haben wir als HTML übergeben, das das Tool offline prüft, im MCP-Server selbst:

<table>
  {% for line in invoice.lines %}
  <tr><td>{{ line.description }}</td><td>{{ line.qty }}</td><td>{{ line.price | money }}</td><td>{{ line.total | money }}</td></tr>
  {% endfor %}
</table>
{% set net = invoice.lines | sum('total') %}
{% set vat = net * invoice.vat_rate / 100 %}
<p>Gesamt {{ (net + vat) | money }}</p>

Mit den Daten, für die sie geschrieben ist, meldet die Prüfung nichts, und die Rechnung endet mit Gesamt 3.760,40 €. Dann die Fassungen des Agenten.

Der Agent lässt die Zeilensummen weg, weil er erwartet, dass die Vorlage Menge mal Preis rechnet. Diese tut es nicht. validate_template antwortet:

{
  "ok": true,
  "diagnostics": [
    {
      "severity": "warning",
      "code": "unknown-variable",
      "message": "\"line.total\" is not present in the sample data",
      "line": 3,
      "column": 101
    }
  ]
}

und die gerenderte Rechnung endet mit Gesamt 0,00 €.

Der Agent nennt die Liste items, weil das für ein Modell, das nicht ins Schema geschaut hat, genauso gut klingt wie lines. Die Prüfung warnt fünfmal, einmal für invoice.lines und einmal für jedes Feld darunter, wieder mit "ok": true. Die gerenderte Rechnung hat keine Positionen und endet mit 0,00 €.

Der Agent schreibt den Satz als "19 %", so wie er im Vertrag stand, den er gelesen hat. Die Offline-Prüfung meldet nichts: Sie weiß, welche Namen die Vorlage liest, nicht, welche Art von Wert hineingehört. Als wir das zum ersten Mal laufen ließen, endete die gerenderte Rechnung mit Gesamt NaN. Das war ein Fehler in unserem Helper money, und er ist behoben: money, number und numToWords brechen das Rendern jetzt mit money: got NaN, the result of a calculation with a value that is missing or not a number ab, statt NaN auf ein Dokument zu drucken. Text, der keine Zahl ist, etwa "auf Anfrage", erscheint weiterhin unverändert.

Die Prüfung, die die Werte kennt

Ein Agent bringt selten sein eigenes HTML mit. Er füllt eine Vorlage, die in Formfeed gespeichert ist, und für eine gespeicherte Vorlage prüft validate_template nicht offline: Es bittet die API, kostenlos und ohne zu rendern, die Version zu prüfen, die ein Render verwenden würde. Diese Prüfung führt die Vorlage einmal mit den Daten aus, sodass die "19 %" von oben als Fehler zurückkommen, bevor etwas gedruckt wird, und sie vergleicht die Daten mit dem gespeicherten JSON Schema der Vorlage:

{
  "severity": "error",
  "code": "data-validation",
  "path": "data.invoice.vat_rate",
  "message": "data.invoice.vat_rate: must be number"
}

Die Schema-Prüfung braucht ein gespeichertes Schema. Jede Vorlage hat eines, das aus ihren Beispieldaten abgeleitet ist — das gibt get_template_schema dem Agenten —, aber nur ein Schema, das Sie gespeichert haben, entscheidet, was als falsch gilt; ein abgeleitetes würde Daten schon ablehnen, weil ein Feld fehlt, das die Beispieldaten zufällig hatten. Im Editor hinterlegt Als Contract hinterlegen im Bereich „Daten und Schema“ eines; mit der CLI eine schema.json neben der Vorlage. Hinterlegen Sie eines für jede Vorlage, die ein Agent füllt.

Daraus folgt in beiden Fällen zweierlei. Erstens heißt ok: true, nichts wird scheitern, nicht die Daten stimmen: Fehlende Daten sind eine Warnung, weil eine Vorlage optionale Felder bewusst weglassen darf. Ein Agent sollte jede Warnung als Frage behandeln, die er vor dem Rendern beantwortet. Zweitens weiß eine Prüfung nur, was man ihr gesagt hat. Verbinden Sie einen Agenten, solange Sie ihn einrichten, mit einem Test-Key: Jeder Render kostet nichts, und eine Handvoll gelesener Ergebnisse prüft, was keine Validierung kann, etwa ob der richtige Kunde auf der Rechnung steht.

Die Offline-Ergebnisse oben stammen aus einem Test im Repository des MCP-Servers selbst. Er ruft die echten Tools mit einem Netzwerk-Stub auf, der bei jeder Anfrage scheitert, und zeigt damit auch, dass Ad-hoc-HTML und seine Daten beim Prüfen den Rechner nicht verlassen, auf dem der Server läuft: validate-agent-data.spec.ts.

Was man dem Agenten sagt

Der MCP-Server sagt einem verbundenen Agenten bereits die Reihenfolge: auflisten, Schema lesen, prüfen, rendern. Was er nicht wissen kann, ist, wie streng er mit Ihren Dokumenten sein soll. Ein paar Zeilen in den Anweisungen des Agenten schließen die Lücke:

Before rendering a document:
1. Read the template's schema and sample data with get_template_schema, and use the same field names.
2. Call validate_template with your data. Fix every diagnostic, warnings included.
3. Numbers are numbers: 19, not "19 %"; 1200.5, not "1.200,50".
4. Before handing a document on, compare its totals with the data you sent.

Das Modell schreibt weiterhin den Teil, der sich ändert. Der Teil, der sich nicht ändern darf, wurde einmal von einem Menschen geschrieben, und der Agent kommt nie an ihn heran.

Die Seite zu KI-Agenten zeigt die Einrichtung für Claude Code, Cursor, VS Code, Codex und ein Dutzend weitere Clients und Frameworks, und die MCP-Dokumentation listet jedes Tool mit seinen Argumenten.

Geprüft am 5. Oktober 2026. Die Schema-Prüfung in validate_template braucht @formfeed/mcp 0.3.6 oder neuer.

Philipp Staudt

Heute mit dem Rendern beginnen

Der kostenlose Plan enthält 100 Einheiten im Monat und kostenlose Testrenderings. Wechseln Sie den Plan, wenn Sie mehr brauchen.