【How-to Guides】Azure Apps — Build Your First EchoBot with Python

Kellen
26 min readMar 24, 2024

--

End-to-end 串接整個流程往往是最具挑戰性的部分。本文記錄了在串接 Azure Bot 過程中容易忽略的細節和文件中的關鍵點,避免造成踩雷或撞牆的點。當然也可以通過深入研讀官方文件,了解如何統一管理整個組織的應用程式,並確保資訊安全性,同時確保 App 具有適當的權限分配(確時是要花時間理解並實際作業)。

前置準備作業

花時間看官方文檔以達事半功倍

雖然官方文檔寫的像百科全書,不過先啃一次,比較知道有一些屬於 Azure Platform 的要求與限制!文檔寫的文向出發點會偏 Platform Engineering 的大戰略也可以學到更多 Azure 在設計的毛。

  • 📙 Provision and publish a Bot
  • 先選擇你想要的 Chatbot solution,此次選擇偏好程式碼設計的 Bot Framework SDK 作說明
  • 使用 🐍Python 的 Developer 應知此資訊 — Python 需選擇 MultiTenant 至於身份的認證可以在 config.py 以環境變數注入來取得
  • 應用程式註冊權限限制: 在許多組織中,普通用戶沒有權限在 Azure AD中創建應用程式註冊,這是保留給予租戶管理員的特權。因此,對於開發人員來說,在創建機器人時需要指定預定義的應用程式 ID 和密碼要與管理員合作,以獲取所需的應用程式 ID 和密碼,若是個人戶使用就到 Microsoft Entra ID 搞定權限吧!

開發工具推坑

  • Visual Studio Code:Azure Account + Azure App Service
  • 模擬器下載

輸入 Bot 的 URL,通常為 http://localhost:3978/api/messages,若要然載Bot Framework V4 Emulator 可至 GitHub releases page.

https://github.com/Microsoft/BotFramework-Emulator/blob/master/README.md

範例程式碼及 Local 端測試準備

先準備好要建立 Bot 的目錄並使用虛擬環境來處理相依性套件,會讓你的環境比較乾淨!

# 建立虛擬環境(Python)
python3 -m venv venv
source venv/bin/activate # 啟用虛擬環境

# 下載範本,指向 Microsoft 的 BotBuilder-Samples 儲存庫中的一個 ZIP 檔案
# 而 "cookiecutter" 這個詞通常是指一種模板化的工具
# 可以根據預定義的模板來生成專案的骨架或樣板
# 在這個情況下,它可能指的是該 ZIP 檔案中包含的一個 BotBuilder-Samples 專案的模板
cookiecutter https://github.com/microsoft/BotBuilder-Samples/releases/download/Templates/echo.zip

# 安裝相依性(Python)
pip install -r requirements.txt

# 啟動 Bot(Python)
python app.py

使用 Bot Framework SDK 建立 Bot

介紹此次使用到的 Azure 資源

  • Bot Service:用於建置和部署聊天機器人的託管服務,託管帶來方便並直接提供整套工具和功能,簡化聊天機器人的開發、部署和管理。並支援多種平台和管道,包括微軟的聊天平台(如 Microsoft Teams、Skype、微信等)以及Web、行動應用程式等。開發者更可以透過 Bot Builder SDK、Azure Bot Framework 和 AI 功能等,快速建立聊天機器人
新圖示變成這個喔!
這個是舊圖示了!以前的操作畫面由於加入不少的認知 AI 及通道管理,流程上有一些不同!
  • Web App:Azure 提供的一種託管式雲端服務(PaaS),用於託管和運行 Web 應用程式,底層是使用 Docker Container 作為基礎架構,也提供了與其他 Azure 服務的整合,如 Azure SQL 資料庫、Azure Cosmos DB、Azure Functions 等,以及用於 CI/CD、監控等

本次架構示意圖如下(先忽略後端的 Storage Accounts 們)

https://ithelp.ithome.com.tw/articles/10310712

示意關係,以下的 Miscrosoft 相關的 App ID ClientID, Secret, 都已經移除了

建立資源群組及 Bot Service

👣Step01. Create a resource group

  • 建立一個全新的 resource group
  • 因為是實驗性質,之後要砍資源的時候會很方便

👣Step02. 向 Azure 註冊機器人 建立與設定 Azure Bot

  • 在 EchoBotDemo 的 resource group 中建立 Azure Bot
  • Marketplace 搜尋框輸入 Azure Bot

然後開始設定 Bot 的資訊

Python 的選擇 MultiTenant

Azure Bot Build 結果

👣Step04. 取得 Bot Type + App ID(ClientID)+ Client Secret

  • 回到 Bot 的 menu,並點選 Configuration 有一個應用程式 ID 為Miscrosoft App ID(a.k.a ClientID)> 找地方 📝 起來
  • Bot Type 為 MultiTenant > 找地方 📝 起來
  • 選左側的 Configuration > 再點選 Manage Password 會被跳轉到 App Registration 的頁面,這邊先刪除掉既有的再次建立一個 Client Secret
只會顯示一次

🚧 排障點 — 驗證機器人的應用程式 ID 和密碼

先確認 Bot 配發的 ClietnID 與 Secret 是有效的🐞🐛,建議花時間確認權限是否正常,否則也不用再往下執行了

  • 若要驗證機器人的 ClietnID 和密碼是否有效,請使用cURL發出以下請求,並將 APP_ID 和替換 APP_PASSWORD 為您的機器人的 ClietnID 和密碼
使用 Postman
正常結果
異常結果 — 在開發和配置機器人時,開發人員應與管理員合作,確保正確建立應用程式註冊並取得所需的應用程式ID和密碼。這將確保機器人能夠與 Azure AD 進行身份驗證並存取所需資源

建立與設定 Azure App Service

👣Step05. Create App Service

  • 先至 resource group 下,開始建立一個 Web App

可以查詢 resource group 項下的資源

📙 值得一提的是,在 Azure App 背後都會有 Service Plan 是 Azure App Service的資源模型,它定義了應用程式運行的基礎架構,例如根據負載和資源需求,在多個虛擬機器實例之間自動進行擴展和縮減。

👣Step06. Find the Web App Default domain

  • Web AppDefault domain 找地方 📝 起來

👣Step07. 設定 Web App Configuration

這邊的用途是將相關的環境變數放至在 Web App 上,來讓之後 Bot.py 的程式碼可以引入相關 ClientID, Password 等環境變數

  • 點選左側的 Configuration > 再點選 + New application setting

需建立以下幾個 Application setting

  • MicrosoftAppId: Step04
  • MicrosoftAppPassword: Step04
  • MicrosoftAppTenant: 官方文件有提到 Python Develpoer 這邊留空即可
  • MicrosoftAppType:MultiTenant
  • WEBSITES_PORT: 8000

對應一下官方文件

👣Step08. 設定 Azure Bot 與取得 Webhook URL

設定 Azure Bot 的 Messaging endpoint

👣Step09. 引入官網 Echo Bot demo

  • 調整 app.py 主要是調整 init_func()函數會傳回已建立的 APP 物件,以便在應用程式啟動時使用。透過將應用程式程式碼封裝在 init_func() 函數,組織和管理應用程式的初始化操作,提高程式碼的可執行性和可維護性,使應用程式的啟動過程更加清晰。
  • MS Bot SDK Framework 使用 AIOHTTP ,該套件不僅提供與 requests 套件相同的功能,更支援 asyncio,可以在不使用 multiprocessingthreading 模組,就能夠達到相當高的執行效率,因為只是示例,建議可以使用 DjangoFastAPI 等框架又更完整,來開發 server 端應用程式
  • 🐍MS Bot SDK Framework 程式碼結構

app.py

import sys
import traceback
from datetime import datetime
from http import HTTPStatus

from aiohttp import web
from aiohttp.web import Request, Response, json_response
from botbuilder.core import (
BotFrameworkAdapterSettings,
TurnContext,
BotFrameworkAdapter,
)
from botbuilder.core.integration import aiohttp_error_middleware
from botbuilder.schema import Activity, ActivityTypes
from bots import EchoBot
from config import DefaultConfig

CONFIG = DefaultConfig()

# Create adapter.
# See https://aka.ms/about-bot-adapter to learn more about how bots work.
SETTINGS = BotFrameworkAdapterSettings(CONFIG.APP_ID, CONFIG.APP_PASSWORD)
ADAPTER = BotFrameworkAdapter(SETTINGS)
# Catch-all for errors.
async def on_error(context: TurnContext, error: Exception):
# This check writes out errors to console log .vs. app insights.
# NOTE: In production environment, you should consider logging this to Azure
# application insights.
print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr)
traceback.print_exc()

# Send a message to the user
await context.send_activity("The bot encountered an error or bug.")
await context.send_activity(
"To continue to run this bot, please fix the bot source code.")
# Send a trace activity if we're talking to the Bot Framework Emulator
if context.activity.channel_id == "emulator":
# Create a trace activity that contains the error object
trace_activity = Activity(
label="TurnError",
name="on_turn_error Trace",
timestamp=datetime.utcnow(),
type=ActivityTypes.trace,
value=f"{error}",
value_type="https://www.botframework.com/schemas/error",
)
# Send a trace activity, which will be displayed in Bot Framework Emulator
await context.send_activity(trace_activity)

ADAPTER.on_turn_error = on_error
# Create the Bot
BOT = EchoBot()

# Listen for incoming requests on /api/messages
async def messages(req: Request) -> Response:
# Main bot message handler.
if "application/json" in req.headers["Content-Type"]:
body = await req.json()
else:
return Response(status=HTTPStatus.UNSUPPORTED_MEDIA_TYPE)
activity = Activity().deserialize(body)
auth_header = req.headers["Authorization"] if "Authorization" in req.headers else ""
response = await ADAPTER.process_activity(activity, auth_header, BOT.on_turn)
if response:
return json_response(data=response.body, status=response.status)
return Response(status=HTTPStatus.OK)

# 這邊開始調整如下
def init_func(argv):
APP = web.Application(middlewares=[aiohttp_error_middleware])
APP.router.add_post("/api/messages", messages)
return APP

if __name__ == "__main__":
APP = init_func(None)
try:
web.run_app(APP, host="localhost", port=CONFIG.PORT)
except Exception as error:
raise error

echo_bot.py

它只會將收到的訊息原封不動地回傳給使用者,並在有新成員加入對話時發送歡迎訊息。

# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from botbuilder.core import ActivityHandler, MessageFactory, TurnContext
from botbuilder.schema import ChannelAccount


class EchoBot(ActivityHandler):
async def on_members_added_activity(
self, members_added: [ChannelAccount], turn_context: TurnContext
):
for member in members_added:
if member.id != turn_context.activity.recipient.id:
await turn_context.send_activity("Hello and welcome!")

async def on_message_activity(self, turn_context: TurnContext):
return await turn_context.send_activity(
MessageFactory.text(f"Echo: {turn_context.activity.text}")
)

Bot Framework Service 是 Azure AI Bot Service 的元件,會在使用者 Bot 連線的應用程式與 Bot 之間傳送資訊。 每個通道都可以在他們傳送的活動中包含其他資訊。

此圖說明使用者與回應 Bot 通訊時,可能會交換的兩種活動類型: “交談更新” 和 “訊息”

👣Step10. Web App 新增 startup script

  • 因之後會把 app.py 佈署在 Web App 上方
python3 -m aiohttp.web -H 0.0.0.0 -P 8000 app:init_func
  • python3 -m aiohttp.web:使用 Python 3 執行 aiohttp.web 模組,這是aiohttp 框架中用於啟動 Web 伺服器的模組
  • -H 0.0.0.0:設定 Web 伺服器監聽的IP位址為 0.0.0.0,這表示允許從所有網路介面接受確定的請求
  • -P 8000:設定 Web 伺服器監聽的連接埠為 8000
  • app:init_func:指定要執行的初始化函數,即 init_func
aiohttp 框架不直接使用 WSGI 接口,而是提供了自己的非同步 Web 伺服器實現,即模組 aiohttp.web

🚧 排障點

  • 這邊有過發生異常+1,後來藉助 Web App Diagnose and solve problem 因為是在 Docker 容器中啟動您的應用程序,該容器會監聽來自主機的請求,並使用指定的初始化函數來處理和回應這些請求,看 docker run 的 port 號是 8000 因此都先調整成 8000 port

各個參數的說明:

  • -d:將 Container 設定為在背景運行
  • --expose=8000:將容器內部的連接埠 8000 暴露給主機
  • -e WEBSITES_PORT=8000:指定環境變數 WEBSITES_PORT 的值為 8000,表示應用程式將在該連接埠上監聽建立的請求,這個也在 Step07 定好環境變數
  • python3 -m aiohttp.web -H 0.0.0.0 -P 8000 app:init_func:指定要執行的啟動命令,其中 aiohttp.web 表示使用 aiohttp 框架啟動 Web 伺服器,-H 0.0.0.0 -P 8000 指定要監聽的 IP 位址和端口,app:init_func 指定要執行的初始化函數

部署 Bot Framework 到 App Service

👣Step11. Deploy to Web App

  • 選擇所建立的 Azure App Service > 選擇好資料夾後 Deploy to Web App
部署完成

其他佈署選項 — 透過 Azure Cli 方式

亦可將您的 Bot 應用程式檔封裝在 zip 封存中,然後使用 az webapp deployment source config-zip 命令,將 Bot 程式碼部署至您先前建立的 Azure Web App 之中。

# 切換至要佈署的資料夾中,並將程式碼壓縮至 zip file
$ zip -r bot_code.zip app.py bots config.py requirements.txt
adding: app.py (deflated 57%)
adding: bots/ (stored 0%)
adding: bots/__init__.py (deflated 16%)
adding: bots/__pycache__/ (stored 0%)
adding: bots/__pycache__/__init__.cpython-310.pyc (deflated 20%)
adding: bots/__pycache__/echo_bot.cpython-310.pyc (deflated 40%)
adding: bots/echo_bot.py (deflated 55%)
adding: config.py (deflated 37%)
adding: requirements.txt (stored 0%)

# Azure webapp Deploy
az webapp deployment source config-zip --resource-group "<resource-group-name>" \
--name "<name-of-web-app>" \
--src "python-echo-bot.zip"
# Terminal Log
Getting scm site credentials for zip deployment
Starting zip deployment. This operation can take a while to complete ...
Deployment endpoint responded with status code 202
{
"active": true,
"author": "N/A",
"author_email": "N/A",
"build_summary": {
"errors": [],
"warnings": []
},
"complete": true,
"deployer": "Push-Deployer",
"end_time": "2024-03-18T11:25:42.0943268Z",
"id": "xxxx-9f41-4775-9801-xxxxx",
"is_readonly": true,
"is_temp": false,
"last_success_end_time": "2024-03-18T11:25:42.0943268Z",
"log_url": "https://xxxxxxx.scm.azurewebsites.net/api/deployments/xxxxx/log",
"message": "Created via a push deployment",
"progress": "",
"received_time": "2024-03-18T11:24:14.8308504Z",
"site_name": "xxxxxxx",
"start_time": "2024-03-18T11:24:16.6715418Z",
"status": 4,
"status_text": "",
"url": "https://xxxxxx.scm.azurewebsites.net/api/deployments/xxxxxxx"
}

🚧 排障點

  • 過程中也是把程式碼打包成鏡像並佈署至 Web App 上,排障上若有問題可以從容器打包有否成功、容器的端口映射到主機的端口,底下的兩個地方是可以查找 log、問 Genie、SSH 進入到容器排障
cat /proc/1/cgroup ~ 顯示有關進程 1 的 cgroup 資訊,看到輸出中包含/docker /lxc的路徑

👣Step12. 測試 Test in Web Chat

🐱‍🏍環境設定完成

👣Step13. 投放機器人至指定 Channel — LINE

  • Create a Messaging API channel
  • 取得 Channel Secret > 點選 Basic setting > Channel Secret > 找地方 📝 起來
  • 取得 Channel Access Token,到 Messaging API > 到 Channel access token > 點選 Issue > 找地方 📝 起來

👣Step14. 設定 Azure Bot 並取得 Webhook URL

  • 加入 LINE 到 Channel 並取得 Webhook URL 並注入 Step13 中的 Channel Secret + Channel Access Token

👣Step15. Line 加入 Bot 後測試

👣Step16. 搭建的 Azure Web App 的 CI/CD 流程

因為不是一次性的作業,為了之後還要再 echo_bot.py 加入元素以便可以提高整個的佈署節奏

總結

花了好幾個步驟總算上架 Bot 完成,雖然微軟後來倡導可以從 Power Virtual Agents(PVA)上手,讓偏好無程式碼體驗的公民都可以從 Bot Framework SDK 和工具開始建置機器人,不過 Bot 的調教有滿多工作要處理,準備上架時還是依循以下的程序

  1. 計畫: 在進行任何程式開發之前,先確定 Bot 的目標、功能和使用者需求。檢視 Bot 設計指南以了解最佳實踐,確定 Bot 的需求
  2. 組建: 開始編寫 Bot 程式碼。您可以使用 Azure AI Bot 服務和 Microsoft Bot Framework 提供的 SDK 來開發 Bot,並搭配 CLI 工具來協助建立、管理和測試 Bot
  3. 測試: 在發布之前,確保測試 Bot 的正常運作。您可以使用 Bot Framework 模擬器在本機測試 Bot,也可以在網路上測試 Bot
  4. 發佈:當 Bot 已經準備好在 Web 上使用時,將其部署至 Azure 或自己的 Web 服務。對於有導入 M365 及 Teams 的公司也可以使用 PVA + Power Automate + Azure Bot 來使用。
  5. 連線: 將 Bot 連接到不同的通道,如 Messenger、Slack、Microsoft Teams。Bot Framework 會處理這些通道傳送和接收訊息大部分工作
  6. 評估: 使用 Azure 入口網站中收集的數據來評估 Bot 的效能和功能。這包括服務等級、流量、延遲、整合等數據,以及使用者、訊息和頻道數據的交談層級報告

--

--

Kellen

Backend(Python)/K8s and Container eco-system/Technical&Product Manager/host Developer Experience/早期投入資料創新與 ETL 工作,近期堆疊 Cloud☁️ 解決方案並記錄實作與一些雲端概念💡