Multimodale Bestellfunktionen mit der Streaming API erstellen

In diesem Leitfaden finden Sie Anleitungen und Best Practices für Entwickler, die mit der RPC-Methode FoodOrderingService.BidiProcessOrder Funktionen für die Essensbestellung erstellen. Diese bidirektionale Streaming-API in Echtzeit ist das Herzstück des KI-Agents für die Essensbestellung und ermöglicht die dynamische, dialogbasierte Bestellannahme in verschiedenen Anwendungen wie mobilen Apps, Sprachassistenten, Drive-thrus und Kiosken.

Übersicht über BidiProcessOrder

Mit der Methode BidiProcessOrder wird ein dauerhafter, bidirektionaler Kommunikationskanal zwischen Ihrer Clientanwendung und dem KI-Agenten für die Essensbestellung eingerichtet. Im Gegensatz zu standardmäßigen unären RPCs für Anfragen und Antworten ermöglicht dieser Streaming-Ansatz Folgendes:

  • Interaktion mit geringer Latenz: Kontinuierlicher Austausch von Informationen ohne den Overhead wiederholter HTTP-Anfragen.
  • Multimodale Eingabe:Verarbeitung von Audio-Streams (für Sprachbestellungen), Texteingaben und clientseitigen Ereignissen.
  • Echtzeitantworten:Der Agent kann während des Gesprächs Audio, Text, Bestellaktualisierungen und andere Signale zurücksenden.

BidiProcessOrder kann nicht über REST aufgerufen werden. Integrationen müssen ein verbindungsorientiertes Protokoll verwenden:

  • gRPC (empfohlen): Bietet ein robustes und effizientes Framework für bidirektionales Streaming.
  • WebSocket:Geeignet für Clients oder Umgebungen, in denen gRPC aufgrund von Programmiersprachen- oder Netzwerkbeschränkungen nicht infrage kommt.

Ausführliche Typdefinitionen finden Sie in der BidiProcessOrder API Reference. Bei WebSocket-Integrationen werden JSON-Darstellungen dieser Typen verwendet, wie im WebSocket-Abschnitt beschrieben.

Vorbereitung

Vor der Integration in BidiProcessOrder:

  1. API aktivieren:Achten Sie darauf, dass die Food Ordering AI Agent API in Ihrem Google Cloud-Projekt aktiviert ist. bash gcloud services enable foodorderingaiagent.googleapis.com --project=PROJECT_ID

  2. Authentifizierung:Wählen Sie eine Authentifizierungsmethode aus und richten Sie alle erforderlichen Dienstkonten und IAM-Rollen ein, wie unter Authentifizierung beschrieben.

  3. Menü-Ingestion:Ein gültiges Menü muss aufgenommen und mit einem Store verknüpft werden. Weitere Informationen finden Sie unter Menüdaten einbinden.

Authentifizierung

Um eine sichere Verbindung zum BidiProcessOrder-RPC herzustellen, muss sich Ihre Anwendung mit einem Google Cloud Dienstkonto authentifizieren.

1. Dienstkonto konfigurieren

  • Dienstkonto erstellen:Erstellen Sie in Ihrem Google Cloud Projekt ein Dienstkonto, das Ihre Anwendung zur Authentifizierung bei der Food Ordering AI Agent API verwendet. Weitere Informationen finden Sie unter Dienstkonten erstellen und verwalten.
  • IAM-Rollen zuweisen:Weisen Sie diesem Dienstkonto die erforderlichen IAM-Rollen zu. Die primäre Rolle, die zum Aufrufen von BidiProcessOrder erforderlich ist:

    • Food Ordering Agent User (roles/foodorderingaiagent.agentUser): Ermöglicht dem Dienstkonto, eine Verbindung zum Bestellservice herzustellen und Sitzungen zu verarbeiten.

    Sie können diese Rolle über die Google Cloud -Konsole oder gcloud zuweisen: bash gcloud projects add-iam-policy-binding PROJECT_ID \ --member="serviceAccount:SERVICE_ACCOUNT_EMAIL" \ --role="roles/foodorderingaiagent.agentUser"

2. Authentifizierungsablauf für Anwendungen

Der genaue Authentifizierungsablauf hängt von der Architektur Ihrer Anwendung ab, insbesondere davon, ob die Clientanwendung (z.B. mobile App, Kiosksoftware) direkt oder über Ihr eigenes Backend eine Verbindung herstellt.

Häufiges Szenario: Authentifizieren einer kundenorientierten Clientanwendung

Das ist ein typisches Muster für mobile Apps oder Webanwendungen:

  1. Client-to-YourAuth::Die Client-App für Endnutzer (mobil, Web) wird mit dem vorhandenen Nutzerauthentifizierungssystem authentifiziert (z. B. Firebase Authentication oder Ihr eigener OAuth-Server).
  2. Token Exchange:Nachdem die Client-App den Nutzer authentifiziert hat, fordert sie ein kurzlebiges Token von einem sicheren Backend-Dienst an, den Sie kontrollieren (z.B. ein „API Token Service“).
  3. Generierung von Zugriffstokens:Ihr Backend-Dienst generiert mit den Anmeldedaten des Google Cloud Dienstkonto-Principals, der in Schritt 1 konfiguriert wurde, ein standardmäßiges OAuth 2.0-Zugriffstoken für den Bereich https://www.googleapis.com/auth/cloud-platform. Dazu können Sie die Google Cloud -Clientbibliotheken für die Authentifizierung verwenden.

    • Sicherheit:Dienstkontoschlüssel oder Anmeldedaten, die zum Generieren dieser Tokens verwendet werden, müssen sicher in Ihrem Backend gespeichert und verwaltet werden. Geben Sie private Schlüssel von Dienstkonten niemals direkt in Clientanwendungen für Endnutzer frei. Weitere Informationen finden Sie unter Best Practices für die Verwaltung von Dienstkontoschlüsseln.
  4. Token an Client:Ihr Back-End-Dienst gibt das generierte Google-Zugriffstoken an die Client-App zurück.

  5. API-Aufruf:Die Client-App verwendet dieses Google-Zugriffstoken, um ihre gRPC- oder WebSocket-Verbindung zum BidiProcessOrder-RPC zu authentifizieren.

3. Token verwenden

  • gRPC:Die Google gRPC-Clientbibliotheken verarbeiten in der Regel die Aktualisierung von Tokens und die Einbeziehung in die Aufrufmetadaten, wenn Anmeldedaten für Dienstkonten bereitgestellt werden.
  • WebSocket (nicht im Browser): Fügen Sie das Token in den Authorization: Bearer TOKEN-Header ein.
  • WebSocket (Browser): Wie im Abschnitt zu WebSocket beschrieben, können für direkte Browser-WebSocket-Verbindungen keine Autorisierungsheader verwendet werden. Ein serverseitiger Streamingproxy ist erforderlich, um die Verbindung Ihrer Clients zu Google Cloudzu authentifizieren.

Verbindung zur API herstellen

Sie können einen Stream mit gRPC-Clientbibliotheken oder einer WebSocket-Verbindung einrichten.

gRPC

Die Verwendung von gRPC ist der empfohlene Ansatz. Sie verwenden die Clientbibliotheken für die Sprache Ihrer Wahl (z.B. Node.js), die auf der BidiProcessOrder API-Referenz basieren.

Die grundlegenden Schritte sind:

  1. Erstellen Sie einen gRPC-Channel zum Food Ordering AI Agent API-Endpunkt (z.B. foodorderingaiagent.googleapis.com).
  2. Client-Stub für FoodOrderingService abrufen
  3. Rufen Sie die Methode BidiProcessOrder auf, die ein Stream-Objekt zum Senden von Anfragen und Empfangen von Antworten zurückgibt.
  4. Implementieren Sie die Geschäftslogik entsprechend Ihrem Anwendungsfall, wobei gleichzeitig Folgendes gilt:
    • Sendet Audio-, Text- und Ereigniseingaben vom Endnutzer.
    • Verarbeitet Nachrichten vom Agenten, einschließlich Audio, Text und Ereignisse.

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

Bei WebSocket-Verbindungen lautet der URL-Pfad:

wss://foodorderingaiagent.googleapis.com/ws/google.cloud.foodorderingaiagent.v1beta.FoodOrderingService/BidiProcessOrder/locations/LOCATION

  • LOCATION: z.B. us

Erforderliche Header:

  • Authorization: Bearer TOKEN – Dabei ist TOKEN ein OAuth 2.0-Zugriffstoken, das für Ihr Dienstkonto abgerufen wurde.

Nachrichtenformat:

  • Client zu Server:Nachrichten, die an die API gesendet werden (z.B. Config, AudioInput, TextInput, EventInput), müssen JSON-Darstellungen des BidiProcessOrderRequest-Protokolls sein, die als websocket.TextMessage gesendet werden.
  • Server zu Client:Von der API empfangene Nachrichten (BidiProcessOrderResponse) werden als websocket.BinaryMessage gesendet. Der Inhalt dieser binären Nachrichten ist jedoch eine JSON-Nutzlast.
  • Binärdaten:Binärdaten in den JSON-Nutzlasten (z.B. customerAudio in AudioInput, agentAudio in AgentAudio) müssen base64-codiert sein.

Node.js-WebSocket-Beispiel

Hier sehen Sie ein Beispiel dafür, wie Sie mit der ws-Bibliothek in Node.js eine Verbindung zur API herstellen und mit ihr interagieren:

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');
});

Sitzungslebenszyklus

Mit jedem Aufruf von BidiProcessOrder wird eine Sitzung gestartet. Die Sitzung bleibt aktiv, solange der Stream geöffnet ist.

1. Initiierung (Konfigurationsnachricht)

  • Nachdem die Verbindung hergestellt wurde, muss die erste Nachricht, die vom Client gesendet wird, ein BidiProcessOrderRequest mit der Nachricht Config sein.
  • Pflichtfelder in Config:
    • session: Eine eindeutige, vom Client generierte Sitzungskennung. Format: projects/PROJECT/locations/LOCATION/sessions/SESSION_ID.
      • store: Der Ressourcenname der Store. Format: projects/PROJECT/locations/LOCATION/brands/BRAND/stores/STORE.
        • Der Agent verwendet store, um das entsprechende Menü und die entsprechende Konfiguration zu laden.

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. Eingaben senden

  • Nach dem ersten Config kann der Client einen Stream von BidiProcessOrderRequest-Nachrichten mit einer der folgenden Eingaben senden:
    • AudioInput: Rohaudiodaten (in der Regel 16-Bit-PCM mit 16.000 Hz, keine Header). Wird für Sprachinteraktionen verwendet.
    • TextInput: SMS vom Nutzer.
    • EventInput: Signale für Ereignisse wie DriveOffEvent (für Drive-thru-Anwendungsfälle, wenn das Fahrzeug abfährt), CrewInterjectionEvent (für jede Situation, in der ein Mensch die Rolle der Bestellannahme während der Unterhaltung übernimmt) oder OrderStateUpdateEvent (wenn die Bestellung clientseitig geändert wird, z.B. über eine Touch-Oberfläche).

Node.js

// Stream user inputs over the active connection
stream.write({textInput: {text: 'Hi, I\'d like to order a cheeseburger.'}});

3. Antworten erhalten

  • Gleichzeitig sendet der Agent einen Stream von BidiProcessOrderResponse-Nachrichten zurück. Ihr Client muss in der Lage sein, verschiedene Antworttypen im Feld oneof response zu verarbeiten:
    • AgentAudio: Synthetisierte Audio-Bytes, die für Sprachinteraktionen für den Nutzer wiedergegeben werden.
    • AgentText: Textversion der Antwort des Agenten.
    • SpeechRecognition: Transkript der erkannten Sprache des Nutzers.
    • UpdatedOrderState: Enthält den vollständigen aktuellen Status des Order des Kunden, wenn er vom Kundenservicemitarbeiter aktualisiert wird. Verwenden Sie diesen, um die Auftragsdarstellung Ihrer Anwendung zu aktualisieren. Dies sollte in der Regel zu einer Aktualisierung der Benutzeroberfläche oder eines Systems für die Aufzeichnung von Informationen zum Bestellstatus führen, z. B. eines Kassensystems.
    • InterruptionSignal: Gibt an, dass der Nutzer die Sprachausgabe des Agents unterbrochen hat. Der Client sollte die Wiedergabe aller ausgehenden AgentAudio sofort beenden.
    • AgentEvent: Besondere Ereignisse, z. B. RestartOrder>, die eine Clientaktion erfordern.
    • SuggestedOptions: Bietet kontextbezogene Optionen, die ein Nutzer als Nächstes auswählen könnte. Diese sind nützlich für die Anzeige auf einem Bildschirm.
    • EndSession: Gibt an, dass die Sitzung vom Kundenservicemitarbeiter beendet wurde (z.B. Bestellung abgeschlossen, Nutzer fährt weg oder Kundenservicemitarbeiter eskaliert).

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. Stream schließen

  • Der Stream kann vom Client oder vom Server geschlossen werden. Normalerweise signalisiert der Server das Ende einer Unterhaltung mit einer EndSession-Nachricht. Der Client sollte den Stream schließen, wenn diese Nachricht empfangen wird.

Bestimmte Nachrichtentypen verarbeiten

In den folgenden Abschnitten wird beschrieben, wie Sie mit bestimmten Antworttypen umgehen, die Ihr Client beim Aufrufen von BidiProcessOrder erhält.

AudioInput

  • Audio in Chunks streamen, sobald es verfügbar ist.
  • Format: 16-Bit-Linear-PCM, 16.000 Hz Abtastrate.
  • Audio-Chunks enthalten nicht die Audio-Header, die normalerweise einer WAV-Datei vorangestellt werden.
  • Geben Sie für Drive-thru-Szenarien mit aktivierter Echounterdrückung (enable_echo_cancellation in Config) sowohl customer_audio als auch crew_audio an.

UpdatedOrderState

  • Diese Nachricht enthält den vollständigen Status der Bestellung bei jedem Senden. Ersetzen Sie alle lokalen Caches der Bestellung durch den Inhalt der empfangenen Order-Nachricht.
  • Verwenden Sie die custom_integration_attributes in den Order-Elementen und ‑Modifizierern, um die Order-Inhalte entsprechenden Entitäten im System Ihrer Anwendung zuzuordnen.

InterruptionSignal

  • Halte nach dem Empfang sofort die Wiedergabe von AgentAudio an und lösche alle zwischengespeicherten Agent-Audiodaten. So wird ein natürlicher Konversationsablauf gewährleistet, wenn der Nutzer die Sprachausgabe des Agents unterbricht.

EndSession

  • Prüfen Sie EndType (z.B. DRIVE_OFF, AGENT_ESCALATION).
  • Ihre Anwendung sollte die Verbindung ordnungsgemäß schließen und den Nutzer entsprechend weiterleiten (z.B. im Fall von AGENT_ESCALATION einen menschlichen Supervisor benachrichtigen oder zu einem Bestellbestätigungsstatus wechseln).

Best Practices

  • Nachrichten asynchron verarbeiten:Minimieren Sie die Latenz, indem Sie Threads oder nicht blockierende E/A verwenden, um Anfragen gleichzeitig zu senden und eingehende Antworten zu verarbeiten.
  • Logik für die erneute Verbindung:Implementieren Sie eine robuste Logik für die erneute Verbindung im Falle von Netzwerkproblemen. Denken Sie daran, die ursprüngliche Config-Nachricht mit derselben Sitzungs-ID zu senden, um die Fortsetzung zu versuchen.
  • Fehlerbehandlung:Überwachen Sie den Stream auf Fehler. gRPC- und WebSocket-Bibliotheken bieten Mechanismen zum Erkennen von Stream-Schließungen oder Transportfehlern. Protokollieren Sie diese Ereignisse und gehen Sie sorgfältig damit um.
  • Audio-Buffering:Audio-Puffer sorgfältig verwalten und bei Bedarf implementieren, um eine reibungslose Wiedergabe von AgentAudio und eine rechtzeitige Bereitstellung von AudioInput zu gewährleisten. Wägen Sie bei der Entscheidung für ein Pufferschema sorgfältig den Kompromiss zwischen Latenz und Wiedergabequalität ab.
  • Verwaltung von Sitzungs-IDs:Achten Sie darauf, dass Sitzungs-IDs für jede einzelne Bestellung/Unterhaltung eindeutig sind.
  • Ressourcenverwaltung:Schließen Sie Streams und geben Sie Ressourcen frei, wenn die Sitzung abgeschlossen ist oder nicht behebbarer Fehler auftreten.
  • Timeouts:Der Stream selbst kann zwar lange bestehen (standardmäßig bis zu 15 Minuten), aber bei Bedarf sollten Sie Timeouts auf Anwendungsebene für bestimmte Status in Betracht ziehen.

Beispiel für einen Integrationsablauf (konzeptionell)

  1. Eine Bestellung wird von einer Client-App (z.B. einer mobilen App) initiiert.
  2. Stellen Sie eine gRPC-/WebSocket-Verbindung zu BidiProcessOrder her.
  3. Senden Sie BidiProcessOrderRequest mit Config (Sitzungs-ID, Händler-ID).
  4. Empfangen Sie die erste AgentAudio (z.B. eine Willkommensnachricht) und spielen Sie sie ab.
  5. Nutzer spricht: Audio aufnehmen und in AudioInput-Nachrichten streamen.
  6. Sie erhalten SpeechRecognition (Transkript anzeigen), AgentAudio (Antwort abspielen) und möglicherweise UpdatedOrderState (Warenkorb in der Benutzeroberfläche aktualisieren).
  7. Wenn der Nutzer den Vorgang unterbricht, empfange InterruptionSignal und beende die Wiedergabe.
  8. Der Austausch von Audio- oder Texteingaben und Antworten des KI-Agenten wird fortgesetzt.
  9. Nutzer bestätigt Bestellung: Der Kundenservicemitarbeiter sendet das endgültige UpdatedOrderState.
  10. Der Kundenservicemitarbeiter sendet EndSession: Der Kunde schließt den Stream und die Bestellung im Kassensystem mit den Daten aus dem letzten UpdatedOrderState ab.

Vollständiges Beispiel

In der Anleitung oben werden die Streamingkonzepte Schritt für Schritt erläutert. Hier sehen Sie, wie ein vollständiger End-to-End-Integrationsablauf aussieht.

Node.js

Bevor Sie dieses Beispiel anwenden, folgen Sie der Node.js-Einrichtungsanleitung in der Kurzanleitung zur Verwendung von Clientbibliotheken für den KI-Agenten für die Essensbestellung.

Richten Sie zur Authentifizierung beim KI-Agent für die Essensbestellung Standardanmeldedaten für Anwendungen ein. Weitere Informationen finden Sie unter Authentifizierung für eine lokale Entwicklungsumgebung einrichten.

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.'}});
}