Zum Inhalt springen

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:

subtypeWas es istDeklariert inputs?
fragmentEin zusammensetzbarer Baustein, der von einem anderen Dokument referenziert wird.Selten — das komponierende Dokument deklariert sie.
templateEine 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

frontmatter
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: terse

Jeder 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

typeFür
textFreier Text, ein Wert.
numberEin numerischer Wert.
booleanWahr oder falsch.
selectEine Auswahl aus einer deklarierten Menge.
multiselectBeliebig viele Auswahlen aus einer deklarierten Menge.
file-uploadEine oder mehrere hochgeladene Dateien.
sortEine deklarierte Menge, die der Nutzer in eine Reihenfolge bringt.
associatePaare aus zwei Mengen.

Modifikatoren

SchlüsselGilt fürBedeutung
descriptionalleWofür der Wert da ist. Wird demjenigen angezeigt, der ihn liefert.
labelalleEin lesbarer Name für das Feld. seit v0.16.0
defaultalleWird verwendet, wenn nichts gebunden ist.
requiredalleDer Aufrufer muss einen Wert liefern — siehe Eine Bindung validieren. Ein deklarierter default erfüllt die Bedingung.
optionsselect, multiselect, sort, associateDie deklarierte Menge. Bei sort ist die geschriebene Reihenfolge die Ausgangsreihenfolge; bei associate ist sie die linke Menge.
associate_withassociateDie rechte Menge, gegen die gepaart wird.
acceptfile-uploadAkzeptierte Dateitypen.
max_size_bytesfile-uploadGrößenobergrenze pro Datei.
max_filesfile-uploadWie 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~}
Liestalles im Ausführungskontext — was auch immer der Aufrufer übergeben hatnur das, was dieses Dokument in seinem eigenen inputs-Block deklariert hat
Gedacht fürKontext, der von außerhalb des Dokuments kommteinen Wert, den der eigene Vertrag des Dokuments zusagt
Ein nicht deklarierter Nameliest, was zufällig da ist — oder nichtskann nicht vorkommen — erreichbar sind nur deklarierte Namen
summarizer.exons
---
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:

text
{~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:

main.go
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:

main.go
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

DeklarationWird gemeldet, wenn
required: trueder 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 optionsein gelieferter Wert ist keine der deklarierten Optionen.
file-upload mit max_fileses wurden mehr Dateien geliefert als die Deklaration erlaubt.
file-upload mit max_size_byteseine 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:

WertRendert als
Ein String, eine Zahl oder ein BooleanSich selbst.
Eine ListeIhre Elemente als Fließtext verbunden. seit v0.20.0 Das Trennzeichen setzt du mit join=", ".
Ein ObjektSeine lesbaren Felder statt eines Go-förmigen Dumps.
Eine Liste von ObjektenJeder Eintrag nacheinander gerendert.
Die Bytes einer hochgeladenen DateiNie 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:

09-typed-inputs-report.exons
---
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