Abstracts
Große Sprachmodelle (Large Language Models, LLMs) haben weltweit großes Interesse geweckt und ermöglichen viele bisher schwer fassbare KI-Anwendungen. LLMs werden durch sehr ausdrucksstarke textuelle Aufforderungen gesteuert und geben textuelle Antworten zurück. Dieser unstrukturierte Text der Eingabe und Ausgabe macht LLM-basierte Anwendungen jedoch anfällig. Dies hat zur Entwicklung von Prompting-Frameworks geführt, die darauf abzielen, die Interaktion von LLMs mit der Außenwelt zu regulieren. Bestehende Hinting-Frameworks haben jedoch entweder eine hohe Lernkurve oder entziehen den Entwicklern die Kontrolle über präzise Hinweise. PDL ist eine einfache deklarative, datengesteuerte Sprache, die auf YAML basiert und das Prompting in den Mittelpunkt stellt. PDL funktioniert gut mit verschiedenen LLM-Plattformen und LLMs, unterstützt das Schreiben von interaktiven Anwendungen, die LLMs und Tools aufrufen, und ist einfach zu implementieren, z. B. Chatbots, RAGs oder Agenten, darunter gängigen Anwendungsfällen wie Chatbots, RAGs oder Proxys. Wir hoffen, dass die PDL die Prompt-Programmierung einfacher, robuster und angenehmer machen wird.
1. einleitung
Große Sprachmodelle (Large Language Models, LLMs) haben große Fortschritte gemacht und gezeigt, dass sie eine Vielzahl nützlicher Aufgaben erfüllen können. Da LLMs durch natürlichsprachliche Hinweise gesteuert werden, hat sich das Cue-Engineering zu einer Ad-hoc-Methode zur Verbesserung der Genauigkeit entwickelt (White et al.2023). Lernen durch Hinweisschemata, wie z. B. kontextuelles Lernen (Brown et al.2020), mehrere LLM-Aufrufketten (Chase et al.2022), erweiterte Generation (RAG) (Lewis et al.2020), Werkzeuggebrauch (Schick et al.2023), das prozedurale Hilfssprachmodell (PAL) (Gao et al.2023) und Agenzien (Yao et al.2023) kann weitere Funktionen freischalten. Trotz seiner Leistungsfähigkeit ist LLM jedoch immer noch anfällig: manchmal halluziniert er oder hält sich nicht an die erwartete Syntax und die Typen.
Das Cueing Framework (Liu et al.2023) ermöglicht es Entwicklern, LLM und verwandte Hint-Muster einfacher zu nutzen und gleichzeitig ihre Anfälligkeit zu verringern. Einige Frameworks, wie z. B. LangChain (Chase et al.2022) und AutoGen (Wu et al.2023), indem sie spezielle Funktionen für beliebte Muster wie RAGs oder Proxys bereitstellen. Solche Funktionen können den Benutzern jedoch die Kontrolle über grundlegende Prompts entziehen und sie zwingen, viele komplexe Framework-Funktionen zu erlernen. Im Gegensatz dazu bieten Low-Level-Prompting-Frameworks wie Guidance (Microsoft.2023) und LMQL (Beurer-Kellner et al.2023), die durch Syntax und Typen mehr Kontrolle bieten. Sie erfordern jedoch, dass die Benutzer in imperativen Sprachen wie Python oder TypeScript programmieren. Frameworks am anderen Ende des Spektrums, wie z. B. DSPy (Khattab et al.2023) und Vieira (Li et al.2024), um handschriftliche Eingabeaufforderungen durch automatische Generierung von Eingabeaufforderungen ganz zu vermeiden. Leider wird dadurch dem Entwickler die Kontrolle weiter entzogen. Es stellt sich also die Frage, wie die LLM-Programmierung robuster gestaltet werden kann und wie der Entwickler die Kontrolle behalten kann, ohne die Einfachheit zu verlieren.
Um dieses Problem zu lösen, greifen wir auf bewährte Ideen zur Gestaltung von Programmiersprachen zurück. Das Prinzip der Orthogonalität befürwortet die Verwendung einer kleinen und einfachen Menge von Funktionen, die kombiniert werden, um eine leistungsstarke Funktionalität zu erreichen (van Wijngaarden et al.1977). In diesem Zusammenhang bedeutet Orthogonalität, dass Sonderfälle so weit wie möglich vermieden werden. Für Hinting-Frameworks ist Orthogonalität eine Möglichkeit, bestimmte Merkmale zu vermeiden. Wenn die Sprache die Typ- und Rollenprüfung bestehen kann (Hugging Face, die2023) strukturell erzwungen werden, werden die Entwickler weniger mit der Verwundbarkeit zu kämpfen haben. Es bleibt ein hartnäckiges Spannungsverhältnis: Einerseits wollen wir, dass die Entwickler in der Lage sind, präzise Hinweise zu kontrollieren, andererseits brauchen wir eine einfache deklarative Sprache. Aus diesem Grund haben wir uns für eine datenorientierte Sprache entschieden, die absichtlich die Grenze zwischen Prozeduren (z. B. für Ketten und Werkzeuge) und Daten (für Hinweise) verwischt. Diese Inspiration stammt aus dem alten Konzept von Code-als-Daten (McCarthy.1960), und bahnbrechende Arbeiten zur schichtenlosen Programmierung (Cooper et al.2006).
In diesem Beitrag wird die Prompt Declaration Language (PDL) vorgestellt, eine orthogonale und typisierte datenorientierte Sprache. Im Gegensatz zu anderen Prompt-Sprachen, die in imperative Sprachen eingebettet sind, basiert PDL auf YAML (Ben-Kiki et al.2004YAML ist ein Format zur Serialisierung von Daten, das sowohl menschenlesbar (durch eine einfache Syntax für unstrukturierte Zeichenketten) als auch strukturiert (JSON-kompatibel) ist.Variablen in PDL enthalten ebenfalls JSON-Werte und verwenden optional JSON-Schema (Pezoa et al.2016PDL wird derzeit durch einen Interpreter implementiert, der eine dynamische Typüberprüfung durchführt. Ein Vorteil der Repräsentation von Programmen als Daten ist die Einfachheit der Programmtransformation (Mernik et al.2005), z. B. zur Optimierung. Das Rendern von Programmen in einem Datenrepräsentationsformat kann es PDL-Programmen sogar erleichtern, PDL-Programme durch ein großes Sprachmodell zu erzeugen, ähnlich wie bei PAL (Gao et al.2023).
PDL-Programme bestehen aus Blöcken (YAML-Objekten), von denen jeder dem Prompt-Kontext Daten hinzufügt. Diese Denkweise ist ideal für die Verwendung mit Prompting-Technologien wie Chatbots oder Agenten: Die Programmausführung baut implizit den Dialog oder die Trajektorie ohne explizites Pipelining auf. PDL unterstützt sowohl native LLMs als auch LLMs, die von verschiedenen Anbietern gehostet werden, einschließlich, aber nicht beschränkt auf das Open-Source-Modell Granite auf IBM Watsonx1 und Replicate2 (Abdelaziz et al.2024Granite Team, IBM.2024PDL bietet Schleifen und bedingte Kontrollstrukturen sowie Funktions- und Dateieinführungen zur Modularisierung und verwendet Jinja2 (Ronacher.2008) Ausdrücke, um nicht nur Hinweise, sondern ganze Programme vorzugeben.
Dieser Artikel gibt einen Überblick über PDL anhand eines einführenden Beispiels (Abschnitt 2), gefolgt von einer ausführlichen Beschreibung der Sprache (Abschnitt 3). Es beschreibt die Werkzeuge zum Ausführen und Bearbeiten von PDL-Programmen (Abschnitt 4), und bietet Fallstudien zur Demonstration weiterer Anwendungen von PDL (Abschnitt 5). Schließlich wird auf verwandte Arbeiten eingegangen (Abschnitt 6), und inAbschnitt 7Die PDL ist quelloffen und findet sich in der https://github.com/IBM/prompt-declaration-language Es bekommen. Insgesamt ist PDL eine einfache, aber leistungsfähige neue LLM-Sofortprogrammiersprache.
2. übersicht
Dieser Abschnitt gibt einen Überblick über die PDL-Funktionen anhand eines Chatbot-Beispiels. Ein PDL-Programm führt eine Reihe von Klumpen (der Erde)Die von den einzelnen Blöcken erzeugten Daten werden dem Hintergrundkontext zugeführt. Es gibt verschiedene Arten von Blöcken, die auf unterschiedliche Weise Daten erzeugen können: Modellaufrufe, Lesen von Daten aus stdin oder Dateien, direktes Erstellen verschiedener JSON-Daten und Ausführen von Code. Darüber hinaus gibt es eine Vielzahl von Kontrollblöcken (if-then-else, for und repeat), mit denen PDL-Benutzer umfangreiche Datenpipelines und KI-Anwendungen erstellen können.
Karte 1(a) zeigt den PDL-Code für einen einfachen Chatbot. Der read:-Block in den Zeilen 1-4 gibt eine Nachricht aus, in der der Benutzer aufgefordert wird, eine Abfrage einzugeben, und liest diese von stdin. Abb. 1(b) Es wird eine Spur der Ausführung desselben Programms gezeigt. Ein Benutzer könnte zum Beispiel fragen: "Was ist ein Sprachsalat?" . Um Wiederholungen zu vermeiden, fügt die Klausel "attribute: [context]" die Antwort des Benutzers in den Hintergrundkontext ein, nicht aber das Ergebnis (was auf stdout ausgegeben wird).
Der repeat:until:-Block in den Zeilen 5-16 enthält einen verschachtelten text:-Block, der wiederum eine Folge von zwei verschachtelten Blöcken enthält. text:-Blöcke wandeln die Ergebnisse ihrer verschachtelten Blöcke in Zeichenketten um und verketten sie. Der model:-Block in den Zeilen 7-9 ruft ein großes Sprachmodell (LLM) auf, das den aktuellen akkumulierten Kontext als Hinweis verwendet. Bei der ersten Iteration der Schleife besteht der Kontext nur aus zwei Zeilen: "Wie lautet Ihre Anfrage?" und "Was ist ein Sprachsalat?". Der Modellparameter 'stop: [\n\n]' veranlasst den LLM, die Generierung von Token zu beenden, nachdem zwei aufeinanderfolgende Zeilenumbrüche erzeugt wurden. 1(b) zeigt, dass LLM in diesem Beispiel "Ein Sprachsalat ist [...]" erzeugt hat. Der read:-Block in den Zeilen 10-15 gibt die Nachricht in der mehrzeiligen Stringsyntax von YAML aus (beginnend mit einem senkrechten Strich (|)). Dieses Beispiel zeigt, wie PDL die Eingabeaufforderung an die erste Stelle setzt, während sie gleichzeitig gut lesbar ist und dem Entwickler eine präzise Kontrolle ermöglicht. Die Interpreterspur auf der rechten Seite zeigt, dass der Benutzer "Say it as a poem!" eingegeben hat, was in Zeile 10 als Variable question auf der linken Seite definiert und in Zeile 12 an den Kontext angehängt wird. Die until: Klausel in Zeile 16 legt fest, dass der Jinja2-Ausdruck '${Frage == "quit"}'
PDL verwendet die '${...}' Syntax zur Einbettung von Jinja2-Vorlagen anstelle von '{{...}}' anstelle von '{{...}}', da letztere nicht mit den YAML-Sonderzeichen (geschweifte Klammern) kompatibel ist.
Bei der zweiten Iteration der Schleife enthält der Kontext die Auswirkungen der ersten Iteration der Schleife. So sieht die zweite Ausführung des model: Blocks die Ausgabe der ersten Ausführung und kann sie als Gedicht paraphrasieren, "In einer Welt, wo viele Zungen [...]", wie hier gezeigt 1(b). Während der Ausführung des zweiten read:-Blocks dieses Beispiels tippt der Benutzer schließlich "quit", wodurch die Schleife beendet wird. Nachdem wir nun einige gängige PDL-Blöcke (read:, repeat:, text: und model:) in Aktion gesehen haben, können wir mit dem zweiten read: Block fortfahren. 3 Abschnitt, in dem die übrigen Blöcke und Sprachfunktionen beschrieben werden.
(a) Code
- lesen.
Beitrag: [Kontext]
Nachricht: |
Wie lautet Ihre Anfrage?
- wiederholen: | [Kontext] Nachricht: | [Kontext] Nachricht: | Wie lautet Ihre Anfrage?
text.
- Modell: watsonx/ibm/granite-13b-chat-v2
parameter: stop: ["\n\n"]]
stop: ["\n\n"]
- def: frage
lesen: ["\n\n"] def: frage
beitrag: [kontext]
message: |
Geben Sie eine Abfrage ein oder sagen Sie "quit" zum Beenden.
bis: ${Frage == "quit"}
(b) Rückverfolgung von Dolmetschern
Was ist Ihre Anfrage?
Was ist ein Sprachsalat?
Sprachsalat ist ein Begriff, der die Vermischung verschiedener Sprachen und Dialekte in einem einzigen Dialog oder Text beschreibt. Er kann als [...] betrachtet werden.
Geben Sie eine Anfrage ein oder sagen Sie "quit" zum Verlassen.
Ausdruck in Form eines Gedichtes!
In einer Welt der vielen Sprachen.
Sprachsalat wird geboren und wächst in Freude.
Worte verflechten sich und fließen in Harmonie.
Eine bunte Sprache, eine lebendige Blüte.
Geben Sie eine Frage ein oder sagen Sie "quit" zum Verlassen.
Beenden
Abbildung 1: Einfacher Chatbot in PDL
3. die Sprache
Abbildung 2: PDL-Kurzreferenz
PDL ist eine in YAML eingebettete Sprache, so dass jedes PDL-Programm ein gültiges, der PDL-Architektur entsprechendes YAML-Dokument darstellt. Abbildung 2 ist eine Kurzreferenz für die PDL und wird in diesem Abschnitt anhand der Syntaxregeln erläutert. Ein Programm ist ein Satz oder eine Liste von Sätzen, wobei ein Satz ein Ausdruck oder ein strukturierter Satz sein kann, wie die folgenden Syntaxregeln zeigen:
pdl ::= Block | [Block, . . . ,block]
block ::= Ausdruck | structured_block
Alle Syntaxregeln in diesem Abschnitt verwenden die fließende Syntax von YAML (z. B. [block, ...,block]). ...,block]). Derselbe PDL-Code kann z.B. auch in der YAML-Syntax im Blockstil wiedergegeben werden:
- Block
... - Block
Jeder Block enthält einen Blockkörper mit einem Schlüsselwort, das die Art des Blocks angibt (z. B. Modell oder Lesen). Es gibt 15 Arten von Blockkörpern (optionale Felder sind mit Fragezeichen versehen):
block_body ::=
model:expression,input:?pdl,parameters:?
ausdruck
| read:file,message:?
string,nachricht:?
bool
| text:pdl
| lastOf:pdl
| array:pdl
| objekt:pdl
| data:json
| include:datei
| function:args,return:pdl
| aufruf:𝑓,args:args
| if:ausdruck,then:pdl,else:?pdl
| for:args,repeat:𝑝𝑑𝑙,join:?
join
| repeat:pdl,num_iterations:n,join:?
join
| repeat:pdl,until:expression,join:?
join
| code:pdl,lang:string
Die Blöcke model: und read: haben wir bereits im vorigen Abschnitt gesehen. model: Blöcke rufen das große Sprachmodell auf. Die Eingabeaufforderungen werden aus dem aktuellen Kontext übernommen, es sei denn, das optionale Feld input: ist angegeben. Das optionale Feld parameters: wird verwendet, um das Inferenzverhalten des Modells zu konfigurieren. read: Block liest Eingaben aus einer Datei oder von der Standardeingabe, wenn kein Dateiname angegeben ist. Das optionale Feld message: dient dazu, dem Benutzer eine Nachricht anzuzeigen, das optionale Feld multiline: bestimmt, ob bei Zeilenumbrüchen angehalten werden soll.
Die fünf Blocktypen zur Erstellung von Daten sind: text:, lastOf:, array:, object: und data:. Abbildung 2 Sie werden in einem einfachen Beispiel gezeigt. Der Unterschied zwischen dem object:-Block und dem data:-Block besteht darin, dass der PDL-Interpreter das PDL-Schlüsselwort im data:-Block ignoriert und es wie ein normales JSON-Feld behandelt.
Aus Gründen der Modularität unterstützt PDL include: Blöcke und Funktionen. include: Blöcke öffnen PDL-Programme an einem angegebenen relativen Pfad und fügen ihre Ausgabe an der Stelle ein, an der sie erscheint. Die Syntax für Funktionsargumente ist wie folgt:
args::={x:exp ression,... ,x:ausdruck sion}
Jeder x:expressi on ordnet Parameternamen Typspezifikationen (in der Funktion: Definition) oder Werten (im Funktionsaufruf:) zu. return: Schlüsselwort liefert den Körper der Funktion, der verschachtelte Blöcke enthalten kann; Abbildung 2 Ein einfaches Beispiel für einen Jinja2-Ausdruck wird gezeigt. Das optionale Schlüsselwort pdl_context: setzt den Kontext während eines Aufrufs zurück, z.B. auf den leeren Kontext [].
Es gibt drei Arten von Kontrollblöcken: if:, for:, und verschiedene repeat: Formen. Sie können verschachtelte Blöcke oder einfache Ausdrücke enthalten; wenn sie eine Liste von Blöcken enthalten, verhält sich die Liste standardmäßig als lastOf:. Wenn Sie das lastOf:-Verhalten nicht wünschen, besteht ein gängiger Ansatz darin, den Schleifenkörper in einem text:-Block zu kapseln oder die Ergebnisse von Schleifeniterationen mit dem join:-Schlüsselwort zusammenzufassen:
join::=as:? (text∣array∣lastOf),mit:? String
Die oben genannten 15 Blöcke können in Kombination mit null oder mehr optionalen Schlüsselwörtern verwendet werden, die für jeden Block gelten:
strukturiert _block::=
{ block_body.
Beschreibung:?
string, def:?
def:?
x, defs:?
defs:?
defs,
def:?x, defs:?defs, Rolle:?
defs:?
defs:?
beitragen, parser:?parser, parser:?parser, parser
parser:?
spec:?
type }
description: ist ein spezieller Kommentar. def: weist das Ergebnis eines Blocks einer Variablen zu; Abbildung 1 Es gibt bereits ein Beispiel in Zeile 10 der PDL. Im Gegensatz dazu erstellt defs: mehrere Variablendefinitionen, jede mit einem eigenen Namen, x, und weist Werte durch eine verschachtelte PDL-Prozedur zu:
defs::={x:pdl ,...,x:pdl}
Die PDL-Aufrufe an das Chat-Modell folgen der üblichen Praxis moderner Chat-APIs, indem sie Paare von Sequenzen ({content:str, role:str}) und keinen reinen Text als Eingabeaufforderung übergeben. Die Modell-API wendet dann die modellspezifische Chat-Vorlage an und flacht die Sequenz durch Einfügen geeigneter Kontroll-Tags ab, wodurch das PDL-Programm eine gewisse Modellunabhängigkeit erhält. Wenn der Block nicht explizit die Rolle: angibt, wird der Modellblock standardmäßig auf "Assistent" und die anderen Blöcke standardmäßig auf "Benutzer" gesetzt. Die Rollen der eingebetteten Blöcke sind mit denen der äußeren Blöcke konsistent. In der zukünftigen Forschung planen wir auch die Implementierung von erlaubnisbasierten Sicherheitsmechanismen unter Verwendung von Rollen.
Das Schlüsselwort contribute: kann verwendet werden, um eine (möglicherweise leere) Teilmenge für zwei Ziele anzugeben: "Ergebnis" oder "Kontext". Standardmäßig trägt jedes Modul zu seinem eigenen Ergebnis und zum Hintergrundkontext bei, der für nachfolgende Aufrufe des Large Language Model (LLM) verwendet wird. Abbildung 1 Zeile 2 zeigt ein Beispiel für die Beschränkung der Modulbeiträge auf den Hintergrundkontext, um die Ausgabe zu vereinfachen.
Mit dem Schlüsselwort parser: kann ein Modul, das normalerweise nur flache Strings erzeugt (z. B. ein LLM-Aufruf), strukturierte Daten erzeugen. Zu den unterstützten Parsern gehören json, yaml, regex und jsonl. spec: Das Schlüsselwort gibt den Typ an. Die Typen für PDL sind eine Untermenge von JSON Schema (Pezoa et al. 2016), Abb. 2 Eine kurze Demonstration der gebräuchlichen Kurzsyntax finden Sie in. Der Typ "{Fragen: [str], Antworten: [str]}" ist beispielsweise ein Objekt mit zwei Feldern für Fragen und Antworten, die beide Arrays von Zeichenketten enthalten. Das erste 5 Abschnitt wird zeigen, wie parser: und spec: zusammenarbeiten. Zukünftige Arbeiten werden diese Schlüsselwörter auch für die Dekodierung von Beschränkungen verwenden (Scholak et al. 2021).
Ein atomarer Block ist ein Ausdruck:
Ausdruck ::= bool | Zahl | String | ${𝑗𝑖𝑛𝑗𝑎_𝑒𝑥𝑝𝑟𝑒 𝑠𝑠𝑖𝑜𝑛} | string_expression
Ausdrücke können Basiswerte sein, Jinja2-Ausdrücke (Ronacher. 2008Jinja2 ist eine bequeme Art, Vorlagen für Hinweise zu spezifizieren, wobei Teile des Hinweises fest kodiert sind und andere Teile durch Ausdrücke ausgefüllt werden. Die PDL erweitert jedoch die Verwendung von Jinja2, indem sie es Entwicklern ermöglicht, nicht nur einzelne Hinweise, sondern ganze Modellaufrufketten und andere Module als Vorlage zu verwenden. Obwohl wir empfehlen, dass der Leser in der Jinja2-Dokumentation eine vollständige Liste der möglichen Ausdrücke nachschlägt, zeigt Abbildung 2 Die PDL verwendet nur Jinja2-Ausdrücke, ohne Jinja2-Anweisungen wie {% wenn ... %}
vielleicht {% für ... %}
weil diese sich bereits mit den if: und for: Funktionen von PDL überschneiden.
Nicht zuletzt verfügt PDL über ein Code:-Modul, das die Ausführung von Code in einer bestimmten Programmiersprache ermöglicht (zum Zeitpunkt der Erstellung dieses Dokuments wird nur Python unterstützt). Der nächste Abschnitt beschreibt die PDL-Werkzeuge, einschließlich des Interpreters, der eine Sandbox-Funktion bietet, um das Risiko der Ausführung von beliebigem Code zu verringern. Weitere Informationen finden Sie unter dem Link zum Tutorial im GitHub-Repository von PDL.
4. werkzeuge
Die PDL bietet Werkzeuge, mit denen PDL-Programme leicht zu schreiben, auszuführen und zu verstehen sind.
Erstens: Die PDL Dolmetscher ist eine Ausführungsmaschine mit einer Befehlszeilenschnittstelle, wie man sie von einer Skriptsprache erwarten würde. Der Interpreter unterstützt den Streaming-Modus, bei dem die LLM-Ausgabe nach und nach sichtbar wird, während sie generiert wird, und somit ein interaktiveres Chat-Erlebnis bietet. Der Interpreter unterstützt auch Sandboxing, wodurch er in einem Container gestartet werden kann, was bei der Ausführung von LLM-generierten Aktionen oder Code empfohlen wird.
PDL IDE-Unterstützung VSCode wurde verbessert, um das Schreiben von PDL-Code durch Syntaxhervorhebung, Autovervollständigung, Tooltips für PDL-Schlüsselwörter und Fehlerprüfung zu erleichtern. Diese Funktionen werden zum Teil durch den PDL-Metamodus, das JSON-Schema, das eine gültige PDL definiert, gesteuert.
%%pdl
Einheit der Magie Mit Jupyter Notebooks können Entwickler Codeeinheiten direkt in PDL schreiben. Auf diese Weise kann die gehostete Notebook-Plattform als einfache Spielwiese für die interaktive Erkundung von Eingabeaufforderungen genutzt werden. Bei mehreren PDL-Codeeinheiten im selben Notizbuch können spätere Einheiten Variablen verwenden, die in früheren Einheiten definiert wurden. Außerdem wird der Hintergrundkontext der späteren Einheit von der früheren Einheit übernommen; wenn dies nicht erwünscht ist, können Entwickler die %%pdl --reset-context
um dieses Verhalten außer Kraft zu setzen.
PDL Dokument-Visualisierung in Echtzeit Eine spezifische Spur der Ausführung des PDL-Programms wird in Form von farbigen, verschachtelten Kästchen angezeigt, ähnlich der typischen Grafik, die man in einer Dissertation oder einem Blogbeitrag über LLM-Tipps findet. Der Benutzer kann dann eines der Kästchen auswählen, um den entsprechenden PDL-Code anzuzeigen, ähnlich wie eine Tabellenkalkulationszelle, die Daten anzeigt, aber der Benutzer kann sie auswählen, um die Formel zu untersuchen, die diese Daten erzeugt hat. Diese Echtzeitansicht ermöglicht es dem Benutzer, die spezifischen Daten schnell zu verstehen und dann zum Verständnis des Codes überzugehen, der diese Daten erzeugt hat.
Schließlich hat die PDL eine SDK(Software Development Kit), eine kleine Python-Bibliothek zum Aufrufen von PDLs aus Python heraus, die nützlich ist, um größere Python-Anwendungen um hint-basierte Prozeduren wie Agenten zu erweitern. Wie in Abschnitt 3 Wie in Abschnitt besprochen, können PDL-Dateien Python in Codeblöcken enthalten. Bei der Entwicklung größerer Anwendungen mit PDL hat es sich als nützlich erwiesen, diesen Code auf wenige Zeilen zu beschränken, indem die Funktion in einer separaten Python-Datei definiert und dann aus PDL aufgerufen wird. Es hat sich bewährt, Daten zwischen PDL und Python in Form von JSON-Objekten zu übergeben. Optional können Sie die PDL-Funktion spez.
Schlüsselwort, und TypedDict oder Pydantic auf der Python-Seite für die Typüberprüfung, wie im nächsten Abschnitt gezeigt. 3 Abgebildet.
(a) PDL-Codes
1Text.
2- lang: python
3 code: |
4 importieren rag_mbpp
5 PDL_SESSION.mbpp = rag_mbpp.initialise()
6 ergebnis = ""
7- defs.
8 test_query: >-
9 Schreiben Sie eine Python-Funktion, die das erste und letzte Vorkommen eines bestimmten Zeichens aus einer Zeichenkette entfernt.
12 abgerufen.
13 lang: python
14 spec: [{Abfrage: str, Antwort: str}]
15 code: |
16 importiere rag_mbpp
17 result = rag_mbpp.retrieve(
18 PDL_SESSION.mbpp, "${test_query}", 5
19 )
20 text: >
21 Gegebener Text nach "Q:", erzeugen Sie eine Python-Funktion nach "A:".
24 Hier sind einige Beispiele, beende das letzte:
25- for.
26 few_shot_sample: ${retrieved}
27 wiederholen: |
28 Q: ${wenige_Schussprobe.abfrage}
29 A: '''${einzelne_Schussprobe.Antwort}''''
30- |-
31 F: ${test_query}
32 A.
33- Modell: watsonx/ibm/granite-3-8b-instruct
34 Parameter.
35 stop: ["Q:", "A:"]
(b) Python-Code
1vom Typing importieren TypedDict
2importiere Datensätze
3from sklearn.feature_extraction.text \
4importieren TfidfVectorizer
5
6def initialize().
7 train_in = datasets.load_dataset(
8 "mbpp", "sanitised", split="train"
9 )
10 corpus = [row["prompt"] for row in train_in]
11 tfidf = TfidfVectorizer().fit(corpus)
12 def embed(text).
13 sparse_result = tfidf.transform(
14 raw_documents=[text]
15 )
16 return sparse_result.toarray().flatten()
17 train_em = train_in.map(
18 lambda row: {"em": embed(row["prompt"])}
19 )
20 vec_db = train_em.add_faiss_index("em")
21 return vec_db, embed
22
23 QA = TypedDict("QA", {"Anfrage":str, "Antwort":str})
24def retrieve(mbpp, query, n: int) -> list[QA].
25 vec_db, embed = mbpp
26 key = embed(Abfrage)
27 nearest = vec_db.get_nearest_examples(
28 "em", schlüssel, n
29 )
30 abfragen = nearest.examples["prompt"]
31 answers = nearest.examples["code"]
32 return [
33 {"abfrage": q, "antwort": a}
34 for q, a in zip(abfragen, antworten)
35 ]
Abbildung 3. RAG Beispiel in PDL
5. fallstudien
Wir sind in der ersten 2 Ein einfaches Beispiel für einen PDL-Chatbot wurde in diesem Abschnitt gezeigt. In diesem Abschnitt werden einige etwas komplexere Anwendungsfälle für PDL gezeigt: RAGs, Proxys und die Erzeugung von PDLs aus PDLs.
5.1 Abrufen der Erweiterungserzeugung
Abruf der erweiterten Generation, oder RAGdas zunächst den relevanten Kontext abruft und ihn dann zu den Aufforderungen des Modells hinzufügt, um Antworten zu generieren (Lewis et al. 2020). Abb. 3(a) Zeigt ein PDL-Programm, das RAG verwendet, um eine kleine Anzahl von Beispielen für eine Codegenerierungsaufgabe abzurufen. Code: Die Zeilen 2-6 verwenden Python, um eine Vektordatenbank mit Trainingssplits für den MBPP-Datensatz von "mostly basic Python programs" (Austin et al., 2008) zu initialisieren. 2021). Sie verwendet den Graphen 3Python-Funktion, die in (b) definiert ist, und eine spezielle PDL_SESSION-Variable, die es ermöglicht, den Zustand an spätere Codeblöcke zu übergeben. Abbildung 3In den Zeilen 8-11 von (a) wird die Variable test_query mit der natürlichsprachlichen Anfrage initialisiert, die den Python-Code erzeugt hat. In den Zeilen 12-19 wird die Variable retrieved mit den fünf ähnlichsten Beispielen aus den Trainingsdaten initialisiert.
Die Zeilen 20-24 fügen dem Kontext Anweisungen hinzu, die Zeilen 25-29 fügen dem Kontext eine Handvoll Beispiele hinzu, und die Zeilen 30-32 fügen dem Kontext Testabfragen hinzu. Die for:-Schleife in Zeile 25 ist eine gängige Methode zur Erzeugung von Daten mit PDL, in diesem Fall für das Kontextlernen. Schließlich rufen die Zeilen 33-35 ein Granite-3-Modell auf (Granite Team, IBM 2024), wobei der kumulative Kontext verwendet wird, der es veranlasst, Python-Funktionen zu erzeugen, die Abfrageanfragen testen. Obwohl dies ein einfaches Beispiel ist, verwenden wir PDL auch mit Codellm-Devkit (Krishna et al. 2024) wird in Verbindung mit einem Werkzeug verwendet, das Quellcode aus einer Vielzahl von Programmiersprachen statisch analysiert, um weitere relevante Zusammenhänge zu ermitteln, wenn der LLM zu Codierungsaufgaben aufgefordert wird.
(a) Code
1Text.
2- lesen: react_few_shot_samples.txt
3- |
4- lesen: react_few_shot_samples.txt
5 Wann wurde der Entdecker des Hudson River geboren?
6- wiederhole.
7 - Text.
8 - def: gedacht
9 modell: watsonx/ibm/granit-34b-code-instruct
10 Parameter.
11 stop: ["Act:"]
12 include_stop_sequence: true
13 - def: Handlung
14 model: watsonx/ibm/granite-34b-code-instruct
15 Parameter.
16 stop: ["\n"]
17 parser: json
18 spec: {Name: str, Argumente: {Thema: str}}
19 - def: Beobachtung
20 if: ${ action.name == "Suche" }
21 then.
22 text.
23 - "Beobachtung: "
24 - lang: python
25 code: |
26 import wikipedia
27 abfrage = "${ action.arguments.topic }"
28 ergebnis = wikipedia.summary(abfrage)
29 bis: ${ action.name ! = "Suche" }
(b) Rückverfolgung von Dolmetschern
Wie groß ist der Höhenbereich der östlichen Region der Colorado-Orogenese?
Tho: Ich muss die Colorado-Orogenese durchsuchen, um die Reichweite der östlichen Region herauszufinden.
Act: {"name": "Suche", "Argumente": {"Thema ": "Colorado-Orogenese"}}
Obs: Die Colorado-Bergbaukampagne ist ein Ereignis [...]
[...]
Wann wurde der Entdecker des Hudson River geboren?
Tho: Ich muss nach dem Entdecker des Hudson River suchen, um herauszufinden, wann er geboren wurde.
Act: {"name": "Suche", "Argumente": {"Thema ": "Entdecker des Hudson River"}}
Obs: Der Hudson River ist ein 315 Meilen langes [...]...
Tho: Der Entdecker des Hudson River war Henry Hudson Ich muss nach Henry Hudson suchen, um herauszufinden, wann er geboren wurde.
Act: {"name": "Suche", "Argumente": {"Thema ": "Henry Hudson"}}
Obs: Henry Hudson (ca. 1565 - verschwunden [...])
Tho: Henry Hudson wurde im Jahr 1565 geboren. act: {"name": "Finish", "arguments": { "Thema": "1565"}}
Abbildung 4. ReAct in einer verantwortungsvollen Position für jemanden handeln
5.2.ReAct Agent
Basierend auf einem umfangreichen Sprachmodell in einer verantwortungsvollen Position für jemanden handeln Ermöglicht die Auswahl und Konfiguration umfangreicher Sprachmodelle Bewegungin Matrix in dem diese Aktionen ausgeführt werden, und die Ausgabe der Aktionen wird an das große Sprachmodell zurückgegeben als beachten Sie. Es gibt verschiedene Modelle für solche Mittel, wie z. B. ReAct (Yao et al. 2023) und ReWOO (Xu et al. 2023). Die Aktionen basieren auf einem umfangreichen Sprachmodell Werkzeug Aufruf (Schick et al. 2023), während Agenten mehrere Tool-Aufrufe in einer dynamischen, umfangreichen Sprachmodell-Bootstrap-Sequenz aneinanderreihen. Ziel ist es, KI-basierte Anwendungen weniger präskriptiv und stärker zielorientiert zu gestalten. Darüber hinaus kann der Agent Beobachtungen als Feedback nutzen, um sich zu erholen, wenn Aktionen schief gehen.
Karte 4 Das Herzstück von ReAct ist eine Think-Act-Observe-Schleife, die im Code als Variablendefinitionen für Think (Zeile 8), Act (Zeile 13) und Observe (Zeile 19) dargestellt wird. Denken ist die natürliche Sprache der Modellerzeugung, z.B. in Abbildung 4Ich muss nach dem Entdecker des Hudson River suchen, um herauszufinden, wann er geboren wurde" in der Interpreterspur von (b). Die Aktion ist das JSON, das vom Modell erzeugt wird, um die Werkzeuge des Granite-Modells mit den Trainingsdaten abzustimmen (Abdelaziz et al. 2024). Die Zeilen 17 und 18 auf der linken Seite stellen sicher, dass die Ausgabe des großen Sprachmodells als JSON geparst wird und dem Schema {Name, Argumente} entspricht, während die Interpreterspur auf der rechten Seite zeigt, dass das Modell tatsächlich solche Objekte erzeugt. Dadurch ist es möglich, mit Jinja2 auf Felder des Objekts zuzugreifen, wie z. B. ${ action.arguments.topic } in Zeile 27. Beobachtungen werden von der Umgebung erzeugt, in diesem Fall vom Python-Code, der Wikipedia aufruft. Wie in Zeile 27 gezeigt, wird die 4 Wie in Abschnitt erörtert, empfehlen wir für Fälle, in denen (teilweise) aus einem großen Sprachmodell generierter Code ausgeführt werden soll, die Verwendung der Sandboxing-Funktionen von PDL.
Karte 4Die Interpreterspur in (b) zeigt, dass diese Ausführung zwei Iterationen der Agentenschleife umfasst. Obwohl es sich hier um ein einfaches Beispiel handelt, haben wir auch einen Agenten zur Codebearbeitung mit der PDL implementiert, der in der SWE-Bench-Lite-Rangliste als Teil des Commit4 verwendet wurde (Jimenez et al. 2024). Die Einreichung löst die erste Instanz von 23,7% nur mit dem Open-Source-Modell, was höher ist als alle bisherigen Ergebnisse mit dem Open-Source-Modell und vergleichbar mit den Ergebnissen des Frontier-Modells.
5.3.Generierung von PDLs aus PDLs unter Verwendung des Big Language Model
In den vorangegangenen Abschnitten wurde gezeigt, wie menschliche Entwickler PDL verwenden können, um verschiedene Cueing-Muster zu kodieren. Dieser Abschnitt wendet sich großen Sprachmodellen zu und zeigt, wie sie auch zur Generierung von PDLs verwendet werden können. Diese Meta-PDL-Generierung ist nützlich, wenn große Sprachmodelle Problemlösungspläne erstellen müssen, z. B. als Teil eines Agentenworkflows. Traditionell bestehen diese Pläne nur aus Text, JSON oder Python-Code. Mit PDL können diese Pläne eine Kombination aus vollständig ausführbaren Modellen und Codeaufrufen sein. In diesem Abschnitt wird die Verwendung der PDL-Meta-Generierung für den GSMHard-Datensatz5 untersucht.
GSMHard ist eine schwierigere Version von GSM8k, die Mathematikaufgaben aus dem Schuljahr enthält, die einfaches arithmetisches oder symbolisches Denken erfordern. GSMHard enthält einen Input, die mathematische Problemstellung, und einen Output, den Python-Code, der das Problem löst. Wir implementierten PAL (Gao et al. 2023), aber anstatt Python-Code zu generieren, wird das große Sprachmodell gebeten, PDL zu generieren. Das Denken in Textketten wird als PDL-Textblöcke dargestellt, während die Arithmetik mit PDL-Codeblöcken durchgeführt wird.
Karte 6 Es wird ein PDL-Programm gezeigt, das PDL-Code erzeugt und ihn im selben Programm ausführt. Die Variable demos enthält eine kleine Anzahl von Beispielen, die dem Modell beibringen sollen, wie man PDL-Code erzeugt. In Zeile 32 verwendet ein Modellaufrufblock diese Beispiele und ein Problem mit freien Variablen als Eingabe. Das Ergebnis ist ein PDL-Programm zur Lösung des Problems. Zeile 38 extrahiert das PDL-Programm und führt es in Python aus. Das Programm wird auf den GSMHard-Datensatz angewendet, wobei das Problem das Eingabeproblem auffüllt.
Bei diesem Experiment stellte sich heraus, dass 10% für den GSMHard-Datensatz tatsächlich falsch war, weil die Grundwahrheit nicht mit den gestellten Fragen übereinstimmte. Abb. 6 Beispiele für solche Inkonsistenzen werden gezeigt. Die Verwendung von PDL half dabei, dies aufzudecken, da der generierte PDL-Code für Menschen lesbar war. So konnten wir leicht nach Datenpunkten suchen, die nicht mit der Grundwahrheit übereinstimmten, und fanden heraus, dass die Grundwahrheit in einigen Fällen falsch war. Wir verwendeten ein großes Sprachmodell, um den gesamten Datensatz abzudecken, und wählten systematisch Beispiele aus, die inkonsistent zu sein schienen. Anschließend überprüften wir die Ergebnisse manuell, um falsch positive Ergebnisse zu entfernen, und identifizierten Datenpunkte aus 10% mit diesem Problem.
1 Definition.
2 Beispiel.
3 Daten.
4 Text.
5 Text.
6 ...
6 ...
8 Problem: Roger hat 5 Tennisbälle.
9 Er hat 2 weitere Dosen Tennisbälle gekauft.
10 Jede Dose enthält 3 Tennisbälle.
11 Wie viele Tennisbälle hat er jetzt?
12 Antwort: Roger hat 5 Tennisbälle.
13 Antwort.
14 ```
15 Text.
16 - "Roger hat mit \n angefangen."
17 - Definition: tennis_balls
18 Daten: 5
19 - "\n Tennisbälle. \n"
20 - "2 Dosen, jede mit 3 Tennisbällen, insgesamt \n"
21 - Sprache: python
22 Definition: gekauft_bälle
23 Code: Ergebnis = 2 * 3
24 - "\n Tennisbälle. \n"
25 - "Das Ergebnis ist: \n"
26 - Sprache: python
27 - Definition: ERGEBNIS
28 Code: result = ${ tennis_balls } + ${ bought_balls }
29 ``
30 Rohdaten: true
31 Text: ``` - Modell: watson``s
32 - Modell: watsonx/meta-llama/llama-3-70b-instruct
33 Definition: PDL
34 Eingabe: wahr
35 Text.
36 - ${ demos.text }
37 - "Frage: ${ Frage }"
38 - Sprache: python
39 Code: |
40 from pdl.pdl import exec_str
41 s = """${ PDL }""""
42 pdl = s.split("````")[1]
43 ergebnis = exec_str(pdl)
44 Definition: ERGEBNIS
Abbildung 5.
James hat beschlossen, 1793815 Sprints pro Woche zu laufen.
Jeder Sprint ist 60 Meter lang.
Wie viele Meter läuft er insgesamt pro Woche?
def lösung():
sprints_per_day = 1793815
Tage_pro_Woche = 3
meter_pro_sprint = 60
total_sprints = sprints_per_day * days_per_week
gesamt_meter = gesamt_sprints * meter_pro_sprint
Ergebnis = gesamt_Meter
gesamt_meter = gesamt_sprints * meter_pro_sprint ergebnis
Abbildung 6: Datenpunkte des GSMHard-Beispielproblems
6. verwandte Arbeiten
Eine aktuelle Studie stellt fest Queue-Rahmen Definiert als eine Schicht, die die Interaktion zwischen dem LLM und Benutzern, Werkzeugen oder anderen Modellen verwaltet, vereinfacht und erleichtert (Liu et al.2023). Die Studie macht deutlich, dass ein großes Manko des Cueing-Rahmens die steile Lernkurve ist.
Das derzeit wohl populärste Prompting-Framework ist LangChain (Chase et al.2022MiniChain zielt vor allem darauf ab, genau diese Komplexität zu vermeiden (Rush.2023), das weniger, einfachere Funktionen bietet, die für fortgeschrittene Anwendungen kombiniert werden können. Allerdings sind sowohl LangChain als auch MiniChain Python-basierte Frameworks, was sie unklarer macht, da Entwickler imperativen Code schreiben müssen.PDL hat eine ähnliche Motivation wie MiniChain, geht aber noch einen Schritt weiter, indem es YAML statt Python als Basis verwendet.
Wie bei anderen Prompting-Frameworks besteht das Ziel von PDL darin, LLM robuster zu machen (Microsoft.2023) ist ein auf Python basierendes Framework, das ein strukturierteres Design bietet, aber auf einer niedrigeren Ebene angesiedelt ist als LangChain. Ähnlich ist LMQL (Beurer-Kellner et al.2023PDL lehnt sich in ihrer Verflechtung von Hinweisen und Programmierung an LMQL an, ist aber im Gegensatz zu LMQL weniger auf imperativen Python-Code angewiesen.Crouse et al. verwenden endliche Zustandsautomaten, um den internen Ablauf verschiedener intelligenter Schleifen formal zu spezifizieren (Crouse et al.2024); dies ist zwar ein faszinierendes Unterfangen, führt aber nicht zu einer ausgefeilten Cueing-Sprache.
Einer der Vorteile von domänenspezifischen Sprachen ist, dass sie Programmtransformationen, z.B. zur Optimierung, durchführen können (Mernik et al.2005Das DSPy Cueing Framework (Khattab et al.2023) hat das Motto "Programming, not hints" (Programmieren, nicht Hinweise): Es generiert Hinweise automatisch, so dass die Entwickler sie nicht manuell schreiben müssen. Ähnlich ist Vieira (Li et al.2024DSPy und Vieira sind beides sehr fortschrittliche Frameworks, aber im Gegensatz zu PDL schwächen beide die Kontrolle des Entwicklers über spezifische Prompts. Lale (Baudart et al.) ist eine Erweiterung von Prolog, die LLM als probabilistische Relation verwendet und automatisch Prompts erzeugt.2021) ist eine Sprache, mit der Benutzer die Kompromisse zwischen Automatisierung und Kontrolle in der KI-Pipeline schrittweise anpassen können, aber sie konzentriert sich nicht auf LLM-Hinweise. dspy, Vieira und Lale optimieren für die Vorhersageleistung, während die andere Optimierung auf die Rechenleistung abzielt. sglang (Zheng et al.2023) erreicht dies durch eine bessere Ausnutzung des Präfix-Caches, was zu mehr Cache-Treffern im KV-Cache führt (Kwon et al.2023). Künftige Arbeiten werden untersuchen, ob die deklarative Natur von PDL ähnliche Optimierungen der Rechenleistung ermöglichen kann.
In jüngster Zeit sind mehrere auf Large Language Modelling (LLM) basierende Prompting-Frameworks entstanden, die sich auf LLM-Agenten konzentrieren: AutoGen (Wu et al.2023) ist ein Multi-Agenten-Framework, bei dem alle Inhalte aus Agenten und Dialogen bestehen. Andere Multi-Agenten-Frameworks sind CrewAI (Moura.2023) und GPTSwarm (Zhuge et al.2024). PDL unterstützt zwar auch Proxies, verfolgt aber einen ausgewogeneren Ansatz und behandelt Proxies als eine von mehreren Cueing-Techniken.
7. schlussfolgerung
PDL ist eine deklarative, datenorientierte Sprache: Programme bestehen aus YAML-Blöcken, von denen jeder entweder ein Literal oder generative Daten enthält. Das Denkmodell besteht darin, einen Block auszuführen, indem seine Daten an einen Hintergrundkontext angehängt werden, der von nachfolgenden Aufrufen des größeren Sprachmodells als Hinweis verwendet wird. In dieser Arbeit wird die Sprache anhand von Beispielprogrammen und einer geführten Tour durch die Syntax und die Werkzeuge vorgestellt. Die deklarative Natur der Sprache macht es auch einfach, automatische Optimierungen für Geschwindigkeit, Genauigkeit und Sicherheit zu implementieren, die in zukünftigen Arbeiten schrittweise implementiert werden.PDL ist jetzt einsatzbereit und steht als Open Source unter der folgenden URL zur Verfügung:https://github.com/IBM/prompt-declaration-language.