De zero a herói "Ações no Google": seu código

imagem


Na primeira parte, descobrimos os princípios básicos do design e desenvolvimento de aplicativos para o Google Assistant. Agora é hora de escrever seu próprio assistente para que os usuários possam finalmente escolher um filme para a noite. Shipa_o , raenardev e designer do ComradeGuest continuam conversando.


Escreva seu código

Vamos tentar escrever algo mais complicado.
Digamos que nosso agente recomenda filmes por gênero.
Pedimos a ele para "Mostrar o horror", e o agente analisará o gênero, procurará um filme na coleção por gênero e o exibirá na tela.


Para começar, armazenaremos a coleção de filmes em uma variável:
var json = { "filmsList": [ { "id": "1", "title": " :  ", "description": "   ", "genres": ["", "", ""], "imageUrl": "http://t3.gstatic.com/images?q=tbn:ANd9GcQEA5a7K9k9ajHIu4Z5AqZr7Y8P7Fgvd4txmQpDrlQY2047coRk", "trailer": "https://www.youtube.com/watch?v=RNksw9VU2BQ" }, { "id": "2", "title": " :  2 –  ", "description": "   ", "genres": ["", "", "", ""], "imageUrl": "http://t3.gstatic.com/images?q=tbn:ANd9GcTPPAiysdP0Sra8XcIhska4MOq86IaDS_MnEmm6H7vQCaSRwahQ", "trailer": "https://www.youtube.com/watch?v=vX_2QRHEl34" }, { "id": "3", "title": "", "description": "  ", "genres": ["", "", ""], "imageUrl": "https://www.kinopoisk.ru/images/film_big/386.jpg", "trailer": "https://www.youtube.com/watch?v=xIe98nyo3xI" } ] }; 

 exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => { const agent = new WebhookClient({ request, response }); let result = request.body.queryResult; let parameters = result.parameters; let outputContexts = result.outputContexts; let intentMap = new Map(); //    agent  parameters,       intentMap.set('search-by-genre', searchByGenre.bind(this, agent, parameters)); agent.handleRequest(intentMap); }); function searchByGenre(agent, parameters) { let filmsList = json.filmsList; //     let filteredFilms = filmsList.filter((film) => { // ,             return film.genres.some((genre) => genre == parameters.genre); }); //       let firstFlim = filteredFilms[0]; //    agent.add(firstFlim.title); //       agent.add(new Card({ title: firstFlim.title, imageUrl: firstFlim.imageUrl, text: firstFlim.description, buttonText: ' ', buttonUrl: firstFlim.trailer })); //      agent.add([ "    ?", new Suggestion("  ?"), new Suggestion(""), new Suggestion("") ]); } 

Agora a resposta se tornou mais informativa.
Exibimos texto, um cartão com informações e dicas:


Exibir informações do filme

Uma boa característica do Dialogflow é que ele é adaptado para diferentes dispositivos.
Se o dispositivo tiver alto-falantes, todas as frases que enviarmos para o método add serão exibidas e, se não houver tela, os objetos Card e Suggestion simplesmente não serão exibidos.


Nós conectamos o banco de dados

Vamos complicar a tarefa e adicionar a obtenção de dados do banco de dados (DB).
A maneira mais fácil é usar o banco de dados em tempo real do firebase.
Por exemplo, usaremos a API do banco de dados do administrador.


Primeiro, você precisa criar um banco de dados e preenchê-lo.
Você pode fazer isso no mesmo projeto que foi criado para o Cloud Functions :


Banco de dados preenchido em tempo real da base de firmas

Depois que o banco de dados estiver cheio, conecte-o ao cumprimento:
 //   firebase-admin const firebaseAdmin = require('firebase-admin'); //  firebaseAdmin firebaseAdmin.initializeApp({ credential: firebaseAdmin.credential.applicationDefault(), databaseURL: 'https://<ID->.firebaseio.com' }); //  ,      function getFilmsList() { return firebaseAdmin .database() .ref() .child('filmsList') .once('value') .then(snapshot => { const filmsList = snapshot.val(); console.log('filmsList: ' + JSON.stringify(filmsList)); return filmsList; }) .catch(error => { console.log('getFilmsList error: ' + error); return error; }); } 

O acesso ao banco de dados requer multithreading. A API do banco de dados firebase foi projetada para usar o Promise . O .once('value') retorna uma promessa. Em seguida, obtemos nossos dados no bloco then() e retornamos o Promise com eles como resultado da execução da função.
É importante retornar essa promessa ao método handleRequest() , caso contrário, o agente sairá com nosso retorno de chamada sem aguardar uma resposta e processar o resultado.


Filme de pesquisa de versão por gênero, usando o banco de dados:
 'use strict'; const functions = require('firebase-functions'); const firebaseAdmin = require('firebase-admin'); const { WebhookClient } = require('dialogflow-fulfillment'); const { Card, Suggestion } = require('dialogflow-fulfillment'); firebaseAdmin.initializeApp({ credential: firebaseAdmin.credential.applicationDefault(), databaseURL: 'https://<ID->.firebaseio.com' }); exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => { const agent = new WebhookClient({ request, response }); let result = request.body.queryResult; let parameters = result.parameters; let outputContexts = result.outputContexts; let intentMap = new Map(); intentMap.set('search-by-genre', searchByGenre.bind(this, agent, parameters)); agent.handleRequest(intentMap); }); function getFilmsList() { return firebaseAdmin .database() .ref() .child('filmsList') .once('value') .then(snapshot => { const filmsList = snapshot.val(); console.log('filmsList: ' + JSON.stringify(filmsList)); return filmsList; }) .catch(error => { console.log('getFilmsList error: ' + error); return error; }); } function searchByGenre(agent, parameters) { return getFilmsList() .then(filmsList => { let filteredFilms = filmsList.filter((film) => { return film.genres.some((genre) => genre == parameters.genre); }); let firstFlim = filteredFilms[0]; agent.add(firstFlim.title); agent.add(new Card({ title: firstFlim.title, imageUrl: firstFlim.imageUrl, text: firstFlim.description, buttonText: ' ', buttonUrl: firstFlim.trailer })); agent.add([ "    ?", new Suggestion("  ?"), new Suggestion(""), new Suggestion("") ]); }) .catch(error => { console.log('getFilmsList error' + error); }); } 

Adicione imprevisibilidade

Nossa habilidade produzirá filmes em uma sequência de cada vez. As mesmas respostas irritarão o usuário a princípio e, em seguida, ele deixará de falar com o nosso robô.


Corrija isso com a biblioteca de mistura de arrays de matriz aleatória.


Inclua a dependência no arquivo package.json :


 "dependencies": { // ... "shuffle-array": "^1.0.1" // ... } 

Adicione a mistura da matriz:
 //   const shuffle = require('shuffle-array'); function searchByGenre(agent, parameters) { return getFilmsList() .then(filmsList => { let filteredFilms = filmsList.filter((film) => { return film.genres.some((genre) => genre == parameters.genre); }); //    shuffle(filteredFilms); let firstFlim = filteredFilms[0]; agent.add(firstFlim.title); agent.add(new Card({ title: firstFlim.title, imageUrl: firstFlim.imageUrl, text: firstFlim.description, buttonText: ' ', buttonUrl: firstFlim.trailer })); agent.add([ "    ?", new Suggestion("  ?"), new Suggestion(""), new Suggestion("") ]); }) .catch(error => { console.log('getFilmsList error' + error); }); } 

Agora, toda vez que um novo filme será lançado.
Da mesma forma, você pode adicionar a saída de frases diferentes: crie uma matriz com frases, misture-a e pegue a primeira da matriz.


Trabalhando com contexto

Pedimos ao agente:


Mostrar fantasia

O agente nos exibe o filme "O Senhor dos Anéis".
Então perguntamos:


Do que ele está falando?

As pessoas não dizem: "Qual é o filme" O Senhor dos Anéis "" não é natural. Portanto, precisamos salvar informações sobre o filme exibido. Isso pode ser feito no contexto de:


 //    ,       agent.setContext({ name: 'current-film', lifespan: 5, parameters: { id: firstFlim.id } }); 

Então podemos ler as informações sobre o filme assim:
 function genreSearchDescription(agent) { //   current-film const context = agent.getContext('current-film'); console.log('context current-film: ' + JSON.stringify(context)); //  id    const currentFilmId = context.parameters.id; //    return getFilmsList() .then(filmsList => { //    id const currentFilm = filmsList.filter(film => film.id === currentFilmId); agent.add(currentFilm[0].description); agent.add([ ' ?', new Suggestion(''), new Suggestion(' ') ]); }) .catch(error => { console.log('getFilmsList error:' + error); }); } 

Da mesma forma, podemos filtrar a lista de filmes já mostrados.


Integração de telegrama


Documentação e links úteis:



Para integrar com o Telegram, quase nada é necessário, mas há vários recursos que precisam ser considerados.


1) Se em fullfilment para exibir Card ou Suggestion, no Telegram eles também funcionarão.
Mas há um erro : para respostas rápidas, você deve especificar um título, caso contrário "Escolha um item" será exibido no Telegram.
Até o momento, não conseguimos resolver o problema de indicar o título em arquivo completo.


2) Se a intenção usar chips de sugestão para o assistente do Google


Exemplo

a mesma funcionalidade do Telegram pode ser implementada de duas maneiras:


Respostas rápidas


Configurando respostas rápidas

Carga útil personalizada
Aqui você pode implementar respostas rápidas usando o teclado principal:


Captura de tela do teclado

  { "telegram": { "text": "      :", "reply_markup": { "keyboard": [ [ "", "", "", "", "" ] ], "one_time_keyboard": true, "resize_keyboard": true } } } 

e teclado embutido:


Captura de tela do teclado embutido

  { "telegram": { "text": "      :", "reply_markup": { "inline_keyboard": [ [{ "text": "", "callback_data": "" }], [{ "text": "", "callback_data": "" }], [{ "text": "", "callback_data": "" }], [{ "text": "", "callback_data": "" }], [{ "text": "", "callback_data": "" }] ] } } } 

O teclado principal enviará uma mensagem que será armazenada no histórico, enquanto o teclado interno não.


É importante lembrar que o teclado principal não desaparece com o tempo. Há uma solicitação especial para isso na API do Telegram. Portanto, você precisa garantir que o usuário sempre tenha dicas relevantes.


3) Se você precisar de uma lógica diferente para o Telegram e o Google Assistant, poderá fazer o seguinte:


 let intentRequest = request.body.originalDetectIntentRequest; if(intentRequest.source == 'google'){ let conv = agent.conv(); conv.ask('    ?'); agent.add(conv); } else { agent.add('    ?'); } 

4) O envio de um arquivo de áudio pode ser implementado da seguinte maneira:


 { "telegram": { "text": "https://s0.vocaroo.com/media/download_temp/Vocaroo_s0bXjLT1pSXK.mp3" } } 

5) O contexto no Dialogflow será armazenado por 20 minutos. Você precisa considerar isso ao projetar um bot do Telegram. Se o usuário estiver distraído por 20 minutos, ele não poderá continuar do mesmo local.


Exemplos


Em breve publicaremos o código fonte da habilidade. Imediatamente após seu lançamento.


PS. O que aconteceu no hackathon.


imagem


Foram 2 dias ocupados.
No início, houve palestras educacionais e, à tarde, começamos a implementar nossos projetos.
O dia seguinte estava revisando ativamente os projetos e preparando as apresentações.


Os caras do Google todo esse tempo nos ajudaram e responderam a um monte de perguntas que inevitavelmente surgem no trabalho. Foi uma ótima oportunidade para aprender muito e deixar feedback enquanto o ferro ainda está quente.


Obrigado a todos os participantes, aos organizadores do Google e aos especialistas que deram palestras e nos ajudaram durante o hackathon!


A propósito, ficamos em segundo lugar.


Se você tiver dúvidas, pode escrever:
shipa_o
raenardev
comradeguest


E também há um bate-papo do Telegram dedicado à discussão de interfaces de voz, vá:
https://t.me/conversational_interfaces_ru

Source: https://habr.com/ru/post/pt420111/


All Articles