Semantic Kernel (Python 版) を Azure Functions から呼び出そう！

# はじめに
昨今、生成 AI を活用したアプリケーションの開発のニーズが日に日に高まりつつあることを、パートナー企業の開発者様と会話する中でひしひしと感じる今日この頃です。一方、生成 AI を活用したアプリケーションの開発手法については、まだまだ発展途上で試行錯誤の多い開発プロセスを皆様試行錯誤されているのではないかと考えています。

この記事では、Microsoft が提供する、生成 AI 活用アプリケーションを開発するための SDK である、Semantic Kernel を、これまた Microsoft が提供するサーバーレスの仕組みである、Azure Functions から呼び出す方法について、コードを交えながらご紹介していきます。

:::note warn
本記事中で紹介するコードは、見通しを良くするためにエラー処理などを省略したり、実験を行いやすくするために API の認証処理を意図的に外していたりする部分があります。
本番運用で利用される際につきましては、ご利用者様の責任において、十分なエラー処理やセキュリティ対策、テストを行ったうえで是非ご活用いただければ幸いです。
:::

# Semantic Kernel を Azure Functions から呼び出すための手順
## 事前準備
### 開発環境にインストールが必要な物
- Python
    - 今回大川は 3.11.2 を venv で使いました
- Visual Studio Code
    - 最低限、以下の拡張機能をインストールする必要があります
        - Azure Tools 拡張機能
        - Python 拡張機能
        - Azurite 拡張機能
- Azure Functions Core Tools

https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-develop-vs-code?tabs=node-v3%2Cpython-v2%2Cisolated-process&pivots=programming-language-python

https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-run-local?tabs=windows%2Cisolated-process%2Cnode-v4%2Cpython-v2%2Chttp-trigger%2Ccontainer-apps&pivots=programming-language-python

https://learn.microsoft.com/ja-jp/azure/storage/common/storage-use-azurite?tabs=visual-studio-code%2Cblob-storage

### クラウド側で必要なリソース
- Azure Functions
- Azure OpenAI

Azure Functions については、コード開発の途中で Visual Studio Code にてデプロイが可能ですので、先に作っておく必要はありません。
Azure OpenAI については、下記ドキュメントを参考にして、GTP-35-Turbo モデルのデプロイまで完了させておきます。

https://learn.microsoft.com/ja-jp/azure/ai-services/openai/how-to/create-resource?pivots=web-portal

## 開発手順
### HTTP Trigger Function を作る
まずは Visual Studio Code を開き、Azure のアイコンをクリックするか、<kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd>A</kbd> のキーボードショートカットで、Azure 拡張機能を左ペインから選びます。

:::note warn
ここで Azure 拡張機能が選べない場合、Visual Studio Code の拡張機能としてインストールされていない可能性があります。<kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>X</kbd> のキーボードショートカットで Extensions タブに移動し、Azure Tools 拡張がインストールされているか確認しましょう。
:::

https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-develop-vs-code?tabs=node-v3%2Cpython-v2%2Cisolated-process&pivots=programming-language-python

Azure Tools の画面から、"WORKSPACE" をみつけ、その横にある Azure Functions アイコンをクリックします。
展開されるメニューから、"Create New Project" を選択しましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199379/f61b0a85-babe-42d2-283c-6adeeb172469.png)

その後は画面上部のコマンドバーのところでプロジェクトの設定を聞かれます。
- プロジェクトの保存先
    - 任意のディレクトリで OK です
- プロジェクトの言語
    - Python を選択しましょう
- ホスティングモデルの選択
    - V2 を選択します
- トリガー
    - 今回は HTTP trigger を選びます
- 関数名
    - 任意で OK です
- 認証レベル
    - 今回は Anonymous を選択します
    - 本番環境で使うのでであれば、Fuction レベルを選び、呼び出し時に Key が渡ってこない場合には関数呼び出しが成功しないようにする方が良いでしょう
- プロジェクトの開き方
    - ここは任意で OK です

ここまでの手順を進めると、HTTP 呼び出しに応答する Azure Function のテンプレートコードが VS Code のプロジェクト内に設定されているはずです。

### Semantic Kernel をプロジェクトで利用可能にする
Semantic Kernel をプロジェクトから利用可能にするために、まずは Python のパッケージをインストールしましょう。VS Code のターミナルに以下のようにコマンドを入力して、Semantic Kernel のパッケージをインストールします。

```terminal
pip install semantic-kernel
```

### Semantic Kernel を呼び出すコードを書いてみる
まずは、Semantic Kernel を Python にインポートします。

既にある HTTP Function のコードの冒頭の import 部分を以下の様に書き換えましょう。

```python
import os
import logging
import azure.functions as func

# Semantic Kernel のインポート
import semantic_kernel as sk
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.core_skills import TimeSkill
```

続いて、HTTP Function が定義されているコードの一番末尾に以下の様に Semantic Kernel を呼び出すための関数を定義します。

```python
async def callsk(input):
    kernel = sk.Kernel()
    time = kernel.import_skill(TimeSkill())
    result = await kernel.run_async(time["today"])

    return result
```

また、非同期メソッドを中で使った関数を呼び出すため、Azure Functions の本体も async にする必要があります。http_trigger 関数の冒頭に async キーワードを付けます。

```python
async def http_trigger(req: func.HttpRequest) -> func.HttpResponse:
```

そして、http_trigger 関数の return の行を以下の様に置き換えましょう。

```python
result = await callsk(name)
return func.HttpResponse(f"Result : {result}")
```

これで、Semantic Kernel を内部で呼び出す Azure Function のコードが完成したはずです。

### 試しにローカル実行してみる
ここで一旦ローカル実行を試してみます。

Functions のプロジェクトテンプレートで自動生成された、local.settings.json というファイルの "Values" という要素配下に、以下の内容が無ければ追記します。

```json
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
```

続いて、<kbd>F1</kbd> キーを押してコマンドパレットで "Azurite: Start" と検索して、Azure Storage エミュレーターを実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199379/b2422cea-4d98-4082-ddd1-9fd94c46cb3a.png)

その後、左ペインを "RUN AND DEBUG" に切り替えます。初回のデバッグ実行時には、デバック実行用のプロジェクト設定がされていないので "create a launch.json" ボタンを押し、デバッグ実行の設定用のファイルを作成します。

その後 Attach to Python Function ボタンをクリックすると、デバッグ実行が始まりターミナルに URL が表示されるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199379/95158be4-1cd4-ebf6-a44a-0ad623ebc61b.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199379/b6cddb03-1217-e33f-8bde-f4c03e7f304c.png)

URL が表示されたら、その URL をブラウザーで開いてみます。その結果として "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response." という文字列が表示されれば、一旦 Azure Functions はローカル実行が出来る状態になっていることを確認できます。

続いて、先ほどコピーした URL の末尾に "?name=test" という文字列を追加してみます。すると応答が今日の日付に変わったはずです。この日付は、先ほど追記したコードの中で、Semantic Kernel の TimeSkill というスキルを使って取得したものですので、Functions の中で Semantic Kernel が呼び出せていることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199379/17873c1a-2f4c-d9e8-07e8-b7945a1214ca.png)

### Semantic Function を実装してみる
ここまでで基本の設定をしてきましたが、実はまだ Azure OpenAI を呼び出すところまではいっていません。そこでこれから、Azure OpenAI を呼び出すための設定を追加していきます。

まずは、Semantic Kernel の内部で使うための、自然言語で定義した関数である、Semantic Function を実装していきます。

VS Code の左ペインから EXPLORER を開きプロジェクトのルートのフォルダ配下に以下のような形でフォルダを掘っていきます

`
プロジェクトのルートフォルダ > plugins > WriterPlugin > ShortPoem
`

そしてShortPoem フォルダの中に以下の二つのファイルを作ります。内容はとりあえずコピー&ペーストで OK です。

```config.json
{
  "schema": 1,
  "type": "completion",
  "description": "Turn a scenario into a short and entertaining poem.",
  "completion": {
    "max_tokens": 200,
    "temperature": 0.5,
    "top_p": 0.0,
    "presence_penalty": 0.0,
    "frequency_penalty": 0.0
  },
  "input": {
    "parameters": [
      {
        "name": "input",
        "description": "The scenario to turn into a poem.",
        "defaultValue": ""
      }
    ]
  }
}

```

```skprompt.txt
Generate a short funny poem or limerick to explain the given event.
Be creative and be funny.
Let your imagination run wild.

Event:{{$input}}
```

見覚えのないファイルの様に見えるかもしれませんが、skprompt.txt が Semantic Kernel から LLM を呼び出すときの関数の処理の内容を書いた実装本体で、config.json が関数の定義に当たるファイルであるということだけ、いまのところは覚えておけばいいと思います。（LLM 時代のプログラミングでは、関数をプログラムコードで定義するだけではなく、自然言語でも定義出来るぞ、というのが結構面白いポイントではないでしょうか。）

https://learn.microsoft.com/en-us/semantic-kernel/ai-orchestration/plugins/semantic-functions/serializing-semantic-functions?tabs=Csharp

### Semantic Kernel から OpenAI を呼び出す
続いて、この OpenAI を呼び出すためのコードを追記していきます。

まずは、OpenAI の資格情報を local.settings.json ファイルの "Values" 要素に追記します。

```json
    "OPENAI_API_BASE" : "https://<あなたがデプロイした Azure OpenAI の名前>.openai.azure.com/",
    "OPENAI_API_KEY" : "<Azure OpenAI の KEY>",
    "AZURE_OPENAI_DEPLOYMENT_NAME" : "<Azure OpenAI 内でデプロイした gpt-35-turbo モデルのデプロイ名>"
```

その後、function_app.py の import 行の下に資格情報を読み込むためのコードを追記します。

```function_app.py
AZURE_OPENAI_ENDPOINT = os.environ["OPENAI_API_BASE"]
AZURE_OPENAI_KEY = os.environ["OPENAI_API_KEY"]
AZURE_OPENAI_DEPLOYMENT_NAME = os.environ["AZURE_OPENAI_DEPLOYMENT_NAME"]
```

更に、callsk 関数を以下の様に置き換えます。

```function_app.py
async def callsk(input):
    kernel = sk.Kernel(log=sk.NullLogger())
    # ここで Azure OpenAI への接続情報を Semantic Kernel に設定する
    kernel.add_chat_service(
        "chat_completion",
        AzureChatCompletion(
            deployment_name=AZURE_OPENAI_DEPLOYMENT_NAME,
            endpoint=AZURE_OPENAI_ENDPOINT,
            api_key=AZURE_OPENAI_KEY,
        ),
    )
    plugins_directory = "./plugins"

    # Writer Plugin を指定したプラグインディレクトリから読み込む
    writer_plugin = kernel.import_semantic_skill_from_directory(
        plugins_directory, "WriterPlugin"
    )

    # ここで、Semantic Kernel 経由で ShortPoem というプラグインを使って、Azure OpenAI を呼び出す。
    result = await kernel.run_async(
        writer_plugin["ShortPoem"],input_str=input
    )

    return result
```

これで、Semantic Kernel から Azure OpenAI を呼び出す設定は完了です。

### ローカル実行をして、意図したとおりに動作しているか確認する
先ほどと同じようにデバッグ実行して、URL のクエリ文字列に `name=Microsoft Azure` と追加した URL にアクセスしてみましょう。
すると、以下の様に英語の謎ポエムが表示されると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199379/67dced9f-d60a-cc77-69b0-7675d81330ce.png)

これは、Semantic Kernel が先ほど追加した WriterPlugin の設定に従って、Azure OpenAI を呼び出し、引数に渡した "Microsoft Azure" というお題に対してポエムを書いてくれた結果です。

### Semantic Function をちょっと弄ってみる。
さて、これで一応 Azure Functions から Semantic Kernel を呼び出す方法としては以上なのですが、もうちょっと遊んでみましょう。

例えば、skprompt.txt を以下の様に書き換えてみます。
元々の Semantic Function の内容に「応答は日本語で返してね！」というプロンプトを追加したわけです。

```skprompt.txt
Generate a short funny poem or limerick to explain the given event.
Be creative and be funny.
Let your imagination run wild.

Result must be in Japanese only.
With no translations.

Event:{{$input}}
```

そしてデバッグ実行をするとこんな結果になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199379/0e9e1327-78eb-6f55-24c1-702f6c0b0ba5.png)

自然言語でプロンプトの形で関数定義をして、それがちゃんと実行結果に反映されていることがわかります！

### デプロイの準備をする
幾つかデプロイのための準備をする必要があります。

まずは、requirements.txt に必要なパッケージのリストを追記します。今回は、Semantic Kernel を追加で pip でインストールして import しましたので、その設定だけ追加です。

```requirements.txt
azure-functions
semantic-kernel
```



### Azure にデプロイする
遂に Azure 上にデプロイです！

まずは、VSCode の Azure 拡張機能から、Azure にサインインします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199379/b5c17235-fe07-d615-f925-4ca8a72c92f9.png)

無事ログインが出来たら、[リソース] 領域の "+" アイコンを選択し、[Azure に関数アプリを作成] オプションを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199379/bd459a0e-5806-4ebc-1de4-6efccf9a98f9.png)

コマンドバーのプロンプトで以下の内容を聞かれると思いますので、適切な内容を設定します。

- デプロイ先のサブスクリプション
    - 任意の自分が管理できるサブスクリプションを設定します
- 関数名
    - URL に含まれる値になるので、グローバルでユニークに値を設定する必要があります
- ランタイムスタック
    - アプリ開発に使っているランタイムのバージョンを選びます
- ロケーション
    - デプロイ先ロケーションを選びます。特にこだわりがなければ、"Japan East" もしくは "Japan West" で良いと思います

デプロイが完了したら、左ペインの Azure の RESOURCES のなかから、先ほどデプロイした Functions を確認して、[Application Settings] > [Upload Local Settings] を選択し、アプリケーション設定をクラウド環境にコピーします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199379/cfa565d4-ee7b-f647-3cfa-7359cd3ddd4a.png)

ここまで構成したら、あとはブラウザーから `https://<デプロイした Azure Function 名>.azurewebsites.net/api/http_trigger?name=Microsoft%20Azure` のような形の URL でアクセスすることで、Semantic Kernel からの応答を得られることを確認できるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199379/49adcb5f-3a1b-42c7-43c1-ec4aec6c6a53.png)


## この後やること
あとは、ここまで作ったプロジェクトに必要な機能を追加していっていただければ、Python の Azure Functions から Semantic Kernel を使って Azure OpenAI にアクセスするような機能を持つ REST API を自由に作れます！

まだ、Python 版は Coming Soon な状況ではありますが、今後 Semantic Kernel を組み込んだ Azure Functions を OpenAI Plugin として公開可能な手順が出てくるはずです。

https://learn.microsoft.com/en-us/semantic-kernel/agents/plugins/openai-plugins

そうなれば、自分なりの OpenAI Plugin や Copilot を作ることも、比較的簡単にできる様になりそうですね！

今後の Semantic Kernel の発展に期待しつつ、是非色々試してみてください！

# 補足
## サンプルコード
今回の手順でご紹介した内容を反映済みの Azure Functions のサンプルコードは以下の GitHub リポジトリに置いてありますので、もし途中で手順がわからなくなってしまったりしたら参考にしてみてください。

https://github.com/tokawa-ms/sk_demo_func_py

## 参考文献
Semantic Kernel についてのドキュメントは以下の URL からご確認ください！

https://learn.microsoft.com/ja-jp/semantic-kernel/

## 連動企画
この記事は、"Microsoft Top Partner Engineer's Advent Calendar 2023" の連動企画としてお送りいたしました！

https://japan.zdnet.com/extra/ms_202311/35211313/

ご一緒してくださっている、日本のトップパートナーエンジニアの皆さん、ありがとうございました！