Abstrakte Wikipedia/Vorlagensprache für Wikifunctions/Scribunto-basierte Implementierung

Dies ist eine Übersichtsdokumentationsseite eines Scribunto-basierten NLG-Systems, basierend auf dem Vorschlag zur Erstellung einer NLG-Vorlagensprache zur Verwendung im Projekt Abstrakte Wikipedia. Das Scribunto-basierte System verfolgt zwei Ziele: Erstens soll es als Prototyp-Implementierung fungieren, die die zukünftige Implementierung eines ähnlichen Systems in Wikifunctions selbst beeinflussen kann, und zweitens soll es als eigenständiges NLG-System basierend auf Wikidata-Lexemen innerhalb der Scribunto-Umgebung fungieren.

Demo des Systems

Diese Seite besteht aus zwei verschiedenen Teilen:

  1. Beschreibung des Code-Flusses;
  2. Anleitung für Beitragende, die möglicherweise neue abstrakte Inhalte oder Renderer für bestimmte Sprachen schreiben möchten.

Code-Fluss: Von der QID zum verbalisierten Inhalt

Die verschiedenen im System verwendeten Module verfügen über eigene Dokumentationsseiten. Ziel dieses Abschnitts ist es, einen Überblick über das Zusammenwirken der Komponenten zu geben.

Der Fluss des Programms orientiert sich eng an der vorgeschlagenen NLG-Architektur für die Abstrakte Wikipedia.

 

Von der QID zu Vorlagen

Der Einstiegspunkt der Inhaltsverbalisierung ist die Funktion content, die (in einem Frame-Objekt) eine QID sowie einen (optionalen) Realisierungs-Sprachcode übernimmt (wenn der Sprachcode nicht angegeben ist, wird stattdessen die Standardsprache des Wikis für den Benutzer verwendet).

Inhaltsabruf

Die Funktion content ruft zunächst den abstrakten Inhalt (d. h. instanziierte Konstruktoren) für die angegebene QID mithilfe des Konstruktoren-Moduls ab. Dieser Inhalt kann einer von zwei Typen sein:

  • Manuell kuratierter abstrakter Inhalt für die angegebene QID, gespeichert im Modul für Abstrakten Inhalt.
  • Abstrakter Inhalt, der dynamisch (durch Code im Konstruktoren-Modul) basierend auf Wikidata-Eigenschaften des Wikidata-Datenobjekts generiert wird, auf das sich die QID bezieht.

Diese beiden Arten des Inhaltsabrufs wurden in den Neuigkeiten zu Modellartikeln besprochen.

Der abgerufene Inhalt hat die Form einer Liste instanziierter Konstruktoren, die der Gliederung des generierten Artikels entsprechen sollte.

Vorlagenauswahl

Für jeden Konstruktor der Inhaltsliste muss eine entsprechende Vorlage ausgewählt und realisiert werden. Dies geschieht innerhalb der Funktion content durch die Realisierung einer "Wrapper-Vorlage" der Form {root:main} bei der Übergabe eines einzelnen Vorlagenarguments mit dem Namen main, dessen Inhalt der Konstruktor selbst ist, an die Vorlagenrealisierungsfunktion. Die Vorlagenrealisierungslogik (unten detailliert beschrieben) interpretiert dies als eine Vorlage mit einer einzelnen Lücke, die nach der Interpolation (d. h. Verbalisierung) des Arguments main fragt. Da für die Interpolation eines Arguments, das ein Konstruktor ist, das Abrufen einer entsprechenden Vorlage erforderlich ist, kommt dies einer Erweiterung der Wrapper-Vorlage mit der konstruktorspezifischen Vorlage (für die gegebene Realisierungssprache) gleich.

Vorlagenrealisierung

Der Hauptablauf der Vorlagenrealisierung erfolgt innerhalb der Funktion realizeTemplate. Diese Funktion kann auch direkt von einer Wikiseite aus mit der Funktion render aufgerufen werden (hier dokumentiert). Dies ist nützlich für die Fehlerbehebung oder wenn eine Ad-hoc-Vorlage realisiert werden muss.

Die Vorlagenrealisierung besteht aus mehreren Schritten, die genau dem für die Vorlagensprache angegebenen generischen Realisierungsalgorithmus folgen (die entsprechenden Phasen dieses Algorithmus sind unten in Klammern angegeben).

Initialisierung

Die Funktion initialize richtet einige globale Variablen ein, die in der gesamten Realisierungspipeline verwendet werden sollen. Dies sind die folgenden:

  • Die Variable language enthält den Sprachcode der Realisierungssprache. Sie wird entweder vom Benutzer bereitgestellt oder auf die Inhaltssprache des Projekts eingestellt.
  • Die Variable functions ist eine Tabelle aller Funktionen, auf die die Vorlagen zugreifen können. Diese verweisen auf eine sprachspezifische Implementierung oder, falls diese nicht vorhanden ist, auf eine sprachunabhängige Implementierung.
  • Die Variable relations ist eine Tabelle aller Beziehungsfunktionen, auf die die Vorlagen zugreifen können. Diese verweisen auf eine sprachspezifische Implementierung oder, falls diese nicht vorhanden ist, auf eine sprachunabhängige Implementierung.
  • Die Variable renderers ist eine Tabelle für sprachspezifische Renderer von Konstruktoren.
  • Die Variable applyPhonotactics verweist auf eine sprachspezifische Implementierung des Phonotaktik-Moduls.

Beachte, dass in Wikifunctions alle diese Elemente (mit Ausnahme des Realisierungssprachcodes) global als Wikifunctions-Funktionen zugänglich sein sollten.

Parsen und Auswerten von Vorlagen (Phasen 1 & 2)

Die Funktion evaluateTemplate (Teil des Moduls TemplateEvaluator) ist für die Ausführung des ersten quadratischen Felds im obigen Flussdiagramm ("Vorlagen-Renderer") verantwortlich. Seine Ausgabe ist eine Wurzel-Lexem-Liste: eine geordnete Liste von Elementen, die selbst entweder Lexeme oder Wurzel-Lexem-Listen sind, mit einem zusätzlichen Feld root, das eines dieser Elemente als Wurzel identifiziert. Im obigen Flussdiagramm wird diese Datenstruktur Lemma-Baum genannt. Beachte, dass es sich effektiv um einen flachen Baum handelt, bei dem alle Knoten Kinder des Wurzelknotens sind (dieser Baum ist jedoch nicht unbedingt derselbe, der durch die Abhängigkeitsbeziehungen der Vorlage angegeben wird, außer dass sie dieselbe Wurzel haben).

Diese Phase besteht aus drei Teilen:

  1. Die Funktion parse des Moduls TemplateParser wird aufgerufen, um die als Zeichenkette angegebene Vorlage in zwei Listen umzuwandeln: eine strukturierte Listendarstellung ihrer Elemente und eine Listendarstellung der anzuwendenden Abhängigkeitsbeziehungen. Der Parser gibt außerdem den Index des Wurzelelements zurück, der in der Darstellung der Wurzel-Lexem-Liste gespeichert werden soll. Siehe die Dokumentation des Moduls für das genaue Rückgabeformat.
  2. Die Funktion evaluateElements durchläuft die Elementliste und wertet jedes Vorlagenelement in eine Darstellung als Lexem oder Wurzel-Lexem-Liste aus. Die Auswertung erfolgt entsprechend des Typs jedes Elements:
    • Textelemente (sei es normaler Text, Leerzeichen oder Satzzeichen) werden mit der Funktion TemplateText ausgewertet.
    • Zahlen (innerhalb von Lücken) werden mit der Funktion Cardinal ausgewertet.
    • Lexem-Identifikatoren (innerhalb von Lücken), auch LIDs genannt, werden mit der Funktion Lexeme ausgewertet.
    • Datenobjekt-Identifikatoren (innerhalb von Lücken), auch QIDs genannt, werden mit der Funktion Label ausgewertet.
    • Die Interpolation von Argumenten wird ausgewertet (in der Funktion evaluateInterpolation), indem das Argument abgerufen und dann mit der gleichen Logik wie oben ausgewertet wird (z. B. wenn das Argument eine LID ist, wird es mithilfe der Funktion Lexeme ausgewertet werden). In folgenden Fällen kommt eine besondere Logik zum Einsatz:
      • Wenn das Argument zu einer Untervorlage ausgewertet wird (d. h. eine Zeichenkette, die { } enthält), wird diese Untervorlage mithilfe der Funktion evaluateTemplate rekursiv ausgewertet (wodurch eine Wurzel-Lexem-Liste zurückgegeben wird).
      • Wenn das Argument zu einer Liste von Variantenvorlagen ausgewertet wird (eine Liste von Tabellen, die ein Feld template enthalten), wird eine geeignete Vorlage gemäß ihren Vorbedingungen ausgewählt (durch die Funktion selectTemplate Funktion) und ausgewertet.
      • Wenn das Argument zu einem Konstruktor ausgewertet wird (d. h. eine Tabelle, die ein Feld _predicate enthält), wird die entsprechende Vorlage aus dem entsprechenden sprachspezifischen Renderer-Modul abgerufen (gemäß den gegebenen Vorbedingungen) und ausgewertet (dies geschieht in der Funktion evaluateConstructor).
    • Funktionen werden ausgewertet (in der Funktion evaluateFunction), indem die entsprechende Funktion mit den angegebenen Argumenten aufgerufen wird. Als Argumente an die Funktion übergebene Funktionen und Interpolationen werden rekursiv ausgewertet (in evaluateFunctionArgument), andere Argumenttypen werden jedoch nicht einfach ohne weitere Verarbeitung als Zeichenkettenargumente an die aufgerufene Funktion übergeben.
  3. Die in der Vorlage angegebenen Beziehungen werden angewendet (in applyRelations): Grundsätzlich jede Beziehung, die einer bekannten Beziehungsfunktion entspricht, die im Beziehungs-Modul definiert ist (oder eine sprachspezifische Implementierung davon) wird auf die relevanten Elemente (oder ihre Wurzeln, wenn die Elemente zu einer Wurzel-Lexem-Liste ausgewertet wurden) angewendet. Diese Beziehungsfunktionen vereinheitlichen die grammatikalischen Merkmale ihrer beiden Argumente gemäß der angegebenen grammatikalischen Funktion und verbreiten und teilen grammatikalische Funktionen effektiv zwischen den Lexemen im Einklang mit der allgemeinen sprachlichen Struktur.

Anwendung morphosyntaktischer Einschränkungen (Phase 3, unter Verwendung leichter Bereinigung)

Die Ausgabe der letzten Phase ist ein Baum von Lexemen, wobei jedes Lexem mit einer Reihe grammatikalischer Funktionen verknüpft ist, die möglicherweise von einem anderen Lexem stammen. Diese Funktionen können nun als Einschränkungen fungieren, um die Liste der Formen jedes Lexems zu bereinigen, sodass nur Formen beibehalten werden, die die Einschränkungen erfüllen. Zu diesem Zeitpunkt wird die Baumstruktur nicht mehr benötigt, daher wird sie in eine einfache Liste von Lexemen abgeflacht.

Das Bereinigen der Lexeme erfolgt in der Funktion applyConstraints durch Aufrufen der Methode filterForms des Lexem-Moduls. Der Filteralgorithmus funktioniert wie folgt:

  • Iterieren aller Formen des Lexems. Für jede Form:
    • Iterieren der mit dem Lexem verbundenen grammatikalischen Einschränkungen. Für jede Kategorie grammatikalischer Einschränkungen:
      • Wenn die Form eine Funktion derselben Kategorie hat:
        • Verwerfen der Form, wenn die Funktion der Form mit der Funktion der Einschränkung nicht vereinbar ist (und fortfahren mit der nächsten Form).

In der Prototyp-Implementierung ist jede Funktion nur mit sich selbst oder mit der leeren Funktion vereinbar. In einer vollständigen Implementierung kann eine linguistische Hierarchie von Funktionen wünschenswert sein, die es ermöglicht, dass eine Funktion mit jeder Unter- oder Oberfunktion von ihr vereinbar ist. Der obige Algorithmus stellt nur sicher, dass die Funktionen von Kategorien, die zwischen der Form und den Einschränkungen des Lexems geteilt werden, vereinbar (d. h. kompatibel) sind, wodurch zusätzliche Funktionen in den Formen möglich sind, die nicht in den Einschränkungen erwähnt werden. Ein strengerer Bereinigungsalgorithmus könnte sicherstellen, dass die Funktionen der Form eine Teilmenge der dem Lexem auferlegten Einschränkungen darstellen. Da wir jedoch nicht die vollständige Kontrolle über die Funktionen jeder (aus Wikidata importiert) Form haben, ist der obige Algorithmus vorzuziehen.

Beachte, dass dieser Bereinigungsalgorithmus nicht gut bei unären Funktionen funktioniert, z. B. Funktionen, die entweder in einer Form erscheinen können oder nicht, aber keiner anderen Funktion entgegenstehen (z. B. die Verschmelzungsfunktion, die kontrahierte englische Verben markieren kann). Solche Funktionen werden von diesem Bereinigungsalgorithmus nicht beeinflusst, unabhängig davon, ob sie in den Einschränkungen oder in den Formen erscheinen (da der Algorithmus davon ausgeht, dass das Fehlen einer Funktion mit jeder anderen Funktion kompatibel ist). Wenn die Unterstützung solcher Funktionen erforderlich ist, sollte ein zusätzlicher Schritt hinzugefügt werden, um zu überprüfen, ob solche Funktionen sowohl in den Einschränkungen als auch in den Funktionen der Form vorhanden sind oder fehlen.

Im Idealfall sollte der Bereinigungsalgorithmus genau eine Form des Lexems beibehalten. In der Praxis können mehrere Formen bestehen bleiben. Um in solchen Fällen konsistent die weniger markierte Form auszugeben, werden die Formen gemäß einer kanonischen Reihenfolge in der Funktion sortForms geordnet. Diese Funktion sortiert die Formen gemäß einer lexikografischen Ordnung über die Kategorien und Funktionen, wie in der Tabelle cannonical_order angegeben. Wenn zum Beispiel keine Personenfunktion für ein verbales Lexem angegeben ist, bleiben die verschiedenen Personenflexionen des Lexems beim Bereinigungsprozess erhalten, aber die Sortierung stellt sicher, dass eine Form der dritten Person zuerst erscheint (falls unter den Originalformen des Lexems vorhanden).

Das Ergebnis dieser Phase ist eine Liste von Lexemen, wobei jedes Lexem eine bereinigte Liste von Formen hat, die mit den dem Lexem auferlegten grammatikalischen Einschränkungen kompatibel sind. Sofern im Rest der Pipeline keine weiteren Änderungen vorgenommen werden, wird die erste Form in der Liste der bereinigten Formen für die Darstellung des NLG-Textes verwendet. (Wenn nach dem Bereinigen keine Formen vorhanden sind, wird das Lemma des Lexems als Rückfalloption verwendet). Beachte, dass das Anfordern der Zeichenkettenform eines Lexems (mit der integrierten Funktion tostring) diese erste Form (oder möglicherweise das Lemma) ergibt, da die Funktion innerhalb des Moduls lexeme neu definiert wurde.

Anwendung phonotaktischer Regeln (Phase 4)

In dieser Phase müssen sprachspezifische phonotaktische und orthografische Regeln angewendet werden. Im Allgemeinen können Formen in Abhängigkeit von ihren Nachbarformen einer phonotaktischen Veränderung unterliegen (ohne Berücksichtigung von Abständen und leeren Formen), daher erfordert dieser Prozess eine lineare Durchquerung der Formen und die Anpassung derjenigen, die geändert werden sollen. Die phonotaktische Variante jeder Form kann in der vorhandenen Liste ihrer Formen gespeichert werden. In diesem Fall muss die richtige Form an die erste Position in der Liste der Formen heraufgestuft werden oder es könnten spezielle Regeln gelten, die die vorhandene erste Form der Liste ändern (unter Verwendung der Hilfsfunktion replaceByForm des Moduls lexeme).

Konkret erfolgt die Anwendung der Phonotaktik mit einer Funktion namens applyPhonotactics. Diese Funktion ist (in der Initialisierungsphase) an eine sprachspezifische Implementierung gebunden, die in einem Modul Module:Sandbox/AbstractWikipedia/Phonotactics/xx zu finden ist (wobei xx für den Sprachcode steht). Wenn eine solche Funktion nicht definiert ist, wird eine standardmäßige Null-Funktion angewendet.

Als Beispiele können wir uns die englischen und hebräischen Implementierungen hiervon ansehen:

  • Die englische Implementierung durchsucht die Lexeme nach dem unbestimmten Artikel a (identifiziert durch sein Lemma und seine Wortart). Wenn ein solches Lexem gefunden wird, wird das folgende Lexem (ohne Berücksichtigung von Leerzeichen und leeren Formen) überprüft und wenn es mit einem Vokal beginnt, wird die Form des Artikels durch die Form an ersetzt. Beachte, dass in der aktuellen Implementierung eine einfache Liste regulärer Ausdrücke verwendet wird, um zu bestimmen, ob eine Form mit einem Vokal beginnt. Idealerweise sollten diese Informationen für jedes Lexem auf Wikidata gespeichert und von dort abgerufen werden.
  • Die hebräische Implementierung kümmert sich um bestimmte orthografische und phonotaktische Wechsel, die nach den hebräischen Proklitika auftreten. Sie durchsucht die Liste der Lexeme und wenn ein Proklitikum gefunden wird, das durch sein Lemma identifiziert wird, können die folgenden Lexeme auf folgende Weise geändert werden:
    • Leerzeichen nach Proklitika werden entfernt.
    • Der bestimmte Artikel (identifiziert durch seine Wortart) wird nach bestimmten Proklitika entfernt.
    • Wenn auf das Proklitikum eine durch Ziffern geschriebene Zahl folgt, wird gemäß den orthografischen Konventionen des Hebräischen ein Bindestrich hinzugefügt.
    • Wenn einem Proklitikum ein Wort folgt, das mit dem Buchstaben Waw beginnt, wird dieser Buchstabe gemäß den Schreibregeln für unvokalisierten Text des Hebräischen verdoppelt.

Das Ergebnis dieser Phase ist eine Liste von Lexemen, wobei jedes Lexem als erste Form eine Form haben sollte, die mit den ihm auferlegten morphosyntaktischen und phonotaktischen Einschränkungen kompatibel ist.

Konstruktion des finalen Textes (Phase 5)

Die Konstruktion des gerenderten Textes erfolgt in der Funktion constructText. Im Grunde verkettet diese Funktion die Zeichenketten-Darstellung aller aus der vorherigen Stufe übergebenen Lexeme. Denke daran, dass die Zeichenketten-Darstellung eines Lexems normalerweise die erste Form in seiner Formenliste ist, die den phonotaktischen und morphosyntaktischen Einschränkungen entsprechen sollte. Dabei kümmert sich die Funktion auch um die spezielle Darstellung von Leerzeichen und Satzzeichen und wendet die erforderliche Groß- und Kleinschreibung an. Derzeit sind hierzu folgende Regeln implementiert:

  • Wenn aufeinanderfolgende spacing-Lexeme gefunden werden (z. B. Lexeme, die nur Leerzeichen enthalten), wird nur das letzte beibehalten. Dies ist notwendig, um mehrere aufeinanderfolgende Leerzeichen zu vermeiden, die andernfalls um Lücken entstehen könnten, die zu einer leeren Zeichenkette ausgewertet wurden.
  • Wenn aufeinanderfolgende Satzzeichen gefunden werden (wie in der Tabelle trailing_punctuation definiert), wird nur das Satzzeichen mit der höchsten Priorität beibehalten (z. B. unterdrückt ein Punkt ein Komma).
  • Nachgestellte Satzzeichen unterdrücken auch alle vorangehenden Leerzeichen.
  • Das erste Wort des realisierten Textes sowie jedes Wort, das auf ein Satzzeichen folgt, das die Großschreibung auslöst (wie in der Tabelle capitalization definiert), wird großgeschrieben. Dies erfolgt mithilfe der Funktion ucfirst, die alle speziellen Groß- und Kleinschreibungsregeln für die Realisierungssprache berücksichtigt.

Der Zweck der oben genannten Regeln besteht darin, den Autoren der Vorlagen zu ermöglichen, die Vorlagen so natürlich wie möglich zu schreiben, ohne sich zu viele Gedanken darüber zu machen, wo sie Leerzeichen oder Satzzeichen einfügen sollen. In den meisten Fällen behalten diese Heuristiken nur die erforderlichen Leerzeichen und Satzzeichen bei. (Beachte, dass der Vorlagenautor sie in einen TemplateText-Funktionsaufruf einschließen könnte, wodurch ihnen der Lexemtyp text zugewiesen wird, wenn die Auslassung von Satzzeichen oder Leerzeichen erforderlich ist, damit sie nicht auf diese Art weiter verarbeitet werden).

Die Ausgabe dieser Phase ist eine Textzeichenkette, die einer einzelnen Vorlage oder einem einzelnen Konstruktor entspricht.

Rendering eines ganzen Artikels

Da der abstrakte Inhalt einer QID mehrere Konstruktoren enthalten kann, besteht die letzte Phase des Renderings darin, das Rendering jedes Konstruktors zusammenzufügen. Dies geschieht derzeit ganz einfach durch Verketten der Ausgabezeichenketten aus der letzten Phase, wobei bei Bedarf ein anfängliches Leerzeichen dazwischen eingefügt wird. Beachte, dass Konstruktoren, die keinem Renderer für eine bestimmte Sprache zugeordnet sind, bei der Realisierung einfach weggelassen werden. Dies ermöglicht die teilweise Realisierung von Inhalten für diese Sprachen, anstatt dass die gesamte Realisierung fehlschlägt.

Anleitung für Beitragende

Neuen Abstrakten Inhalt schreiben

Du kannst für jedes Datenobjekt neue abstrakte Inhalte erstellen, indem du das Modul AbstractContent bearbeitest. Der abstrakte Inhalt für jedes Datenobjekt ist ein Eintrag in der Tabelle content, kodiert durch die QID des Datenobjektes.

Der Eintrag ist selbst eine Tabelle, die aus einer Liste von Konstruktoren besteht, die in der angegebenen Reihenfolge gerendert werden sollen. Jeder Konstruktor ist eine Tabelle, deren Felder den Inhalt des Konstruktors angeben. Derzeit gibt es keine festen Richtlinien zum Entwerfen von Konstruktoren, sie sollten jedoch im Allgemeinen so sprachunabhängig wie möglich sein. Typischerweise sollten die Werte der Tabellenfelder Zeichenketten sein, wie QIDs, es gibt jedoch keine strengen Einschränkungen, da Felder auch numerische oder boolesche Werte enthalten können. Insbesondere können Werte von Feldern Unterkonstruktoren, d. h. Tabellen, sein.

Die einzige Voraussetzung für Konstruktoren ist, dass sie ein Feld _predicate enthalten. Dieses Feld sollte eine Zeichenkette enthalten, die den Konstruktortyp identifiziert. Beim Rendern des Konstruktors wird nach einem entsprechenden Renderer mit demselben Namen gesucht.

Beachte, dass es auch möglich ist, innerhalb des Konstruktor-Moduls Lua-Code zu schreiben, wodurch automatisch Konstruktoren für bestimmte Arten von Datenobjekten erstellt werden. Die Logik zur Auswahl des richtigen Konstruktortyps sollte in der Funktion Constructors erfolgen. Der Rückgabewert sollte eine Liste von Konstruktoren sein (möglicherweise bestehend aus einem einzelnen), wie oben angegeben.

Erstellen neuer Renderer

Unabhängig davon, ob du Renderer zu einer Sprache hinzufügst, für die bereits einige definiert sind, oder ein neues Renderer-Modul für eine neue Sprache erstellst, sollten sich die Renderer in einem Modul mit dem Namen Module:Sandbox/AbstractWikipedia/Renderers/xx befinden, wobei xx für den entsprechenden Sprachcode steht. Das sprachneutrale Modul Module:Sandbox/AbstractWikipedia/Renderers definiert einige allgemeine Hilfsfunktionen für Auswertungsrenderer.

Die sprachspezifischen Renderer-Module exportieren eine Tabelle (normalerweise p genannt), von der jedes Feld ein Renderer für einen bestimmten Konstruktortyp ist, mit dem es den Namen teilt (z. B. soll der Renderer Person einen Konstruktor Person verbalisieren). Jeder Renderer besteht aus einer Reihe von Vorlagengruppen (unten erklärt), von denen eine main heißen muss. Wenn ein Renderer verwendet wird (normalerweise, weil ein Konstruktor des entsprechenden Typs verbalisiert werden muss), wird die Vorlagengruppe main verbalisiert. Die anderen Vorlagengruppen können als Untervorlagen-Interpolationen innerhalb der Vorlagen main und untereinander verwendet werden.

Eine Vorlagengruppe ist im Grunde eine Liste von Variantenvorlagen. Wenn die Vorlagengruppe ausgewertet wird, wird die erste Vorlagenvariante in der Liste, deren Vorbedingungen gelten, ausgewählt und verbalisiert. Wenn keine solche Vorlagenvariante gefunden wird, wird eine leere Vorlage zurückgegeben, die zu einer leeren Verbalisierung führt.

Jede Variantenvorlage ist eine Tabelle mit drei Feldern:

  • Das Pflichtfeld template enthält die eigentliche Vorlage, die unter Verwendung der Syntax der Vorlagensprache (mit einigen unten erläuterten Ergänzungen) verbalisiert werden soll.
  • Ein optionales Feld validity listet die Felder des Konstruktors auf, die vorhanden sein müssen, damit diese Variante realisiert werden kann. Beachte, dass alle Vorlagen in einem Renderer Zugriff auf die Felder des realisierten Konstruktors haben (verwendbar als Interpolationsargumente).
  • Ein optionales Feld roles listet die möglichen grammatikalischen Rollen auf, zu denen diese Variante passt. Im Wesentlichen handelt es sich dabei um eine Bedingung für die Abhängigkeitsbezeichnung, die zusammen mit dem Aufruf der Untervorlage oder des entsprechenden Konstruktors verwendet wird. Nur wenn diese Bezeichnung zu einer der in der Liste roles aufgeführten Bezeichnungen passt, wird die Vorlage ausgewertet. Eine leere Zeichenkette "" kann auf das Fehlen einer Bezeichnung hinweisen.
  • In Zukunft kann ein zusätzliches Feld conditions hinzugefügt werden, um die Auswertung beliebiger bedingter Ausdrücke in den Feldern des Konstruktors zu ermöglichen.

Unterschiede zur kanonischen Vorlagensyntax

Wie oben erwähnt, ermöglicht die Implementierung eine leicht erweiterte Syntax im Vergleich zu der im Dokument Syntax der Vorlagensprache angegebenen. Diese Erweiterungen umfassen Folgendes:

  • LIDs (ein L gefolgt von Ziffern) und QIDs (ein Q gefolgt von Ziffern) können verwendet werden, ohne dass sie zitiert werden. Wenn sie als Argumente für eine Funktion dienen, werden sie in Zeichenketten umgewandelt. Wenn sie allein in Lücken erscheinen (entweder als Literale oder als interpolierte Argumente), fungieren sie als Kurznotationen für den Aufruf der Vorlagenfunktionen Lexeme(L-id) bzw. Label(Q-id).
  • In ähnlicher Weise fungieren Zahlen, die allein in Lücken erscheinen (entweder als Literale oder als interpolierte Argumente), als Abkürzung für den Aufruf der Vorlagenfunktion Cardinal(number).
  • Interpolationsargumente können Zeichenketten enthalten, die als Vorlagen interpretiert werden können (da sie eine Lücke enthalten: etwas Text, der von { } eingeschlossen ist). In diesem Fall wird das Interpolationsargument als Untervorlage ausgewertet (die Zugriff auf dieselben Argumente wie die aufrufende Vorlage hat).
  • Ebenso können Interpolationsargumente Vorlagengruppen enthalten (wie oben definiert). Dies geschieht insbesondere im Rahmen der Definition von Renderern. In diesem Fall besteht die Interpolation aus der Auswahl der entsprechenden Vorlagenvariante und der Auswertung ihrer Vorlage.
  • Die Erweiterung um Konditionalfunktionen wurde in diesem Prototyp nicht implementiert.
  • Da die Vorlagen im Lua-Code definiert sind, können im Anschluss Kommentare mit der Standard-Lua-Kommentar-Syntax hinzugefügt werden.

Implementierung neuer Beziehungsfunktionen

Beim Erstellen von Inhalten für eine neue Sprache ist es wahrscheinlich, dass neue Abhängigkeitsbeziehungen definiert oder bestehende Definitionen geändert werden müssen. Damit eine in den Vorlagen verwendete Abhängigkeitsbeziehung Auswirkungen auf die Verbalisierung hat, muss eine entsprechende Beziehungsfunktion definiert sein. Eine solche Funktion kann entweder für alle Sprachen in Module:Sandbox/AbstractWikipedia/Relations oder für eine bestimmte Sprache in Module:Sandbox/AbstractWikipedia/Relations/xx definiert werden, wobei xx für den Sprachcode steht.

Generell empfiehlt es sich, Beziehungen möglichst sprachunabhängig zu definieren. Wir wissen zum Beispiel, dass es in vielen Sprachen eine Subjekt-Verb-Übereinstimmung gibt, aber die genauen Funktionen, die übereinstimmen, können von Sprache zu Sprache unterschiedlich sein. Anstatt für jede Funktionskombination eine sprachspezifische Implementierung zu definieren, können wir eine sprachunabhängige subj-Beziehungsfunktion definieren, die eine Übereinstimmung für die Funktionen person, number und gender erzwingt und dem Subjekt einen nominative-Fall zuweist. Eine solche Funktion würde sogar für Sprachen funktionieren, die nur eine Teilmenge dieser Übereinstimmungsfunktionen aufweisen oder keine Fallmorphologie aufweisen, da die irrelevanten Operationen zu No-Op-Operationen werden. In manchen Fällen ist jedoch eine sprachspezifische Implementierung erforderlich; wenn wir beispielsweise einem Subjekt den Fall ergative zuweisen möchten, würde dies eine separate, sprachspezifische Implementierung der Beziehungsfunktion erfordern. (Es könnte auch möglich sein, Sprachen in einer Sprachhierarchie zu gruppieren, in der ähnliche Sprachen die Implementierungen teilen, dies wurde jedoch im Prototyp nicht durchgeführt.)

Damit die NLG-Pipeline ordnungsgemäß funktioniert, sollten die Beziehungsfunktionen immer mit zwei Eingabeargumenten definiert werden, source und target, die den Lücken entsprechen, in denen die Beziehung ausgeführt wird. Darüber hinaus sollte der Hauptteil der Funktion grundsätzlich nur vier Operationen verwenden, um sicherzustellen, dass die Reihenfolge der Anwendung der Beziehungen unerheblich ist (das Präfix l bezieht sich auf das Lexem-Modul):

  • verifyPos(slot, part-of-speech): Dies ermöglicht die Plausibilitätsprüfung, ob die relevante Lücke ein Lexem mit einer Wortart ist, die mit dem gegebenen vereinbar ist. Allerdings ist diese Prüfung in der Praxis häufig zu restriktiv und kann daher vermieden werden. Grundsätzlich könnten die Wortarten in einer Typhierarchie angeordnet werden, was eine flexiblere Typprüfung ermöglichen würde, dies wurde jedoch im Prototyp noch nicht implementiert.
  • l.unifyFeatures(category-to-unify, source, target): Vereinheitlicht die Funktionen der angegebenen Kategorie der Quell- und Ziel-Lücken.
  • l.unifyFeatures(source-category-to-unify, source, target, target-category-to-unify): Vereinheitlicht die Funktion der Quellkategorie und die Funktion der Zielkategorie.
  • l.unifyWithFeature(category, slot, feature): Vereinheitlicht die in der Lückenkategorie angegebene Funktion mit der übergebenen Funktion. Wenn die Lücke noch keine solche Kategorie hat, bedeutet dies, dass die neue Funktion der Kategorie der jeweiligen Lücke zugewiesen wird.

Beachte, dass jede dieser Funktionen fehlschlagen kann, wenn die angegebenen Funktionen nicht vereinbar sind, was zum Fehlschlagen der gesamten Realisierung (mit einer entsprechenden Fehlermeldung) führen kann.

Implementierung neuer Funktionen zur Nutzung in Vorlagen

Ein wichtiger Teil der Vorlagensprache ist die Fähigkeit, Funktionen innerhalb von Lücken aufzurufen, die eine variable Anzahl von Argumenten annehmen. Sprachunabhängige Implementierungen dieser Funktionen werden im Modul Module:Sandbox/AbstractWikipedia/Functions definiert, während sprachspezifische Implementierungen in Module:Sandbox/AbstractWikipedia/Functions/xx definiert werden, wobei xx für den Sprachcode steht. Die Funktionen werden wie alle anderen exportierten Scribunto-Modulfunktionen definiert.

Funktionen, die auf Lückenebene aufgerufen werden (im Gegensatz zu denen, die als Argumente für andere Funktionen aufgerufen werden), müssen ein einzelnes Lexem oder eine Liste von Lexemen zurückgeben. Um den Datentyp lexeme einfach zu erstellen, ist das Modul Lexeme praktisch (importiert als l). Viele der definierten Funktionen extrahieren Daten aus Wikidata; zu diesem Zweck können sie das Hilfsmodul Wikidata (importiert als wd) verwenden.

Im Allgemeinen werden sprachspezifische Implementierungen benötigt, wenn eine Funktion auf ein sprachspezifisches Wikidata-Lexem zugreifen oder einige sprachspezifische Phänomene modellieren muss. Wann immer möglich ist es besser, sprachunabhängige Funktionen zu schreiben. Beachte, dass diese als Rückfall-Implementierungen dienen können, die dann durch eine sprachspezifische Implementierung überschrieben werden können.

Einige der definierten Funktionen werden vom System benötigt, um ordnungsgemäß zu funktionieren: Dies sind die Funktionen TemplateText, Lexeme, Label und Cardinal, die implizit für bestimmte Elemente der Vorlagensprache aufgerufen werden, wie oben erläutert.

Schreiben von Funktionen als Untervorlagen

Im Allgemeinen erfordert das Schreiben von Funktionen in diesen Modulen gewisse Programmierkenntnisse in Lua. Es ist jedoch möglich und ratsam, Funktionen zu definieren, die lediglich Auswertungen von Untervorlagen sind, bei denen die Funktionsargumente an Vorlagenargumente gebunden sind. Als Beispiel wird hier wiederholt, wie die Funktion QuantifiedNoun definiert ist:

 function p.QuantifiedNoun(num, noun)
   return te.evaluateTemplate("{nummod:Cardinal(num)} {root:noun}", { num = num, noun = noun})
 end

Die zu verwendende Untervorlage wird als erstes Argument an die Funktion evaluateTemplate übergeben (das Präfix te steht für das Modul TemplateEvaluater, importiert im Funktionsmodul). Diese Untervorlage folgt der normalen Syntax der Vorlagensprache, einschließlich der Verwendung von Lücken, Beziehungen, Funktionsaufrufen und Interpolationsargumenten. Die so definierte Vorlage hat jedoch nur Zugriff auf die Interpolationsargumente, die in der Tabelle definiert sind, die als zweites Argument an evaluateTemplate übergeben wird (im Beispiel { num = num, noun = noun}). Wie du siehst, handelt es sich dabei lediglich um Bindungen der Funktionsargumente an Namen von Interpolationsargumenten (normalerweise mit demselben Namen, dies ist jedoch nicht erforderlich). Wenn du diese Art der Funktionsdefinition verwendest, musst du nicht wirklich wissen, wie man in Lua programmiert, da du deine Funktion größtenteils mithilfe der Syntax der Vorlagensprache definieren kannst.