📚

Prompty を読み込んで LangChain と Semantic Kernel で実行するのを試してみた

2024/05/29に公開

Microsoft Build 2024 で発表された Prompty について試してみたのでメモです。

Visual Studio Code の Prompty 拡張機能 をインストールすることで簡単に使用できます。

Prompty は プロンプトとプロンプトの概要、パラメーターなどを定義することが出来るファイルです。以下のような感じになっています。

---
name: ExamplePrompt
description: This is an example prompty file for the Python QA.
authors:
  - Kazuki Ota
model:
  api: chat
  parameters:
    max_tokens: 3000
sample:
  question: リスト内包表記とは何ですか?
---
system:
あなたは Python の質問に答える AI アシスタントです。

user:
{{question}}

Visual Studio Code で呼び出し先のモデルを構成しておけば手元で動かして確認することも出来ます。

各種フレームワークへの対応

この Prompty 形式のファイルは LangChain や Semantic Kernel も対応しているので、可搬性も高いといった特徴があります。

LangChain

LangChain には langchain-prompty というパッケージがあり、これを使うことで Prompty ファイルを読み込んで実行することが出来ます。
上記のような Prompty ファイルを basic.prompty という名前で保存しておくと以下のような感じで読み込んで実行することが出来ます。

from langchain_prompty import create_chat_prompt
from langchain_openai import AzureChatOpenAI
import os
from dotenv import load_dotenv
from pathlib import Path

load_dotenv()

# Prompty ファイルを読み込む
promptyFilePath = Path(__file__).parent / 'basic.prompty'
prompt = create_chat_prompt(promptyFilePath)
# question パラメーターに値を設定した状態のものを作成する
promptText = prompt.invoke(
    {
        'question': 'クラスの定義の仕方について教えてください。'
    }
)

# OpenAI にプロンプトを投げる
llm = AzureChatOpenAI(
    openai_api_version=os.getenv('AZURE_OPENAI_API_VERSION'),
    azure_deployment=os.getenv('AZURE_OPENAI_DEPLOYMENT_NAME'),
)
response = llm.invoke(promptText)
print(response.content)

Semantic Kernel

Semantic Kernel (C#) では Microsoft.SemanticKernel.Prompty (現時点ではアルファ版) というパッケージが公開されていて、これを使うことで Prompty ファイルを読み込んで Semantic Kernel の関数として実行することが出来ます。

using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.SemanticKernel;

// 構成ファイルを読み込み
var configuration = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json")
    .Build();

// カーネルを作成
var kernel = Kernel.CreateBuilder()
    .AddAzureOpenAIChatCompletion(
        configuration["OpenAI:DeploymentName"]!,
        configuration["OpenAI:Endpoint"]!,
        new AzureCliCredential())
    .Build();

// Prompty ファイルを読み込んで KernelFunction を作成
#pragma warning disable SKEXP0040
var prompty = kernel.CreateFunctionFromPromptyFile("basic.prompty");
#pragma warning restore SKEXP0040

// 実行して結果を表示
var answer = await prompty.InvokeAsync<string>(kernel,
    new KernelArguments
    {
        ["question"] = "クラスの定義方法について教えてください。"
    });
Console.WriteLine(answer);

実行結果

上記のプログラムを動かすと以下のような結果になります。ちゃんと動いてて良き。

    もちろんです!Python でクラスを定義する方法について説明します。
    
    ### 基本的なクラスの定義
    
    Python でクラスを定義するには、`class` キーワードを使用します。以下に基本的なクラスの定義方法を示します。
    
    ```python
    class MyClass:
        def __init__(self, value):
            self.value = value
    
        def display_value(self):
            print(f"The value is {self.value}")
    ```
    
    この例では、`MyClass` というクラスを定義しています。
    
    1. `__init__` メソッド:
       - コンストラクタとも呼ばれ、オブジェクトが生成されるときに呼び出される特別なメソッドです。
       - `self` はインスタンス自身を指します。これにより、インスタンス変数を定義できます。
       - `value` はコンストラクタに渡される引数で、インスタンス変数 `self.value` に代入されます。
    
    2. `display_value` メソッド:
       - `display_value` はインスタンスメソッドであり、インスタンス変数 `self.value` を表示します。
    
    ### クラスのインスタンス化
    
    クラスを使ってオブジェクトを作成する方法は以下の通りです。
    
    ```python
    obj = MyClass(10)
    obj.display_value()  # 出力: The value is 10
    ```
    
    ### 継承
    
    Python では、クラスは他のクラスを継承することができます。以下にその例を示します。
    
    ```python
    class BaseClass:
        def __init__(self, base_value):
            self.base_value = base_value
    
        def display_base_value(self):
            print(f"Base value is {self.base_value}")
    
    class DerivedClass(BaseClass):
        def __init__(self, base_value, derived_value):
            super().__init__(base_value)
            self.derived_value = derived_value
    
        def display_derived_value(self):
            print(f"Derived value is {self.derived_value}")
    ```
    
    この例では、`DerivedClass` が `BaseClass` を継承しています。`super()` 関数を使用して、基底クラスのコンストラクタを呼び出しています。
    
    ### インスタンス化とメソッドの呼び出し
    
    ```python
    derived_obj = DerivedClass(5, 10)
    derived_obj.display_base_value()      # 出力: Base value is 5
    derived_obj.display_derived_value()   # 出力: Derived value is 10
    ```
    
    ### その他のポイント
    
    - クラス変数とインスタンス変数: クラス変数はクラス全体で共有される変数で、インスタンス変数は各インスタンスごとに個別に持つ変数です。
    - プライベート変数とメソッド: 名前の前にアンダースコア `_` をつけることで、非公開(プライベート)として扱うことができます。
    
    ```python
    class MyClass:
        def __init__(self, value):
            self._private_value = value  # プライベート変数
    
        def _private_method(self):
            print("This is a private method")
    ```
    
    これで基本的なクラスの定義方法が理解できたと思います。質問があれば、どうぞお知らせください。

まとめ

Prompty は、結構シンプルな書式で手元の Visual Studio Code で実行することが出来て、さらにはプログラムでそのまま読み込んで実行できます。さらには有名どころのフレームワークでは対応していたりするので、普及してくれたら結構いい感じになるのではと思っています。

今までも Semantic Kernel では Yaml 形式や JSON + テキスト形式などでプロンプトとパラメーターなどを定義する方法がありましたが、それよりも Prompty の方が他のフレームワークでも使えるので好みです。

ということで流行りますように。

ここで書いたコードを書きなおした記事を追加しました。
https://zenn.dev/microsoft/articles/lets-start-prompty

Microsoft (有志)

Discussion