Crea una experiencia de pedidos multimodal con la API de transmisión

En esta guía, se proporcionan instrucciones y prácticas recomendadas para los ingenieros que crean experiencias de pedidos de comida con el método RPC FoodOrderingService.BidiProcessOrder. Esta API de transmisión bidireccional en tiempo real es el núcleo del agente de IA de pedidos de comida, lo que permite la toma de pedidos conversacionales y dinámicos en varias aplicaciones, como apps para dispositivos móviles, asistentes de voz, autoservicios y quioscos.

Descripción general de BidiProcessOrder

El método BidiProcessOrder establece un canal de comunicación bidireccional persistente entre tu aplicación cliente y el agente de IA de pedidos de comida. A diferencia de las RPC de solicitud y respuesta unarias estándar, este enfoque de transmisión permite lo siguiente:

  • Interacción de baja latencia: Intercambio continuo de información sin la sobrecarga de solicitudes HTTP repetidas.
  • Entrada multimodal: Manejo de transmisiones de audio (para pedidos por voz), entradas de texto y eventos del cliente.
  • Respuestas en tiempo real: El agente puede enviar audio, texto, actualizaciones de pedidos y otros indicadores a medida que se desarrolla la conversación.

BidiProcessOrder No se puede invocar con REST. Las integraciones deben usar un protocolo orientado a la conexión:

  • gRPC (recomendado): Proporciona un framework sólido y eficiente para la transmisión bidireccional.
  • WebSocket: Adecuado para clientes o entornos en los que gRPC no es adecuado debido a restricciones de lenguaje de programación o de red.

Consulta la referencia de la API de BidiProcessOrder para obtener definiciones de tipos detalladas. Las integraciones de WebSocket usan representaciones JSON de estos tipos, como se describe en la sección WebSocket.

Requisitos previos

Antes de realizar la integración con BidiProcessOrder, haz lo siguiente:

  1. Habilita la API: Asegúrate de que la API del agente de IA de pedidos de comida esté habilitada en tu Google Cloud proyecto. bash gcloud services enable foodorderingaiagent.googleapis.com --project=PROJECT_ID

  2. Autenticación: Decide tu enfoque de autenticación y configura las cuentas de servicio y los roles de IAM necesarios, como se describe en Autenticación.

  3. Ingesta de menú: Se debe ingerir un menú válido y asociarlo con un Store. Consulta Integra datos de menú Data para obtener más detalles.

Autenticación

Para conectarte de forma segura a la BidiProcessOrder RPC, tu aplicación debe autenticarse con una Google Cloud cuenta de servicio.

1. Configura una cuenta de servicio

  • Crea una cuenta de servicio: En tu Google Cloud proyecto, crea una cuenta de servicio que tu aplicación usará para autenticarse en la API del agente de IA de pedidos de comida. Consulta Crea y administra cuentas de servicio.
  • Otorga roles de IAM: Otorga los roles de IAM necesarios a esta cuenta de servicio. El rol principal necesario para llamar a BidiProcessOrder es el siguiente:

    • Usuario del agente de pedidos de comida (roles/foodorderingaiagent.agentUser): Permite que la cuenta de servicio se conecte al servicio de pedidos y procese sesiones.

    Puedes otorgar este rol con la Google Cloud consola de o gcloud: bash gcloud projects add-iam-policy-binding PROJECT_ID \ --member="serviceAccount:SERVICE_ACCOUNT_EMAIL" \ --role="roles/foodorderingaiagent.agentUser"

2. Flujo de autenticación de la aplicación

El flujo de autenticación exacto depende de la arquitectura de tu aplicación, en especial si la aplicación cliente (p.ej., app para dispositivos móviles, software de quiosco) se conecta directamente o a través de tu propio backend.

Situación común: Autenticación de una aplicación cliente para el consumidor

Este es un patrón típico para aplicaciones web o para dispositivos móviles:

  1. Client-to-YourAuth: La app cliente del usuario final (para dispositivos móviles o web) se autentica con tu sistema de autenticación de usuarios existente (puede ser Firebase Authentication, tu propio servidor de OAuth, etcétera).
  2. Intercambio de tokens: Después de autenticar al usuario, la app cliente solicita un token de corta duración de un servicio de backend seguro que controlas (p.ej., un "servicio de tokens de API").
  3. Generación de tokens de acceso: Tu servicio de backend, con las credenciales de la Google Cloud principal de la cuenta de servicio configurada en el paso 1, genera un token de acceso estándar de OAuth 2.0 para el https://www.googleapis.com/auth/cloud-platform alcance. Esto se puede hacer con las Google Cloud bibliotecas cliente de Authentication.

    • Seguridad: Las claves o credenciales de la cuenta de servicio que se usan para generar estos tokens deben almacenarse y administrarse de forma segura en tu backend. Nunca expongas las claves privadas de la cuenta de servicio directamente a las aplicaciones cliente del usuario final. Consulta las prácticas recomendadas para administrar claves de cuenta de servicio.
    Google Cloud
  4. Token para el cliente: Tu servicio de backend devuelve el token de acceso de Google generado a la app cliente.

  5. Llamada a la API: La app cliente usa este token de acceso de Google para autenticar su conexión gRPC o WebSocket a la RPC BidiProcessOrder.

3. Usa el token de

  • gRPC: Por lo general, las bibliotecas cliente de gRPC de Google controlan la actualización de tokens y la inclusión en los metadatos de la llamada cuando se proporcionan credenciales de cuenta de servicio.
  • WebSocket (no navegador): Incluye el token en el encabezado Authorization: Bearer TOKEN.
  • WebSocket (navegador): Como se indicó en la sección WebSocket, las conexiones directas de WebSocket del navegador no pueden usar encabezados de autorización. Se necesita un proxy de transmisión del servidor para autenticar la conexión de tus clientes a Google Cloud.

Conéctate a la API

Puedes establecer una transmisión con las bibliotecas cliente de gRPC o una conexión WebSocket.

gRPC

Se recomienda usar gRPC. Usarás las bibliotecas cliente para el lenguaje que elijas (p.ej., Node.js), que se basan en la referencia de la API de BidiProcessOrder.

Los pasos básicos son los siguientes:

  1. Crea un canal gRPC para el extremo de API del agente de IA de Pedidos de comida (p.ej., foodorderingaiagent.googleapis.com).
  2. Obtén un stub de cliente para FoodOrderingService.
  3. Invoca el método BidiProcessOrder, que devuelve un objeto de transmisión para enviar solicitudes y recibir respuestas.
  4. Implementa la lógica empresarial según tu caso de uso, que se ejecuta de forma simultánea:
    • Envía audio, texto y entrada de eventos del usuario final.
    • Maneja mensajes del agente, incluidos audio, texto y eventos.

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

Para las conexiones WebSocket, la ruta de URL es la siguiente:

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

  • LOCATION: p.ej., us

Encabezados obligatorios:

  • Authorization: Bearer TOKEN (donde TOKEN es un token de acceso de OAuth 2.0 obtenido para tu cuenta de servicio)

Formato del mensaje:

  • Cliente a servidor: Los mensajes enviados a la API (p.ej., Config, AudioInput, TextInput, EventInput) deben ser representaciones JSON del proto BidiProcessOrderRequest, que se envían como websocket.TextMessage.
  • Servidor a cliente: Los mensajes recibidos de la API (BidiProcessOrderResponse) se enviarán como websocket.BinaryMessage, pero el contenido de estos mensajes binarios es una carga útil de JSON.
  • Datos binarios: Los datos binarios dentro de las cargas útiles de JSON (p.ej., customerAudio en AudioInput, agentAudio en AgentAudio) deben estar codificados en base64.

Ejemplo de WebSocket de Node.js

A continuación, se muestra un ejemplo de cómo conectarse e interactuar con la API mediante WebSockets en Node.js con la biblioteca 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');
});

Ciclo de vida de la sesión

Cada llamada a BidiProcessOrder inicia una sesión. La sesión permanece activa mientras la transmisión esté abierta.

1. Inicio (mensaje de configuración)

  • Al establecer la conexión, el primer mensaje que envía el cliente debe ser un BidiProcessOrderRequest que contenga el Config mensaje.
  • Campos obligatorios en Config:
    • session: Un identificador de sesión único generado por el cliente. Formato: projects/PROJECT/locations/LOCATION/sessions/SESSION_ID.
    • store: El nombre del recurso de Store. Formato: projects/PROJECT/locations/LOCATION/brands/BRAND/stores/STORE.
      • El agente usa el store para cargar el menú y la configuración adecuados.
    • mode (opcional para BidiProcessOrder): El valor predeterminado es HYBRID (voz y texto). Ten en cuenta que, si usas la API de REST o gRPC unaria ProcessOrder en lugar de BidiProcessOrder, mode debe establecerse de forma explícita en TEXT.

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. Envía entradas

  • Después de la Config inicial, el cliente puede enviar una transmisión de mensajes BidiProcessOrderRequest que contenga una de las siguientes entradas:
    • AudioInput: Datos de audio sin procesar (por lo general, PCM lineal de 16 bits a 16,000 Hz, sin encabezados). Se usa para interacciones de voz.
    • TextInput: Mensajes de texto del usuario.
    • EventInput: Indicadores para eventos como DriveOffEvent (para casos de uso de autoservicio cuando el vehículo se va), CrewInterjectionEvent (para cualquier situación en la que un humano asume el rol de toma de pedidos en medio de la conversación) o OrderStateUpdateEvent (si el pedido se modifica en el cliente, p.ej., con una interfaz táctil).

Node.js

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

3. Recibe respuestas

  • De forma simultánea, el agente envía una transmisión de mensajes BidiProcessOrderResponse. Tu cliente debe estar preparado para controlar varios tipos de respuesta dentro del campo oneof response:
    • AgentAudio: Bytes de audio sintetizados para reproducir al usuario, que se usan para interacciones de voz.
    • AgentText: Versión de texto de la respuesta del agente.
    • SpeechRecognition: Transcripción del discurso reconocido del usuario.
    • UpdatedOrderState: Contiene el estado actual completo del cliente Order cada vez que el agente lo actualiza. Úsalo para actualizar la representación del pedido de tu aplicación. Por lo general, esto debería generar una actualización de una interfaz de usuario o un sistema de registro para la información del estado del pedido, como un sistema de punto de venta.
    • InterruptionSignal: Indica que el usuario interrumpió el discurso del agente. El cliente debe dejar de reproducir de inmediato cualquier AgentAudio saliente.
    • AgentEvent:Eventos especiales, comoRestartOrder,que requieren la acción del cliente.
    • SuggestedOptions: Proporciona opciones contextualmente relevantes que un usuario podría seleccionar a continuación, lo que es útil para mostrar en una pantalla.
    • EndSession: Indica que el agente finalizó la sesión (p.ej., pedido completado, el usuario se fue o escalamiento del agente).

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. Cierra la transmisión

  • El cliente o el servidor pueden cerrar la transmisión. Por lo general, el servidor indica el final de una conversación con un mensaje EndSession. El cliente debe cerrar la transmisión cuando se recibe este mensaje.

Maneja tipos de mensajes específicos

En las siguientes secciones, se describe cómo manejar tipos de respuesta específicos que recibirá tu cliente cuando llame a BidiProcessOrder.

AudioInput

  • Transmite audio en fragmentos a medida que esté disponible.
  • Formato: PCM lineal de 16 bits, tasa de muestreo de 16,000 Hz.
  • Los fragmentos de audio no incluyen los encabezados de audio que suelen preceder a un archivo WAV.
  • Para situaciones de autoservicio con cancelación de eco habilitada (enable_echo_cancellation en Config), proporciona customer_audio y crew_audio.

UpdatedOrderState

  • Este mensaje proporciona el estado completo del pedido cada vez que se envía. Reemplaza cualquier caché local del pedido por el contenido del mensaje Order recibido.
  • Usa los custom_integration_attributes dentro de los elementos y modificadores Order para asignar el contenido Order a entidades equivalentes dentro del sistema de registro de tu aplicación.

InterruptionSignal

  • Al recibirlo, detén de inmediato la reproducción de cualquier AgentAudio y borra cualquier audio del agente almacenado en búfer. Esto garantiza un flujo conversacional natural cuando el usuario interrumpe el discurso del agente.

EndSession

  • Verifica el EndType (p.ej., DRIVE_OFF, AGENT_ESCALATION).
  • Tu aplicación debe cerrar la conexión correctamente y realizar la transición del usuario de forma adecuada (p.ej., notificar a un supervisor humano en el caso de AGENT_ESCALATION o realizar la transición a un estado de confirmación del pedido).

Prácticas recomendadas

  • Maneja mensajes de forma asíncrona: Minimiza la latencia con subprocesos o E/S sin bloqueo para enviar solicitudes y procesar respuestas entrantes de forma simultánea.
  • Lógica de reconexión: Implementa una lógica de reconexión sólida en caso de problemas de red. Recuerda enviar el mensaje Config inicial con el mismo ID de sesión para intentar reanudar la conexión.
  • Manejo de errores: Supervisa la transmisión en busca de errores. Las bibliotecas de gRPC y WebSocket proporcionan mecanismos para detectar el cierre de la transmisión o errores de transporte. Registra estos eventos y manéjalos correctamente.
  • Almacenamiento en búfer de audio: Administra los búferes de audio con cuidado. Implementa el almacenamiento en búfer si es necesario para garantizar la reproducción fluida de AgentAudio y la entrega oportuna de AudioInput. Considera cuidadosamente la compensación entre la latencia y la calidad de reproducción cuando decidas tu esquema de almacenamiento en búfer.
  • Administración de ID de sesión: Asegúrate de que los IDs de sesión sean únicos para cada pedido o conversación distintos.
  • Administración de recursos: Cierra las transmisiones y libera recursos cuando se complete la sesión o si se producen errores irrecuperables.
  • Tiempos de espera: Si bien la transmisión en sí puede ser de larga duración (hasta 15 minutos de forma predeterminada), considera los tiempos de espera a nivel de la aplicación para estados específicos si es necesario.

Ejemplo de flujo de integración (conceptual)

  1. La app cliente (p.ej., app para dispositivos móviles) inicia un pedido.
  2. Establece la conexión gRPC o WebSocket a BidiProcessOrder.
  3. Envía BidiProcessOrderRequest con Config (ID de sesión, ID de tienda).
  4. Recibe el AgentAudio inicial (p.ej., mensaje de bienvenida) y reprodúcelo.
  5. El usuario habla: Captura el audio y transmítelo en mensajes AudioInput.
  6. Recibe SpeechRecognition (muestra la transcripción), AgentAudio (reproduce la respuesta) y, posiblemente, UpdatedOrderState (actualiza el carrito de la IU).
  7. Si el usuario interrumpe, recibe InterruptionSignal y detiene la reproducción.
  8. Continúa el intercambio de entradas de audio o texto y respuestas del agente.
  9. El usuario confirma el pedido: El agente envía el UpdatedOrderState final.
  10. El agente envía EndSession: El cliente cierra la transmisión y finaliza el pedido en el sistema de PdV con los datos del último UpdatedOrderState.

Ejemplo de extremo a extremo

Si bien las instrucciones anteriores desglosan los conceptos de transmisión paso a paso, a continuación, se muestra cómo es un flujo de integración completo de extremo a extremo.

Node.js

Antes de probar este ejemplo, sigue las instrucciones de configuración que se encuentran en la guía de inicio rápido del agente de IA de pedidos de comida sobre el uso de bibliotecas cliente.Node.js

Para autenticarte en el agente de IA de pedidos de comida, configura las credenciales predeterminadas de la aplicación. Para obtener más información, consulta Configura la autenticación para un entorno de desarrollo 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.'}});
}