https://a.storyblok.com/f/270183/64022/ec306da7b4/live-ai-assistant.png

Aprimore suas videoconferências com o ChatGPT: Conheça seu assistente de IA em tempo real

Publicado em November 16, 2023

Tempo de leitura: 6 minutos

Introdução

A tecnologia, especialmente a inteligência artificial, mudou a maneira como nos comunicamos e trabalhamos online. À medida que o mundo se volta cada vez mais para interações online por meio de webinars, conferências e reuniões individuais, há uma necessidade inegável de uma ferramenta capaz de aumentar a produtividade desses encontros virtuais.

Imagine o seguinte: um assistente de IA ao seu lado durante esses encontros virtuais, pronto para responder a perguntas, fazer anotações sobre ações a serem realizadas e sintetizar as informações essenciais em resumos concisos. Hoje, estamos criando esse assistente, com foco principalmente em conversas individuais. No entanto, as possibilidades são ilimitadas, já que esse conceito se aplica a diversos cenários. Então, sem mais delongas, vamos criar o “Sushi”!

Aqui você encontra um Video de demonstração:

Pré-requisitos

Para configurar o aplicativo no seu computador, primeiro clone o repositório.

git clone https://github.com/hamzanasir/vonage-openai-demo

Agora acesse o repositório e instale os pacotes associados da seguinte forma:

npm install

Agora precisamos configurar nossa chave e nosso segredo da API da Vonage. Vamos começar copiando o modelo do arquivo .env:

cp .envcopy .env

Agora, basta substituir a chave e o segredo da API no arquivo .env pelas suas credenciais. Você pode encontrar sua chave e seu segredo da Video API da Vonage na página do projeto do seu Account da Video API da Vonage (https://tokbox.com/account). Seu arquivo .env deve ficar assim:

# enter your TokBox API key after the '=' sign below
TOKBOX_API_KEY=your_api_key

# enter your TokBox api secret after the '=' sign below
TOKBOX_SECRET=your_project_secret

Você também precisa criar um projeto na OpenAI. Para isso, acesse https://platform.openai.com/signup. Depois de se cadastrar, você pode acessar a seção “Chaves de API” para criar uma nova chave secreta.

OpenAI Dashboard showing the API key section

Cole sua chave secreta no arquivo .env.

# enter your OpenAI Secret after the '=' sign below
OPENAI_SECRET=your_openai_secret

Agora que isso está feito, você pode iniciar o aplicativo com:

npm start

Agora acesse http://localhost:8080/. Para começar a conversar com a Sushi, certifique-se de clicar nos botões de ativação das legendas no canto inferior esquerdo para iniciar o serviço de legendas ao vivo da Vonage. Para uma experiência melhor, certifique-se de estar usando fones de ouvido, para que a voz da Sushi não cause feedback no microfone do apresentador.

Todo o código sobre o qual falamos neste blog pode ser encontrado no public/js/app.js arquivo. Se você estiver curioso para saber como as legendas em tempo real funcionam, não deixe de conferir o routes/index.js arquivo para ver como o serviço de legendas é iniciado e encerrado.

Arquitetura de alto nível

Para compreender o funcionamento interno do nosso assistente virtual, vamos nos aprofundar em sua estrutura arquitetônica.

Flow chart of the AI assistant

No centro dessa configuração está o gerenciamento das conversas dos usuários e das interações com a API da OpenAI. Escolhemos o modelo GPT-3.5 Turbo da OpenAI, que se destaca no tratamento de conversas. No entanto, a OpenAI oferece uma variedade de modelos para atender aos objetivos específicos do seu projeto.

Quando enviamos uma solicitação ao GPT, ele leva um momento para elaborar uma resposta. Vamos adotar uma abordagem mais dinâmica. Transmitimos os dados à medida que são gerados, garantindo uma experiência de conversa mais rápida e natural. Essa abordagem também permite que os usuários interrompam e conduzam a conversa, imitando as interações da vida real.

À medida que o GPT formula sua resposta, convertemos o texto gerado em fala audível usando um sintetizador de voz. Existem várias opções disponíveis para esse fim, incluindo sintetizadores de voz de terceiros pagos, capazes de produzir uma voz com som mais natural. Para o nosso projeto, optamos pelo módulo SpeechSynthesisUtterance nativo do navegador. Para lidar com interrupções causadas pela fala do usuário, utilizaremos o AbortController do navegador.

Publicação e assinatura de legendas

Vamos ver como iniciamos e gerenciamos nosso serviço de legendagem, um componente essencial do nosso sistema.

Para começar, ativamos o serviço de legendas enviando uma chamada POST ao nosso servidor na rota /captions/start, fornecendo nosso ID de sessão. Com essa configuração, estamos prontos para receber legendas para todos os assinantes da sessão. No entanto, também queremos que nossas próprias legendas sejam geradas como editor. Para isso, nos inscrevemos em nosso próprio editor com volume zero, garantindo que não haja repetição de nossa própria voz.

Aqui está um trecho do código que faz isso acontecer:

const publisherOptions = {
  insertMode: 'append',
  width: '100%',
  height: '100%',
  publishCaptions: true,
};
publisher = OT.initPublisher('publisher', publisherOptions, (err) => {
        if (err) {
          handleError(err);
        } else {
          session.publish(publisher, () => {
            if (error) {
              console.error(error);
            } else {
              const captionOnlySub = session.subscribe(
                publisher.stream,
                document.createElement('div'),
                {
                  audioVolume: 0
                },
              );
              speakText(greetingMessage);
              captionOnlySub.on('captionReceived', async (event) => {
                if (event.isFinal) {
                  stopAiGenerator();
                  startAiGenerator(event.caption)
                }
              });
            }
          });
        }
      });

Neste código, publicamos nosso áudio e Video na sessão e, em seguida, nos inscrevemos na mesma instância para receber nossas legendas. Para que esse processo funcione perfeitamente, associamos um manipulador de eventos 'captionReceived' ao nosso assinante simulado. Esse manipulador de eventos captura a transcrição do que falamos à medida que falamos. Como esse evento geralmente é acionado periodicamente enquanto falamos, usamos o booleano “isFinal” para identificar quando terminamos de falar. Assim que nosso texto gerado estiver pronto, nós o passamos para o nosso startAiGenerator para processamento posterior.

Geração de respostas

A espinha dorsal da nossa lógica está na função `startAiGenerator`. Como se trata de uma função extensa, vamos dividi-la em partes mais fáceis de lidar. Primeiro, vamos examinar mais de perto a lógica para fazer chamadas à API:

async function startAiGenerator(message) {
  let aiText = '';
  let utterableText = ''

  abortController = new AbortController();
  const userMessage = {
    'role': 'user',
    'content': message
  }

  const reqBody = {
    messages: [...messages, userMessage],
    temperature: 1,
    max_tokens: 256,
    top_p: 1,
    frequency_penalty: 0,
    presence_penalty: 0,
    model: 'gpt-3.5-turbo',
    stream: true
  };

  try {
    const response = await fetch('https://api.openai.com/v1/chat/completions', {
      headers: {
        'Authorization': `Bearer ${openAISecret}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(reqBody),
      method: 'POST',
      signal: abortController.signal
    });

Neste trecho de código, temos duas variáveis: aiText e utterableText. aiText armazena a resposta completa à nossa consulta, enquanto utterableText captura a última frase ou expressão completa do fluxo de dados legível recebido da OpenAI. Também configuramos uma instância do AbortController para lidar com interrupções do usuário enquanto o assistente estiver falando. Antes de iniciarmos o gerador de IA, uma função separada, stopAiGenerator(), é chamada para interromper qualquer processo de geração de IA em andamento.

function stopAiGenerator() {
  if (abortController) {
    abortController.abort();
    abortController = null;
  }
  window.speechSynthesis.cancel();
}

Por fim, chamamos a OpenAI com o histórico das conversas armazenado na matriz global de mensagens. Agora, vamos dar uma olhada em como fazemos o streaming de dados da OpenAI:

    const reader = response.body.getReader();
    const decoder = new TextDecoder('utf-8');

    while (true) {
      const chunk = await reader.read();
      const { done, value } = chunk;
      if (done) {
        break;
      }

      const decodedChunk = decoder.decode(value);
      const lines = decodedChunk.split('\n');
      const parsedLines = lines
        .map(l => l.replace(/^data: /, '').trim())
        .filter(l => l !== '' && l !== '[DONE]')
        .map(l => JSON.parse(l));
      for (const line of parsedLines) {
        const textChunk = line?.choices[0]?.delta?.content;
        if (textChunk) {
          utterableText += textChunk
          if (textChunk.match(/[.!?:,]$/)) {
            speakText(utterableText);
            utterableText = '';
          }
          aiText += textChunk;
        }
      }
    }

Nesta seção, apresentamos ao leitor o objeto `fetch` que usamos para fazer a solicitação ao GPT. Utilizamos um loop `while` para ler continuamente do fluxo até que ele se esgote. Para cada trecho de texto, o anexamos às duas variáveis definidas anteriormente. Se um trecho incluir um sinal de pontuação, acionamos a função `speakText()`, que pronuncia a frase ou expressão mais recente gerada e redefine `utterableText` como uma string vazia. Lembre-se de que esse processo pode ser interrompido a qualquer momento por meio de um sinal de parada do nosso `AbortController`.

Por fim, quer encerremos o fluxo ou permitamos que ele seja concluído, salvamos tanto a entrada do usuário quanto a resposta da OpenAI da seguinte maneira:

messages.push(userMessage);
  messages.push({
    content: aiText,
    role: 'assistant'
  })

Algumas considerações sobre a engenharia de prompts

A “engenharia de prompts” no processamento de linguagem natural consiste em elaborar instruções ou perguntas para orientar modelos de IA. Trata-se de encontrar o equilíbrio certo entre clareza e ambiguidade para alcançar resultados específicos. Com a engenharia de prompts adequada, você pode transformar o Sushi em um consultor jurídico fictício, um especialista médico, um amigo atrevido etc. Para esta demonstração, estamos usando este prompt:

const messages = [
  {
    'role': 'system',
    'content': "You are a participant called Sushi in a live call with someone. Speak concisely, as if you're having a one-on-one conversation with someone. " // Prompt engineering for AI assistant
  }
];

Tente alterar o conteúdo desse código para ver como o assistente vai se comportar!

Síntese de fala

Esta parte do projeto permite certa flexibilidade. Você pode optar por soluções mais naturais e avançadas para a síntese de fala. A maioria delas é paga; por isso, nesta demonstração, estamos optando pela funcionalidade padrão SpeechSynthesisUtterance do navegador.

function speakText(text) {
  let captions = '';
  const utterThis = new SpeechSynthesisUtterance(text);

  utterThis.voice = voices.find((v) => v.name.includes('Samantha'));

  utterThis.onboundary = (event) => {
    captions += `${event.utterance.text.substring(event.charIndex, event.charIndex + event.charLength)} `;
    displayCaptions(captions, 'ai-assistant');
  };

  utterThis.onstart = () => {
    animateVoiceSynthesis();
  };

  utterThis.onend = function() {
    stopAnimateVoiceSynthesis();
  };

  window.speechSynthesis.speak(utterThis);
}

Então, primeiro, inicializamos o módulo SpeechSynthesisUtterance com o texto que queremos reproduzir. Depois disso, precisamos definir uma Voice disponível no navegador. Elas são carregadas de forma assíncrona, desta forma:

let voices = window.speechSynthesis.getVoices();

if (speechSynthesis.onvoiceschanged !== undefined)
  speechSynthesis.onvoiceschanged = updateVoices;

function updateVoices() {
  voices = window.speechSynthesis.getVoices();
}

Usamos o onstart e onend para animar as barras no meio, a fim de indicar quando o Sushi está falando. Além disso, o evento onboundary é usado para inserir legendas de texto que mostram qual palavra o sintetizador de voz está reproduzindo.

Conclusão

É importante observar que este é apenas um exemplo do que você pode fazer com uma integração com o GPT. O objetivo desta demonstração é mostrar como você pode criar o fluxo de trabalho entre o Vonage Video Live Captions e a OpenAI, mas não há limites para o que você pode criar com prompts elaborados com maestria.

Confira a seção de exemplos do GPT para ver a infinidade de Applications. Mal podemos esperar para ver o que você vai criar!

Conte para a gente o que você está desenvolvendo com a Video API da Vonage. Converse conosco na nossa Comunidade Vonage no Slack ou no X, antes conhecido como Twitter, @VonageDev.

Compartilhar:

https://a.storyblok.com/f/270183/400x390/09555431c6/hamza-nasir.png
Hamza NasirDesenvolvedor Full Stack

Hamza é engenheiro de software e mora em Chicago. Ele trabalha na Webrtc.ventures, uma empresa líder no fornecimento de soluções WebRTC. Ele também atua como desenvolvedor full stack na Vonage, colaborando com a plataforma de Video para atender melhor às necessidades dos clientes. Como um introvertido que se orgulha disso, ele gosta de passar seu tempo livre brincando com seus gatos.