1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ChainlitをAzure Web AppsにデプロイしてLLM使用

Last updated at Posted at 2025-07-07

Azure AI Foundry のLLMを使うChainlitのChat画面をAzure Web Appsにデプロイしてみました。その内容を記録しておきます。LLMへの接続はSemantic Kernelを使っています。ユーザの会話履歴保存まではしていません。
以下のサンプルをコードのベースにしています。

添付ファイル対応は以下の記事に書いています。

実装アプリ

普通のChainlitです。モデル切替ができるようにしたため、明示的に何のモデルを使ったかを出力しています。
image.png

プロンプト入力の歯車マークをクリックするとこんな選択ができるようにしています。
image.png

o3 も使えます。
image.png

Step

0. 前提

種類 Version 備考
OS Ubuntu22.04.5 LTS WSL2で動かしています
Python 3.13.2
Poetry 2.1.3
VS Code 1.101.2

Application Insights を有効にするにはPython3.11以前が条件なので、注意してください。Application Insightsが有効だとOpenTelemetry関連のパッケージがAzure指定のバージョンでインストールされ、それが他パッケージと適合せずにエラー起きたことがありました。

VSCode 拡張

種類 Version 備考
Azure App Service 0.26.2

Python パッケージ

種類 Version 備考
chainlit 2.5.5
openai 1.93.0
python-dotenv 1.1.1
semantic-kernel 1.34.0

その他

  • Azure上にサブスクリプションとリソースグループ作成済
  • ローカルのプロジェクトのディレクトリ作成、Poetryの初期設定済

1. Web Apps 作成

Azure Portalから作成します。App Services から Web アプリを選択。
image.png

基本は以下の内容を選択。価格プランはFreeでも行けるのかもしれませんが、後でエラー起きると面倒だと思いBasic B1にしています。あとはデフォルト設定のまま作成。
image.png

2. Chainlit 初期設定

以下のコマンドでChainlitの初期設定をします。

$ poetry run chainlit init

2025-07-06 16:49:17 - Created default config file at /home/fukuhara/repositories/chainlit-sample/.chainlit/config.toml
2025-07-06 16:49:17 - Created default translation directory at /home/fukuhara/repositories/chainlit-sample/.chainlit/translations
2025-07-06 16:49:17 - Created default translation file at /home/fukuhara/repositories/chainlit-sample/.chainlit/translations/ta.json
2025-07-06 16:49:17 - Created default translation file at /home/fukuhara/repositories/chainlit-sample/.chainlit/translations/ja.json
2025-07-06 16:49:17 - Created default translation file at /home/fukuhara/repositories/chainlit-sample/.chainlit/translations/nl.json
2025-07-06 16:49:17 - Created default translation file at /home/fukuhara/repositories/chainlit-sample/.chainlit/translations/mr.json
2025-07-06 16:49:17 - Created default translation file at /home/fukuhara/repositories/chainlit-sample/.chainlit/translations/kn.json
2025-07-06 16:49:17 - Created default translation file at /home/fukuhara/repositories/chainlit-sample/.chainlit/translations/he-IL.json
2025-07-06 16:49:17 - Created default translation file at /home/fukuhara/repositories/chainlit-sample/.chainlit/translations/gu.json
2025-07-06 16:49:17 - Created default translation file at /home/fukuhara/repositories/chainlit-sample/.chainlit/translations/bn.json
2025-07-06 16:49:17 - Created default translation file at /home/fukuhara/repositories/chainlit-sample/.chainlit/translations/hi.json
2025-07-06 16:49:17 - Created default translation file at /home/fukuhara/repositories/chainlit-sample/.chainlit/translations/en-US.json
2025-07-06 16:49:17 - Created default translation file at /home/fukuhara/repositories/chainlit-sample/.chainlit/translations/te.json
2025-07-06 16:49:17 - Created default translation file at /home/fukuhara/repositories/chainlit-sample/.chainlit/translations/zh-CN.json
2025-07-06 16:49:17 - Created default translation file at /home/fukuhara/repositories/chainlit-sample/.chainlit/translations/ml.json
2025-07-06 16:49:19 - Config file already exists at /home/fukuhara/repositories/chainlit-sample/.chainlit/config.toml

この処理によって作成される.chainlit/config.tomlでいろいろな制御ができます。
例えば、以下の場所でファイルアップロードの挙動を制御しています。

.chainlit/config.toml 抜粋
# Authorize users to spontaneously upload files with messages
[features.spontaneous_file_upload]
    enabled = true
    # Define accepted file types using MIME types
    # Examples:
    # 1. For specific file types:
    #    accept = ["image/jpeg", "image/png", "application/pdf"]
    # 2. For all files of certain type:
    #    accept = ["image/*", "audio/*", "video/*"]
    # 3. For specific file extensions:
    #    accept = { "application/octet-stream" = [".xyz", ".pdb"] }
    # Note: Using "*/*" is not recommended as it may cause browser warnings
    accept = ["*/*"]
    max_files = 20
    max_size_mb = 500

3. Web Apps プログラム実装

3.1. 主スクリプト

ルートディレクトリ直下にapp.pyを作成。

app.py
import os

import chainlit as cl
import semantic_kernel as sk
from chainlit.input_widget import Select
from dotenv import load_dotenv
from semantic_kernel.connectors.ai import FunctionChoiceBehavior
from semantic_kernel.connectors.ai.open_ai import (
    AzureChatCompletion,
    OpenAIChatPromptExecutionSettings,
)
from semantic_kernel.contents import ChatHistory

# 何に使われているか未確認
request_settings = OpenAIChatPromptExecutionSettings(
    function_choice_behavior=FunctionChoiceBehavior.Auto(
        filters={"excluded_plugins": ["ChatBot"]}
    )
)

load_dotenv(override=True)
MODELS = ["gpt-4.1-mini", "gpt-4o-mini", "o3"]
ENDPOINT = os.getenv("AZURE_OPENAI_ENDPOINT")
deployment = os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME")
API_KEY = os.getenv("AZURE_OPENAI_KEY")


@cl.on_chat_start
async def on_chat_start():
    # Setup Semantic Kernel
    kernel = sk.Kernel()
    ai_service = AzureChatCompletion(
        endpoint=ENDPOINT,
        deployment_name=MODELS[0],
        api_key=API_KEY,
        api_version="2024-12-01-preview",
    )
    kernel.add_service(ai_service)

    # Instantiate and add the Chainlit filter to the kernel
    # This will automatically capture function calls as Steps
    cl.SemanticKernelFilter(kernel=kernel)

    await cl.ChatSettings(
        [
            Select(
                id="model_choice",
                label="使用するモデルを選択してください",
                values=MODELS,
                initial_index=0,
            )
        ]
    ).send()

    # モデルの初期設定
    cl.user_session.set("model_name", MODELS[0])
    cl.user_session.set("kernel", kernel)
    cl.user_session.set("chat_history", ChatHistory())


# モデル設定変更時のイベントハンドラ
@cl.on_settings_update
async def on_settings_update(settings):
    model_name = settings.get("model_choice")
    cl.user_session.set("model_name", model_name)

    kernel = cl.user_session.get("kernel")
    kernel.remove_all_services()
    ai_service = AzureChatCompletion(
        endpoint=ENDPOINT,
        deployment_name=model_name,
        api_key=API_KEY,
        api_version="2024-12-01-preview",
    )
    kernel.add_service(ai_service)
    cl.user_session.set("kernel", kernel)
    cl.user_session.set("model_name", model_name)


@cl.on_message
async def on_message(message: cl.Message):
    print(f"{message.content=}")
    kernel = cl.user_session.get("kernel")  # type: sk.Kernel
    model_name = cl.user_session.get("model_name")
    chat_history = cl.user_session.get("chat_history")  # type: ChatHistory
    ai_service = kernel.get_service()

    # メッセージが邪魔ならコメントアウト
    await cl.Message(content=f"モデル {model_name} を使用").send()

    chat_history.add_user_message(message.content)

    # Create a Chainlit message for the response stream
    answer = cl.Message(content="")

    async for msg in ai_service.get_streaming_chat_message_content(
        chat_history=chat_history,
        user_input=message.content,  # 必要か不明
        settings=request_settings,
        kernel=kernel,
    ):
        if msg.content:
            await answer.stream_token(msg.content)

    # Add the full assistant response to history
    chat_history.add_assistant_message(answer.content)

    # Send the final message
    await answer.send()

3.2. 環境変数

.envに環境変数を設定。今回はAzure OpenAI系。

.env
AZURE_OPENAI_ENDPOINT="https://<name>.openai.azure.com/"
AZURE_OPENAI_KEY="<your key>"

3.3. ローカル PCで確認

Chainlitを実行して、ローカルPCで確認します。

poetry run chainlit run app.py -w

デフォルトでは http://localhost:8000/ から確認できます。
image.png

4. Web Apps デプロイ

4.1. スタートアップ

Azure Portalのメニュー 設定 -> 構成から「スタートアップ コマンド」に以下を設定。

python -m chainlit run app.py --host 0.0.0.0 --port 8000

4.2. .vscode/settings.json設定

ディレクトリ.vscodeを作成して、配下にsettings.jsonを作成
前にVS Code で自動で作ってくれたやつからコピペしたものに"files{,/**}", ".files{,/**}", の2つのエントリを追加。appService.zipIgnorePattern では.gitignoreのようにWeb Appにデプロイしないファイルを定義しています。Chainlitは.filesにChat画面での添付ファイルをためるため、Web Appsにデプロイしないように追加(基本は添付ファイルは消えるのですが、強制終了時など消えないこともあります)。
あと、appService.defaultWebAppToDeployでデプロイ先を定義

.vscode/settings.json
{
    "appService.zipIgnorePattern": [
        "__pycache__{,/**}",
        "*.py[cod]",
        "*$py.class",
        ".Python{,/**}",
        "build{,/**}",
        "develop-eggs{,/**}",
        "dist{,/**}",
        "downloads{,/**}",
        "eggs{,/**}",
        ".eggs{,/**}",
        "files{,/**}",
        ".files{,/**}",
        "lib{,/**}",
        "lib64{,/**}",
        "parts{,/**}",
        "sdist{,/**}",
        "var{,/**}",
        "wheels{,/**}",
        "share/python-wheels{,/**}",
        "*.egg-info{,/**}",
        ".installed.cfg",
        "*.egg",
        "MANIFEST",
        ".env{,/**}",
        ".venv{,/**}",
        "env{,/**}",
        "venv{,/**}",
        "ENV{,/**}",
        "env.bak{,/**}",
        "venv.bak{,/**}",
        ".vscode{,/**}"
    ],
    "appService.defaultWebAppToDeploy": "/subscriptions/<subscription id>/resourceGroups/<resource group name>/providers/Microsoft.Web/sites/<web apps name>",
    "appService.deploySubpath": "."
}

4.3. requirements.txt

Poetryでrequirements.txt作成

poetry export -f requirements.txt --output requirements.txt --without-hashes

4.4. デプロイ

コマンドパレットで「Azure App Service: Deploy to Web App...」を選択。
image.png

Yes を選択
image.png

Deploy を選択
image.png

その他

認証追加

Azure Portalのメニュー 設定 -> 認証 から「ID プロバイダーを追加」ボタンをクリック。
image.png

IDプロバイダーにMicrosoftを選び、他はすべてデフォルト項目のまま
image.png
image.png

これで、画面アクセス時に認証が有効になりました。

ログ確認

Azure Portal のメニュー 監視 -> ログストリーム でリアルタイムでログが確認できます。トラブルシュート時にはここを見ていました。
image.png

デプロイ時のログは、メニュー デプロイ -> デプロイセンターで 「ログ」タブから確認できます。
image.png

また、メニュー 開発ツール -> 高度なツール で 「移動」をクリックしてSSH接続ができたり、サーバ上のファイルの状態を確認できたりします。非常に便利です。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?