LLMのプロンプトもTSXで書く時代
VS Code拡張向けライブラリprompt-tsxの紹介。
prompt-tsxとは
@vscode/prompt-tsxはVSCode拡張で利用できるライブラリで、Copilot Chat向けの@コマンドで呼び出すエージェントの開発や、それ以外のLM APIを使った拡張とともに使うことができる。
利用イメージ


以上のようにLM APIに渡すパラメータをTSX(JSX)コンポーネントとして管理する。
prompt-tsxが必要な背景
prompt-tsxが必要な背景だが、なぜわざわざTSXで書けるようにしたのかというと、リポジトリの“Why TSX?”にはこう書かれている。
なぜTSXなのか?
AIエンジニアとして、私たちの製品はテキストプロンプトで構成されたチャットメッセージを使用して、大規模言語モデルと通信します。Copilot Chatの開発中に、単純な文字列だけでプロンプトを構成するのは扱いにくく、不満の多いことであるとわかりました。
私たちが遭遇した課題には、以下のようなものがあります。
1. プロンプトの構成に、プログラム的な文字列連結またはテンプレート文字列のいずれかを使用していました。プログラム的な文字列連結は、プロンプトテキストを時間の経過とともにますます読みにくく、保守しにくく、更新しにくくしました。テンプレート文字列ベースのプロンプトは硬直的で、不要な空白などの問題が発生しやすかったです。
2. どちらの場合も、私たちのプロンプトとRAGによって生成されたコンテキストは、モデルをアップグレードするにつれて変化するコンテキストウィンドウの制約に適応できませんでした。プロンプトは最終的に単なる文字列であるため、文字列連結で構成された後に編集することが困難でした。
チャットメッセージに入れる会話のプロンプトというのは抽象化すると (ROLE, MESSAGE)
のタプルのリスト構造データだ。それがモデルに到達するまでには最終的に独自形式のテキストに変換される。
しかし我々が直接触っているのはLLMのプロバイダーが提供するSKDのAPIが基本になる。一般的なのはOpenAI SDKでお馴染みの会話形式のデータを表現するテキスト生成のChat Completion APIがある。

現在の方法ではこれに渡すMESSAGE
部分のテキストデータをあの手この手で、連結や分岐で動的に処理している。
そしてこのテキストはユーザーが入力した内容とイコールではない。まず開発者が用意する事前のシステムプロンプトと呼ばれるテキストがあり、これは設定や状態によって動的に変形することがある。会話の経過中にLLMに呼び出し指示されたツール(外部呼び出し)の結果を「ユーザーの入力」としてプロンプトに乗せることもある。そこに最終的にユーザー自身の発言を組み合わされてはじめてLLMのAPIに渡されるメッセージになる。
これは一定の形式を持つプレーンテキストの構造化データで、何かに似ている。そう、ウェブアプリのHTMLの構築だ。プロンプトの組み立ては現在、ステートフルなHTMLの組み立てに近づいている。なのでTSXの出番になる。
prompt-tsxのようなTSXでメッセージデータ構造を変換するアプローチによって、従来のフロントエンド開発で利用するTSXの利点を享受できる。TypeScriptとして型サポートが付くし、ツリー構造で全体の構造を分割できる。コンポーネントを純粋な関数化することもできるし、依存する値を引数にして分離もできる。つまり複雑な動的生成するプロンプトにも作用する。
(余談だが、複雑な動的プロンプトを構築する例としては、Jina AIのリポジトリにあるコードを例としてあげる。これはAgent系ではまだシンプルな方だ)
なぜVSCode専用なのか
prompt-tsxがVSCode拡張専用と前に述べた。なのでNode.jsスクリプトで利用したり、ブラウザで動かしたりできない。これはVSCodeのLM API向けのライブラリだからだ。独自調査によると公式Copilot Chat拡張のコードの中にも依存として入っている。ドキュメントが以下にある。

prompt-tsxのレンダラーは入力としてTSXを受け取るが、変換先はLM APIに渡せるデータ構造である。つまりプレーンテキストにはしない。
LM APIとの統合による強い制約は特にないので、移植できる可能性もありそうだ。
TSXからオブジェクトへの変換は素直にJSXファクトリ関数で行うし、レンダラーの返すオブジェクトをOpenAIやAnthropicのSDK互換にすれば、VSCodeの外でも使えるものを再実装できるはず。
ただ、prompt-tsxについてはユーティリティ系関数でVSCode依存が多々存在する。トークン数計算などは内部でLM APIを呼び出す。このレンダラーには全体のプロンプトから送信するモデルの最大入力トークン上限に対する予算を作って、priorityを基準に削除する。古い会話履歴などは優先度が低いので消えてゆく。つまり入力トークンが増える(長い会話や大きいファイルの参照、ツール呼び出しが増えるタスク)と中身が圧縮されていくというのを、我々Copilotユーザーとしては知っておくといいだろう。
追記:元ネタ情報(priompt)
投稿後にprompt-tsxの基礎となるアイデアを実装していたpriomptについて教えてもらった(ryoppippiさんに感謝)
prompt-tsxのアルファリリース時にも言及されているのでこれが元ネタになってることでまちがいないだろう。
priomptはVSCodeへの依存がなくNode.jsとOpenAIのSDK向けにデザインされていた。しかし実験的なプロジェクトでアップデートは熱心にされていないようだった。
筆者としてはVercel AI SDKあたりにこのTSXでプロンプトを書く機能が備わっているとありがたい。他に興味ある人はいるだろうか。