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 pedidos de comida com o método RPC FoodOrderingService.BidiProcessOrder. Essa API de streaming bidirecional em tempo real é o núcleo do Agente de IA do Pedido de comida, permitindo a realização de pedidos conversacionais e dinâmicos em vários aplicativos, como apps para dispositivos móveis, assistentes de voz, drive-thrus e quiosques.

Visão geral do BidiProcessOrder

O método BidiProcessOrder estabelece um canal de comunicação bidirecional e persistente entre o aplicativo cliente e o Agente de IA do Pedido de comida. Ao contrário das RPCs de solicitação e resposta unárias padrão, 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 streams de áudio (para pedidos de 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 à medida que a conversa se desenvolve.

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

  • gRPC (recomendado) : fornece um framework robusto 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 Reference para definições de tipo detalhadas. As integrações do WebSocket usam representações JSON desses tipos, conforme descrito na seção WebSocket.

Pré-requisitos

Antes de fazer a integração com BidiProcessOrder, você precisa:

  1. Ativar a API: verifique se a API do Agente de IA do Pedido de comida está ativada no seu Google Cloud projeto. 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 cardápio válido precisa ser ingerido e associado a uma Store. Consulte Integrar dados de cardápio para mais detalhes.

Autenticação

Para se conectar com segurança à RPC BidiProcessOrder, o aplicativo precisa ser autenticado usando uma Google Cloud conta de serviço.

1. Configurar uma conta de serviço

  • Criar uma conta de serviço:no seu Google Cloud projeto, crie uma conta de serviço que o aplicativo vai usar para autenticar na API do Agente de IA do Pedido de comida. Consulte Como criar e gerenciar contas de serviço.
  • Conceder 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 Google Cloud console ou gcloud: bash gcloud projects add-iam-policy-binding PROJECT_ID \ --member="serviceAccount:SERVICE_ACCOUNT_EMAIL" \ --role="roles/foodorderingaiagent.agentUser"

2. Fluxo de autenticação do aplicativo

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: autenticação de um aplicativo cliente voltado ao consumidor

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

  1. Client-to-YourAuth::o app cliente do usuário final (para dispositivos móveis, Web) é autenticado com seu sistema de autenticação de usuário atual (pode ser o Firebase Authentication, seu próprio servidor OAuth etc.).
  2. Troca de tokens: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 configurada na etapa 1, gera um token de acesso OAuth 2.0 padrão para o https://www.googleapis.com/auth/cloud-platform escopo. Google Cloud Isso pode ser feito usando as Google Cloud bibliotecas de cliente de autenticação.

    • 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 back-end. Nunca exponha chaves privadas da conta de serviço diretamente a aplicativos cliente do usuário final. Consulte Práticas recomendadas para gerenciar conta de serviço de serviço.
    Google Cloud
  4. Token para 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 a RPC BidiProcessOrder.

3. Como usar o token

  • gRPC:as bibliotecas de cliente do gRPC do Google geralmente processam a atualização e a inclusão de tokens nos metadados de chamada quando fornecidas com credenciais de 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, as 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 stream usando bibliotecas de cliente do gRPC ou uma conexão WebSocket.

gRPC

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

As etapas básicas envolvem:

  1. Criar um canal gRPC para o endpoint de API do Agente de IA do Pedido de comida (por exemplo, foodorderingaiagent.googleapis.com).
  2. Receber um stub de cliente para FoodOrderingService.
  3. Invocar o método BidiProcessOrder, que retorna um objeto de stream para enviar solicitações e receber respostas.
  4. Implementar a lógica de negócios de acordo com seu caso de uso, que simultaneamente:
    • Envia entrada de áudio, texto e evento 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 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.
  • Servidor para 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 stream estiver aberto.

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

  • Ao estabelecer a conexão, a primeira mensagem enviada pelo cliente precisa ser um BidiProcessOrderRequest que contenha a Config mensagem.
  • 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 da Store. Formato: projects/PROJECT/locations/LOCATION/brands/BRAND/stores/STORE.
      • O agente usa a store para carregar o cardápio e a configuração adequados.
    • mode (opcional para BidiProcessOrder): o padrão é HYBRID (voz e texto). Observe que, se você estiver usando a API unária REST ou gRPC ProcessOrder em vez de BidiProcessOrder, mode precisará ser definido explicitamente como 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),
  }
});

Configsession

2. Como enviar entradas

  • Após a Config inicial, o cliente pode enviar um stream 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 pedido 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. Como receber respostas

  • Simultaneamente, o agente envia um stream de mensagens BidiProcessOrderResponse. O cliente precisa estar preparado para processar vários tipos de resposta no campo oneof response:
    • AgentAudio: bytes de áudio sintetizados a serem reproduzidos para o usuário, usados para interações de voz.
    • AgentText: versão de texto da resposta do agente.
    • SpeechRecognition: transcrição da fala reconhecida do usuário.
    • UpdatedOrderState: Contém o estado atual completo do cliente Order sempre que ele é atualizado pelo agente. Use isso para atualizar a representação do pedido do aplicativo. Isso normalmente 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 a reprodução de qualquer AgentAudio de saída.
    • AgentEvent: eventos especiais, como RestartOrder, que exigem ação do cliente.
    • SuggestedOptions: fornece opções contextualmente relevantes que um usuário pode selecionar em seguida, úteis para exibição em uma tela.
    • EndSession: sinaliza 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. Como fechar o stream

  • O stream 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 for recebida.

Como processar tipos de mensagens específicos

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

AudioInput

  • Transmita áudio em blocos à medida que ele fica disponível.
  • Formato: PCM linear de 16 bits, taxa de amostragem de 16.000 Hz.
  • Os blocos de áudio não incluem os cabeçalhos de áudio que normalmente 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 ele é enviado. Substitua qualquer cache local do pedido pelo conteúdo da mensagem Order recebida.
  • Use os custom_integration_attributes nos itens e modificadores Order para mapear o conteúdo Order em entidades equivalentes no sistema de registro do aplicativo.

InterruptionSignal

  • Ao receber, interrompa imediatamente a reprodução de qualquer AgentAudio e limpe qualquer áudio do agente armazenado em buffer. Isso garante um fluxo conversacional 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 do pedido).

Práticas recomendadas

  • Processar mensagens de forma assíncrona:minimize a latência usando linhas de execução ou E/S sem bloqueio para enviar solicitações e processar respostas recebidas simultaneamente.
  • Lógica de reconexão:implemente uma lógica de reconexão robusta em caso de problemas de rede, lembrando de enviar a mensagem Config inicial com o mesmo ID de sessão para tentar a retomada.
  • Processamento 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 processe-os normalmente.
  • Buffer de áudio:gerencie os buffers de áudio com cuidado, implementando o buffer, se necessário, para garantir a reprodução suave de AgentAudio e a entrega oportuna de AudioInput. Considere cuidadosamente a compensação entre latência e qualidade de reprodução ao decidir o esquema de buffer.
  • Gerenciamento de ID da sessão:verifique se os IDs de sessão são exclusivos para cada pedido/conversa.
  • 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 stream 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, app para dispositivos móveis) inicia um pedido.
  2. Estabeleça a conexão gRPC/WebSocket com BidiProcessOrder.
  3. Envie BidiProcessOrderRequest com Config (ID da sessão, ID da loja).
  4. Receba o AgentAudio inicial (por exemplo, mensagem de boas-vindas) e reproduza-o.
  5. O usuário fala: capture o áudio e transmita-o em mensagens AudioInput.
  6. Receba SpeechRecognition (exiba a transcrição), AgentAudio (reproduza a resposta) e, possivelmente, UpdatedOrderState (atualize o 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 stream 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, confira como é um fluxo de integração completo.

Node.js

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

Para autenticar no Agente de IA do Pedido de comida, configure o Application Default Credentials. Se quiser 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.'}});
}