Ce guide fournit des instructions et des bonnes pratiques aux ingénieurs qui créent des expériences de commande de repas avec la méthode RPC FoodOrderingService.BidiProcessOrder.
Cette API de streaming bidirectionnel en temps réel est au cœur de l'agent IA de commande de repas. Elle permet de prendre des commandes de manière dynamique et conversationnelle dans diverses applications telles que les applications mobiles, les assistants vocaux, les services au volant et les bornes.
Présentation de BidiProcessOrder
La méthode BidiProcessOrder établit un canal de communication bidirectionnel persistant entre votre application cliente et l'agent IA de commande de repas. Contrairement aux RPC de requête et de réponse unaires standards, cette approche de streaming permet :
- Une interaction à faible latence : échange continu d'informations sans la surcharge de requêtes HTTP répétées.
- Une entrée multimodale : gestion des flux audio (pour les commandes vocales), des entrées de texte et des événements côté client.
- Des réponses en temps réel : l'agent peut renvoyer de l'audio, du texte, des mises à jour de commandes et d'autres signaux au fur et à mesure de la conversation.
BidiProcessOrder ne peut pas être appelé à l'aide de REST. Les intégrations doivent utiliser un protocole orienté connexion :
- gRPC (recommandé) : fournit un framework robuste et efficace pour le streaming bidirectionnel.
- WebSocket : convient aux clients ou aux environnements où gRPC n'est pas adapté en raison de contraintes liées au langage de programmation ou au réseau.
Consultez la documentation de référence de l'API BidiProcessOrder pour obtenir des définitions de types détaillées. Les intégrations WebSocket utilisent des représentations JSON de ces types, comme décrit dans la section WebSocket.
Prérequis
Avant de procéder à l'intégration avec BidiProcessOrder :
Activez l'API : assurez-vous que l'API de l'agent IA de commande de repas est activée dans votre Google Cloud projet.
bash gcloud services enable foodorderingaiagent.googleapis.com --project=PROJECT_IDAuthentification : choisissez votre approche d'authentification et configurez les comptes de service et les rôles IAM nécessaires, comme décrit dans la section Authentification.
Ingestion de menus : un menu valide doit être ingéré et associé à un
Store. Pour en savoir plus, consultez Intégrer des données de menu.
Authentification
Pour vous connecter de manière sécurisée au BidiProcessOrder RPC, votre application doit
s'authentifier à l'aide d'un Google Cloud compte de service.
1. Configurer un compte de service
- Créez un compte de service : dans votre Google Cloud projet, créez un compte de service que votre application utilisera pour s'authentifier auprès de l'API de l'agent IA de commande de repas. Consultez Créer et gérer des comptes de service.
Attribuez des rôles IAM : attribuez les rôles IAM nécessaires à ce compte de service. Le rôle principal requis pour appeler
BidiProcessOrderest le suivant :- Utilisateur de l'agent de commande de repas (
roles/foodorderingaiagent.agentUser) : permet au compte de service de se connecter au service de commande et de traiter les sessions.
Vous pouvez attribuer ce rôle à l'aide de la Google Cloud console ou
gcloud:bash gcloud projects add-iam-policy-binding PROJECT_ID \ --member="serviceAccount:SERVICE_ACCOUNT_EMAIL" \ --role="roles/foodorderingaiagent.agentUser"- Utilisateur de l'agent de commande de repas (
2. Flux d'authentification de l'application
Le flux d'authentification exact dépend de l'architecture de votre application, en particulier si l'application cliente (par exemple, une application mobile ou un logiciel de borne) se connecte directement ou via votre propre backend.
Scénario courant : authentifier une application cliente en contact avec les clients
Il s'agit d'un modèle typique pour les applications mobiles ou Web :
- Client-to-YourAuth: : l'application cliente de l'utilisateur final (mobile, Web) s'authentifie auprès de votre système d'authentification utilisateur existant (il peut s'agir de Firebase Authentication, de votre propre serveur OAuth, etc.).
- Échange de jetons : après avoir authentifié l'utilisateur, l'application cliente demande un jeton éphémère à un service de backend sécurisé que vous contrôlez (par exemple, un "service de jetons d'API").
Génération de jetons d'accès : votre service de backend, à l'aide des identifiants du compte de service principal configuré à l'étape 1, génère un jeton d'accès OAuth 2.0 standard pour le
https://www.googleapis.com/auth/cloud-platformchamp d'application. Google Cloud Pour ce faire, vous pouvez utiliser les bibliothèques clientes d'authentification.Google Cloud- Sécurité : les clés de compte de service ou les identifiants utilisés pour générer ces jetons doivent être stockés et gérés de manière sécurisée sur votre backend. N'exposez jamais directement les clés privées de compte de service aux applications clientes de l'utilisateur final. Consultez Bonnes pratiques de gestion des clés de compte de service.
Jeton vers le client : votre service de backend renvoie le jeton d'accès Google généré à l'application cliente.
Appel d'API : l'application cliente utilise ce jeton d'accès Google pour authentifier sa connexion gRPC ou WebSocket au RPC
BidiProcessOrder.
3. Utiliser le jeton
- gRPC : les bibliothèques clientes gRPC Google gèrent généralement l'actualisation et l'inclusion des jetons dans les métadonnées d'appel lorsqu'elles sont fournies avec des identifiants de compte de service.
- WebSocket (non navigateur) : incluez le jeton dans l'en-tête
Authorization: Bearer TOKEN. - WebSocket (navigateur) : comme indiqué dans la section WebSocket, les connexions WebSocket directes du navigateur ne peuvent pas utiliser d'en-têtes d'autorisation. Un proxy de streaming côté serveur est nécessaire pour authentifier la connexion de vos clients à Google Cloud.
Se connecter à l'API
Vous pouvez établir un flux à l'aide des bibliothèques clientes gRPC ou d'une connexion WebSocket.
gRPC
L'utilisation de gRPC est l'approche recommandée. Vous utiliserez les bibliothèques clientes pour le langage de votre choix (par exemple, Node.js), qui sont basées sur la documentation de référence de l'API BidiProcessOrder.
Les étapes de base sont les suivantes :
- Créez un canal gRPC vers le point de terminaison de l'API de l'agent IA de commande de repas (par exemple,
foodorderingaiagent.googleapis.com). - Obtenez un stub client pour
FoodOrderingService. - Appelez la méthode
BidiProcessOrder, qui renvoie un objet de flux pour l'envoi de requêtes et la réception de réponses. - Implémentez la logique métier en fonction de votre cas d'utilisation, qui effectue les opérations suivantes simultanément :
- Envoie l'entrée audio, texte et événement de l'utilisateur final.
- Gère les messages de l'agent, y compris l'audio, le texte et les événements.
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
Pour les connexions WebSocket, le chemin d'URL est le suivant :
wss://foodorderingaiagent.googleapis.com/ws/google.cloud.foodorderingaiagent.v1beta.FoodOrderingService/BidiProcessOrder/locations/LOCATION
LOCATION: par exemple,us
En-têtes obligatoires :
Authorization:Bearer TOKEN- oùTOKENest un jeton d'accès OAuth 2.0 obtenu pour votre compte de service.
Format des messages :
- Client vers serveur : les messages envoyés à l'API (par exemple,
Config,AudioInput,TextInput,EventInput) doivent être des représentations JSON du protoBidiProcessOrderRequest, envoyées en tant quewebsocket.TextMessage. - Serveur vers client : les messages reçus de l'API (
BidiProcessOrderResponse) sont envoyés en tant quewebsocket.BinaryMessage, mais le contenu de ces messages binaires est une charge utile JSON. - Données binaires : les données binaires dans les charges utiles JSON (par exemple,
customerAudiodansAudioInput,agentAudiodansAgentAudio) doivent être encodées en base64.
Exemple WebSocket Node.js
Voici un exemple d'interaction avec l'API à l'aide de WebSockets dans Node.js avec la bibliothèque ws :
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');
});
Cycle de vie des sessions
Chaque appel à BidiProcessOrder lance une session. La session reste active tant que le flux est ouvert.
1. Initialisation (message de configuration)
- Lors de l'établissement de la connexion, le premier message envoyé par le client
doit être un
BidiProcessOrderRequestcontenant leConfigmessage. - Champs obligatoires dans
Config:session: identifiant de session unique généré par le client. Format:projects/PROJECT/locations/LOCATION/sessions/SESSION_ID.store: nom de ressource duStore. Format:projects/PROJECT/locations/LOCATION/brands/BRAND/stores/STORE.- L'agent utilise le
storepour charger le menu et la configuration appropriés.
- L'agent utilise le
mode(facultatif pourBidiProcessOrder) : la valeur par défaut estHYBRID(voix et texte). Notez que si vous utilisez l'API unaire REST ou gRPCProcessOrderau lieu deBidiProcessOrder,modedoit être défini explicitement surTEXT.
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. Envoyer des entrées
- Après le
Configinitial, le client peut envoyer un flux de messagesBidiProcessOrderRequestcontenant l'une des entrées suivantes :AudioInput: données audio brutes (généralement PCM linéaire 16 bits à 16 000 Hz, sans en-têtes). Utilisé pour les interactions vocales.TextInput: messages texte de l'utilisateur.EventInput: signaux pour des événements tels queDriveOffEvent(pour les cas d'utilisation au volant lorsque le véhicule quitte le service),CrewInterjectionEvent(pour toute situation dans laquelle une personne reprend le rôle de prise de commande en milieu de conversation) ouOrderStateUpdateEvent(si la commande est modifiée côté client, par exemple à l'aide d'une interface tactile).
Node.js
// Stream user inputs over the active connection
stream.write({textInput: {text: 'Hi, I\'d like to order a cheeseburger.'}});
3. Recevoir des réponses
- Simultanément, l'agent renvoie un flux de messages
BidiProcessOrderResponse. Votre client doit être prêt à gérer différents types de réponses dans le champoneof response:AgentAudio: octets audio synthétisés à lire à l'utilisateur, utilisés pour les interactions vocales.AgentText: version texte de la réponse de l'agent.SpeechRecognition: transcription de la parole reconnue de l'utilisateur.UpdatedOrderState: contient l'état actuel complet de laOrderdu client chaque fois qu'elle est mise à jour par l'agent. Utilisez-le pour mettre à jour la représentation de la commande de votre application. Cela devrait généralement entraîner une mise à jour d'une interface utilisateur ou d'un système d'enregistrement pour les informations sur l'état de la commande, tel qu'un système de point de vente.InterruptionSignal: indique que l'utilisateur a interrompu la parole de l'agent. Le client doit immédiatement arrêter la lecture de toutAgentAudiosortant.AgentEvent: événements spéciaux, tels queRestartOrder, nécessitant une action du client.SuggestedOptions: fournit des options contextuellement pertinentes qu'un utilisateur peut sélectionner ensuite, utiles pour l'affichage à l'écran.EndSession: signale que la session a été arrêtée par l'agent (par exemple, commande terminée, départ de l'utilisateur ou escalade de l'agent).
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. Fermer le flux
- Le flux peut être fermé par le client ou le serveur. En règle générale, le serveur signale la fin d'une conversation à l'aide d'un message
EndSession. Le client doit fermer le flux lorsque ce message est reçu.
Gérer des types de messages spécifiques
Les sections suivantes décrivent comment gérer des types de réponses spécifiques que votre client recevra lors de l'appel de BidiProcessOrder.
AudioInput
- Diffusez l'audio en streaming par blocs à mesure qu'il devient disponible.
- Format : PCM linéaire 16 bits, fréquence d'échantillonnage de 16 000 Hz.
- Les blocs audio n'incluent pas les en-têtes audio qui préfixent généralement un fichier WAV.
- Pour les scénarios au volant avec l'annulation de l'écho activée (
enable_echo_cancellationdansConfig), fournissez à la foiscustomer_audioetcrew_audio.
UpdatedOrderState
- Ce message fournit l'état complet de la commande chaque fois qu'il est envoyé.
Remplacez tout cache local de la commande par le contenu du message
Orderreçu. - Utilisez les
custom_integration_attributesdans les éléments et les modificateursOrderpour mapper le contenuOrderdans des entités équivalentes au sein du système d'enregistrement de votre application.
InterruptionSignal
- À la réception, arrêtez immédiatement la lecture de tout
AgentAudioet effacez tout audio d'agent mis en mémoire tampon. Cela garantit un flux conversationnel naturel lorsque l'utilisateur interrompt la parole de l'agent.
EndSession
- Vérifiez le
EndType(par exemple,DRIVE_OFF,AGENT_ESCALATION). - Votre application doit fermer la connexion de manière appropriée et faire passer l'utilisateur à l'état approprié (par exemple, avertir un superviseur humain dans le cas de
AGENT_ESCALATIONou passer à un état de confirmation de commande).
Bonnes pratiques
- Gérez les messages de manière asynchrone : réduisez la latence en utilisant des threads ou des E/S non bloquantes pour envoyer des requêtes et traiter les réponses entrantes simultanément.
- Logique de reconnexion : implémentez une logique de reconnexion robuste en cas de problèmes réseau, en vous souvenant d'envoyer le message
Configinitial avec le même ID de session pour tenter de reprendre la session. - Gestion des erreurs : surveillez le flux pour détecter les erreurs. Les bibliothèques gRPC et WebSocket fournissent des mécanismes permettant de détecter la fermeture du flux ou les erreurs de transport. Consignez ces événements et gérez-les de manière appropriée.
- Mise en mémoire tampon de l'audio : gérez soigneusement les mémoires tampons audio, en implémentant la mise en mémoire tampon si nécessaire, pour garantir une lecture fluide d'
AgentAudioet une diffusion rapide d'AudioInput. Tenez compte du compromis entre la latence et la qualité de la lecture lorsque vous choisissez votre schéma de mise en mémoire tampon. - Gestion des ID de session : assurez-vous que les ID de session sont uniques pour chaque commande/conversation distincte.
- Gestion des ressources : fermez les flux et libérez les ressources lorsque la session est terminée ou si des erreurs irrécupérables se produisent.
- Délais avant expiration : bien que le flux lui-même puisse être de longue durée (jusqu'à 15 minutes par défaut), envisagez des délais avant expiration au niveau de l'application pour des états spécifiques si nécessaire.
Exemple de flux d'intégration (conceptuel)
- L'application cliente (par exemple, une application mobile) lance une commande.
- Établissez une connexion gRPC/WebSocket à
BidiProcessOrder. - Envoyez
BidiProcessOrderRequestavecConfig(ID de session, ID de magasin). - Recevez l'
AgentAudioinitial (par exemple, un message de bienvenue) et lisez-le. - L'utilisateur parle : capturez l'audio et diffusez-le en streaming dans les messages
AudioInput. - Recevez
SpeechRecognition(affichez la transcription),AgentAudio(lisez la réponse) et éventuellementUpdatedOrderState(mettez à jour le panier de l'interface utilisateur). - Si l'utilisateur interrompt, recevez
InterruptionSignalet arrêtez la lecture. - Continuez l'échange d'entrées audio ou texte et de réponses de l'agent.
- L'utilisateur confirme la commande : l'agent envoie le
UpdatedOrderStatefinal. - L'agent envoie
EndSession: le client ferme le flux et finalise la commande dans le système POS à l'aide des données du dernierUpdatedOrderState.
Exemple de bout en bout
Bien que les instructions ci-dessus décomposent les concepts de streaming élément par élément, voici à quoi ressemble un flux d'intégration complet de bout en bout.
Node.js
Avant d'essayer cet exemple, suivez les instructions de configuration Node.js décrites dans le guide de démarrage rapide de l'agent IA de commande de repas à l'aide des bibliothèques clientes.
Pour vous authentifier auprès de l'agent IA de commande de repas, configurez le service Identifiants par défaut de l'application. Pour en savoir plus, consultez Configurer l'authentification pour un environnement de développement local.
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.'}});
}