言語モデルのプロンプトを作成する
言語モデルのプロンプトは文字列連結で作成できますが、機能を組み合わせたり、プロンプトが言語モデルのコンテキストウィンドウ内に収まるようにしたりするのは困難です。これらの制限を克服するには、@vscode/prompt-tsx ライブラリを使用できます。
@vscode/prompt-tsx ライブラリは、以下の機能を提供します
- TSX ベースのプロンプトレンダリング: TSX コンポーネントを使用してプロンプトを構成し、読みやすく、保守しやすくします
- 優先度ベースの剪定: プロンプトの重要度の低い部分を自動的に剪定して、モデルのコンテキストウィンドウ内に収まるようにします
- 柔軟なトークン管理:
flexGrow、flexReserve、flexBasisなどのプロパティを使用して、トークン予算を協調的に使用します - ツール統合: VS Code の言語モデルツール API と統合します
すべての機能の完全な概要と詳細な使用方法については、完全な README を参照してください。
この記事では、ライブラリを使用したプロンプト設計の具体的な例を説明します。これらの例の完全なコードは、prompt-tsx リポジトリで見つけることができます。
会話履歴の優先順位を管理する
プロンプトに会話履歴を含めることは、ユーザーが以前のメッセージに対して追加の質問をできるようにするため重要です。しかし、履歴は時間が経つにつれて大きくなる可能性があるため、その優先順位が適切に扱われるようにする必要があります。最も理にかなっているパターンは、通常、次の順序で優先順位を付けることであることがわかっています
- 基本のプロンプト指示
- 現在のユーザーのクエリ
- チャット履歴の最後の数ターン
- サポートデータ
- 収まるだけ残りの履歴
このため、プロンプト内で履歴を2つの部分に分割し、最近のプロンプトターンが一般的な文脈情報よりも優先されるようにします。
このライブラリでは、ツリー内の各TSXノードには優先順位があり、これは zIndex に概念的に似ており、数値が高いほど優先順位が高くなります。
ステップ1: HistoryMessages コンポーネントを定義する
履歴メッセージを一覧表示するには、HistoryMessages コンポーネントを定義します。この例は良い出発点となりますが、より複雑なデータ型を扱う場合は拡張する必要があるかもしれません。
この例では、PrioritizedList ヘルパーコンポーネントを使用しています。これは、各子に昇順または降順の優先順位を自動的に割り当てます。
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>
);
}
}
ステップ2: Prompt コンポーネントを定義する
次に、基本指示、ユーザーのクエリ、および適切な優先順位の履歴メッセージを含む MyPrompt コンポーネントを定義します。優先順位の値は兄弟間でローカルです。プロンプト内の他の要素に触れる前に、履歴内の古いメッセージをトリミングしたい場合があることを覚えておいてください。そのため、2つの <HistoryMessages> 要素を分割する必要があります
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>
</>
);
}
}
これで、ライブラリがプロンプトの他の要素を剪定しようとする前に、すべての古い履歴メッセージが剪定されます。
ステップ3: History コンポーネントを定義する
消費を少し簡単にするために、履歴メッセージをラップし、passPriority 属性を使用してパススルーコンテナとして機能する History コンポーネントを定義します。passPriority を使用すると、その子は優先順位付けの目的で、含まれる要素の直接の子であるかのように扱われます。
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} />
</>
);
}
}
これで、この単一の要素を使用してチャット履歴を含めることができます。
<History history={this.props.history} passPriority older={0} newer={80}/>
ファイルコンテンツをフィットさせるために成長させる
この例では、ユーザーが現在見ているすべてのファイルのコンテンツをプロンプトに含めたいと考えています。これらのファイルは非常に大きく、すべて含めるとテキストが剪定されてしまう可能性があります。この例では、flexGrow プロパティを使用してファイルコンテンツのサイズを協調的に調整し、トークン予算内に収める方法を示します。
ステップ1: 基本指示とユーザーのクエリを定義する
まず、基本指示を含む UserMessage コンポーネントを定義します。
<UserMessage priority={100}>Here are your base instructions.</UserMessage>
次に、UserMessage コンポーネントを使用してユーザーのクエリを含めます。このコンポーネントは、基本指示の直後に含まれるように高い優先順位を持っています。
<UserMessage priority={90}>{this.props.userQuery}</UserMessage>
ステップ2: ファイルコンテンツを含める
これで、FileContext コンポーネントを使用してファイルコンテンツを含めることができます。基本指示、ユーザーのクエリ、および履歴の後にレンダリングされるように、flexGrow の値を 1 に割り当てます。
<FileContext priority={70} flexGrow={1} files={this.props.files} />
flexGrow の値を持つ要素は、その render() および prepare() 呼び出しに渡される PromptSizing オブジェクト内の*未使用*のトークン予算を受け取ります。フレックス要素の動作の詳細については、prompt-tsx ドキュメントを参照してください。
ステップ3: 履歴を含める
次に、以前作成した History コンポーネントを使用して履歴メッセージを含めます。これは少しトリッキーです。なぜなら、履歴の一部を表示したいが、ファイルコンテンツがプロンプトの大部分を占めるようにしたいからです。
したがって、History コンポーネントに flexGrow の値 2 を割り当てて、<FileContext /> を含む他のすべての要素の後にレンダリングされるようにします。しかし、履歴のために総予算の1/5を確保するために、flexReserve の値 "/5" も設定します。
<History
history={this.props.history}
passPriority
older={0}
newer={80}
flexGrow={2}
flexReserve="/5"
/>
ステップ3: プロンプトのすべての要素を組み合わせる
次に、すべての要素を MyPrompt コンポーネントに結合します。
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} />
</>
);
}
}
ステップ4: FileContext コンポーネントを定義する
最後に、ユーザーが現在見ているファイルのコンテンツを含む FileContext コンポーネントを定義します。flexGrow を使用したため、PromptSizing の情報を使用して、各ファイルの「興味深い」行周辺の行をできるだけ多く取得するロジックを実装できます。
簡潔にするため、getExpandedFiles の実装ロジックは省略されています。これはprompt-tsx リポジトリで確認できます。
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.
}
}
まとめ
これらの例では、基本指示、ユーザーのクエリ、履歴メッセージ、およびさまざまな優先順位のファイルコンテンツを含む MyPrompt コンポーネントを作成しました。flexGrow を使用して、ファイルコンテンツのサイズを協調的に調整し、トークン予算内に収めました。
このパターンに従うことで、プロンプトの最も重要な部分が常に含まれるようにし、重要度の低い部分は必要に応じてモデルのコンテキストウィンドウに収まるように剪定されるようにすることができます。getExpandedFiles メソッドと FileContextTracker クラスの完全な実装の詳細については、prompt-tsx リポジトリを参照してください。