
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:

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 :

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

a mesma funcionalidade do Telegram pode ser implementada de duas maneiras:
Respostas rápidas

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

{ "telegram": { "text": " :", "reply_markup": { "keyboard": [ [ "", "", "", "", "" ] ], "one_time_keyboard": true, "resize_keyboard": true } } }
e 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.

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