しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

Prompty を C# で扱うライブラリがリリースされたので試した

今年の Microsoft Build で突然 Seth が発表した Prompty ですが、これまで扱いが悩ましかったシステムプロンプト周りを上手く外部ファイルとして扱えるのと、VS Code 拡張を使うとファイルを直接 OpenAI で実行できるのでプロンプトの開発段階から、アプリケーションへの組み込みまで使えて便利です。

特に AI アプリケーションの開発ではプロンプトだけ修正することが多いので、Prompty を使って外部ファイル化しておけば VS Code で開発したプロンプトをそのままデプロイ出来るので、アプリケーションの改善を行いやすいメリットもあります。

しかし、これまで Prompty をアプリケーションに組み込むには Python 向けに用意されたライブラリか Semantic Kernel の Prompty 拡張しか選択肢が無く、C# のアプリケーションから扱いにくかったのですが最近 C# 向けライブラリがリリースされて組み込みやすくなりました。

名前から想像つくように Prompty.Core は Prompty ファイルの解析やレンダリングまでを行い、実際に OpenAI を叩くという部分は別ライブラリで提供する予定のようです。

ソースコードは Prompty のリポジトリで公開されているので、実装を確認することや Pull Request を送ることも可能になっています。実装としては Python 向けを踏襲しているようです。

ここからは実際に Prompty.Core を使って OpenAI で実行するところまでやっていきます。前述したように現在のライブラリは Prompty の解析とレンダリングまでしか行わないため、OpenAI にメッセージを投げる部分は別途用意する必要があります。

Prompty ファイルを読み込んで OpenAI に投げる直前までを行うコードは以下のようにシンプルです。最初に InvokerFactory.AutoDiscovery を呼ぶ必要がある点は注意が必要です。

using Prompty.Core;

InvokerFactory.AutoDiscovery();

var prompty = await Prompty.Core.Prompty.LoadAsync("Prompts/Basic.prompty");

var messages = await prompty.PrepareAsync(new
{
    firstName = "Tatsuro",
    lastName = "Shibamura",
    question = "Microsoft について教えてください"
});

やっている内容はシンプルで Prompty.LoadAsync を呼び出して Prompty ファイルを読み込み、更に PrepareAsync を呼び出して与えたパラメータを基にプロンプトのレンダリング結果を取得しています。

今回使用している Prompty ファイルは以下のような単純なものです。PrepareAsync に渡すオブジェクトは sampleinput で定義したものと同じ名前で渡す形になります。

---
name: Basic Prompt
authors:
  - shibayan
model:
  api: chat
sample:
  firstName: Tauchi
  lastName: Kazuaki
  question: 労働の意味は?
---
system:
あなたは AI アシスタントとして質問に回答してください。回答は完結に分かりやすくしてください。

# ユーザー情報
質問するユーザーの名前は {{firstName}} {{lastName}} です。回答には名前を入れるようにしてください。

user:
{{question}}

アプリケーション内で利用する Prompty ファイルは CopyToOutputDirectoryPreserveNewest などを指定して出力ディレクトリにコピーされるように設定する必要があります。

以下のようにワイルドカードを利用して定義しておくと、この例では Prompts ディレクトリ以下にある拡張子 .prompty は全て出力ディレクトリにコピーされるようになります。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Prompty.Core" Version="0.0.11-alpha" />
  </ItemGroup>

  <ItemGroup>
    <None Update="Prompts\*.prompty">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>

</Project>

ここまでのコードを実行してみると、以下のように PrepareAsync の実行結果として Prompty をレンダリングしたものが返ってきていることが確認出来ます。Prompty の中で system と user で 2 つのロールを定義していますが、ちゃんと別のメッセージとしてレンダリングされています。

PrepareAsync の戻り値の型が object なので分かりにくいのですが、現状のライブラリは Microsoft.Extensions.AI ベースになっているので、そこで定義されている ChatMessage が返ってくるようになっています。つまり MEAI を使えば Prompty のレンダリング結果を簡単に実行できると言う訳です。

Microsoft.Extensions.AI についてはリリース時にエントリを書いたので参照してください。

上記のエントリで紹介した Azure OpenAI を利用するサンプルコードのプロンプト部分を Prompty に置き換えたコードが以下になります。Microsoft.Extensions.AI が AI 周りの基盤で使われているので Prompty から、Azure OpenAI の呼び出しまでシームレスに繋がっています。

using System.ClientModel;

using Azure.AI.OpenAI;

using Microsoft.Extensions.AI;

using Prompty.Core;

InvokerFactory.AutoDiscovery();

var prompty = await Prompty.Core.Prompty.LoadAsync("Prompts/Basic.prompty");

var messages = (ChatMessage[])await prompty.PrepareAsync(new
{
    firstName = "Tatsuro",
    lastName = "Shibamura",
    question = "Microsoft について教えてください"
});

var client =
    new AzureOpenAIClient(new Uri("https://***.openai.azure.com/"), new ApiKeyCredential("***"))
        .AsChatClient("gpt-4o");

var result = await client.CompleteAsync(messages);

Console.WriteLine(result.Message);

実行すると Prompty を VS Code で実行した時と同じような結果が返ってきます。簡単な Prompty なのでプログラムに埋め込むのとあまり差を感じないかもしれませんが、これが RAG シナリオのように複雑なコンテキストを含む場合には、Prompty の表現力の高さが力を発揮します。

Prompty クラスには ExecuteAsync メソッドが用意されているので、将来的に OpenAI 向けの Executor ライブラリが提供されれば以下のように ExecuteAsync を呼び出すだけで終わるようになります。

using Prompty.Core;

InvokerFactory.AutoDiscovery();

var prompty = await Prompty.Core.Prompty.LoadAsync("Prompts/Basic.prompty");

await prompty.ExecuteAsync(inputs: new
{
    firstName = "Tatsuro",
    lastName = "Shibamura",
    question = "Microsoft について教えてください"
});

現状では OpenAI 向けの Executor が提供されていないので、以下のようなエラーになります。独自の Executor を作成することも出来るので、SLM の利用シナリオでも活用できるかもしません。

これまでも Semantic Kernel 経由では Prompty を使うことは出来ましたが、今回リリースされた Prompty.Core を使うことで圧倒的に気軽に Prompty を組み込めるようになったのは、今後の AI アプリケーション開発での大きなメリットだと思います。