Azure AI Foundry のLLMを使うChainlitのChat画面をAzure Web Appsにデプロイしてみました。その内容を記録しておきます。LLMへの接続はSemantic Kernelを使っています。ユーザの会話履歴保存まではしていません。
以下のサンプルをコードのベースにしています。
添付ファイル対応は以下の記事に書いています。
実装アプリ
普通のChainlitです。モデル切替ができるようにしたため、明示的に何のモデルを使ったかを出力しています。

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

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 アプリを選択。

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

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でいろいろな制御ができます。
例えば、以下の場所でファイルアップロードの挙動を制御しています。
# 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を作成。
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系。
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/ から確認できます。

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でデプロイ先を定義
{
"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...」を選択。

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

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


これで、画面アクセス時に認証が有効になりました。
ログ確認
Azure Portal のメニュー 監視 -> ログストリーム でリアルタイムでログが確認できます。トラブルシュート時にはここを見ていました。

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

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


