本文說明如何使用 Azure OpenAI 多模式模型,在聊天應用程式中產生使用者訊息和上傳影像的回應。 此聊天應用程式範例也包含布建 Azure OpenAI 資源所需的所有基礎結構和設定,以及使用 Azure 開發人員 CLI 將應用程式部署至 Azure Container Apps。
遵循本文中的指示,您將會:
- 部署使用受控識別進行驗證的 Azure 容器聊天應用程式。
- 上傳要作為聊天串流一部分使用的影像。
- 使用 OpenAI 連結庫與 Azure OpenAI 多模式大型語言模型 (LLM) 聊天。
完成本文之後,您可以使用自定義程式代碼開始修改新專案。
注意
本文使用一或多個 AI 應用程式範本 作為本文範例和指引的基礎。 AI 應用程式範本提供您妥善維護且易於部署的參考實作,以協助確保 AI 應用程式的高品質起點。
架構概觀
聊天應用程式是以 Azure 容器應用程式的形式執行。 應用程式會透過 Microsoft Entra ID 使用受控識別,在生產環境中向 Azure OpenAI 進行驗證,而不是 API 金鑰。 在開發期間,應用程式支援多種驗證方法,包括 Azure 開發人員 CLI 認證、API 金鑰和 GitHub 模型,以便在沒有 Azure 資源的情況下進行測試。
應用程式架構依賴下列服務和元件:
- Azure OpenAI 代表我們傳送用戶查詢的 AI 提供者。
- Azure Container Apps 是裝載應用程式的容器環境。
- 受控識別 可協助我們確保頂級安全性,並免除身為開發人員安全地管理秘密的需求。
- 布建 Azure 資源的 Bicep 檔案 ,包括 Azure OpenAI、Azure Container Apps、Azure Container Registry、Azure Log Analytics 和角色型訪問控制 (RBAC) 角色。
- Microsoft AI 聊天通訊協定 提供跨 AI 解決方案和語言的標準化 API 合約。 聊天應用程式符合Microsoft AI 聊天通訊協定。
- Python Quart ,使用
openai套件來產生具有上傳圖像檔的使用者訊息回應。 - 基本 HTML/JavaScript 前端,可透過 ReadableStream 使用 JSON Line 串流來自後端的回應。
成本
嘗試讓此範例中的定價盡可能低,大部分的資源都會使用基本或耗用量定價層。 根據您的預期使用量,視需要改變階層層級。 若要停止產生費用,請在完成本文時刪除資源。
深入瞭解 範例存放庫中的成本。
必要條件
開發容器環境可提供完成本文所需的所有相依性。 您可以在 GitHub Codespaces (瀏覽器中) 或使用 Visual Studio Code 在本機執行開發容器。
若要使用本文,您必須滿足下列必要條件:
Azure 訂用帳戶 - 免費建立一個訂用帳戶
Azure 帳戶許可權 - 您的 Azure 帳戶必須具有
Microsoft.Authorization/roleAssignments/write許可權,例如 使用者存取系統管理員 或 擁有者。GitHub 帳戶
開啟開發環境
使用下列指示來部署預先設定的開發環境,其中包含完成本文所需的所有相依性。
GitHub Codespaces 會使用網頁版 Visual Studio Code 作爲使用者介面,執行由 GitHub 管理的開發容器。 如需最直接的開發環境,請使用 GitHub Codespaces,使得您有已預先安裝的正確開發人員工具和相依性,以便完成本文。
重要
所有 GitHub 帳戶每月都可以免費使用 Codespaces 長達 60 小時,並使用兩個核心實例。 如需詳細資訊,請參閱 GitHub Codespaces 每月包含的儲存體和核心時數。
使用下列步驟,在 GitHub 存放庫的main分支上Azure-Samples/openai-chat-vision-quickstart建立新的 GitHub Codespace。
以滑鼠右鍵按下列按鈕,然後選取新視窗中的 [開啟連結]。 此動作可讓您擁有開發環境和可供檢閱的檔。
在 [ 建立代碼空間] 頁面上,檢閱然後選取 [ 建立新的程式代碼空間]
等候 Codespace 開始。 此啟動程序可能需要幾分鐘的時間。
使用畫面底部終端機中的 Azure 開發人員 CLI 登入 Azure。
azd auth login從終端機複製程式碼,然後將它貼到瀏覽器中。 遵循指示使用 Azure 帳戶進行驗證。
本文中的其餘工作會在此開發容器的內容中進行。
部署和執行
範例存放庫包含聊天應用程式 Azure 部署的所有程式代碼和組態檔。 下列步驟會逐步引導您完成範例聊天應用程式 Azure 部署程式。
部署聊天應用程式至 Azure
重要
為了降低成本,此範例會針對大部分資源使用基本或耗用量定價層。 視需要調整階層,並在完成時刪除資源,以避免產生費用。
針對 Azure 資源布建和原始程式碼部署執行下列 Azure 開發人員 CLI 命令:
azd up使用下表回答提示:
提示 回答 環境名稱 保持簡短和小寫。 新增您的名稱或別名。 例如: chat-vision。 它用來作為資源組名的一部分。訂用帳戶 選取要在其中建立資源的訂用帳戶。 位置 (用於載入) 從清單中選取您附近的位置。 Azure OpenAI 模型的位置 從清單中選取您附近的位置。 如果有與您的第一個位置相同的位置,請選取該位置。 等候應用程式部署。 部署通常需要 5 到 10 分鐘才能完成。
使用聊天應用程式向大型語言模型詢問問題
終端機會在應用程式部署成功之後顯示 URL。
選取標示為
Deploying service web的 URL,以在瀏覽器中開啟聊天應用程式。在瀏覽器中,按兩下 [ 選擇檔案 ] 並選取影像,以上傳影像。
詢問已上傳影像的問題,例如「映像是什麼?」。
答案來自 Azure OpenAI,結果隨即顯示。
探索範例程序代碼
雖然 OpenAI 和 Azure OpenAI 服務依賴一般 Python 用戶端連結庫,但使用 Azure OpenAI 端點時需要小型程式代碼變更。 此範例會使用 Azure OpenAI 多模式模型來產生使用者訊息和上傳影像的回應。
Base64 編碼前端中上傳的影像
上傳的影像必須經過Base64編碼,才能直接作為訊息的一部分使用作為數據URI。
在範例中,檔案標籤script中的src/quartapp/templates/index.html下列前端代碼段會處理該功能。
toBase64箭號函式會使用 readAsDataURL 的FileReader方法,以異步方式讀取上傳的影像檔作為base64編碼字串。
const toBase64 = file => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
});
函 toBase64 式是由表單 submit 事件上的接聽程式所呼叫。
submit事件接聽程式會處理完整的聊天互動流程。 當使用者提交訊息時,會發生下列流程:
- 隱藏「無訊息標題」元素,以顯示交談已經開始。
- 取得上傳的影像檔案並進行 Base64 編碼(若有)
- 在聊天中建立並顯示使用者的訊息,包括上傳的影像
- 準備含有「正在輸入...」指示器的助理訊息容器。
- 將使用者的訊息新增至訊息歷程記錄陣列
- 使用訊息歷史記錄和上下文(包括 Base64 編碼的圖像和檔案名稱)來呼叫 AI 聊天協定用戶端的
getStreamedCompletion()方法 - 處理串流回應區塊,並使用 Showdown.js 將 Markdown 轉換為 HTML
- 處理串流期間的任何錯誤
- 在收到完整回應後新增語音輸出按鈕,以便使用者可以聽到回應
- 清除輸入欄位並將焦點返回以輸入下一則訊息
form.addEventListener("submit", async function(e) {
e.preventDefault();
// Hide the no-messages-heading when a message is added
document.getElementById("no-messages-heading").style.display = "none";
const file = document.getElementById("file").files[0];
const fileData = file ? await toBase64(file) : null;
const message = messageInput.value;
const userTemplateClone = userTemplate.content.cloneNode(true);
userTemplateClone.querySelector(".message-content").innerText = message;
if (file) {
const img = document.createElement("img");
img.src = fileData;
userTemplateClone.querySelector(".message-file").appendChild(img);
}
targetContainer.appendChild(userTemplateClone);
const assistantTemplateClone = assistantTemplate.content.cloneNode(true);
let messageDiv = assistantTemplateClone.querySelector(".message-content");
targetContainer.appendChild(assistantTemplateClone);
messages.push({
"role": "user",
"content": message
});
try {
messageDiv.scrollIntoView();
const result = await client.getStreamedCompletion(messages, {
context: {
file: fileData,
file_name: file ? file.name : null
}
});
let answer = "";
for await (const response of result) {
if (!response.delta) {
continue;
}
if (response.delta.content) {
// Clear out the DIV if its the first answer chunk we've received
if (answer == "") {
messageDiv.innerHTML = "";
}
answer += response.delta.content;
messageDiv.innerHTML = converter.makeHtml(answer);
messageDiv.scrollIntoView();
}
if (response.error) {
messageDiv.innerHTML = "Error: " + response.error;
}
}
messages.push({
"role": "assistant",
"content": answer
});
messageInput.value = "";
const speechOutput = document.createElement("speech-output-button");
speechOutput.setAttribute("text", answer);
messageDiv.appendChild(speechOutput);
messageInput.focus();
} catch (error) {
messageDiv.innerHTML = "Error: " + error;
}
});
使用後端處理映像
在檔案中 src\quartapp\chat.py ,映像處理的後端程式代碼會在設定無密鑰驗證之後開始。
注意
如需如何使用無密鑰連線進行 Azure OpenAI 驗證和授權的詳細資訊,請參閱 開始使用 Azure OpenAI 安全性建置組塊 Microsoft Learn 一文。
驗證設定
該函數在 configure_openai() 應用程序開始服務請求之前設置 OpenAI 客戶端。 它使用 Quart 的 @bp.before_app_serving 裝飾器根據環境變數來配置身份驗證。 這個靈活的系統允許開發人員在不同的環境中工作,而無需更改程式碼。
驗證模式說明
-
本地開發 (
OPENAI_HOST=local): 無需身份驗證即可連接到本地 OpenAI 兼容的 API 服務(如 Ollama 或 LocalAI)。 使用此模式進行測試,無需網際網路或 API 成本。 - GitHub 模型(
OPENAI_HOST=github):使用 GitHub 的 AI 模型市場並通過GITHUB_TOKEN來驗證。 使用 GitHub 模型時,請在模型名稱openai/前加上 (例如openai/gpt-4o)。 此模式可讓開發人員在佈建 Azure 資源之前先嘗試模型。 - 具有 API 金鑰 ( 環境變數)
AZURE_OPENAI_KEY_FOR_CHATVISION:使用 API 金鑰進行驗證。 避免在生產環境中使用此模式,因為 API 金鑰需要手動輪換,如果暴露會帶來安全風險。 將其用於 Docker 容器內的本機測試,而不需要 Azure CLI 認證。 - 使用受控識別的生產環境 (
RUNNING_IN_PRODUCTION=true):使用容器應用程式的受控識別用來進行 Azure OpenAI 的驗證。 建議將此方法用於生產環境,因為它不需要管理密碼。 Azure 容器應用程式會在透過 Bicep 部署期間自動提供受控識別並授與許可權。 -
使用 Azure CLI 進行開發 (預設模式) :用於
AzureDeveloperCliCredential使用本機登入的 Azure CLI 認證向 Azure OpenAI 進行驗證。 此模式簡化了本地開發,而無需管理 API 金鑰。
重要實作詳細數據
- 函數
get_bearer_token_provider()會重新整理 Azure 認證,並將其用作承載權杖。 - Azure OpenAI 端點路徑包含
/openai/v1/符合 OpenAI 用戶端程式庫的需求。 - 日誌記錄顯示哪種身份驗證模式處於作用中狀態。
- 函式是非同步的,以便支援 Azure 憑證操作。
以下是完整的 chat.py身份驗證設置代碼:
@bp.before_app_serving
async def configure_openai():
bp.model_name = os.getenv("OPENAI_MODEL", "gpt-4o")
openai_host = os.getenv("OPENAI_HOST", "github")
if openai_host == "local":
bp.openai_client = AsyncOpenAI(api_key="no-key-required", base_url=os.getenv("LOCAL_OPENAI_ENDPOINT"))
current_app.logger.info("Using local OpenAI-compatible API service with no key")
elif openai_host == "github":
bp.model_name = f"openai/{bp.model_name}"
bp.openai_client = AsyncOpenAI(
api_key=os.environ["GITHUB_TOKEN"],
base_url="https://models.github.ai/inference",
)
current_app.logger.info("Using GitHub models with GITHUB_TOKEN as key")
elif os.getenv("AZURE_OPENAI_KEY_FOR_CHATVISION"):
bp.openai_client = AsyncOpenAI(
base_url=os.environ["AZURE_OPENAI_ENDPOINT"],
api_key=os.getenv("AZURE_OPENAI_KEY_FOR_CHATVISION"),
)
current_app.logger.info("Using Azure OpenAI with key")
elif os.getenv("RUNNING_IN_PRODUCTION"):
client_id = os.environ["AZURE_CLIENT_ID"]
azure_credential = ManagedIdentityCredential(client_id=client_id)
token_provider = get_bearer_token_provider(azure_credential, "https://cognitiveservices.azure.com/.default")
bp.openai_client = AsyncOpenAI(
base_url=os.environ["AZURE_OPENAI_ENDPOINT"] + "/openai/v1/",
api_key=token_provider,
)
current_app.logger.info("Using Azure OpenAI with managed identity credential for client ID %s", client_id)
else:
tenant_id = os.environ["AZURE_TENANT_ID"]
azure_credential = AzureDeveloperCliCredential(tenant_id=tenant_id)
token_provider = get_bearer_token_provider(azure_credential, "https://cognitiveservices.azure.com/.default")
bp.openai_client = AsyncOpenAI(
base_url=os.environ["AZURE_OPENAI_ENDPOINT"] + "/openai/v1/",
api_key=token_provider,
)
current_app.logger.info("Using Azure OpenAI with az CLI credential for tenant ID: %s", tenant_id)
聊天處理程式函式
此 chat_handler() 函式會處理傳送至端點 /chat/stream 的聊天請求。 它會收到具有遵循 Microsoft AI 聊天協議的 JSON 載荷的 POST 請求。
JSON 有效負載包括:
-
messages:對話記錄清單。 每則訊息都有
role(「使用者」或「助理」) 和content(訊息文字)。 -
context:用於處理的額外資料,包括:
-
檔案:Base64 編碼的影像資料 (例如
data:image/png;base64,...)。 - file_name:上傳影像的原始檔案名稱(可用於記錄或識別影像類型)。
-
檔案:Base64 編碼的影像資料 (例如
- temperature (可選):控制回應隨機性的浮點數 (預設值為 0.5)。
處理常式會擷取訊息歷程記錄和影像資料。 如果未上傳任何影像,則影像值為 null,程式碼會處理此案例。
@bp.post("/chat/stream")
async def chat_handler():
request_json = await request.get_json()
request_messages = request_json["messages"]
# Get the base64 encoded image from the request context
# This will be None if no image was uploaded
image = request_json["context"]["file"]
# The context also includes the filename for reference
# file_name = request_json["context"]["file_name"]
建置視覺要求的訊息陣列
函 response_stream() 式會準備傳送至 Azure OpenAI API 的訊息陣列。
@stream_with_context裝飾器在串流回應時保留請求內容。
訊息準備邏輯
-
從對話歷史記錄開始:該函數以 開
all_messages頭,其中包括系統訊息和除最近一條 ()request_messages[0:-1]之外的所有先前訊息。 - 根據影像狀態處理目前的使用者訊息:
-
使用影像:將使用者的訊息格式化為具有文字和image_url物件的多部分內容陣列。 物件
image_url包含 Base64 編碼的影像資料和參數detail。 - 無影像:將使用者的訊息附加為純文字。
-
使用影像:將使用者的訊息格式化為具有文字和image_url物件的多部分內容陣列。 物件
-
參數:
detail設定為「auto」,讓模型根據影像大小在「低」和「高」細節之間進行選擇。 低細節更快、更便宜,而高細節則為複雜影像提供更準確的分析。
@stream_with_context
async def response_stream():
# This sends all messages, so API request may exceed token limits
all_messages = [
{"role": "system", "content": "You are a helpful assistant."},
] + request_messages[0:-1]
all_messages = request_messages[0:-1]
if image:
user_content = []
user_content.append({"text": request_messages[-1]["content"], "type": "text"})
user_content.append({"image_url": {"url": image, "detail": "auto"}, "type": "image_url"})
all_messages.append({"role": "user", "content": user_content})
else:
all_messages.append(request_messages[-1])
注意
如需影像 detail 參數和相關設定的詳細資訊,請參閱「使用已啟用視覺的聊天模型」Microsoft Learn 文章中的 詳細參數設定 一節。
接下來, bp.openai_client.chat.completions 透過 Azure OpenAI API 呼叫取得聊天完成,並串流回應。
chat_coroutine = bp.openai_client.chat.completions.create(
# Azure OpenAI takes the deployment name as the model name
model=bp.model_name,
messages=all_messages,
stream=True,
temperature=request_json.get("temperature", 0.5),
)
最後,回應會串流回用戶端,並處理任何例外狀況的錯誤。
try:
async for event in await chat_coroutine:
event_dict = event.model_dump()
if event_dict["choices"]:
yield json.dumps(event_dict["choices"][0], ensure_ascii=False) + "\n"
except Exception as e:
current_app.logger.error(e)
yield json.dumps({"error": str(e)}, ensure_ascii=False) + "\n"
return Response(response_stream())
前端函式庫和功能
前端使用現代瀏覽器 API 和函式庫來創建互動式聊天體驗。 開發人員可以透過了解這些元件來自訂介面或添加功能:
語音輸入/輸出:自訂 Web 元件會使用瀏覽器的語音 API:
<speech-input-button>:使用 Web Speech API 的SpeechRecognition將語音轉換為文字。 它提供了一個麥克風按鈕,用於監聽語音輸入,並觸發一個帶有轉錄文本的speech-input-result事件。<speech-output-button>:使用SpeechSynthesisAPI 朗讀文字。 它出現在每個助理回應之後,並帶有揚聲器圖示,讓使用者聽到回應。
為什麼要使用瀏覽器 API 而不是 Azure 語音服務?
- 免費 - 完全在瀏覽器中運行
- 即時回應 - 無網路延遲
- 隱私 - 語音數據保留在用戶的設備上
- 不需要額外的 Azure 資源
這些元件位於
src/quartapp/static/speech-input.js和speech-output.js中。圖像預覽: 在分析提交之前在聊天中顯示上傳的圖像以進行確認。 選取檔案時,預覽會自動更新。
fileInput.addEventListener("change", async function() { const file = fileInput.files[0]; if (file) { const fileData = await toBase64(file); imagePreview.src = fileData; imagePreview.style.display = "block"; } });Bootstrap 5 和 Bootstrap 圖示:提供響應式 UI 元件和圖示。 該應用程序使用 Bootswatch 的 Cosmo 主題來打造現代外觀。
基於模板的消息渲染: 使用 HTML
<template>元素進行可重複使用的消息佈局,確保一致的樣式和結構。
要探索的其他範例資源
除了聊天應用程式範例之外,存放庫中還有其他資源可供進一步學習。 請檢視目錄中的下列筆記本 notebooks :
| Notebook | 描述 |
|---|---|
| chat_pdf_images.ipynb | 此筆記本示範如何將 PDF 頁面轉換成影像,並將其傳送至視覺模型以進行推斷。 |
| chat_vision.ipynb | 此筆記本是針對使用應用程式中所使用的視覺模型進行手動實驗。 |
在地化內容:筆記本的西班牙語版本位於目錄中 notebooks/Spanish/ ,為西班牙語開發人員提供相同的實踐學習。 英文和西班牙文筆記本都顯示:
- 如何直接呼叫視覺模型進行實驗
- 如何將 PDF 頁面轉換為圖像進行分析
- 如何調整參數和測試提示
清除資源
清除 Azure 資源
在本文中建立的 Azure 資源會向您的 Azure 訂用帳戶計費。 如果您預計未來不需要這些資源,請將其刪除,以避免產生更多費用。
若要刪除 Azure 資源並移除原始程式碼,請執行下列 Azure 開發人員 CLI 命令:
azd down --purge
清除 GitHub Codespaces
刪除 GitHub Codespaces 環境,可確保您可將您為帳戶取得的每個核心免費時數權利數量最大化。
重要
如需 GitHub 帳戶權利的詳細資訊,請參閱 GitHub Codespaces 每月包含的儲存體和核心時數。
找出您目前執行中的 Codespaces,而其來源為
Azure-Samples//openai-chat-vision-quickstartGitHub 存放庫。開啟 Codespace 的操作功能表,然後選取 [刪除]。
