Sprachmodell-Prompts erstellen
Sie können Sprachmodell-Prompts durch Zeichenfolgenverkettung erstellen, aber es ist schwierig, Funktionen zu komponieren und sicherzustellen, dass Ihre Prompts innerhalb des Kontextfensters von Sprachmodellen bleiben. Um diese Einschränkungen zu überwinden, können Sie die @vscode/prompt-tsx-Bibliothek verwenden.
Die @vscode/prompt-tsx-Bibliothek bietet die folgenden Funktionen
- TSX-basiertes Prompt-Rendering: Komponieren Sie Prompts mit TSX-Komponenten, um sie lesbarer und wartbarer zu machen.
- Prioritätsbasiertes Beschneiden: Schneiden Sie automatisch weniger wichtige Teile von Prompts ab, um sie an das Kontextfenster des Modells anzupassen.
- Flexibles Token-Management: Verwenden Sie Eigenschaften wie
flexGrow,flexReserveundflexBasis, um Tokenbudgets kooperativ zu nutzen. - Tool-Integration: Integrieren Sie sich mit der Language Model Tools API von VS Code.
Eine vollständige Übersicht über alle Funktionen und detaillierte Anwendungshinweise finden Sie in der vollständigen README-Datei.
Dieser Artikel beschreibt praktische Beispiele für Prompt-Design mit der Bibliothek. Der vollständige Code für diese Beispiele finden Sie im prompt-tsx-Repository.
Prioritäten im Gesprächsverlauf verwalten
Das Einbeziehen des Gesprächsverlaufs in Ihren Prompt ist wichtig, da es dem Benutzer ermöglicht, Folgefragen zu früheren Nachrichten zu stellen. Sie möchten jedoch sicherstellen, dass seine Priorität angemessen behandelt wird, da der Verlauf im Laufe der Zeit groß werden kann. Wir haben festgestellt, dass das Muster, das am sinnvollsten ist, normalerweise darin besteht, in der Reihenfolge zu priorisieren
- Die Basis-Prompt-Anweisungen
- Die aktuelle Benutzeranfrage
- Die letzten Gesprächsrunden des Chats
- Alle unterstützenden Daten
- So viel vom verbleibenden Verlauf, wie Sie unterbringen können
Aus diesem Grund teilen Sie den Verlauf im Prompt in zwei Teile auf, wobei neuere Prompt-Runden Vorrang vor allgemeinen kontextuellen Informationen haben.
In dieser Bibliothek hat jeder TSX-Knoten im Baum eine Priorität, die konzeptionell einem zIndex ähnelt, wobei eine höhere Zahl eine höhere Priorität bedeutet.
Schritt 1: Definieren Sie die HistoryMessages-Komponente
Um Verlaufsmeldungen aufzulisten, definieren Sie eine HistoryMessages-Komponente. Dieses Beispiel bietet einen guten Ausgangspunkt, aber Sie müssen es möglicherweise erweitern, wenn Sie mit komplexeren Datentypen arbeiten.
Dieses Beispiel verwendet die Hilfskomponente PrioritizedList, die jedem ihrer Kinder automatisch auf- oder absteigende Prioritäten zuweist.
import {
UserMessage,
AssistantMessage,
PromptElement,
BasePromptElementProps,
PrioritizedList,
} from '@vscode/prompt-tsx';
import { ChatContext, ChatRequestTurn, ChatResponseTurn, ChatResponseMarkdownPart } from 'vscode';
interface IHistoryMessagesProps extends BasePromptElementProps {
history: ChatContext['history'];
}
export class HistoryMessages extends PromptElement<IHistoryMessagesProps> {
render(): PromptPiece {
const history: (UserMessage | AssistantMessage)[] = [];
for (const turn of this.props.history) {
if (turn instanceof ChatRequestTurn) {
history.push(<UserMessage>{turn.prompt}</UserMessage>);
} else if (turn instanceof ChatResponseTurn) {
history.push(
<AssistantMessage name={turn.participant}>
{chatResponseToMarkdown(turn)}
</AssistantMessage>
);
}
}
return (
<PrioritizedList priority={0} descending={false}>
{history}
</PrioritizedList>
);
}
}
Schritt 2: Definieren Sie die Prompt-Komponente
Definieren Sie als Nächstes eine MyPrompt-Komponente, die die Basis-Anweisungen, die Benutzeranfrage und die Verlaufsmeldungen mit ihren entsprechenden Prioritäten enthält. Prioritätswerte sind unter Geschwistern lokal. Denken Sie daran, dass Sie möglicherweise ältere Meldungen im Verlauf kürzen möchten, bevor Sie etwas anderes im Prompt ändern, daher müssen Sie zwei <HistoryMessages>-Elemente aufteilen
import {
UserMessage,
PromptElement,
BasePromptElementProps,
} from '@vscode/prompt-tsx';
interface IMyPromptProps extends BasePromptElementProps {
history: ChatContext['history'];
userQuery: string;
}
export class MyPrompt extends PromptElement<IMyPromptProps> {
render() {
return (
<>
<UserMessage priority={100}>
Here are your base instructions. They have the highest priority because you want to make
sure they're always included!
</UserMessage>
{/* Older messages in the history have the lowest priority since they're less relevant */}
<HistoryMessages history={this.props.history.slice(0, -2)} priority={0} />
{/* The last 2 history messages are preferred over any workspace context you have below */}
<HistoryMessages history={this.props.history.slice(-2)} priority={80} />
{/* The user query is right behind the based instructions in priority */}
<UserMessage priority={90}>{this.props.userQuery}</UserMessage>
<UserMessage priority={70}>
With a slightly lower priority, you can include some contextual data about the workspace
or files here...
</UserMessage>
</>
);
}
}
Nun werden alle älteren Verlaufsmeldungen gekürzt, bevor die Bibliothek versucht, andere Elemente des Prompts zu kürzen.
Schritt 3: Definieren Sie die History-Komponente
Um die Verwendung etwas zu erleichtern, definieren Sie eine History-Komponente, die die Verlaufsmeldungen umschließt und das passPriority-Attribut verwendet, um als Durchleitungscontainer zu fungieren. Mit passPriority werden ihre Kinder so behandelt, als ob sie direkte Kinder des enthaltenden Elements für Priorisierungszwecke wären.
import { PromptElement, BasePromptElementProps } from '@vscode/prompt-tsx';
interface IHistoryProps extends BasePromptElementProps {
history: ChatContext['history'];
newer: number; // last 2 message priority values
older: number; // previous message priority values
passPriority: true; // require this prop be set!
}
export class History extends PromptElement<IHistoryProps> {
render(): PromptPiece {
return (
<>
<HistoryMessages history={this.props.history.slice(0, -2)} priority={this.props.older} />
<HistoryMessages history={this.props.history.slice(-2)} priority={this.props.newer} />
</>
);
}
}
Jetzt können Sie dieses einzelne Element verwenden und wiederverwenden, um den Chatverlauf einzuschließen
<History history={this.props.history} passPriority older={0} newer={80}/>
Dateiinhalte erweitern, um sie anzupassen
In diesem Beispiel möchten Sie die Inhalte aller Dateien, die der Benutzer gerade betrachtet, in seinen Prompt einbeziehen. Diese Dateien könnten groß sein, so dass die Aufnahme aller dazu führen würde, dass ihr Text gekürzt wird! Dieses Beispiel zeigt, wie die flexGrow-Eigenschaft verwendet wird, um die Dateiinhalte kooperativ zu skalieren, damit sie in das Token-Budget passen.
Schritt 1: Basis-Anweisungen und Benutzeranfrage definieren
Zuerst definieren Sie eine UserMessage-Komponente, die die Basis-Anweisungen enthält.
<UserMessage priority={100}>Here are your base instructions.</UserMessage>
Anschließend nehmen Sie die Benutzeranfrage auf, indem Sie die UserMessage-Komponente verwenden. Diese Komponente hat eine hohe Priorität, um sicherzustellen, dass sie direkt nach den Basis-Anweisungen enthalten ist.
<UserMessage priority={90}>{this.props.userQuery}</UserMessage>
Schritt 2: Dateiinhalte einbeziehen
Sie können nun die Dateiinhalte aufnehmen, indem Sie die FileContext-Komponente verwenden. Sie weisen ihr einen flexGrow-Wert von 1 zu, um sicherzustellen, dass sie nach den Basis-Anweisungen, der Benutzeranfrage und dem Verlauf gerendert wird.
<FileContext priority={70} flexGrow={1} files={this.props.files} />
Mit einem flexGrow-Wert erhält das Element jedes *ungenutzte* Token-Budget in seinem PromptSizing-Objekt, das in seine render()- und prepare()-Aufrufe übergeben wird. Weitere Informationen zum Verhalten von Flex-Elementen finden Sie in der prompt-tsx-Dokumentation.
Schritt 3: Den Verlauf einbeziehen
Nehmen Sie als Nächstes die Verlaufsmeldungen mit der zuvor erstellten History-Komponente auf. Dies ist etwas kniffliger, da Sie möchten, dass ein Teil des Verlaufs angezeigt wird, aber auch möchten, dass die Dateiinhalte den größten Teil des Prompts ausmachen.
Weisen Sie daher der History-Komponente einen flexGrow-Wert von 2 zu, um sicherzustellen, dass sie nach allen anderen Elementen, einschließlich <FileContext />, gerendert wird. Setzen Sie aber auch einen flexReserve-Wert von "/5", um 1/5 des Gesamtbudgets für den Verlauf zu reservieren.
<History
history={this.props.history}
passPriority
older={0}
newer={80}
flexGrow={2}
flexReserve="/5"
/>
Schritt 3: Alle Elemente des Prompts kombinieren
Kombinieren Sie nun alle Elemente in der MyPrompt-Komponente.
import {
UserMessage,
PromptElement,
BasePromptElementProps,
} from '@vscode/prompt-tsx';
import { History } from './history';
interface IFilesToInclude {
document: TextDocument;
line: number;
}
interface IMyPromptProps extends BasePromptElementProps {
history: ChatContext['history'];
userQuery: string;
files: IFilesToInclude[];
}
export class MyPrompt extends PromptElement<IMyPromptProps> {
render() {
return (
<>
<UserMessage priority={100}>Here are your base instructions.</UserMessage>
<History
history={this.props.history}
passPriority
older={0}
newer={80}
flexGrow={2}
flexReserve="/5"
/>
<UserMessage priority={90}>{this.props.userQuery}</UserMessage>
<FileContext priority={70} flexGrow={1} files={this.props.files} />
</>
);
}
}
Schritt 4: Definieren Sie die FileContext-Komponente
Definieren Sie schließlich eine FileContext-Komponente, die die Inhalte der vom Benutzer gerade betrachteten Dateien enthält. Da Sie flexGrow verwendet haben, können Sie Logik implementieren, die so viele der Zeilen um die 'interessante' Zeile für jede Datei abruft, indem Sie die Informationen in PromptSizing verwenden.
Der Einfachheit halber ist die Implementierungslogik für getExpandedFiles weggelassen. Sie können sie im prompt-tsx-Repo einsehen.
import { PromptElement, BasePromptElementProps, PromptSizing, PromptPiece } from '@vscode/prompt-tsx';
class FileContext extends PromptElement<{ files: IFilesToInclude[] } & BasePromptElementProps> {
async render(_state: void, sizing: PromptSizing): Promise<PromptPiece> {
const files = await this.getExpandedFiles(sizing);
return <>{files.map(f => f.toString())}</>;
}
private async getExpandedFiles(sizing: PromptSizing) {
// Implementation details are summarized here.
// Refer to the repo for the complete implementation.
}
}
Zusammenfassung
In diesen Beispielen haben Sie eine MyPrompt-Komponente erstellt, die Basis-Anweisungen, Benutzeranfrage, Verlaufsmeldungen und Dateiinhalte mit unterschiedlichen Prioritäten enthält. Sie haben flexGrow verwendet, um die Dateiinhalte kooperativ zu skalieren, damit sie in das Token-Budget passen.
Durch Befolgen dieses Musters können Sie sicherstellen, dass die wichtigsten Teile Ihres Prompts immer enthalten sind, während weniger wichtige Teile nach Bedarf gekürzt werden, um in das Kontextfenster des Modells zu passen. Die vollständigen Implementierungsdetails der Methode getExpandedFiles und der Klasse FileContextTracker finden Sie im prompt-tsx-Repo.