Criar uma experiência de pedidos multimodal usando a API de streaming

Este guia fornece instruções e práticas recomendadas para engenheiros que criam experiências de pedido de comida com o método RPC FoodOrderingService.BidiProcessOrder. Essa API de streaming bidirecional em tempo real é o núcleo do agente de IA para pedidos de comida, permitindo a coleta de pedidos dinâmica e conversacional em vários aplicativos, como apps para dispositivos móveis, assistentes de voz, drive-thrus e quiosques.

Visão geral de BidiProcessOrder

O método BidiProcessOrder estabelece um canal de comunicação bidirecional persistente entre o aplicativo cliente e o agente de IA para pedidos de comida. Ao contrário das RPCs unárias padrão de solicitação e resposta, essa abordagem de streaming permite:

  • Interação de baixa latência:troca contínua de informações sem a sobrecarga de solicitações HTTP repetidas.
  • Entrada multimodal:processamento de fluxos de áudio (para pedidos por voz), entradas de texto e eventos do lado do cliente.
  • Respostas em tempo real:o agente pode enviar áudio, texto, atualizações de pedidos e outros sinais conforme a conversa acontece.

BidiProcessOrder não pode ser invocado usando REST. As integrações precisam usar um protocolo orientado a conexão:

  • gRPC (recomendado): oferece uma estrutura robusta e eficiente para streaming bidirecional.
  • WebSocket:adequado para clientes ou ambientes em que o gRPC não é adequado devido a restrições de linguagem de programação ou de rede.

Consulte a referência da API BidiProcessOrder para definições de tipo detalhadas. As integrações do WebSocket usam representações JSON desses tipos, conforme descrito na seção do WebSocket.

Pré-requisitos

Antes de fazer a integração com o BidiProcessOrder:

  1. Ative a API:verifique se a API do agente de IA para pedidos de comida está ativada no seu projeto Google Cloud. bash gcloud services enable foodorderingaiagent.googleapis.com --project=PROJECT_ID

  2. Autenticação:decida sua abordagem de autenticação e configure as contas de serviço e os papéis do IAM necessários, conforme descrito em Autenticação.

  3. Ingestão de cardápio:um Menu válido precisa ser ingerido e associado a um Store. Consulte Como integrar dados de menu para mais detalhes.

Autenticação

Para se conectar com segurança ao RPC BidiProcessOrder, seu aplicativo precisa fazer a autenticação usando uma conta de serviço do Google Cloud .

1. Configurar uma conta de serviço

  • Criar uma conta de serviço:no seu projeto Google Cloud , crie uma conta de serviço que seu aplicativo vai usar para autenticar a API do agente de IA para pedidos de comida. Consulte Como criar e gerenciar contas de serviço.
  • Conceda papéis do IAM:conceda os papéis do IAM necessários a essa conta de serviço. O papel principal necessário para chamar BidiProcessOrder é:

    • Usuário do agente de pedidos de comida (roles/foodorderingaiagent.agentUser): permite que a conta de serviço se conecte ao serviço de pedidos e processe sessões.

    É possível conceder esse papel usando o console do Google Cloud ou o gcloud: bash gcloud projects add-iam-policy-binding PROJECT_ID \ --member="serviceAccount:SERVICE_ACCOUNT_EMAIL" \ --role="roles/foodorderingaiagent.agentUser"

2. Fluxo de autenticação de aplicativos

O fluxo de autenticação exato depende da arquitetura do aplicativo, especialmente se o aplicativo cliente (por exemplo, app para dispositivos móveis, software de quiosque) se conecta diretamente ou pelo seu próprio back-end.

Cenário comum: autenticar um aplicativo cliente voltado ao consumidor

Este é um padrão típico para aplicativos móveis ou da Web:

  1. Client-to-YourAuth::o app cliente do usuário final (dispositivo móvel, Web) faz a autenticação com seu sistema de autenticação de usuário atual (pode ser o Firebase Authentication, seu próprio servidor OAuth etc.).
  2. Troca de token:depois de autenticar o usuário, o app cliente solicita um token de curta duração de um serviço de back-end seguro que você controla (por exemplo, um "serviço de token de API").
  3. Geração de token de acesso:seu serviço de back-end, usando as credenciais do principal da conta de serviço Google Cloud configurado na etapa 1, gera um token de acesso padrão do OAuth 2.0 para o escopohttps://www.googleapis.com/auth/cloud-platform. Isso pode ser feito usando as bibliotecas de cliente de autenticação Google Cloud .

    • Segurança:as chaves ou credenciais da conta de serviço usadas para gerar esses tokens precisam ser armazenadas e gerenciadas com segurança no seu back-end. Nunca exponha as chaves privadas da conta de serviço diretamente aos aplicativos cliente do usuário final. Consulte Práticas recomendadas para gerenciar chaves de conta de serviço.
  4. Token para o cliente:seu serviço de back-end retorna o token de acesso do Google gerado para o app cliente.

  5. Chamada de API:o app cliente usa esse token de acesso do Google para autenticar a conexão gRPC ou WebSocket com o RPC BidiProcessOrder.

3. Como usar o token

  • gRPC:as bibliotecas de cliente gRPC do Google geralmente processam a atualização e a inclusão de tokens nos metadados da chamada quando recebem credenciais da conta de serviço.
  • WebSocket (não navegador): inclua o token no cabeçalho Authorization: Bearer TOKEN.
  • WebSocket (navegador): conforme observado na seção WebSocket, conexões WebSocket diretas do navegador não podem usar cabeçalhos de autorização. Um proxy de streaming do lado do servidor é necessário para autenticar a conexão dos clientes com Google Cloud.

Como se conectar à API

É possível estabelecer um fluxo usando bibliotecas de cliente gRPC ou uma conexão WebSocket.

gRPC

Usar gRPC é a abordagem recomendada. Você vai usar as bibliotecas de cliente para a linguagem de sua escolha (por exemplo, Node.js), que são baseadas na Referência da API BidiProcessOrder.

As etapas básicas envolvem:

  1. Crie um canal gRPC para o endpoint de API do agente de IA para pedidos de comida (por exemplo, foodorderingaiagent.googleapis.com).
  2. Receba um stub de cliente para FoodOrderingService.
  3. Invoque o método BidiProcessOrder, que retorna um objeto de fluxo para envio de solicitações e recebimento de respostas.
  4. Implemente a lógica de negócios de acordo com seu caso de uso, que simultaneamente:
    • Envia áudio, texto e entrada de eventos do usuário final.
    • Processa mensagens do agente, incluindo áudio, texto e 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 conexões WebSocket, o caminho do URL é:

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

  • LOCATION: por exemplo, us

Cabeçalhos obrigatórios:

  • Authorization: Bearer TOKEN, em que TOKEN é um token de acesso do OAuth 2.0 obtido para sua conta de serviço.

Formato da mensagem:

  • Cliente para servidor:as mensagens enviadas à API (por exemplo, Config, AudioInput, TextInput, EventInput) precisam ser representações JSON do proto BidiProcessOrderRequest, enviadas como websocket.TextMessage.
  • Do servidor para o cliente:as mensagens recebidas da API (BidiProcessOrderResponse) serão enviadas como websocket.BinaryMessage, mas o conteúdo dessas mensagens binárias é um payload JSON.
  • Dados binários:os dados binários nos payloads JSON (por exemplo, customerAudio em AudioInput, agentAudio em AgentAudio) precisam ser codificados em base64.

Exemplo de WebSocket do Node.js

Confira um exemplo de como se conectar e interagir com a API usando WebSockets no Node.js com a 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 da sessão

Cada chamada para BidiProcessOrder inicia uma sessão. A sessão permanece ativa enquanto o fluxo estiver aberto.

1. Iniciação (mensagem de configuração)

  • Ao estabelecer a conexão, a primeira mensagem enviada pelo cliente precisa ser um BidiProcessOrderRequest que contenha a mensagem Config.
  • Campos obrigatórios em Config:
    • session: um identificador de sessão exclusivo gerado pelo cliente. Formato: projects/PROJECT/locations/LOCATION/sessions/SESSION_ID.
      • store: o nome do recurso do Store. Formato: projects/PROJECT/locations/LOCATION/brands/BRAND/stores/STORE.
        • O agente usa o store para carregar o menu e a configuração adequados.

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. Envio de entradas

  • Depois do Config inicial, o cliente pode enviar um fluxo de mensagens BidiProcessOrderRequest contendo uma das seguintes entradas:
    • AudioInput: dados de áudio brutos (normalmente PCM linear de 16 bits a 16.000 Hz, sem cabeçalhos). Usado para interações de voz.
    • TextInput: Mensagens de texto do usuário.
    • EventInput: sinais para eventos como DriveOffEvent (para casos de uso de drive-thru quando o veículo sai), CrewInterjectionEvent (para qualquer situação em que um humano assume a função de receber pedidos no meio da conversa) ou OrderStateUpdateEvent (se o pedido for modificado no lado do cliente, por exemplo, usando uma interface de toque).

Node.js

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

3. Recebimento das respostas

  • Ao mesmo tempo, o agente envia de volta um stream de mensagens BidiProcessOrderResponse. Seu cliente precisa estar preparado para lidar com vários tipos de resposta no campo oneof response:
    • AgentAudio: bytes de áudio sintetizados para serem reproduzidos para o usuário, usados em interações por voz.
    • AgentText: versão em texto da resposta do agente.
    • SpeechRecognition: transcrição da fala reconhecida do usuário.
    • UpdatedOrderState: contém o estado atual completo do Order sempre que ele é atualizado pelo agente. Use isso para atualizar a representação de pedido do seu aplicativo. Isso geralmente resulta em uma atualização de uma interface do usuário ou de um sistema de registro de informações de estado do pedido, como um sistema de ponto de venda.
    • InterruptionSignal: indica que o usuário interrompeu a fala do agente. O cliente precisa parar imediatamente de tocar qualquer AgentAudio de saída.
    • AgentEvent: eventos especiais, como RestartOrder, que exigem ação do cliente.
    • SuggestedOptions: oferece opções contextualmente relevantes que um usuário pode selecionar em seguida, útil para exibição em uma tela.
    • EndSession: Indica que a sessão foi encerrada pelo agente (por exemplo, pedido concluído, saída do usuário ou escalonamento do 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. Encerrar o stream

  • O fluxo pode ser fechado pelo cliente ou pelo servidor. Normalmente, o servidor sinaliza o fim de uma conversa usando uma mensagem EndSession. O cliente precisa fechar o stream quando essa mensagem é recebida.

Como processar tipos específicos de mensagens

As seções a seguir descrevem como processar tipos de respostas específicos que seu cliente vai receber ao chamar BidiProcessOrder.

AudioInput

  • Transmita o áudio em partes à medida que ele fica disponível.
  • Formato: PCM linear de 16 bits, taxa de amostragem de 16.000 Hz.
  • Os trechos de áudio não incluem os cabeçalhos que geralmente precedem um arquivo WAV.
  • Para cenários de drive-thru com cancelamento de eco ativado (enable_echo_cancellation em Config), forneça customer_audio e crew_audio.

UpdatedOrderState

  • Essa mensagem fornece o estado completo do pedido sempre que é enviada. Substitua qualquer cache local do pedido pelo conteúdo da mensagem Order recebida.
  • Use o custom_integration_attributes nos itens e modificadores Order para mapear o conteúdo Order em entidades equivalentes no sistema de registro do seu aplicativo.

InterruptionSignal

  • Ao receber, interrompa imediatamente a reprodução de qualquer AgentAudio e limpe o áudio do agente em buffer. Isso garante um fluxo de conversa natural quando o usuário interrompe a fala do agente.

EndSession

  • Verifique o EndType (por exemplo, DRIVE_OFF, AGENT_ESCALATION).
  • O aplicativo precisa fechar a conexão normalmente e fazer a transição do usuário de maneira adequada (por exemplo, notificar um supervisor humano no caso de AGENT_ESCALATION ou fazer a transição para um estado de confirmação de pedido).

Práticas recomendadas

  • Processar mensagens de forma assíncrona:minimize a latência usando linhas de execução ou E/S não bloqueadora para enviar solicitações e processar respostas simultaneamente.
  • Lógica de reconexão:implemente uma lógica de reconexão robusta em caso de problemas de rede. Não se esqueça de enviar a mensagem inicial Config com o mesmo ID de sessão para tentar retomar.
  • Tratamento de erros:monitore o stream em busca de erros. As bibliotecas gRPC e WebSocket fornecem mecanismos para detectar o fechamento do stream ou erros de transporte. Registre esses eventos e lide com eles sem estresse.
  • Buffer de áudio:gerencie os buffers de áudio com cuidado, implementando o buffer se necessário, para garantir a reprodução tranquila de AgentAudio e a entrega pontual de AudioInput. Considere cuidadosamente a compensação entre latência e qualidade de reprodução ao decidir seu esquema de buffer.
  • Gerenciamento de ID da sessão:garanta que os IDs de sessão sejam exclusivos para cada pedido/conversa diferente.
  • Gerenciamento de recursos:feche streams e libere recursos quando a sessão for concluída ou se ocorrerem erros irrecuperáveis.
  • Tempos limite:embora o fluxo em si possa ser de longa duração (até 15 minutos por padrão), considere tempos limite no nível do aplicativo para estados específicos, se necessário.

Exemplo de fluxo de integração (conceitual)

  1. O app cliente (por exemplo, um app para dispositivos móveis) inicia um pedido.
  2. Estabelece uma conexão gRPC/WebSocket com BidiProcessOrder.
  3. Envie BidiProcessOrderRequest com Config (ID da sessão, ID da loja).
  4. Receber e reproduzir a AgentAudio inicial (por exemplo, mensagem de boas-vindas).
  5. O usuário fala: capture o áudio e faça streaming dele em mensagens AudioInput.
  6. Receba SpeechRecognition (exibir transcrição), AgentAudio (tocar resposta) e possivelmente UpdatedOrderState (atualizar carrinho da UI).
  7. Se o usuário interromper, receba InterruptionSignal e pare a reprodução.
  8. Continue a troca de entradas de áudio ou texto e respostas do agente.
  9. O usuário confirma o pedido: o agente envia o UpdatedOrderState final.
  10. O agente envia EndSession: o cliente fecha o fluxo e finaliza o pedido no sistema de PDV usando dados do último UpdatedOrderState.

Exemplo completo

Embora as instruções acima detalhem os conceitos de streaming parte por parte, aqui está um exemplo de fluxo de integração completo de ponta a ponta.

Node.js

Antes de testar esse exemplo, siga as instruções de configuração para Node.js no Guia de início rápido do agente de IA para pedidos de comida: como usar bibliotecas de cliente.

Para autenticar no agente de IA para pedidos de comida, configure o Application Default Credentials. Para mais informações, consulte Configurar a autenticação para um ambiente de desenvolvimento 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.'}});
}