本指南提供相關操作說明和最佳做法,協助工程師使用 FoodOrderingService.BidiProcessOrder RPC 方法建構餐點訂購體驗。這項雙向即時串流 API 是訂餐 AI Agent 的核心,可在行動應用程式、語音助理、得來速和自助服務機等各種應用程式中,動態地以對話方式接單。
BidiProcessOrder 總覽
BidiProcessOrder 方法會在用戶端應用程式和訂餐 AI Agent 之間建立持續的雙向通訊管道。與標準一元要求和回應 RPC 不同,這種串流方法可實現下列功能:
- 低延遲互動:持續交換資訊,無須重複發出 HTTP 要求,因此不會產生額外負擔。
- 多模態輸入:處理音訊串流 (用於語音訂購)、文字輸入和用戶端事件。
- 即時回覆:代理可以在對話過程中傳回音訊、文字、訂單更新和其他信號。
BidiProcessOrder 無法透過 REST 呼叫。整合項目必須使用連線導向通訊協定:
- gRPC (建議):提供強大且有效率的雙向串流架構。
- WebSocket:適用於因程式設計語言或網路限制,而不適合使用 gRPC 的用戶端或環境。
如需詳細的型別定義,請參閱 BidiProcessOrder API 參考資料。WebSocket 整合會使用這些型別的 JSON 表示法,詳情請參閱 WebSocket 專區。
必要條件
與 BidiProcessOrder 整合前,請先完成下列步驟:
啟用 API:確認專案已啟用訂餐 AI 代理 API。 Google Cloud
bash gcloud services enable foodorderingaiagent.googleapis.com --project=PROJECT_ID驗證:決定驗證方法,並設定所有必要的服務帳戶和 IAM 角色,如「驗證」一文所述。
驗證
如要安全地連線至 BidiProcessOrder RPC,應用程式必須使用 Google Cloud 服務帳戶進行驗證。
1. 設定服務帳戶
- 建立服務帳戶:在 Google Cloud 專案中,建立應用程式將用來向訂餐 AI 代理 API 進行驗證的服務帳戶。請參閱「建立及管理服務帳戶」。
授予 IAM 角色:將必要的 IAM 角色授予這個服務帳戶。呼叫
BidiProcessOrder時必須具備的主要角色為:- 訂餐代理使用者 (
roles/foodorderingaiagent.agentUser): 允許服務帳戶連線至訂餐服務並處理工作階段。
您可以使用 Google Cloud 控制台或
gcloud授予此角色:bash gcloud projects add-iam-policy-binding PROJECT_ID \ --member="serviceAccount:SERVICE_ACCOUNT_EMAIL" \ --role="roles/foodorderingaiagent.agentUser"- 訂餐代理使用者 (
2. 應用程式驗證流程
確切的驗證流程取決於應用程式架構,尤其是用戶端應用程式 (例如行動應用程式、資訊亭軟體) 是直接連線,還是透過您自己的後端連線。
常見情境:驗證面向消費者的用戶端應用程式
這是行動或網頁應用程式的典型模式:
- Client-to-YourAuth:使用者用戶端應用程式 (行動裝置、網路) 會透過您現有的使用者驗證系統進行驗證 (可能是 Firebase 驗證、您自己的 OAuth 伺服器等)。
- 權杖交換:用戶端應用程式驗證使用者後,會向您控管的安全後端服務 (例如「API 權杖服務」) 要求短期權杖。
產生存取權杖:後端服務會使用在步驟 1 中設定的 Google Cloud 服務帳戶主體憑證,為
https://www.googleapis.com/auth/cloud-platform範圍產生標準 OAuth 2.0 存取權杖。您可以使用Google Cloud 驗證用戶端程式庫完成這項操作。- 安全性:用於產生這些權杖的服務帳戶金鑰或憑證,必須安全地儲存在後端並妥善管理。切勿直接向使用者用戶端應用程式公開服務帳戶私密金鑰。請參閱「管理服務帳戶金鑰的最佳做法」。
權杖傳送至用戶端:後端服務會將產生的 Google 存取權杖傳回給用戶端應用程式。
API 呼叫:用戶端應用程式會使用這個 Google 存取權杖,向
BidiProcessOrderRPC 驗證其 gRPC 或 WebSocket 連線。
3. 使用權杖
- gRPC:如果提供服務帳戶憑證,Google gRPC 用戶端程式庫通常會處理權杖重新整理作業,並將權杖納入呼叫中繼資料。
- WebSocket (非瀏覽器):在
Authorization: Bearer TOKEN標頭中加入權杖。 - WebSocket (瀏覽器):如「WebSocket」一節所述,直接瀏覽器 WebSocket 連線無法使用 Authorization 標頭。您需要伺服器端串流 Proxy,才能驗證用戶端與 Google Cloud的連線。
連結至 API
您可以使用 gRPC 用戶端程式庫或 WebSocket 連線建立串流。
gRPC
建議使用 gRPC。您將使用所選語言 (例如 Node.js) 的用戶端程式庫,這些程式庫是以 BidiProcessOrder API 參考資料為基礎。
基本步驟包括:
- 建立 gRPC 通道,連線至訂餐 AI Agent API 端點 (例如
foodorderingaiagent.googleapis.com)。 - 取得
FoodOrderingService的用戶端存根。 - 呼叫
BidiProcessOrder方法,該方法會傳回用於傳送要求和接收回應的串流物件。 - 根據您的用途實作商業邏輯,同時:
- 傳送使用者的音訊、文字和事件輸入內容。
- 處理代理傳送的訊息,包括音訊、文字和事件。
Node.js
const {FoodOrderingServiceClient} = require('@google-cloud/foodorderingaiagent');
const client = new FoodOrderingServiceClient();
// The stream is initialized immediately. You can now write commands and attach listeners.
const stream = client.bidiProcessOrder();
WebSocket
如果是 WebSocket 連線,網址路徑為:
wss://foodorderingaiagent.googleapis.com/ws/google.cloud.foodorderingaiagent.v1beta.FoodOrderingService/BidiProcessOrder/locations/LOCATION
LOCATION:例如us
必要標頭:
Authorization:Bearer TOKEN- 其中TOKEN是為服務帳戶取得的 OAuth 2.0 存取權杖。
訊息格式:
- 用戶端到伺服器:傳送至 API 的訊息 (例如
Config、AudioInput、TextInput、EventInput) 必須是BidiProcessOrderRequestproto 的 JSON 表示法,並以websocket.TextMessage形式傳送。 - 伺服器到用戶端:從 API (
BidiProcessOrderResponse) 收到的訊息會以websocket.BinaryMessage形式傳送,但這些二進位訊息的內容是 JSON 酬載。 - 二進位資料:JSON 酬載中的二進位資料 (例如
customerAudioinAudioInput、agentAudioinAgentAudio) 必須採用 base64 編碼。
Node.js WebSocket 範例
以下範例說明如何在 Node.js 中使用 ws 程式庫,透過 WebSocket 連線並與 API 互動:
const WebSocket = require('ws');
// Replace with your actual values
const location = 'LOCATION';
const projectId = 'PROJECT_ID';
const sessionId = 'SESSION_ID';
const brandId = 'BRAND_ID';
const storeId = 'STORE_ID';
const token = 'OAUTH_TOKEN';
const wsUrl = `wss://foodorderingaiagent.googleapis.com/ws/google.cloud.foodorderingaiagent.v1beta.FoodOrderingService/BidiProcessOrder/locations/${location}`;
const ws = new WebSocket(wsUrl, {
headers: {
'Authorization': `Bearer ${token}`
}
});
ws.on('open', () => {
console.log('Connected to WebSocket');
// 1. Send the required initial Config message
const configRequest = {
config: {
session: `projects/${projectId}/locations/${location}/sessions/${sessionId}`,
store: `projects/${projectId}/locations/${location}/brands/${brandId}/stores/${storeId}`
}
};
// Client-to-server messages are sent as TextMessage
ws.send(JSON.stringify(configRequest));
console.log('Sent Config message');
});
ws.on('message', (data, isBinary) => {
// The documentation specifies that server-to-client messages
// are sent as BinaryMessage containing a JSON payload.
if (isBinary) {
try {
const response = JSON.parse(data.toString('utf8'));
console.log('Received response:', response);
if (response.agentText) {
console.log(`Agent: ${response.agentText.text}`);
}
if (response.agentAudio) {
const audioBytes = Buffer.from(response.agentAudio.agentAudio, 'base64');
console.log(`Received ${audioBytes.length} bytes of agent audio.`);
// Play or process the audio bytes here
}
if (response.endSession) {
console.log('Session ended by agent.');
ws.close();
}
} catch (e) {
console.error('Failed to parse JSON response:', e);
}
}
});
ws.on('close', () => {
console.log('Connection closed');
});
工作階段生命週期
每次呼叫 BidiProcessOrder 都會啟動工作階段。只要串流開啟,工作階段就會保持運作。
1. 啟動 (設定訊息)
- 建立連線後,用戶端傳送的第一則訊息必須是包含
Config訊息的BidiProcessOrderRequest。 Config中的必填欄位:session:用戶端產生的專屬工作階段 ID。格式:projects/PROJECT/locations/LOCATION/sessions/SESSION_ID。store:Store的資源名稱。格式:projects/PROJECT/locations/LOCATION/brands/BRAND/stores/STORE。- 代理程式會使用
store載入適當的選單和設定。
- 代理程式會使用
Node.js
// Send the first message containing Config
stream.write({
config: {
session: client.sessionPath(projectId, location, sessionId),
store: client.storePath(projectId, location, brandId, storeId),
}
});
2. 傳送輸入內容
- 初始
Config之後,用戶端可以傳送一連串BidiProcessOrderRequest訊息,其中包含下列其中一項輸入內容:AudioInput: 原始音訊資料 (通常為 16000 Hz 的 16 位元線性 PCM,不含標頭)。 用於語音互動。TextInput: 使用者的簡訊。EventInput: 事件信號,例如DriveOffEvent(適用於車輛離開時的得來速用途)、CrewInterjectionEvent(適用於人類在對話中接手訂單的任何情況),或OrderStateUpdateEvent(如果訂單是在用戶端修改,例如使用觸控介面)。
Node.js
// Stream user inputs over the active connection
stream.write({textInput: {text: 'Hi, I\'d like to order a cheeseburger.'}});
3. 接收回覆
- 同時,代理程式會傳回一連串的
BidiProcessOrderResponse訊息。客戶必須準備好處理oneof response欄位中的各種回應類型:AgentAudio: 要播放給使用者的合成音訊位元組,用於語音互動。AgentText: 代理程式回應的文字版本。SpeechRecognition: 系統辨識的使用者語音轉錄稿。UpdatedOrderState: 包含客戶Order的完整現狀, 每當服務專員更新時,就會一併更新。使用這個方法更新應用程式的訂單代表。這通常會導致使用者介面或訂單狀態資訊的記錄系統 (例如 POS 系統) 更新。InterruptionSignal: 表示使用者中斷了服務專員的語音。用戶端應立即停止播放任何外送AgentAudio。AgentEvent: 特殊事件,例如RestartOrder, 需要用戶端採取行動。SuggestedOptions:提供使用者可能選取的下一個選項,適合在畫面上顯示。EndSession: 表示工作階段已由服務專員終止 (例如訂單完成、使用者離開或服務專員提升權限)。
Node.js
// Attach event listeners to handle responses sequentially
stream.on('data', (response) => {
if (response.agentAudio) {
console.log(`Received ${response.agentAudio.agentAudio.length} bytes of agent audio.`);
} else if (response.agentText) {
console.log(`Agent: ${response.agentText.text}`);
} else if (response.speechRecognition) {
console.log(`Recognized User Speech: ${response.speechRecognition.transcript}`);
} else if (response.updatedOrderState) {
console.log('Order updated.');
} else if (response.interruptionSignal) {
console.log('User interrupted the agent. Stop playing audio!');
} else if (response.endSession) {
console.log(`Session ended. Type: ${response.endSession.type}, Reason: ${response.endSession.reason}`);
stream.end();
}
});
stream.on('error', (err) => {
console.error('Stream error:', err);
});
4. 關閉串流
- 用戶端或伺服器都可以關閉串流。通常伺服器會使用
EndSession訊息,表示對話結束。收到這則訊息時,用戶端應關閉串流。
處理特定訊息類型
以下各節說明如何處理用戶端呼叫 BidiProcessOrder 時收到的特定回應類型。
AudioInput
- 在音訊可用時,以區塊形式串流音訊。
- 格式:16 位元線性 PCM,取樣率為 16000 Hz。
- 音訊區塊不包含通常位於 WAV 檔案開頭的音訊標頭。
- 如果啟用消除回音功能 (
enable_echo_cancellation位於Config),請提供customer_audio和crew_audio。
UpdatedOrderState
- 每次傳送這則訊息時,都會提供訂單的完整狀態。
以收到的
Order訊息內容,取代訂單的任何本機快取。 - 在
Order項目和修飾符中使用custom_integration_attributes,將Order內容對應至應用程式記錄系統中的對等實體。
InterruptionSignal
- 收到後,請立即停止播放所有「
AgentAudio」,並清除所有緩衝的代理程式音訊。確保使用者打斷代理程式的語音時,對話流程仍自然流暢。
EndSession
- 檢查「
EndType」(例如DRIVE_OFF、AGENT_ESCALATION)。 - 應用程式應正常關閉連線,並適當轉移使用者 (例如,在
AGENT_ESCALATION的情況下通知人工主管,或轉移至訂單確認狀態)。
最佳做法
- 以非同步方式處理訊息:使用執行緒或非封鎖 I/O 同時傳送要求及處理傳入的回應,盡量減少延遲。
- 重新連線邏輯:如果發生網路問題,請實作穩健的重新連線邏輯,並記得傳送具有相同工作階段 ID 的初始
Config訊息,嘗試恢復連線。 - 錯誤處理:監控串流是否有錯誤。gRPC 和 WebSocket 程式庫提供偵測串流關閉或傳輸錯誤的機制。記錄這些事件並妥善處理。
- 音訊緩衝:請謹慎管理音訊緩衝區,並視需要實作緩衝區,確保
AgentAudio順暢播放,並及時傳送AudioInput。決定緩衝區配置時,請仔細考量延遲時間與畫質之間的取捨。 - 工作階段 ID 管理:確保每個不同的訂單/對話都有專屬的工作階段 ID。
- 資源管理:工作階段完成或發生無法復原的錯誤時,請關閉串流並釋出資源。
- 逾時:串流本身可以長時間存在 (預設最多 15 分鐘),但如有需要,請考慮特定狀態的應用程式層級逾時。
整合流程範例 (概念)
- 用戶端應用程式 (例如行動應用程式) 會發起訂單。
- 建立與
BidiProcessOrder的 gRPC/WebSocket 連線。 - 傳送
BidiProcessOrderRequest,並附上Config(工作階段 ID、商店 ID)。 - 接收並播放初始
AgentAudio(例如歡迎訊息)。 - 使用者說話:擷取音訊,並在
AudioInput訊息中串流播放。 - 接收
SpeechRecognition(顯示轉錄稿)、AgentAudio(播放回覆) 和可能的UpdatedOrderState(更新 UI 購物車)。 - 如果使用者中斷,請接收
InterruptionSignal並停止播放。 - 繼續交換音訊或文字輸入內容和服務專員回覆。
- 使用者確認訂單:代理傳送最終
UpdatedOrderState。 - 服務專員傳送
EndSession:客戶關閉串流,並使用最後一次UpdatedOrderState的資料,在 POS 系統中完成訂單。
端對端範例
上述操作說明將串流概念逐一分解,以下是完整的端對端整合流程。
Node.js
在試用這個範例之前,請先按照「使用用戶端程式庫的訂餐 AI 代理快速入門導覽課程」中的 Node.js 設定說明操作。
如要向訂餐 AI 代理進行驗證,請設定應用程式預設憑證。詳情請參閱「為本機開發環境設定驗證機制」。
const {FoodOrderingServiceClient} = require('@google-cloud/foodorderingaiagent');
async function bidiProcessOrderSample(projectId, location, brand, store, sessionId) {
const client = new FoodOrderingServiceClient();
// Create the resource names
const sessionPath = client.sessionPath(projectId, location, sessionId);
const storePath = client.storePath(projectId, location, brand, store);
// Initialize the stream using gRPC. See the WebSocket section for the equivalent WebSocket implementation.
const stream = client.bidiProcessOrder();
// Attach event listeners to handle responses sequentially
stream.on('data', (response) => {
if (response.agentAudio) {
console.log(`Received ${response.agentAudio.agentAudio.length} bytes of agent audio.`);
} else if (response.agentText) {
console.log(`Agent: ${response.agentText.text}`);
} else if (response.speechRecognition) {
console.log(`Recognized User Speech: ${response.speechRecognition.transcript}`);
} else if (response.updatedOrderState) {
console.log('Order updated.');
} else if (response.interruptionSignal) {
console.log('User interrupted the agent. Stop playing audio!');
} else if (response.endSession) {
console.log(`Session ended. Type: ${response.endSession.type}, Reason: ${response.endSession.reason}`);
stream.end();
}
});
stream.on('error', (err) => {
console.error('Stream error:', err);
});
// 1. Send the first message containing Config
stream.write({
config: {
session: sessionPath,
store: storePath,
}
});
// 2. Stream user inputs over the active connection
stream.write({textInput: {text: 'Hi, I\'d like to order a cheeseburger.'}});
}