Multimodale Bestellfunktionen mit der Streaming API erstellen

Dieser Leitfaden enthält Anleitungen und Best Practices für Entwickler, die mit der RPC-Methode FoodOrderingService.BidiProcessOrder Lösungen für die Essensbestellung erstellen. Diese bidirektionale Streaming-API in Echtzeit ist das Herzstück des Food Ordering KI-Agenten und ermöglicht die dynamische, dialogorientierte Bestellannahme in verschiedenen Anwendungen wie mobilen Apps, Sprachassistenten, Drive-ins und Kiosken.

Übersicht über BidiProcessOrder

Die Methode BidiProcessOrder stellt einen dauerhaften bidirektionalen Kommunikationskanal zwischen Ihrer Clientanwendung und dem KI-Agenten für die Essensbestellung her. Im Gegensatz zu standardmäßigen unären RPCs für Anfragen und Antworten bietet dieser Streaming-Ansatz folgende Vorteile:

  • Interaktion mit geringer Latenz: Kontinuierlicher Informationsaustausch ohne den Aufwand wiederholter HTTP-Anfragen.
  • Multimodale Eingabe: Verarbeitung von Audiostreams (für die Sprachbestellung), Texteingaben und clientseitigen Ereignissen.
  • Antworten in Echtzeit: Der Agent kann während der Unterhaltung Audio, Text, Bestellaktualisierungen und andere Signale zurücksenden.

BidiProcessOrder kann nicht mit REST aufgerufen werden. Für Integrationen muss ein verbindungsorientiertes Protokoll verwendet werden:

  • gRPC (empfohlen): Bietet ein robustes und effizientes Framework für bidirektionales Streaming.
  • WebSocket: Geeignet für Clients oder Umgebungen, in denen gRPC aufgrund von Einschränkungen bei der Programmiersprache oder dem Netzwerk nicht geeignet ist.

Ausführliche Typdefinitionen finden Sie in der BidiProcessOrder API Referenz. WebSocket-Integrationen verwenden JSON-Darstellungen dieser Typen, wie im Abschnitt zu WebSocket beschrieben.

Vorbereitung

Bevor Sie BidiProcessOrder einbinden:

  1. API aktivieren: Prüfen Sie, ob 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: Legen Sie Ihren Authentifizierungsansatz fest und richten Sie alle erforderlichen Dienstkonten und IAM-Rollen ein, wie unter Authentifizierung beschrieben.

  3. Menüaufnahme: 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 zur 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 Dienst konto, mit dem sich Ihre Anwendung bei der Food Ordering AI Agent API authentifiziert. 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, 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 Console oder gcloud: bash gcloud projects add-iam-policy-binding PROJECT_ID \ --member="serviceAccount:SERVICE_ACCOUNT_EMAIL" \ --role="roles/foodorderingaiagent.agentUser" zuweisen

2. Authentifizierungsablauf für Anwendungen

Der genaue Authentifizierungsablauf hängt von Ihrer Anwendungsarchitektur ab, insbesondere davon, ob die Clientanwendung (z.B. mobile App, Kiosksoftware) eine direkte Verbindung herstellt oder über Ihr eigenes Back-End.

Häufiges Szenario: Authentifizierung einer für Nutzer bestimmten Clientanwendung

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

  1. Client-to-YourAuth: Die Endnutzer-Clientanwendung (mobil, Web) authentifiziert sich mit Ihrem vorhandenen Nutzerauthentifizierungssystem (z. B. Firebase Authentication oder Ihr eigener OAuth-Server).
  2. Tokenaustausch: Nachdem die Clientanwendung den Nutzer authentifiziert hat, fordert sie ein kurzlebiges Token von einem sicheren Backend-Dienst an, den Sie kontrollieren (z.B. ein „API-Token-Dienst“).
  3. Zugriffstoken generieren: Ihr Backend-Dienst generiert mit den Anmeldedaten des in Schritt 1 konfigurierten Dienstkontos ein standardmäßiges OAuth 2.0-Zugriffstoken für den https://www.googleapis.com/auth/cloud-platform Zugriffsbereich. Google Cloud Dies kann mit den Google Cloud Authentifizierungs-Client bibliotheken erfolgen.

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

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

3. Token verwenden

  • gRPC: Die Google gRPC-Clientbibliotheken verarbeiten in der Regel die Tokenaktualisierung und -einbindung in die Aufrufmetadaten, wenn Anmeldedaten für Dienstkonten angegeben werden.
  • WebSocket (nicht im Browser): Fügen Sie das Token in den Authorization: Bearer TOKEN Header ein.
  • WebSocket (Browser): Wie im Abschnitt zu WebSocket erwähnt, können bei direkten WebSocket-Verbindungen im Browser keine Autorisierungsheader verwendet werden. Ein serverseitiger Streaming-Proxy ist erforderlich, um die Verbindung Ihres Clients zu authentifizieren Google Cloud.

Verbindung zur API herstellen

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

gRPC

Die Verwendung von gRPC wird empfohlen. Sie verwenden die Clientbibliotheken für Ihre bevorzugte Sprache (z.B. Node.js), die auf der API-Referenz zu BidiProcessOrder basieren.

Die grundlegenden Schritte sind:

  1. Erstellen Sie einen gRPC-Kanal zum API-Endpunkt des Food Ordering AI Agent (z.B. foodorderingaiagent.googleapis.com).
  2. Rufen Sie einen Client-Stub für FoodOrderingService ab.
  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 geschieht:
    • Audio-, Text- und Ereigniseingaben vom Endnutzer werden gesendet.
    • Nachrichten vom Agenten werden verarbeitet, einschließlich Audio, Text und Ereignissen.

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

Für 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 - Wobei TOKEN ein OAuth 2.0-Zugriffstoken ist, das für Ihr Dienstkonto abgerufen wurde.
finden Sie unter Authentifizierung.

Nachrichtenformat:

  • Client zu Server: Nachrichten, die an die API gesendet werden (z.B. Config, AudioInput, TextInput, EventInput), müssen JSON-Darstellungen des BidiProcessOrderRequest-Protos sein und als websocket.TextMessage gesendet werden.
  • Server zu Client: Nachrichten, die von der API empfangen werden (BidiProcessOrderResponse), werden als websocket.BinaryMessage gesendet. Der Inhalt dieser Binärnachrichten 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 für die Verbindung und Interaktion mit der API über WebSockets in Node.js mit der ws-Bibliothek:

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

Lebenszyklus der Sitzung

Jeder Aufruf von BidiProcessOrder initiiert eine Sitzung. Die Sitzung bleibt aktiv, solange der Stream geöffnet ist.

1. Initiierung (Konfigurationsnachricht)

  • Nach dem Herstellen der Verbindung muss die erste Nachricht , die vom Client gesendet wird , eine BidiProcessOrderRequest mit der Config Nachricht sein.
  • Erforderliche Felder in Config:
    • session: Eine eindeutige vom Client generierte Sitzungs-ID. Format: projects/PROJECT/locations/LOCATION/sessions/SESSION_ID.
    • store: Der Ressourcenname des Store. Format: projects/PROJECT/locations/LOCATION/brands/BRAND/stores/STORE.
      • Der Agent verwendet store, um das entsprechende Menü und die entsprechende Konfiguration zu laden.
    • mode (optional für BidiProcessOrder): Standardmäßig HYBRID (Sprache und Text). Wenn Sie anstelle von BidiProcessOrder die unäre REST- oder gRPC ProcessOrder API verwenden, muss mode explizit auf TEXT gesetzt werden.

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 der ersten Config kann der Client einen Stream von BidiProcessOrderRequest-Nachrichten senden, die eine der folgenden Eingaben enthalten:
    • AudioInput: Rohaudiodaten (in der Regel 16-Bit-Linear-PCM mit 16.000 Hz, keine Header). Wird für Sprachinteraktionen verwendet.
    • TextInput: Textnachrichten vom Nutzer.
    • EventInput: Signale für Ereignisse wie DriveOffEvent (für Drive-in-Anwendungsfälle, wenn das Fahrzeug abfährt), CrewInterjectionEvent (für jede Situation, in der ein Mensch während der Unterhaltung die Rolle der Bestellannahme übernimmt) oder OrderStateUpdateEvent (wenn die Bestellung auf der Clientseite 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 empfangen

  • 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 Audiobytes, die für den Nutzer wiedergegeben werden sollen. Wird für Sprachinteraktionen verwendet.
    • AgentText: Textversion der Antwort des Agenten.
    • SpeechRecognition: Transkript der erkannten Sprache des Nutzers.
    • UpdatedOrderState: Enthält den vollständigen aktuellen Status der Order des Kunden, wenn sie vom Agenten aktualisiert wird. Verwenden Sie diese Option, um die Bestellrepräsentation Ihrer Anwendung zu aktualisieren. Dies sollte in der Regel zu einer Aktualisierung einer Benutzeroberfläche oder eines Aufzeichnungssystems für Bestellstatusinformationen führen, z. B. eines Kassensystems.
    • InterruptionSignal: Gibt an, dass der Nutzer die Rede des Agenten unterbrochen hat. Der Client sollte die Wiedergabe von ausgehendem AgentAudio sofort beenden.
    • AgentEvent: Besondere Ereignisse wie RestartOrder, die eine Clientaktion erfordern.
    • SuggestedOptions: Bietet kontextbezogene Optionen, die ein Nutzer als Nächstes auswählen könnte. Nützlich für die Anzeige auf einem Bildschirm.
    • EndSession: Gibt an, dass die Sitzung vom Agenten beendet wurde (z.B. Bestellung abgeschlossen, Nutzer abgefahren oder Eskalation an einen menschlichen Agenten).

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. In der Regel 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 bestimmte Antworttypen verarbeiten, die Ihr Client beim Aufrufen von BidiProcessOrder erhält.

AudioInput

  • Streamen Sie Audio in Blöcken, sobald es verfügbar ist.
  • Format: 16-Bit-Linear-PCM, Abtastrate 16.000 Hz.
  • Audioblöcke enthalten nicht die Audioheader, die normalerweise einer WAV-Datei vorangestellt sind.
  • Geben Sie für Drive-in-Szenarien mit aktivierter Echounterdrückung (enable_echo_cancellation in Config) sowohl customer_audio als auch crew_audio an.

UpdatedOrderState

  • Diese Nachricht enthält jedes Mal den vollständigen Status der Bestellung, wenn sie gesendet wird. 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 -Modifikatoren, um den Order-Inhalt entsprechenden Entitäten im Aufzeichnungssystem Ihrer Anwendung zuzuordnen.

InterruptionSignal

  • Beenden Sie nach dem Empfang sofort die Wiedergabe von AgentAudio und löschen Sie alle gepufferten Agent-Audiodaten. So wird ein natürlicher Konversationsablauf gewährleistet, wenn der Nutzer die Rede des Agenten 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. einen menschlichen Supervisor im Fall von AGENT_ESCALATION benachrichtigen oder zu einem Bestellbestätigungsstatus übergehen).

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 Wiederherstellung der Verbindung: Implementieren Sie eine robuste Logik für die Wiederherstellung der Verbindung bei Netzwerkproblemen. Senden Sie dazu die erste Config-Nachricht mit derselben Sitzungs-ID, um die Wiederaufnahme zu versuchen.
  • Fehlerbehandlung: Überwachen Sie den Stream auf Fehler. gRPC- und WebSocket-Bibliotheken bieten Mechanismen zum Erkennen von Stream-Schließungen oder Übertragungsfehlern. Protokollieren Sie diese Ereignisse und behandeln Sie sie ordnungsgemäß.
  • Audio-Buffering: Verwalten Sie Audio-Puffer sorgfältig und implementieren Sie bei Bedarf Buffering, um eine reibungslose Wiedergabe von AgentAudio und eine rechtzeitige Übermittlung von AudioInput zu gewährleisten. Berücksichtigen Sie bei der Entscheidung für Ihr Buffering-Schema sorgfältig den Kompromiss zwischen Latenz und Wiedergabequalität.
  • Sitzungs-ID-Verwaltung: 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 wenn nicht behebbare Fehler auftreten.
  • Zeitüberschreitungen: Der Stream selbst kann zwar lange bestehen (standardmäßig bis zu 15 Minuten), aber Sie können bei Bedarf Zeitüberschreitungen auf Anwendungsebene für bestimmte Status festlegen.

Beispiel für einen Integrationsablauf (konzeptionell)

  1. Die Clientanwendung (z.B. mobile App) initiiert eine Bestellung.
  2. Stellen Sie eine gRPC-/WebSocket-Verbindung zu BidiProcessOrder her.
  3. Senden Sie BidiProcessOrderRequest mit Config (Sitzungs-ID, Store-ID).
  4. Empfangen Sie die erste AgentAudio (z.B. Willkommensnachricht) und geben Sie sie wieder.
  5. Nutzer spricht: Audio aufnehmen und in AudioInput-Nachrichten streamen.
  6. Empfangen Sie SpeechRecognition (Transkript anzeigen), AgentAudio (Antwort wiedergeben) und möglicherweise UpdatedOrderState (Warenkorb auf der Benutzeroberfläche aktualisieren).
  7. Wenn der Nutzer unterbricht, empfangen Sie InterruptionSignal und beenden Sie die Wiedergabe.
  8. Setzen Sie den Austausch von Audio- oder Texteingaben und Agent-Antworten fort.
  9. Nutzer bestätigt Bestellung: Agent sendet endgültiges UpdatedOrderState.
  10. Agent sendet EndSession: Client schließt den Stream und schließt die Bestellung im Kassensystem mit Daten aus dem letzten UpdatedOrderState ab.

Vollständiges Beispiel

In der obigen Anleitung werden die Streaming-Konzepte 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 Food Ordering AI Agent-Kurzanleitung zur Verwendung von Clientbibliotheken.

Richten Sie zur Authentifizierung bei Food Ordering AI Agent 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.'}});
}