
Eingabeaufforderung hilft Ihnen dabei, parametrisierte Eingabeaufforderungen für Sprachmodelle zu erstellen, wenn sich der Parametersatz für jedes Beispiel ändern kann. Es verfolgt zwei Ziele:
Machen Sie es Entwicklern leicht, Vorlagen- und Parameter-agnostische Workflows zu erstellen. Dies bedeutet, dass Sie irgendwann in Ihrer App eine schnelle Vorlage mit einigen Parametern kombinieren können, von denen Sie beide nichts wissen. Und wenn das Ergebnis nicht leer ist - können Sie gut gehen.
Erstellen Sie eine einfache und menschlich lesbare Syntax zum Schreiben parametrisierter Vorlagen, so dass selbst die nicht-technischen Menschen dies tun könnten.
Die nette Syntax zum Schreiben von Vorlagen ist ein wesentlicher Bestandteil von Eingabeaufforderung, und Sie können alles darüber im nächsten Abschnitt lesen.
Aber zuerst laufen wir durch die API, damit Sie den Zweck dieses Projekts besser verstehen können. Die Installation ist lässig:
% pip install promptsub
from promptsub import Prompt
template = "Say hello [to {name}]"
prompt = Prompt ( template )Wenn wir eine Instanz der Eingabeaufforderung erstellen, wird die Eingangszeichenfolge analysiert und auf Syntaxfehler geprüft. Wenn welche gefunden werden, erhalten wir eine informative Ausnahme:
invalid_template = "Say hello [to {name}"
Prompt ( invalid_template ) File "<string>", line 1
Say hello [to {name}
^
promptsub.errors.PromptSyntaxError: Template not closed
Nach der Erstellung kann das Eingabeaufforderung Objekt einfach mit gültigen Parametern verwendet werden:
parameters_batch = (
{ "name" : "John" , "age" : 26 },
{ "name" : "" },
{},
}
for params in parameters_batch :
message = prompt . substitute ( params )
print ( message )
# "Say hello to John"
# "Say hello"
# "Say hello" # The following parameters are accepted:
InputParams: TypeAlias = dict[str, str | int | float]
Schreiben wir eine Vorlage für eine Filmvorschlag -App. Wir gehen davon aus, dass nicht alle unsere Benutzer ihre Lieblingstitel ausgefüllt haben. In solchen Fällen ist es besser, sie nach ihren Vorlieben zu fragen. Außerdem kann ein Benutzer Empfehlungen innerhalb eines bestimmten Genres oder ohne Einschränkungen bitten. Schließlich ist es nicht ungewöhnlich, dass der Name eines Benutzers fehlt.
template = """
Recommend a [{movie_genre}|movie] to [{user_name}|the user],
who is a fan of {favourite_title}
|
Ask [{user_name}|the user] about their favourite [{movie_genre}|film]
"""
prompt = Prompt ( template )Okay, keine Syntaxfehler zu haben, ist ein guter Ausgangspunkt. Das nächste, was wir tun sollten, ist, mit dieser Vorlage zu "herumspielen", um zu sehen, wie es sich mit verschiedenen Parametersätzen verhält. Unsere Eingabeaufforderung hat ein überzeugendes Attribut, um alle seine Variablen anzuzeigen:
prompt . variables
# [
# (required={'favourite_title'}, optional={'user_name', 'movie_genre'}),
# (required=set(), optional={'movie_genre', 'user_name'})
# ]Die Vorlage verfügt über zwei Optionen der oberen Ebene, geteilt durch einen Separator: "Empfehlen ..." und "Ask ...". Wenn der erste aufgrund des Fehlens der erforderlichen Variablen in Parametern fehlschlägt, wird der zweite verwendet.
Jetzt können wir beliebige Werte für die Variablen erstellen und die Ergebnisse überprüfen. Beachten Sie, dass ein leerer Zeichenfolgewert einem fehlenden Schlüssel entspricht .
# A similar method will probably be added to the API
from itertools import product
def test_prompt ( prompt : Prompt , test_params : dict ):
value_combinations = product ( * test_params . values ())
for values in value_combinations :
params = dict ( zip ( test_params . keys (), values ))
print ( prompt . substitute ( params )) test_params = {
"movie_genre" : [ "romantic comedy" , "" ],
"favourite_title" : [ "Rio Bravo (1959)" , "" ],
"user_name" : [ "Quentin" , "" ],
}
test_prompt ( prompt , test_params )
# Recommend a romantic comedy to Quentin, who is a fan of Rio Bravo (1959)
# Recommend a romantic comedy to the user, who is a fan of Rio Bravo (1959)
# Ask Quentin about their favourite romantic comedy
# Ask the user about their favourite romantic comedy
# Recommend a movie to Quentin, who is a fan of Rio Bravo (1959)
# Recommend a movie to the user, who is a fan of Rio Bravo (1959)
# Ask Quentin about their favourite film
# Ask the user about their favourite film Hier ist eine einfache App, die eine Eingabeaufforderung und einige Parameter empfängt. Anschließend werden sie kombiniert, um eine Eingabenachricht für ein Sprachmodell zu erhalten, Abfragen, die modellieren und ihre Antwort zurückgeben - und gleichzeitig nichts über die Vorlage oder die Parameter zu wissen.
Es gibt genau drei Dinge, die hier schief gehen können:
In unserem Beispiel melden wir alle Fehler fleißig dem Client.
from functools import partial
from fastapi import FastAPI , HTTPException
from fastapi . responses import JSONResponse
from promptsub import Prompt
from promptsub . errors import ParametersTypeError , PromptSyntaxError
from pydantic import BaseModel
app = FastAPI ()
http_400 = partial ( HTTPException , status_code = 400 )
class Request ( BaseModel ):
template : str
params : dict
def get_response_from_language_model ( message : str ) -> str :
...
@ app . exception_handler ( PromptSyntaxError )
def syntax_exception_handler ( request , exc ):
raise http_400 ( detail = f"Error in template: { exc } " )
@ app . exception_handler ( ParametersTypeError )
def params_exception_handler ( request , exc ):
raise http_400 ( detail = f"Error in params: { exc } " )
@ app . post ( "/ask_language_model" )
def generate_response ( request : Request ):
prompt = Prompt ( request . template )
message = prompt . substitute ( request . params )
if message == "" :
raise http_400 ( detail = "Required variable not provided" )
response = get_response_from_language_model ( message )
return JSONResponse ( content = { "response" : response })In einem echten Projekt können Ihre Vorlage und Paramente aus verschiedenen Orten stammen, z. B. Datenbanken, aber es spielt keine Rolle. Wenn Sie Ihren Kunden nicht vertrauen, können Sie Ihre Eingabeaufforderungen in der App so speichern:
import redis
import settings
template_storage = redis . from_url ( settings . redis_dsn , decode_responses = True )
class Request ( BaseModel ):
params : dict
@ app . post ( "/ask_language_model/{template_id}" )
def generate_response ( template_id : str , request : Request ):
template = template_storage . get ( template_id )
if template is None :
raise HTTPException ( status_code = 404 , detail = "Template not found" )
prompt = Prompt ( template )
...Möglicherweise sind Sie möglicherweise versucht, Ihre schnellen Objekte im Voraus zu instanziieren und im Programmspeicher zu speichern. Ein solcher Ansatz wirkt sich jedoch negativ auf Ihre Anpassungsfähigkeit aus, es sei denn, Sie implementieren einen ausgefeilten Cache, der einen heißen Austausch ermöglicht. Denken Sie daran, dass die Kosten für das Parsen einer Vorlagenzeichenfolge im Vergleich zu einem Aufruf eines Sprachmodells vernachlässigbar sind.
Eingabeaufforderung verwendet nur 4 Sonderzeichen in seiner grundlegenden Syntax: []{} . Dank seiner Logik können Sie bereits elegante und kraftvolle Aufforderungen nur damit schreiben.
Wenn Sie noch mehr Kontrolle über die Substitutionen haben möchten, fügt die erweiterte Syntax 3 Zeichen in das Spiel hinzu, was mehrere bedingte Blöcke Ihres Backend -Codes ersetzen kann: = ~ | .
Dies ist eine Variable : {name} . Wenn wir einen Wert dafür bieten, wird es ersetzt. Wenn wir das nicht tun, erhalten wir eine leere Zeichenfolge:
{name}
| Parameter | Ergebnis |
|---|---|
| {"Name": "John"} | John |
| {"Alter": "26"} | |
| {} |
Offensichtlich besteht der springende Punkt darin, Variablen in einen "festen" Text einfügen. Dieser Text wird als Vorlage bezeichnet. Hier ist ein einfaches:
Say hello to
| Parameter | Ergebnis |
|---|---|
| {"Name": "John"} | Sag Hallo zu |
Nicht sehr nützlich? Vielleicht, aber es lehrt uns, dass eine Vorlage möglicherweise null oder mehr Variablen hat.
Wenn wir unserer Vorlage eine Variable hinzufügen, werden die Dinge interessant:
Say hello to {name}
| Parameter | Ergebnis |
|---|---|
| {"Name": "John"} | Sag Hallo zu John |
| {"Alter": "26"} | |
| {} |
Erwartet, "Hallo zu" zu sehen, nicht wahr? Aber wohl wäre das keine schöne Aufforderung. Wenn wir annehmen , dass in einigen Parametern eine Variable fehlt, sollten wir dies explizit tun. Machen wir also unsere Variable optional:
Say hello to [{name}]
| Parameter | Ergebnis |
|---|---|
| {} | Sag Hallo zu |
EWW, zwei Arten von Klammern zusammen? Richtig, sieht nicht schick aus. Die gute Nachricht ist, dass Sie sie selten so verwenden werden.
Eine Vorlage ist ein Text mit einigen Sonderzeichen, die es ihm ermöglichen, mit Parametern zu interagieren. Die Vorlage kann Null oder mehr Variablen enthalten, und wenn eine von ihnen in den Parametern fehlt, handelt es sich bei dem Ergebnis um eine leere Zeichenfolge. Was ist mit diesen optionalen Variablen? Nun, sie existieren nicht. Um eine Vorlage zu rendern, müssen alle seine Variablen in Paramien enthalten sein. Aber hier ist der wichtige Teil:
Vorlagen können in anderen Vorlagen mit den Quadratklammern verschachtelt werden .
Lassen Sie uns unser vorheriges Beispiel verbessern:
Say hello [to {name}]
| Parameter | Ergebnis |
|---|---|
| {"Name": "John"} | Sag Hallo zu John |
| {} | Sag Hallo |
Was hier passiert, ist, dass die innere Vorlage "zu {Name}" entweder "zu John" oder eine leere Zeichenfolge wird.
Diese einfache Regel ermöglicht es uns, einige vielseitige Eingabeaufforderungen zu erstellen:
Write a [{length}] summary about {subject} [in {language}] [from the perspective of {author}]
| Parameter | Ergebnis |
|---|---|
| {"Subjekt": "Entropie"} | Schreiben Sie eine Zusammenfassung über Entropie |
| {"Subjekt": "The Moon Landing", "Autor": "Aliens"} | Schreiben Sie eine Zusammenfassung über die Mondlandung aus der Perspektive der Außerirdischen |
| {"Länge": "detailliert", "Sprache": "Deutsch"} |
Was wir hier erreicht haben, ist die folgende Vorlagenhierarchie:
Write a summary about {subject}
| - {length}
| - in {language}
| - from the perspective of {author}
Die erforderliche Variable ist "Subjekt", da das Ganze ohne sie eine leere Zeichenfolge werden würde. Die Variablen "Länge", "Sprache" und "Autor" sind für jede ihrer jeweiligen Vorlagen erforderlich, aber da diese Vorlagen verschachtelt sind, können wir uns damit befassen, dass sie leere Saiten werden. Sie können die optionalen und erforderlichen Variablen untersuchen, indem Sie auf das Attribut ".variablen" einer Eingabeaufforderung zugreifen, wie hier gezeigt.
Zusammenfassend This is a template und [This is a template] und sie sind identisch. Die quadratischen Klammern müssen die Grenzen verschachtelter Vorlagen markieren, sind jedoch auf der "obersten" Ebene optional.
Manchmal wollen wir unsere Eingabeaufforderung unter bestimmten Bedingungen erheblich ändern. Um dies zuzulassen, kann eine Vorlage alternative Optionen haben. Dies wird erreicht, indem der Text mit einem speziellen Separatorcharakter geteilt wird - dem vertikalen Schrägstrich "" " Standardmäßig.
Während der Substitution werden die Optionen nacheinander von links nach rechts bewertet und das erste nicht leere Ergebnis wird zurückgegeben:
Examine this picture and [assess the probability of it being taken in {suggested_location} | try to guess where it was taken]
| Parameter | Ergebnis |
|---|---|
| {"Vorgeschlagener_Location": "Japan"} | Untersuchen Sie dieses Bild und bewerten Sie die Wahrscheinlichkeit, dass es in Japan aufgenommen wird |
| {} | Untersuchen Sie dieses Bild und versuchen Sie zu erraten, wo es genommen wurde |
Vergessen wir nicht, dass die Sätze "Top Level" noch Vorlagen sind. Dies ist auch gültig:
Say hello to {name} | Ask the speaker's name
| Parameter | Ergebnis |
|---|---|
| {"Name": "John"} | Sag Hallo zu John |
| {"Alter": "26"} | Fragen Sie den Namen des Sprechers |
Es gibt Situationen, in denen das Vorhandensein einer Variablen wichtig ist, aber ihr Wert nicht. Nehmen wir an, wir möchten unseren bekannten Benutzern einen informellen Gruß geben, ohne ihren Namen zu erwähnen.
if "name" in params :
template = "Hey, mate! What's up?"
else :
template = "Hello, sir, how can I help?"Ja, das wird funktionieren, aber jetzt muss Ihre App etwas über eine Vorlage wissen, die: a) möglicherweise von jemand anderem geschrieben worden sein; b) könnte morgen Änderungen erfordern. Naja, Eingabeaufforderung hat Sie abgedeckt:
Hey, {~name} mate! What's up? | Hello, sir, how can I help?
| Parameter | Ergebnis |
|---|---|
| {"Name": "John"} | Hey, Kumpel! Was ist los? |
| {} | Hallo, Sir, wie kann ich helfen? |
Wenn Sie Ihre Variable einfach mit einer Tilde "~" beginnen, verhalten Sie sich fast wie ein normaler, was bedeutet, dass es seine Vorlage immer noch zu einem leeren Sing macht, wenn es keinen Wert dafür gibt. Aber wenn ein Wert bereitgestellt wird , wird er nur ignoriert und unsere gedämpfte Variable wird "erfolgreich" durch nichts ersetzt.
Wie Sie erraten haben, gibt es keinen Unterschied darin, dass Sie gedämpfte Variablen in Ihre Vorlage platzieren. Da es möglicherweise mehrere davon gibt, wird es manchmal eine Vorlage lesbar, wenn wir sie am Anfang platzieren. Angenommen, wir haben festgestellt, dass unser informeller Begrüß für Menschen zu viel ist, von denen wir nur ihren Namen kennen. Wir entscheiden, dass ein wahrer Freund jemand ist, der uns mit seinen Geheimnissen vertraut. In Ordnung:
{~name} {~bank_account} Hey there, King Midas! | Hello, sir, how can I help?
Ein weiteres Beispiel:
Shall I book you a dinner place? [ {~address} | Where did you stay? ]
| Parameter | Ergebnis |
|---|---|
| {"Adresse": "1600 Pennsylvania Avenue NW, Washington"} | Soll ich dir ein Abendessen buchen? |
| {} | Soll ich dir ein Abendessen buchen? Wo bist du geblieben? |
Die Bedingungen basierend auf dem Vorhandensein einer Variablen in Parametern sind einfach und leistungsstark. Doch sie sind begrenzt. Betrachten Sie diese Aufforderung:
Remind the user to not forget the umbrella
Es wäre eine dumme Sache zu schreiben, wenn es in der Prognose keinen Regen gibt, oder? Versuchen wir, diese Vorlage mit Bedingungen zu optimieren. Unsere erste Idee könnte sein:
{~is_rainy} Remind the user to not forget the umbrella
| Parameter | Ergebnis |
|---|---|
| {"is_rainy": "true"} | Erinnern Sie den Benutzer daran, den Regenschirm nicht zu vergessen |
| {"is_rainy": "false"} | Erinnern Sie den Benutzer daran, den Regenschirm nicht zu vergessen |
Yikes! Es sieht so aus, als ob unser Zustand jetzt nicht nur vom Vorhandensein der Variablen abhängen sollte, sondern auch von ihrem Wert . Zum Glück ist das möglich:
{~is_rainy=true} Remind the user to not forget the umbrella
| Parameter | Ergebnis |
|---|---|
| {"is_rainy": "true"} | Erinnern Sie den Benutzer daran, den Regenschirm nicht zu vergessen |
| {"is_rainy": "false"} |
Wenn wir einer Variablen ein gleiches Vorzeichen hinzufügen, teilt es einen effektiv in einen Schlüssel (den Text vor ihm) und einen Wert (den Text nach). Eine solche Variable wird im Fall eines identischen Schlüsselwertpaares in den Parametern nur durch Vorhandensein eines identischen Schlüsselwerts ersetzt. Dies funktioniert sowohl für das gedämpfte (das obige Beispiel) als auch für reguläre Variablen:
Remind the user to not forget the umbrella because it's {weather=rainy}
| Parameter | Ergebnis |
|---|---|
| {"Wetter": "Rainy"} | Erinnern Sie den Benutzer daran, den Regenschirm nicht zu vergessen, weil er regnerisch ist |
| {"Wetter": "sonnig"} |
{~length=short} Be as consice as possible | Make the story {length=long}, I will tip
| Parameter | Ergebnis |
|---|---|
| {"Länge": "Rainy"} | So eine Konsire wie möglich sein |
| {"Länge": "lang"} | Machen Sie die Geschichte lange, ich werde Trinkgeld geben |
Es gibt nur wenige, aber sie sind wichtig:
| Regel | Anforderungen |
|---|---|
| Namen (Schlüssel) von Variablen | Nicht leere Zeichenfolgen von ASCII-Buchstaben, Ziffern und Unterstrichen |
| Werte von zum Vergleich verwendeten Variablen (in Vorlagen geschrieben) | Nicht leere Zeichenfolgen aller Zeichen, mit Ausnahme der grundlegenden Syntax-Spezialitäten: []{} |
| Werte von Variablen, die in Parametern bereitgestellt werden | Nicht leere Zeichenfolgen von Zeichen; Ganzzahlen oder Schwimmer |
| Variabler Stummschaltcharakter | Kann nur das erste Zeichen in einer Variablen sein |
Außerdem werden Sie sich freuen zu wissen, dass die Anzahl der Variablen, Vorlagenoptionen und die Tiefe der Vorlagenverschachtung nur durch Ihr Gewissen begrenzt sind.
Say hello to {name}
| Parameter | Ergebnis |
|---|---|
| {"Name": "null"} | Sag Hallo zu Null |
| {"Name": "Einige schlechte Wörter"} | Sag Hallo zu einigen schlechten Worten |
| {"Name": ""} |
Wenn Ihre Vorlage mehrere Optionen hat, ist die ohne Variablen immer gültig. Deshalb sollten Sie es zuletzt setzen. Falsch: Ask the user's name | Greet {name}
| Parameter | Ergebnis |
|---|---|
| {"Name": "John"} | Fragen Sie den Namen des Benutzers |
Whitespaces und Newlines können Ihre Rohvorlagen lesbarer machen. Aber Sprachmodelle kümmern sich meistens nicht um sie. Wenn Sie Ihre Eingabeaufforderungen mit unterschiedlichen Parametern testen, möchten Sie wahrscheinlich nicht die zusätzlichen Räume sehen.
Aus diesem Grund wird die Eingabeaufforderung standardmäßig das Ergebnis der Parametersubstitution nacharbeiten, um alle führenden, nachfolgenden oder wiederholten Whitespace -Zeichen zu entfernen, einschließlich ' t', ' n', ' r', ' f', ' V' usw.
Es kann jedoch Ausnahmen davon geben, sodass Sie es ausschalten können, wenn Sie möchten:
template = "Continue the conversation: {text}"
prompt = Prompt ( template )
text = """
- Also, you know what they call a Quarter Pounder with Cheese in Paris?
- They don't call it a Quarter Pounder with Cheese?
"""
params = { "text" : text }
prompt . substitute ( params )
# Continue the conversation: - Also... - They...
prompt . substitute ( params , postprocess_whitespace_reduction = False )
# Continue the conversation:
# - Also...
# - They...Stellen Sie sicher, dass alle Ihre Vorlagen mit verschiedenen Parametersätzen getestet werden, bevor sie in der Produktion verwendet werden.
Aka aktuelle Einschränkungen. In einer willkürlichen Reihenfolge sind dies nur die Ideen für zukünftige Arbeiten ohne konkrete Roadmap. Vorgeschlagene Implementierungen scheinen rückwärtskompatibel mit der aktuellen Syntax zu sein. Sie ändern jedoch wahrscheinlich das Ausgangsformat der Methode "Eingabeaufforderung. Variablen".
In Zukunft werden nicht veränderte Änderungen ausgeschlossen. Achten Sie also auf die Versionierung.
Es ist nicht schwer, sich eine Situation vorzustellen, in der eine Variable als Alternative zur anderen dienen kann. Nehmen wir an, wir möchten einen Benutzer entweder nach seinem Spitznamen oder nach seinem Vornamen ansprechen und wenn Nether ihn nach einem Intruducion bitten, wenn sie beeinträchtigt werden. Im Moment würde es so aussehen:
Hello, {user_nickname}! | Hello, {user_firstname}! | Hey! What's your name?
Diese einfache Wiederholung sieht erträglich aus. Aber wenn wir eine längere Vorlage oder mehr "austauschbare" Variablen hätten, dann würden wir eine weitläufige Schnur haben, die schwer zu lesen und fehleranfällig wäre.
Eine mögliche Lösung wäre, die Unterstützung für mehrere Optionen für Variablen hinzuzufügen, genau wie in Vorlagen: "{user_nickname | user_firstname}". Es wäre erforderlich, die Kompatibilität mit Stummschalt- und Vergleichsymbole zu berücksichtigen. In diesem Fall sollten die Whitespaces wahrscheinlich an bestimmten Positionen in Variablen zulässig sein, um eine bessere Lesbarkeit zu erhalten.
Beachten Sie, wie wir im vorherigen Beispiel die Variablen {user_nickname} und {user_firstname} hatten. In der realen Welt sind diese am wahrscheinlichsten nicht die separaten Schlüsselwertpaare, sondern die Attribute eines user . Das bedeutet, dass Sie irgendwann in Ihrer App so etwas tun müssen:
params = {
"user_firstname" : request . params . user . firstname ,
"user_nickname" : request . params . user . nickname ,
}
result = prompt . substitute ( params )Ein viel besserer Ansatz wäre:
result = prompt.substitute(request.params.model_dump())
Damit dies funktioniert, benötigen wir Eingabeaufforderung, auf die Attribute der bereitgestellten Objekte zuzugreifen. Die einfachste Syntax wäre die Punktnotation: "{user.firstname}". Eine gewisse Validierung müsste stattfinden.
Wenn Sie zur Eingabeaufforderung zur Empfehlung von Film zurückkehren, sollten wir vorschlagen, dass wir zur Verbesserung der Genauigkeit eine Liste der hoch geschätzten Titel des Benutzers einfügen sollten, anstatt nur auf eine einzige zu verweisen. Obwohl möglich, wäre die aktuelle Lösung eher ein Hack, was (Sie erraten) die App in die Parameter einmischt:
user_favourite_titles = [
'The Good, the Bad and the Ugly (1966)' ,
'Rio Bravo (1959)' ,
'Blow Out (1981)' ,
'Taxi Driver (1976)' ,
]
template = "Recommend a movie to the user, who is a fan of {favourite_titles}"
params = { "favourite_titles" : ", " . join ( user_favourite_titles )}Die Unterstützung von Iterables als Parameterwerte würde sorgfältige Berücksichtigung ihrer Validierung, Vergleich und Formatierung erfordern.
Die Einführung zusätzlicher Operatoren zum Wertvergleich (wie <,>,! = Oder "in") würde wohl mehr gut für die Funktionalität als den Schaden für den Einfachheit tun, weil:
Die Implementierung würde eine angemessene Menge an Arbeit am "Variablen" -Modul erfordern. Es sollte bei der Notwendigkeit berücksichtigt werden.