Eingaben
Typisierte Eingaben deklarieren, das exons.input-Tag, und wie ein gebundener Wert in einen Prompt gerendert wird.
Ein Dokument, das ausgeführt werden soll, braucht in der Regel Werte, die es selbst nicht enthält: den zu fassenden Text, die Anzahl der Stichpunkte, den Tonfall. Im inputs-Block deklariert ein Dokument diese Werte, und über {~exons.input~} liest sein Body sie aus.
inputs ist eines der Frontmatter-Felder aus der Format-Referenz. Diese Seite ist sein vollständiges Vokabular.
Zuerst: Fragment oder Template? seit v0.19.0
Ein prompt-Dokument deklariert, welches von beidem es ist:
subtype | Was es ist | Deklariert inputs? |
|---|---|---|
fragment | Ein zusammensetzbarer Baustein, der von einem anderen Dokument referenziert wird. | Selten — das komponierende Dokument deklariert sie. |
template | Eine vollständige Einheit, die mit Werten pro Lauf ausgeführt wird. | Ja. |
subtype gilt nur für prompt-Dokumente und ist optional — ein Prompt ohne Angabe ist ein Fragment. Wenn du ein Fragment, einen skill oder einen agent schreibst, ist der Rest dieser Seite Hintergrundwissen und nichts, was du brauchst.
Eine Eingabe deklarieren
inputs:
text:
type: text
description: The source material to summarise.
max_bullets:
type: number
default: 5
tone:
type: select
label: Tone of voice
options:
- value: neutral
- value: warm
- value: terseJeder Schlüssel ist der Name einer Eingabe. Die Reihenfolge, in der du sie schreibst, bleibt erhalten seit v0.20.0 — eine Oberfläche, die aus diesem Schema ein Formular baut, stellt die Fragen in genau dieser Reihenfolge.
Jeder Eintrag unter options ist ein Objekt mit einem value — das, was gespeichert wird und was das Dokument sieht — und einem optionalen label, das einem Menschen angezeigt wird. Ohne Label fallen Konsumenten auf den Wert zurück.
Eingabetypen seit v0.19.0
type | Für |
|---|---|
text | Freier Text, ein Wert. |
number | Ein numerischer Wert. |
boolean | Wahr oder falsch. |
select | Eine Auswahl aus einer deklarierten Menge. |
multiselect | Beliebig viele Auswahlen aus einer deklarierten Menge. |
file-upload | Eine oder mehrere hochgeladene Dateien. |
sort | Eine deklarierte Menge, die der Nutzer in eine Reihenfolge bringt. |
associate | Paare aus zwei Mengen. |
Modifikatoren
| Schlüssel | Gilt für | Bedeutung |
|---|---|---|
description | alle | Wofür der Wert da ist. Wird demjenigen angezeigt, der ihn liefert. |
label | alle | Ein lesbarer Name für das Feld. seit v0.16.0 |
default | alle | Wird verwendet, wenn nichts gebunden ist. |
required | alle | Der Aufrufer muss einen Wert liefern — siehe Eine Bindung validieren. Ein deklarierter default erfüllt die Bedingung. |
options | select, multiselect, sort, associate | Die deklarierte Menge. Bei sort ist die geschriebene Reihenfolge die Ausgangsreihenfolge; bei associate ist sie die linke Menge. |
associate_with | associate | Die rechte Menge, gegen die gepaart wird. |
accept | file-upload | Akzeptierte Dateitypen. |
max_size_bytes | file-upload | Größenobergrenze pro Datei. |
max_files | file-upload | Wie viele Dateien geliefert werden dürfen. |
exons.input vs exons.var
Zwei Tags, zwei Aufgaben. exons.var hat ein Geschwister-Tag mit einer engeren Aufgabe bekommen.
{~exons.var~} | {~exons.input~} | |
|---|---|---|
| Liest | alles im Ausführungskontext — was auch immer der Aufrufer übergeben hat | nur das, was dieses Dokument in seinem eigenen inputs-Block deklariert hat |
| Gedacht für | Kontext, der von außerhalb des Dokuments kommt | einen Wert, den der eigene Vertrag des Dokuments zusagt |
| Ein nicht deklarierter Name | liest, was zufällig da ist — oder nichts | kann nicht vorkommen — erreichbar sind nur deklarierte Namen |
---
name: summarizer
type: prompt
subtype: template
inputs:
text:
type: text
max_bullets:
type: number
default: 5
---
Summarize into at most {~exons.input name="max_bullets" /~} bullets.
{~exons.input name="text" /~}Deklarierte Eingaben werden unter einer reservierten Kontext-Wurzel namens input injiziert, wobei jeder default bereits angewendet ist seit v0.21.0
. {~exons.input name="max_bullets"~} ist also der Pfad input.max_bullets — und damit braucht der Kontrollfluss keine neue Syntax:
{~exons.if eval="input.verbose"~}Explain your reasoning.{~/exons.if~}
{~exons.for item="s" in="input.sources"~}- {~exons.var name="s" /~}{~/exons.for~}Eine deklarierte Eingabe, die weder gebunden noch mit einem Default versehen ist, ist vorhanden, aber leer: Sie rendert zu nichts, wird als falsch ausgewertet, und eine Schleife darüber läuft null Mal. Das ist Absicht. Weil ein Name unter input nur dann erreichbar ist, wenn die Frontmatter ihn deklariert hat, ist ein fehlender Name ein Tippfehler und keine Mehrdeutigkeit.
exons.var bleibt unverändert
{~exons.var~} liest weiterhin den Laufzeit-Kontext — daran ändert sich nichts. Bis zur Einführung von exons.input seit v0.21.0
trugen beide Aufgaben — „einen Wert lesen, den der Aufrufer übergeben hat“ und „einen Wert lesen, den dieses Dokument deklariert hat“ — denselben Namen, weil es keine Möglichkeit gab, sie auseinanderzuhalten. An bestehenden Dokumenten ist nichts zu ändern: Das neue Tag erlaubt dir auszudrücken, was das bestehende nicht unterscheiden konnte.Werte binden
Eine deklarierte Eingabe ist ein Vertrag mit dem, der das Dokument ausführt. Der Aufrufer übergibt Execute eine Map, indiziert nach Eingabenamen:
output, err := tmpl.Execute(ctx, map[string]any{
"summary": "Checkout lieferte 6 Minuten lang 500",
"severity": 2,
"sources": []string{"logs", "pager"},
})Execute prüft an dieser Map nichts — eine deklarierte Eingabe, die weder gebunden noch mit Default versehen ist, rendert einfach als nichts. Pflichtfelder und Options-Zugehörigkeit werden separat validiert, vom Aufrufer, vor dem Render:
for _, verr := range tmpl.Spec().ValidateInputBinding(values) {
// ein Fehler pro beanstandeter Eingabe; du entscheidest, ob du abbrichst
}Diese Trennung ist Absicht. Ein Formular will einem Menschen sagen, welches Feld falsch ist, ein Renderer will Text erzeugen; zusammengelegt könnte ein halb gefülltes Formular nur als ein undurchsichtiger Render-Fehler scheitern.
Was ValidateInputBinding prüft
| Deklaration | Wird gemeldet, wenn |
|---|---|
required: true | der Wert fehlt oder leer ist und die Eingabe keinen default deklariert. Ein Default erfüllt die Pflicht — der Wert fehlt beim Render nie wirklich, eine Ablehnung würde also ein nicht absendbares Formular erzeugen. |
select, multiselect, sort mit options | ein gelieferter Wert ist keine der deklarierten Optionen. |
file-upload mit max_files | es wurden mehr Dateien geliefert als die Deklaration erlaubt. |
file-upload mit max_size_bytes | eine Datei überschreitet die Obergrenze pro Datei. |
Erzwungen wird ausschließlich, was das Dokument auch aussagt. Insbesondere bleibt das Vokabular der Eingabearten offen: Ein unbekannter type ist kein Fehler, denn ein Dokument kann gegen eine neuere Version der Bibliothek geschrieben sein als die, die es liest.
Wie ein Wert gerendert wird
Ein gebundener Wert muss zu Text werden. Die Regeln sind dieselben, egal ob er über exons.var oder über exons.input kommt:
| Wert | Rendert als |
|---|---|
| Ein String, eine Zahl oder ein Boolean | Sich selbst. |
| Eine Liste | Ihre Elemente als Fließtext verbunden. seit v0.20.0
Das Trennzeichen setzt du mit join=", ". |
| Ein Objekt | Seine lesbaren Felder statt eines Go-förmigen Dumps. |
| Eine Liste von Objekten | Jeder Eintrag nacheinander gerendert. |
| Die Bytes einer hochgeladenen Datei | Nie als Text. Der Rohinhalt einer Datei wird zurückgehalten, stattdessen erscheint eine Beschreibung der Datei. |
Die letzte Zeile solltest du kennen, bevor du auf sie triffst. Ein file-upload-Wert trägt Bytes, und Bytes tragen keinen Hinweis darauf, ob sie Prosa oder ein PNG sind — sie in einen Prompt zu rendern ist deshalb nie die richtige Vermutung. Die Datei wird beschrieben, nicht eingebettet. seit v0.21.2
Ein durchgearbeitetes Beispiel seit v0.24.0
Alles Obige in einem lauffähigen Dokument — fünf typisierte Eingaben, eine davon required, ein select, das dieses Kind gegenüber seinem Eltern-Template verengt, und eine for-Schleife, die abschwächt statt fehlzuschlagen, wenn ihre Liste nicht geliefert wird:
---
name: incident-report
description: An incident report. Extends base-report and restates tone in its own voice.
subtype: template
version: 1.0.0
inputs:
tone:
type: select
description: An incident report is never casual, so this one narrows the parent's choice.
default: formal
options:
- value: formal
label: Formal
summary:
type: text
description: One line describing what happened.
required: true
severity:
type: number
description: 1 (worst) to 5 (cosmetic).
default: 3
sources:
type: multiselect
description: Optional. Where the evidence came from.
options:
- value: logs
label: Service logs
- value: traces
label: Distributed traces
- value: pager
label: Pager timeline
---
{~exons.extends template="base-report" /~}
{~exons.block name="heading"~}# Incident: {~exons.input name="summary" /~}{~/exons.block~}
{~exons.block name="body"~}
Severity {~exons.input name="severity" /~}.
Evidence:
{~exons.for item="src" in="input.sources"~}- {~exons.var name="src" /~}
{~/exons.for~}
Timeline:
{~exons.for item="row" in="input.timeline" onerror="default" default="_No timeline was supplied._"~}- {~exons.var name="row" /~}
{~/exons.for~}
{~/exons.block~}
Es ist byte-identisch mit go-exons/examples/09-typed-inputs/report.exons; dort liegt eine main.go, die Werte bindet, validiert und rendert — go run . in diesem Ordner. Das Eltern-Template steht auf der Seite Beispiele.
Weiter
- Fehlerbehandlung —
onerror=, wie an der Schleife oben verwendet. - Format-Referenz — alle Frontmatter-Felder und Template-Tags.
- Konzept — warum ein Dokument aus zwei Teilen besteht.