Глава 10: Создание веб-перехватчиков выполнения

Выполнение (fulfillment) обычно означает, что вы подключаете свой разговор к веб-сервису. Компании часто уже имеют веб-сервисы и API для отображения контента на веб-сайтах. В этом случае, хотя большая часть работы (например, подключение к базам данных, правила безопасности и бизнес-логика и т. д.) уже проделана, вам все равно нужно будет поработать над дополнительным кодом, который позаботится о маршрутизации (ко всем вашим веб-сервисам), и разговорной части, как вы возвращаете свой диалог обратно пользователю.

Эта глава объяснит, как работают веб-перехватчики выполнения в Dialogflow Essentials. Она покажет вам, как написать свой собственный веб-перехватчик с пакетом npm actions-on-google и пакетом npm dialogflow, а также как писать веб-перехватчики без использования пакета вообще. Будут обсуждаться различные места для запуска кода, такие как облачная функция через встроенный редактор, как контейнер в Cloud Run или ваш собственный сервер с Express или локально. Эта глава также обсудит, как создавать многоязычные веб-перехватчики и как защитить ваш код веб-перехватчика. Она будет содержать много примеров кода, чтобы помочь вам начать работу.

Введение в веб-перехватчики выполнения

Давайте рассмотрим следующий пример; представьте, что я создаю банковского агента для голоса. Допустим, это действие Google Assistant, но оно было связано с учетной записью пользователя. Я мог бы спросить своего агента:

  1. «Моя зарплата сегодня зачислена?»
  2. Dialogflow сопоставляет намерение «зарплата».
  3. Я мог бы получить номер банковского счета моего работодателя из моей учетной записи пользователя, а сущность сегодняшней даты могла бы быть извлечена Dialogflow из высказывания пользователя.
  4. Выполнение включено для намерения в Dialogflow. (Поскольку Dialogflow не переводит деньги, никакие веб-сервисы банка этого не делают.)
  5. Веб-запрос от Dialogflow к коду веб-перехватчика/бэкэнда сделан.
  6. Теперь мы что-то сделаем с данными запроса.
    1. Мы получим имя намерения, параметры и контекст из запроса.
    2. Мы направим другой запрос к существующему банковскому веб-сервису для получения баланса счета.
    3. Этот веб-сервис возвращает код, возможно, данные JSON, например:
      {
       "status": "ok",
       "bankaccount": "IBAN1234567890",
       "datetime": "1593080084",
       "amount": "3000",
       "currency": "USD"
      }

      Мы не можем вернуть этот JSON-канал пользователю, так как это не то, как вы бы вели разговор с вашими пользователями. Это компьютерный язык, а также объект даты-времени является временной меткой вместо понятной даты.

      Вместо этого вы хотели бы ответить вашим пользователям, возможно, даже в расширенной карточке сообщения Google Assistant:

      «Похоже, 3000 долларов были переведены сегодня в 15:00».

      Таким образом, вам понадобится код, который переводит ответы веб-сервиса в понятный человеку разговор.

  7. Веб-перехватчик отправляет ответ агенту, чтобы пользователи могли его увидеть и услышать.

Вы можете увидеть предыдущий пример потока точно на Рисунке 10-1. На шаге 6 это дополнительная работа, как видно в столбце веб-перехватчика. Вам понадобится дизайнер разговорного UX для написания диалога для ответов. Вам понадобится разработчик, который работает в бэкэнд-коде веб-перехватчика; для получения входящего запроса, вызова базовых веб-сервисов для перевода компьютерных ответов в осмысленные диалоги, а затем возврата его агенту.


Рисунок 10-1. Диаграмма, объясняющая, как работает выполнение Dialogflow. Для простоты этого Dialogflow я упростил предыдущую диаграмму. Теоретически, должен быть слой между Dialogflow и веб-перехватчиком, который является вашей собственной реализацией интеграции (которая, вероятно, также включает безопасность); это та часть, которая делает вызовы detectIntent. См. Главу 11 для получения дополнительной информации.

Примечание: Ответ веб-перехватчика должен произойти в течение 5 секунд. Для действий Google Assistant время ответа может быть увеличено до 10 секунд. Кроме того, ответ веб-перехватчика должен быть меньше или равен 64КиБ по размеру. Если эти ограничения не соблюдаются, то запрос веб-перехватчика истечет по времени и выдаст ошибку «Вызов веб-перехватчика не удался. Ошибка: DEADLINE_EXCEEDED», которую вы можете увидеть в статусе Выполнения.

Существуют различные способы выполнения выполнения с помощью Dialogflow:

  • Firebase Functions через встроенный редактор
  • Веб-перехватчик выполнения
    • Cloud Functions
    • Вычислительные решения Google Cloud, такие как App Engine, Cloud Run, Compute Engine или GKE
    • Локальный хостинг
    • Локально

Создание выполнения с помощью встроенного редактора

Для простого тестирования и реализации веб-перехватчика вы можете использовать встроенный редактор. Под капотом он использует бессерверные Cloud Functions для Firebase; см. Рисунок 10-2.


Рисунок 10-2. Простая диаграмма, объясняющая, как работает выполнение Dialogflow для встроенного редактора

Cloud Functions для Firebase — это бессерверный фреймворк, который позволяет автоматически запускать бэкэнд-код в ответ на события, вызванные функциями Firebase и HTTPS-запросами. Ваш код JavaScript или TypeScript хранится в облаке Google и работает в управляемой среде. Нет необходимости управлять и масштабировать собственные серверы.

Примечание: Когда ваши веб-сервисы немного более продвинуты или вас заботит регион, где размещен ваш код, вы, скорее всего, не будете использовать встроенный редактор. Вместо этого вы включите переключатель веб-перехватчика, чтобы вы могли указать URL-адрес своих собственных веб-сервисов.

Включение выполнения

Вам нужно будет включить динамическое выполнение на уровне намерения. Находясь на странице намерения, вы можете включить выполнение с помощью переключателей, как показано на Рисунке 10-3.


Рисунок 10-3. Включить выполнение в Dialogflow для каждого намерения

Переключатель «включить вызов веб-перехватчика для этого намерения» включает динамическое выполнение.

Второй переключатель включит веб-перехватчики для заполнения слотов. Когда намерение сопоставляется во время выполнения, агент Dialogflow продолжает собирать информацию от конечного пользователя до тех пор, пока конечный пользователь не предоставит данные для каждого из требуемых параметров. Этот процесс называется заполнением слотов. По умолчанию Dialogflow не отправляет запрос веб-перехватчика выполнения до тех пор, пока не соберет все необходимые данные от конечного пользователя, прежде чем отправить их на бэкэнд веб-перехватчика, когда параметры помечены как обязательные. (Это означает, что агент будет задавать уточняющие вопросы перед вызовом бэкэнд-кода выполнения.) Dialogflow будет проходить через все требуемые параметры в указанном порядке.

Однако, когда включен переключатель вызова веб-перехватчика для заполнения слотов, Dialogflow отправляет запрос веб-перехватчика выполнения для каждого шага разговора во время заполнения слотов. Вы можете захотеть использовать это, когда у вас большой разговор, и вы хотите хранить все ответы на каждый вопрос каждого шага разговора. Техника, с которой вы можете быть знакомы с веб-сайтов, представляющих большие формы или корзины покупок, заключается в том, что они сохраняют каждое поле формы или шаг перед отправкой всей формы (последняя страница).

Следующий шаг — указать Dialogflow, куда отправлять данные. Бэкэнд-код веб-перехватчика может быть написан в редакторе, или вы можете предоставить один URL-адрес веб-сервиса, куда Dialogflow будет отправлять POST-запросы. Чтобы использовать редактор, убедитесь, что переключатель включить встроенный редактор включен. См. Рисунок 10-4.


Рисунок 10-4. Встроенный редактор в Dialogflow

Использование пакета dialogflow-fulfillment

По умолчанию редактор содержит некоторый пример кода. Он будет использовать библиотеку npm dialogflow-fulfillment. См. файл package.json в Листинге 10-1.

Внимание: Библиотека dialogflow-fulfillment больше не поддерживается. Это не значит, что она будет удалена; она будет работать, но вы не увидите никаких обновлений библиотеки. Ее следует использовать только при использовании встроенного редактора. Позже в этой главе я также покажу вам, как вы можете создать выполнение без библиотеки dialogflow-fulfillment.

Листинг 10-1. Выполнение Dialogflow в редакторе, package.json

{
 "name": "dialogflowFirebaseFulfillment",
 "description": "This is the default fulfillment for a Dialogflow agent using Cloud Functions for Firebase",
 "version": "0.0.1",
 "private": true,
 "license": "Apache Version 2.0",
 "author": "Lee Boonstra",
 "engines": {
  "node": "8"
 },
 "scripts": {
  "start": "firebase serve --only functions:dialogflowFirebaseFulfillment",
  "deploy": "firebase deploy --only functions:dialogflowFirebaseFulfillment"
 },
 "dependencies": {
  "actions-on-google": "^2.12.0",
  "firebase-admin": "^5.13.1",
  "firebase-functions": "^2.0.2",
  "dialogflow-fulfillment": "^0.6.1"
 }
}

Функции Firebase HTTPS начинаются со следующей реализации кода (см. Листинг 10-2) для получения данных из запроса.

Листинг 10-2. Облачная функция в Firebase

const functions = require('firebase-functions');
exports.helloWorld = functions.https.onRequest((request, response) => {
 response.send("Hello from Firebase!");
});

Под капотом этот скрипт Node.js использует веб-фреймворк Node.js Express. Параметр request предоставит вам информацию, такую как параметры из тела запроса. С помощью параметра response вы можете вернуть результаты (обратно в Dialogflow).

Когда Firebase Functions используются в сочетании с пакетом dialogflow-fulfillment, ваш код Dialogflow может выглядеть как файл index.js из Листинга 10-3. Этот пример имеет карту намерений для трех намерений: приветствие по умолчанию, резервное намерение по умолчанию и регулярное выражение для покупки продукта. Он покажет ответ, предоставленный кодом выполнения. Приветственное сообщение и резервное намерение покажут простое сообщение, тогда как намерение покупки продукта покажет вам расширенное сообщение — компонент карточки.

Листинг 10-3. Выполнение Dialogflow в редакторе

'use strict';

const functions = require('firebase-functions');
const { WebhookClient, Card, Suggestion } = require('dialogflow-fulfillment');

process.env.DEBUG = 'dialogflow:debug'; // включает отладочные операторы библиотеки

exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
 const agent = new WebhookClient({ request, response });
 console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
 console.log('Dialogflow Request body: ' + JSON.stringify(request.body));

 function welcome(agent) {
  agent.add(`Welcome to my agent!`);
 }

 function fallback(agent) {
  agent.add(`I didn't understand`);
  agent.add(`I'm sorry, can you try again?`);
 }

 function yourFunctionHandler(agent) {
  agent.add(`Ok. Buying product:`);
  console.log(agent.parameters);
  agent.add(new Card({
   title: agent.parameters.producttype,
   imageUrl: 'https://dummyimage.com/300x200/000/fff',
   text: `This is the body text of a card. You can even use line\n breaks and emoji!  `,
   buttonText: 'This is a button',
   buttonUrl: 'https://console.dialogflow.com/'
   })
  );
  agent.add(new Suggestion(`Quick Reply`));
  agent.add(new Suggestion(`Suggestion`));
  agent.context.set({ name: 'gamestore-picked', lifespan: 2, parameters: { gameStore: 'DialogflowGameStore' }});
 }

 // Запустить правильный обработчик функции на основе сопоставленного имени намерения Dialogflow
 let intentMap = new Map();
 intentMap.set('Default Welcome Intent', welcome);
 intentMap.set('Default Fallback Intent', fallback);
 intentMap.set('Buy product regex', yourFunctionHandler);
 agent.handleRequest(intentMap);
});

Пакет dialogflow-fulfillment выполняет следующие основные действия:

  • Он оборачивает код вокруг запроса/ответов, чтобы упростить синтаксис получения и возврата сопоставленного намерения (с параметрами и ответами).
    Например, agent.add() возвращает ответы в Dialogflow один за другим. Или вы можете создать карту намерений JavaScript и использовать agent.handleRequest() для переключения между различными намерениями. Например, Dialogflow обнаружил намерение ‘Buy product regex’ как совпадение (оно имеет наивысший уровень уверенности). Объект запроса в Cloud Function содержит это обнаруженное намерение. Если строки в карте намерений содержат ту же строку, что и имя сопоставленного намерения (имя, которое вы использовали для создания намерения в пользовательском интерфейсе Dialogflow), то он вызовет функцию (в данном случае yourFunctionHandler). Эта пользовательская функция будет использовать agent.add() для возврата сообщений в вашу реализацию чат-бота, например, веб-интерфейс. Вы также можете получить параметры или контексты из параметра agent.
  • Он оборачивает код вокруг различных пользовательских полезных нагрузок Dialogflow для Dialogflow и других интеграций (таких как Google Assistant), чтобы реализовать их быстрее. Посмотрите на функцию yourFunctionHandler. Чтобы отобразить расширенное сообщение с карточкой Dialogflow, вам не нужно писать длинную полезную нагрузку JSON; вместо этого вы можете использовать класс Card().

Диагностическая информация

Запросы, которые будут отправлены на веб-перехватчик, можно просмотреть с помощью кнопки Diagnostic Info внизу страницы. Откроется окно, как показано на Рисунке 10-5, которое покажет вам запрос выполнения.


Рисунок 10-5. Диагностическая информация ➤ Запрос выполнения, который будет отправлен в API Dialogflow

Вкладка ответа выполнения, как показано на Рисунке 10-6, покажет ответ, возвращенный из веб-перехватчика (сервера).


Рисунок 10-6. Диагностическая информация ➤ Ответ выполнения от API Dialogflow

Статус выполнения может сообщить вам, было ли выполнение веб-перехватчика успешным или нет; см. Рисунок 10-7.


Рисунок 10-7. Диагностическая информация ➤ Статус выполнения, возвращенный API Dialogflow

Журналы Firebase

Поскольку встроенный редактор использует Cloud Functions в Firebase, вы можете получить доступ к журналам через Firebase. Это удобно для отладки, когда что-то идет не так.

Просто откройте Firebase (https://console.firebase.google.com/) и нажмите Функции ➤ Ведение журнала. Рисунок 10-8 покажет вам, как выглядит экран журналов в Firebase.


Рисунок 10-8. Журналы Firebase могут показать вам информацию журнала вашего выполнения Dialogflow из редактора

Примечание: Встроенный редактор в Dialogflow использует Firebase Functions (бесплатный план Spark).

Согласно ценообразованию Firebase, исходящие сетевые подключения для Firebase Cloud Functions поддерживаются с планом Blaze. Сетевой выход бесплатен для первых 5 ГБ. После этого вы будете платить 0,12 доллара США за ГБ. Это означает, что вам понадобится способ оплаты путем обновления плана Firebase, и вам придется платить за запрос других внешних веб-сервисов из Cloud Function.

Использование Actions on Google для создания выполнения Dialogflow

Помимо написания кода выполнения JavaScript с помощью пакета npm dialogflow-fulfillment, существуют и другие способы написания кода выполнения с помощью JavaScript. По сути, вы можете написать любое выполнение на любом языке, который вам нравится, но эта книга использует JavaScript для всех примеров.

Например, вы можете написать выполнения JavaScript с помощью пакета npm Actions on Google (https://www.npmjs.com/package/actions-on-google). Вы можете использовать это, если создаете выполнение для Google Assistant.

Также возможно вручную создать веб-перехватчик без какого-либо пакета npm вообще. Вам нужно будет получить имя сопоставленного намерения, параметры и контекст из входящих запросов веб-перехватчика, вручную создать пользовательские полезные нагрузки и вернуть их агенту.

Давайте рассмотрим пример использования пакета Actions on Google; он будет делать то же самое, что и пример кода в Листинге 10-2. Поскольку Actions on Google является пакетом npm, вам понадобится package.json, как показано в Листинге 10-4.

Листинг 10-4. Пакет Actions on Google package.json

{
 "name": "dialogflowFirebaseFulfillment",
 "description": "This is the default fulfillment for a Dialogflow agents using Cloud Functions for Firebase",
 "version": "0.0.1",
 "private": true,
 "license": "Apache Version 2.0",
 "author": "Lee Boonstra",
 "engines": {
  "node": "8"
 },
 "scripts": {
  "start": "firebase serve --only functions:dialogflowFirebaseFulfillment",
  "deploy": "firebase deploy --only functions:dialogflowFirebaseFulfillment"
 },
 "dependencies": {
  "actions-on-google": "^2.12.0",
  "firebase-admin": "^5.13.1",
  "firebase-functions": "^2.0.2"
 }
}

index.js из Листинга 10-5 содержит код реализации.

Листинг 10-5. Actions on Google index.js

'use strict';

const {
 dialogflow,
 BasicCard,
 Button,
 Image,
 Suggestions
} = require('actions-on-google');

const functions = require('firebase-functions');

process.env.DEBUG = 'dialogflow:debug'; // включает отладочные операторы библиотеки

function welcome(conv) {
 console.log('Dialogflow Request headers: ' + JSON.stringify(conv.headers));
 console.log('Dialogflow Request body: ' + JSON.stringify(conv.body));
 conv.ask(`Welcome to my agent!`);
}

function fallback(conv) {
 console.log('Dialogflow Request headers: ' + JSON.stringify(conv.headers));
 console.log('Dialogflow Request body: ' + JSON.stringify(conv.body));
 conv.ask(`I didn't understand`);
 conv.ask(`I'm sorry, can you try again?`);
}

function yourFunctionHandler(conv, parameters) {
 conv.ask(`Ok. Buying product:`);
 console.log(parameters);
 conv.ask(new BasicCard({
  title: parameters.producttype,
  image: new Image({
   url: 'https://dummyimage.com/300x200/000/fff',
   alt: 'Image alternate text',
  }),
  text: `This is the body text of a card. You can even use line\n breaks and emoji!  `,
  buttons: new Button({
   title: 'This is a button',
   url: 'https://assistant.google.com/',
  })
 }));
 conv.ask(new Suggestions(`Quick Reply`));
 conv.ask(new Suggestions(`Suggestion`));
 conv.contexts.set({ name: 'gamestore-picked', lifespan: 2, parameters: { gameStore: 'DialogflowGameStore' }});
}

const app = dialogflow();

// Запустить правильный обработчик функции на основе сопоставленного имени намерения Dialogflow
app.intent('Default Welcome Intent', welcome);
app.intent('Default Fallback Intent', fallback);
app.intent('Buy product regex', yourFunctionHandler);

// Намерение в Dialogflow под названием `Goodbye`
app.intent('Goodbye', conv => {
 conv.close('See you later!');
});

exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);

Обратите внимание на различия между пакетом npm dialogflow-fulfillment (Листинг 10-3 против 10-5) и пакетом npm actions on google из Листинга 10-5:

  • conv является экземпляром типа DialogflowConversation.
  • app является экземпляром типа DialogflowApp.
  • app принимает опции типа DialogflowOptions.
  • conv.ask отправляет текстовую строку на устройство. Синтезатор Google Assistant произнесет это предложение. На устройстве с экраном вы также увидите текстовый шарик, содержащий этот текст.
  • параметры: Их можно получить как второй аргумент в функции.
  • контексты: Вы можете получать и устанавливать контекст из объекта conv.contexts. (Обратите внимание на «s» после context.)
  • conv.close: Это закроет действие и вернется в родную область Google Assistant.

Кроме того, форматирование для карточек и других расширенных сообщений полностью соответствует спецификации Actions on Google.

Совет: При создании действий для Google Assistant лучше всего использовать симулятор Google Assistant для тестирования опыта (https://console.actions.google.com/).

Вот как выглядит ответ выполнения действия Actions on Google в окне Diagnostic Info Dialogflow; см. Рисунок 10-9.


Рисунок 10-9. Ответ выполнения Dialogflow действия Google Assistant

Создание вашего веб-перехватчика выполнения вручную

Ответ веб-перехватчика, полученный от Dialogflow, обычно выглядит как следующий ответ; см. Листинг 10-6. Обратите внимание на объект намерения, который содержит имя сопоставленного намерения с уровнем уверенности. Он включает outputContexts и параметры, которые были использованы для сбора ответа.

Листинг 10-6. Запрос API Dialogflow для использования для ручного создания выполнения

{
 "responseId": "response-id",
 "session": "projects/project-id/agent/sessions/session-id",
 "queryResult": {
  "queryText": "End-user expression",
  "parameters": {
   "param-name": "param-value"
  },
  "allRequiredParamsPresent": true,
  "fulfillmentText": "Response configured for matched intent",
  "fulfillmentMessages": [
   {
    "text": {
     "text": [
      "Response configured for matched intent"
     ]
    }
   }
  ],
  "outputContexts": [
   {
    "name": "projects/project-id/agent/sessions/session-id/contexts/context-name",
    "lifespanCount": 5,
    "parameters": {
     "param-name": "param-value"
    }
   }
  ],
  "intent": {
   "name": "projects/project-id/agent/intents/intent-id",
   "displayName": "matched-intent-name"
  },
  "intentDetectionConfidence": 1,
  "diagnosticInfo": {},
  "languageCode": "en"
 },
 "originalDetectIntentRequest": {}
}

Чтобы виртуальный агент представил результаты на экране (в зависимости от канала/интеграции), вам нужно будет отправить этот ответ на ваш сервер.

Ответ, который нам нужно будет отправить обратно агенту, должен содержать формат, показанный в Листинге 10-7.

Листинг 10-7. Ответ API Dialogflow для использования для ручного создания выполнения

{
 "fulfillmentMessages": [
  {
   "text": {
    "text": [
     "Ok. Buying product:"
    ]
   }
  },
  {
   "card": {
    "title": "",
    "subtitle": "This is the body text of a card. You can even use line\n breaks and emoji!  ",
    "imageUri": "https://dummyimage.com/300x200/000/fff",
    "buttons": [
     {
      "text": "This is a button",
      "postback": "https://console.dialogflow.com/"
     }
    ]
   }
  },
  {
   "quickReplies": {
    "quickReplies": [
     "Quick Reply",
     "Suggestion"
    ]
   }
  }
 ],
 "outputContexts": [
  {
   "name": "projects/dialogflowcookbook/agent/sessions/0e39ecfb-881d-859e-6ac6-d65c8398311c/contexts/gamestore-picked",
   "lifespanCount": 2,
   "parameters": {
    "gameStore": "DialogflowGameStore"
   }
  }
 ]
}

Посмотрите, как я это реализовал; см. Листинг 10-8.

Листинг 10-8. index.js выполнения Dialogflow без использования библиотеки dialogflow-fulfillment

'use strict';

const functions = require('firebase-functions');

process.env.DEBUG = 'dialogflow:debug'; // включает отладочные операторы библиотеки

function handleRequest(map, request){
 let intent;
 if(request.body && request.body.queryResult && request.body.queryResult.intent){
  intent = request.body.queryResult.intent.displayName;
 }

 let response;
 if (map.has(intent) !== false){
  response = map.get(intent)(request);
 } else {
  response = map.get('Default Fallback Intent')(request);
 }
 return response;
}

function fallback(request) {
 return {
  "fulfillmentMessages": [
   {
    "text": {
     "text": [
      "I didn't understand.",
      "I'm sorry, can you try again?"
     ]
    }
   }
  ]
 };
}

function welcome(request) {
 return {
  "fulfillmentMessages": [
   {
    "text": {
     "text": [
      "Welcome to my agent!"
     ]
    }
   }
  ]
 };
}

function yourFunctionHandler(request) {
 let parameters;
 if(request.body.queryResult.parameters){
  parameters = request.body.queryResult.parameters;
 }
 console.log(parameters);
 return {
  "fulfillmentMessages": [
   {
    "text": {
     "text": [
      "Ok. Buying product:"
     ]
    }
   },
   {
    "card": {
     "title": `${parameters.producttype}`,
     "subtitle": "This is the body text of a card. You can even use line\n breaks and emoji!  ",
     "imageUri": "https://dummyimage.com/300x200/000/fff",
     "buttons": [
      {
       "text": "This is a button",
       "postback": "https://console.dialogflow.com/"
      }
     ]
    }
   },
   {
    "quickReplies": {
     "quickReplies": [
      "Quick Reply",
      "Suggestion"
     ]
    }
   }
  ],
  "outputContexts": [
   {
    "name": `${request.body.session}/contexts/gamestore-picked`,
    "lifespanCount": 2,
    "parameters": {
     "gameStore": "DialogflowGameStore"
    }
   }
  ]
 };
}

exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
 let intentMap = new Map();
 intentMap.set('Default Welcome Intent', welcome);
 intentMap.set('Default Fallback Intent', fallback);
 intentMap.set('Buy product regex', yourFunctionHandler);

 let webhookResponse = handleRequest(intentMap, request);
 console.log(webhookResponse);
 response.json(webhookResponse);
});

Этот пример использует package.json, как показано в Листинге 10-9.

Листинг 10-9. package.json выполнения Dialogflow без использования библиотеки dialogflow-fulfillment

{
 "name": "dialogflowFirebaseFulfillment",
 "description": "This is the default fulfillment for a Dialogflow agents using Cloud Functions for Firebase",
 "version": "0.0.1",
 "private": true,
 "license": "Apache Version 2.0",
 "author": "Lee Boonstra",
 "engines": {
  "node": "8"
 },
 "scripts": {
  "start": "firebase serve --only functions:dialogflowFirebaseFulfillment",
  "deploy": "firebase deploy --only functions:dialogflowFirebaseFulfillment"
 },
 "dependencies": {
  "actions-on-google": "^2.12.0",
  "firebase-admin": "^5.13.1",
  "firebase-functions": "^2.0.2"
 }
}

Как вы заметите, я не использую никаких пакетов npm Dialogflow. Вместо этого я обрабатываю запрос сам с помощью моего пользовательского метода handleRequest, который использует карту JavaScript для получения имени функции для выполнения на основе имени сопоставленного намерения (которое было возвращено в запросе от Dialogflow).

Метод yourFunctionHandler реализует пользовательскую полезную нагрузку, которая принимает правильный формат JSON того, что необходимо для карточки. (Вы можете найти эти форматы в документации Dialogflow.)

Как только я отформатировал JSON, я могу отправить его обратно агенту с помощью

response.json(webhookResponse);

Создание веб-перехватчиков выполнения

Вместо использования встроенного редактора вы также можете выбрать запуск вашего кода выполнения в другом месте, если он работает по HTTPS. Именно здесь вам понадобится опция веб-перехватчика. См. Рисунок 10-10.


Рисунок 10-10. Простая архитектура того, как выполнение работает с кодом веб-перехватчика

Где запускать мой бэкэнд-код?

Полностью вам решать, где вы хотите запускать свой код выполнения. Предыдущий раздел обсуждал встроенный редактор, который запускает ваш код под капотом в Firebase, бессерверных облачных функциях. Он встроен, и его легко использовать.

По соображениям соответствия вы можете захотеть запустить свой код в другом месте, например, локально (внутри компании, где вы поддерживаете свою инфраструктуру) или в Google Cloud.

Совет: Поскольку предприятия используют версию Dialogflow Essentials с оплатой по мере использования, они приняли условия и положения Google Cloud. Ваш код и данные будут в безопасности, и вы являетесь владельцем данных. Вы автоматически получите доступ ко всем различным вариантам вычислений/обслуживания кода в Google Cloud. Поскольку все в Google Cloud использует подводный кабель Google backbone, это соединение может передавать 60 терабайт данных в секунду. Это примерно в 10 миллионов раз быстрее, чем ваше домашнее широкополосное соединение в хороший день. Поскольку Dialogflow является решением Google Cloud и одним из вариантов вычислений, это самый быстрый способ запуска вашего кода.

Вот варианты в Google Cloud, которые вы можете использовать для своих веб-перехватчиков.

Cloud Functions

Cloud Functions позволяет автоматически запускать бэкэнд-код в ответ на события, вызванные функциями Google Cloud/Firebase/Google Assistant и HTTPS-запросами.

Из коробки Cloud Functions обслуживает ваш код по HTTPS. Для работы по HTTPS-соединению требуется действительный и доверенный SSL-сертификат. Cloud Functions настроит готовый действительный, безопасный и доверенный сертификат.

Внимание: Функции являются безсостоятельными, и среда выполнения часто инициализируется с нуля, что называется холодным стартом. Холодные старты могут занимать значительное количество времени для завершения. Кроме того, время выполнения Cloud Function ограничено продолжительностью тайм-аута, которую вы можете указать во время развертывания функции. По умолчанию функция истекает через 1 минуту, но вы можете продлить этот период до 9 минут. Когда выполнение функции превышает тайм-аут, статус ошибки немедленно возвращается вызывающей стороне. Это делает Cloud Functions не всегда правильным решением, особенно когда вам нужно подключаться к нескольким базам данных и сервисам для получения информации.

App Engine (Гибкая среда)

Google App Engine — это платформа облачных вычислений как услуга для разработки и хостинга веб-приложений в управляемых Google центрах обработки данных. Приложения изолированы и работают на нескольких серверах. Есть различные среды на выбор, поддерживающие ваши любимые языки программирования. Управление серверами отсутствует, что упрощает разработчикам перенос своего кода в производство. Из коробки App Engine обслуживает ваш код по HTTPS. Для работы по HTTPS-соединению требуется действительный и доверенный SSL-сертификат. App Engine настроит готовый действительный, безопасный и доверенный сертификат.

Cloud Run

Разрабатывайте и развертывайте высокомасштабируемые контейнеризированные приложения на полностью управляемой бессерверной платформе. Вы можете писать код по-своему, используя ваши любимые языки (Go, Python, Java, Ruby, Node.js и другие). Cloud Run абстрагирует все управление инфраструктурой для простого опыта разработчика. Он также очень портативен, так как создает контейнеры с использованием открытого стандарта Knative. Это означает, что вы можете запускать контейнеры где угодно (Google Cloud, другое облако, локально). Из коробки Cloud Run обслуживает ваш код по HTTPS. Cloud Run настроит действительный, готовый, безопасный и доверенный сертификат.

Эта глава покажет пример использования кода веб-перехватчика в Cloud Run.

Внимание: Cloud Run запускает контейнеры, поэтому для каждого релиза вам нужно будет собрать контейнер и отправить его в Google Cloud. В отличие от гибкой среды App Engine, Cloud Run запускается только при поступлении запросов, поэтому вы не платите за время простоя, но это также означает, что вам придется иметь дело с холодными стартами.

Kubernetes Engine

Google Kubernetes Engine (GKE) — это безопасный и полностью управляемый сервис Kubernetes, что означает, что вам не нужно беспокоиться о настройке и обслуживании Kubernetes на инфраструктуре.

Совет: Возможно создание состоятельных приложений с помощью GKE, которые сохраняют данные на постоянном дисковом хранилище для использования сервером, клиентами и другими приложениями. Примером состоятельного приложения является база данных или хранилище ключ-значение, в которое данные сохраняются и извлекаются другими приложениями. Вам не придется беспокоиться о холодных стартах или тайм-аутах, управляемых событиями. Но вам нужно будет настроить сервер самостоятельно. Чтобы запустить GKE по HTTPS, вам нужно будет подключить доменное имя и прикрепить действительный сертификат.

Compute Engine

Compute Engine позволяет создавать и запускать виртуальные машины на инфраструктуре Google.

Внимание: С виртуальными машинами вам нужно будет настраивать и поддерживать все (например, веб-сервер) самостоятельно. Чтобы работать по HTTPS, вам нужно будет подключить доменное имя и прикрепить действительный сертификат.

Эта глава покажет пример использования кода веб-перехватчика в Compute Engine, чтобы показать вам, как настроить безопасные сертификаты.

Включение веб-перехватчиков

Вам нужно будет предоставить URL-адрес вашей службы выполнения на странице Выполнение Dialogflow, как показано на Рисунке 10-11.


Рисунок 10-11. Настройки веб-перехватчика

Ваша служба веб-перехватчика должна соответствовать следующим требованиям:

  • Она должна обрабатывать HTTPS-запросы. HTTP не поддерживается. Бессерверные вычислительные решения, такие как Cloud Run, Cloud Functions и App Engine, будут поставляться из коробки с URL-адресом, который работает по HTTPS.
  • Служба веб-перехватчика может находиться за базовой аутентификацией или аутентификацией по заголовку.
  • Она должна обрабатывать POST-запросы с телом JSON WebhookRequest.
  • Она должна отвечать на запросы WebhookRequest с телом JSON WebhookResponse.

Реализация Cloud Function

Запуск вашего кода из Google Cloud Functions очень похож на встроенный редактор. Под капотом он даже использует ту же технологию, так как встроенный редактор использует Firebase Functions, а под капотом Firebase Functions — это Google Cloud Functions. Основное отличие заключается в том, что Google Cloud предоставит вам URL-адрес, который вы можете добавить в раздел веб-перехватчика на странице Выполнение Dialogflow.

Есть несколько других преимуществ использования Cloud Functions по сравнению со встроенным редактором. Именно поэтому многие предприятия используют Cloud Functions вместо встроенного редактора Dialogflow:

  • Cloud Functions могут быть написаны на большем количестве языков, чем просто Node.js, таких как Python, Go или Java.
  • Cloud Functions будут поддерживаться в том же проекте Google Cloud, что и ваш проект Dialogflow.
  • Cloud Functions являются частью Google Cloud, используют те же условия и положения и привержены соблюдению требований.
  • Cloud Functions имеют интегрированные возможности мониторинга, ведения журнала и отладки в Google Cloud.
  • Вы можете защитить Cloud Functions с помощью контроля доступа на основе идентификационных данных или сети.
  • Настройки сети Cloud Functions позволяют вам контролировать входящий и исходящий сетевой трафик к и от отдельных функций.
  • Cloud Functions являются региональными, что означает, что инфраструктура, на которой работает ваша Cloud Function, расположена в определенном регионе и управляется Google для избыточной доступности во всех зонах этого региона.

Листинг 10-10 показывает пример кода.

Листинг 10-10. Реализация Cloud Function

// Код выполнения Dialogflow
exports.helloWorld = function helloWorld (request, response) {
 // получить запрос Dialogflow
 // сделать что-то
 // вернуть агенту
 response.send(`Hello from Cloud Functions!`);
};

Реализация Cloud Run, App Engine, Compute Engine, GKE или локальная реализация может использовать фреймворк Express.js. Это фреймворк веб-сервера приложений Node.js, который специально разработан для создания одностраничных, многостраничных и гибридных веб-приложений.

Реализация Express (с Cloud Run)

Использование реализации на фреймворке Node.js Express может иметь несколько преимуществ по сравнению с Cloud Functions (или встроенным редактором Dialogflow). Основным преимуществом будет то, что ваш код может быть намного более организованным. Особенно когда у вас большая кодовая база или вы работаете с большой командой разработчиков, вы бы предпочли выбрать это решение.

Вот пример решения реализации в Cloud Run. С Cloud Run вы будете использовать следующие преимущества:

  • Пишите код по-своему, используя ваши любимые языки (Go, Python, Java, Ruby, Node.js и другие).
  • Cloud Run принимает любой образ контейнера и отлично сочетается с экосистемой контейнеров: Cloud Build, Artifact Registry, Docker. Это также означает, что вы можете настроить конвейеры CI/CD.
  • Простой интерфейс командной строки и пользовательский интерфейс для быстрого развертывания и управления вашими сервисами.
  • Нет инфраструктуры для управления: после развертывания Cloud Run управляет вашими сервисами, чтобы вы могли спать спокойно.
  • Cloud Run автоматически масштабируется вверх или вниз от нуля до N в зависимости от трафика.
  • Сервисы Cloud Run являются региональными, что означает, что инфраструктура, на которой работает ваше приложение Cloud Run, расположена в определенном регионе и управляется Google для избыточной доступности во всех зонах этого региона. Оно может быть автоматически реплицировано в нескольких зонах.
  • Готовая интеграция с Cloud Monitoring, Cloud Logging и Error Reporting для обеспечения работоспособности приложения.
  • Экземпляры контейнеров работают в безопасной песочнице, изолированной от других ресурсов.

Листинг 10-11 показывает пример запуска выполнения Dialogflow в виде реализации Node.js Express в Cloud Run. Вы увидите, что реализация похожа на реализацию встроенного редактора из предыдущего раздела, за исключением того, что реализация Express немного отличается.

Листинг 10-11. Пример выполнения Dialogflow, работающего как приложение Node.js Express в Cloud Run. Index.js

'use strict';

const express = require('express');
const bodyParser = require('body-parser');
const basicAuth = require('express-basic-auth');

const { WebhookClient, Card, Suggestion } = require('dialogflow-fulfillment');

process.env.DEBUG = 'dialogflow:debug'; // включает отладочные операторы библиотеки

// Код выполнения Dialogflow
function welcome(agent) {
 agent.add(`Welcome to my agent!`);
}

function fallback(agent) {
 agent.add(`I didn't understand`);
 agent.add(`I'm sorry, can you try again?`);
}

function yourFunctionHandler(agent) {
 agent.add(`Ok. Buying product:`);
 console.log(agent.parameters);
 agent.add(new Card({
  title: agent.parameters.producttype,
  imageUrl: 'https://dummyimage.com/300x200/000/fff',
  text: `This is the body text of a card. You can even use line\n breaks and emoji!  `,
  buttonText: 'This is a button',
  buttonUrl: 'https://console.dialogflow.com/'
  })
 );
 agent.add(new Suggestion(`Quick Reply`));
 agent.add(new Suggestion(`Suggestion`));
 agent.context.set({ name: 'gamestore-picked', lifespan: 2, parameters: { gameStore: 'DialogflowGameStore' }});
}

// Код Express
const app = express().use(bodyParser.json());

// Базовая аутентификация
app.use(basicAuth({
 users: { 'admin': 'supersecret' }
}));

app.use(function(req, res, next) {
 if (!req.headers['x-auth']) { // Проверка заголовка x-auth
  return res.status(403).json({ error: 'No auth headers sent!' });
 }
 next();
});

app.post('/fulfillment', (request, response) => {
 const agent = new WebhookClient({ request, response });
 console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
 console.log('Dialogflow Request body: ' + JSON.stringify(request.body));

 // Запустить правильный обработчик функции на основе сопоставленного имени намерения Dialogflow
 let intentMap = new Map();
 intentMap.set('Default Welcome Intent', welcome);
 intentMap.set('Default Fallback Intent', fallback);
 intentMap.set('Buy product regex', yourFunctionHandler);
 agent.handleRequest(intentMap);
});

app.get('/', (req, res) => {
 res.send(`OK`);
});

const port = process.env.PORT || 8080;
app.listen(port, () => {
 console.log('Dialogflow Fulfillment listening on port', port);
});

Этот index.js использует package.json, как показано в Листинге 10-12, для загрузки пакетов npm.

Листинг 10-12. Необходимые пакеты в package.json

{
 "name": "dialogflow-fulfillment",
 "description": "This is the default fulfillment for a Dialogflow agents using Cloud Run",
 "version": "0.0.1",
 "private": true,
 "license": "Apache Version 2.0",
 "author": "Lee Boonstra",
 "engines": {
  "node": "8"
 },
 "scripts": {
  "start": "node index.js",
  "build": "gcloud builds submit --tag gcr.io/dialogflowcookbook/dialogflow",
  "deploy": "gcloud run deploy --image gcr.io/dialogflowcookbook/dialogflow --platform managed"
 },
 "dependencies": {
  "@google-cloud/dialogflow": "^2.0.0", // Уточнено для совместимости, версия может отличаться
  "actions-on-google": "^2.5.0", // Версия может отличаться
  "body-parser": "^1.19.0",
  "dialogflow-fulfillment": "^0.6.1", // Как обсуждалось, использовать с осторожностью
  "express": "^4.17.1",
  "express-basic-auth": "^1.2.0"
 }
}

Вам нужно будет включить Dockerfile для сборки контейнера; см. Листинг 10-13.

Листинг 10-13. Dockerfile, относящийся к этому примеру для запуска контейнера в Cloud Run

# Использовать официальный легковесный образ Node.js 12.
# https://hub.docker.com/_/node
FROM node:12-slim

# Создать и перейти в рабочий каталог приложения.
WORKDIR /usr/src/app

# Скопировать манифесты зависимостей приложения в образ контейнера.
# Используется маска, чтобы скопировать и package.json, И package-lock.json.
# Копирование этого отдельно предотвращает повторный запуск npm install при каждом изменении кода.
COPY package*.json ./

# Установить производственные зависимости.
RUN npm install --only=production

# Скопировать локальный код в образ контейнера.
COPY . ./

# Запустить веб-сервис при запуске контейнера.
CMD [ "npm", "start" ]

Файл .dockerignore (Листинг 10-14) гарантирует, что определенные файлы не будут скопированы в образ. Например, папка node_modules будет сгенерирована из файла package.json, нет необходимости копировать все эти большие файлы.

Листинг 10-14. Файл Docker ignore

Dockerfile
README.md
node_modules
npm-debug.log

С помощью следующих команд командной строки я могу развернуть контейнер Cloud Run.

Сначала включите API реестра контейнеров:

gcloud services enable containerregistry.googleapis.com

Войдите в gcloud:

gcloud init

Соберите контейнер и присвойте образу тег:

gcloud builds submit --tag gcr.io/PROJECT_ID/dialogflow

Разверните контейнер, используя имя тега образа (используйте управляемую платформу, а не Anthos):

gcloud run deploy --image gcr.io/PROJECT_ID/dialogflow --platform managed

Это проведет вас через мастер. Вы можете выбрать регион. Разрешите неаутентифицированные вызовы, чтобы сделать URL общедоступным. Как только вы закончите, он создаст URL-адрес HTTPS для вашего выполнения. Это URL-адрес, который вы можете добавить в поле URL веб-перехватчика в консоли Dialogflow:

https://.a.run.app

Листинг 10-15 показывает пример кода.

Листинг 10-15. Выполнение Dialogflow с Express

const express = require('express');
const bodyParser = require('body-parser');

// Код выполнения Dialogflow
const expressApp = express().use(bodyParser.json())
expressApp.post('/fulfillment', (request, response) => {
 // Карта намерений агента
});
expressApp.get('/', (req, res) => {
 // получить запрос Dialogflow
 // сделать что-то
 // вернуть агенту
 res.send(`Hello from Google Cloud!`);
});

const port = process.env.PORT || 8080;
expressApp.listen(port, () => {
 console.log('Dialogflow Fulfillment listening on port', port);
});

Google Cloud Logging

При использовании продуктов Google Cloud ведение журнала поставляется из коробки. См. Рисунок 10-12.


Рисунок 10-12. Журналы веб-перехватчика выполнения в консоли Google Cloud (ранее известной как Stackdriver)

Создание многоязычного веб-перехватчика выполнения

При работе с разными языками вы, скорее всего, будете работать с другими форматами чисел, иностранными валютами или строками даты/времени. Пригодятся дополнительные инструменты:

  • moment.js: Для разбора, проверки, манипулирования и отображения дат и времени в JavaScript.
  • numeral.js: Библиотека JavaScript для форматирования и манипулирования числами.

Когда ваш бэкэнд веб-перехватчика является бэкэндом Node.js, доступны замечательные фреймворки, которые могут помочь вам лучше организовать ваш код и локализовать ваши строки.

Этот инструмент поможет вам:

  • i18n: Легковесный, простой модуль перевода с динамическим хранилищем JSON. Поддерживает простые приложения Node.js.

Dialogflow отправит код языка (например, «en» для английского или «nl» для голландского) с каждым результатом запроса:

request.body.queryResult.languageCode;

Вы будете использовать это для инициализации библиотеки i18n. Затем вы можете ссылаться на эти многоязычные текстовые строки, используя следующие вызовы:

i18n.__(KEY)

Все текстовые строки могут храниться в JSON-файлах, таких как:

  • locales/en-US.json
  • locales/nl-NL.json

Содержимое будет выглядеть так:

en-US.json

{
 "WELCOME_BASIC": "Hi, I am the virtual video game chat agent. I can talk with you about video games or tell you which latest games have been released. What would you like to know from me?",
 "DELIVERY_DATE": "The date is %s.",
 "PRICE": "It costs %s.",
 "TOTAL_AMOUNT": "The total is: %s.",
 "FALLBACK": "Oops, something went wrong. Would you like to try again?"
}

nl-NL.json

{
 "WELCOME_BASIC": "Hoi, ik ben de virtual video game chat agent. Ik kan over video games praten en ik kan je vertellen welke games net uit zijn. Wat wil je weten?",
 "DELIVERY_DATE": "De datum is %s.",
 "PRICE": "Het kost %s.",
 "TOTAL_AMOUNT": "Het totaal is: %s.",
 "FALLBACK": "Oeps, er ging iets mis. Kun je het opnieuw proberen?"
}

Совет: Поскольку i18n использует JSON-файлы для хранения языковых строк, вы можете хранить эти JSON-файлы вместе с вашим кодом и развертывать ваш веб-перехватчик в облаке с помощью, например, Cloud Run. Если вы предпочитаете использовать встроенный редактор или Cloud Functions, вы, скорее всего, захотите хранить JSON-файлы в Google Cloud Storage.

Пример кода i18n

Давайте рассмотрим полный пример кода реализации многоязычного веб-перехватчика, развернутого с помощью Cloud Run.

Файл package.json будет содержать эти дополнительные зависимости:

"i18n": "^0.10.0",
"moment": "^2.27.0",
"numeral": "^2.0.6"

Локализованные JSON-файлы будут выглядеть как Листинг 10-16. Обратите внимание, что некоторые строки могут содержать переменные (%s).

Листинг 10-16. Вы можете создать локальные JSON-файлы для использования в вашем коде веб-перехватчика

locales/en-US.json

{
 "WELCOME_BASIC": "Hi, I am the virtual video game chat agent. I can talk with you about video games or tell you which latest games have been released. What would you like to know from me?",
 "DELIVERY_DATE": "The date is %s.",
 "PRICE": "It costs %s.",
 "TOTAL_AMOUNT": "The total is: %s.",
 "FALLBACK": "Oops, something went wrong. Would you like to try again?"
}

locales/nl-NL.json

{
 "WELCOME_BASIC": "Hoi, ik ben de virtual video game chat agent. Ik kan over video games praten en ik kan je vertellen welke games net uit zijn. Wat wil je weten?",
 "DELIVERY_DATE": "De datum is %s.",
 "PRICE": "Het kost %s.",
 "TOTAL_AMOUNT": "Het totaal is: %s.",
 "FALLBACK": "Oeps, er ging iets mis. Kun je het opnieuw proberen?"
}

Листинг 10-17 показывает, как вы настраиваете свой код веб-перехватчика, чтобы использовать эти языковые файлы JSON.

Листинг 10-17. Код многоязычного веб-перехватчика выполнения Dialogflow с использованием библиотеки i18n

'use strict';

const express = require('express');
const bodyParser = require('body-parser');
const { WebhookClient } = require('dialogflow-fulfillment');

//1
const i18n = require('i18n');
i18n.configure({
 locales: ['en-US', 'nl-NL'],
 directory: __dirname + '/locales',
 defaultLocale: 'en-US'
});

//2
const moment = require('moment');
require('moment/locale/nl'); // Загрузка локали для голландского языка

//3
const numeral = require('numeral');
// Регистрация локали для голландского языка для numeral.js
numeral.register('locale', 'nl', {
 delimiters: {
  thousands: '.', // Использование точки в качестве разделителя тысяч
  decimal: ','   // Использование запятой в качестве десятичного разделителя
 },
 abbreviations: {
  thousand: 'k',
  million: 'mln', // Изменено на 'mln' для голландского
  billion: 'mld', // Изменено на 'mld' для голландского
  trillion: 'bln' // Изменено на 'bln' для голландского
 },
 ordinal: function (number) {
  // Голландские порядковые числительные обычно заканчиваются на 'e' или 'ste'
  return 'e'; // Упрощенный вариант, может потребоваться более сложная логика
 },
 currency: {
  symbol: '€'
 }
});

process.env.DEBUG = 'dialogflow:debug'; // включает отладочные операторы библиотеки

//4
function welcome(agent) {
 agent.add(i18n.__('WELCOME_BASIC'));
}

function fallback(agent) {
 agent.add(i18n.__('FALLBACK_BASIC'));
}

function getPrice(agent) {
 // Используем numeral для форматирования цены в соответствии с локалью
 agent.add(i18n.__('PRICE', numeral(399).format('$0,0.00'))); // Пример форматирования для en-US
}

function getDeliveryDate(agent) {
 // Используем moment для форматирования даты в соответствии с локалью
 agent.add(i18n.__('DELIVERY_DATE', moment().format('LL')));
}

function getTotalNumber(agent) {
 // Используем numeral для форматирования числа в соответствии с локалью
 agent.add(i18n.__('TOTAL_AMOUNT', numeral(1000).format('0,0'))); // Пример форматирования для en-US
}

// Код Express
const app = express().use(bodyParser.json());

app.post('/fulfillment', (request, response) => {
 const agent = new WebhookClient({ request, response });
 console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
 console.log('Dialogflow Request body: ' + JSON.stringify(request.body));

 //5
 var lang = request.body.queryResult.languageCode;
 var langCode;
 // Определяем код локали для i18n, moment и numeral
 if (lang === "nl") langCode = "nl-NL";
 if (lang === "en") langCode = "en-US";
 i18n.setLocale(langCode || 'en-US'); // Устанавливаем локаль для i18n
 moment.locale(lang || 'en');       // Устанавливаем локаль для moment
 numeral.locale(langCode || 'en-US'); // Устанавливаем локаль для numeral

 //6
 let intentMap = new Map();
 intentMap.set('Default Welcome Intent', welcome);
 intentMap.set('Default Fallback Intent', fallback);
 intentMap.set('Get_Price', getPrice);
 intentMap.set('Get_Delivery_Date', getDeliveryDate);
 intentMap.set('Get_Total_Number', getTotalNumber);
 agent.handleRequest(intentMap);
});

app.get('/', (req, res) => {
 res.send(`OK`);
});

const port = process.env.PORT || 8080;
app.listen(port, () => {
 console.log('Dialogflow Fulfillment listening on port', port);
});
  1. Сначала мы потребуем библиотеку i18n для обработки многоязычной поддержки. Нам нужно будет настроить ее, чтобы объяснить, какие локали мы используем, где они находятся и какая локаль будет по умолчанию.
  2. Затем мы потребуем moment.js для объектов даты и времени.
  3. По умолчанию он работает для английского языка. Все остальные локали нужно будет требовать отдельно.
  4. Затем мы потребуем numeral.js для обработки валют и чисел. Чтобы он работал с другими локалями, нам нужно будет зарегистрировать локали и предоставить опции. Например, в европейской нотации десятичный разделитель — это «.», а не «,».
  5. Здесь мы присваиваем ключи i18n выходным данным ответа. Фреймворк i18n найдет значение (правильную переведенную текстовую строку) в файле локали.
  6. Мы получим languageCode из объекта queryResult Dialogflow. Как вы видели, i18n использует коды языков ISO, соответствующие стандарту ISO 639-1, дополненные двухбуквенными кодами стран, где это необходимо, например («nl-NL» или «en-US»), поэтому нам нужно будет создать это сопоставление.
  7. Это намерения, которые будут использовать многоязычные ответы.

Рисунки 10-13 и 10-14 покажут, как результат выглядит в симуляторе. Рисунок 10-13 покажет пример на английском языке. Обратите внимание также на знак валюты.


Рисунок 10-13. Тестирование многоязычных ботов, использующих выполнение, английский язык

Рисунок 10-14. Тестирование многоязычных ботов, использующих выполнение, голландский язык

Рисунок 10-14 покажет пример на голландском языке. Обратите внимание также на знак валюты.

Использование локальных веб-перехватчиков

Запуск вашего выполнения локально может быть удобен во время разработки, так как вам не нужно развертывать ваше решение. Мы можем использовать для этого инструмент ngrok. Вы можете загрузить его из npm.

Рисунок 10-15 показывает, как это работает. Пользователь взаимодействует с агентом Dialogflow. Как только Dialogflow потребуется получить выполнение, он подключится к ngrok.io для выполнения вашего локального кода веб-перехватчика.


Рисунок 10-15. Тестирование выполнения локально с помощью ngrok.io

Ngrok

Ngrok позволяет вам выставить веб-сервер, работающий на вашем локальном компьютере, в Интернет. Просто скажите ngrok, на каком порту слушает ваш веб-сервер. Если вы не знаете, на каком порту слушает ваш веб-сервер, это, вероятно, порт 80, по умолчанию для HTTP.

Вы можете запустить следующую команду из папки с кодом вашего локального веб-перехватчика выполнения в командной строке, чтобы выставить ваш веб-перехватчик:

ngrok http 80

Когда вы запустите ngrok, он отобразит общедоступный URL вашего туннеля и другую статусную и метрическую информацию о соединениях, установленных через ваш туннель:

Tunnel Status                 online
Version                       2.0/2.0
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://92832de0.ngrok.io -> localhost:80
Forwarding                    https://92832de0.ngrok.io -> localhost:80

Connections                   ttl     opn     rt1     rt5     p50     p90
                              0       0       0.00    0.00    0.00    0.00

Сгенерированный URL-адрес переадресации HTTPS — это то, что вам нужно будет ввести в ваш раздел Веб-перехватчик на странице Выполнение Dialogflow; см. Рисунок 10-16.


Рисунок 10-16. Веб-перехватчики с ngrok

Когда вы запускаете команду ngrok из папки с кодом вашего локального приложения, он подключается к облачному сервису ngrok, который принимает трафик по общедоступному адресу и перенаправляет этот трафик процессу ngrok, работающему на вашей машине, а затем на указанный вами локальный адрес.

Тестирование вашего выполнения без Dialogflow и ngrok

Также возможно протестировать ваш код веб-перехватчика без подключения к Dialogflow и без использования ngrok. Это имеет смысл, если вы просто хотите протестировать свой локальный код, что может быть удобно при написании кода. Мы можем сделать это, отправив POST-запросы с заглушками JSON-файлов на ваш веб-перехватчик (притворяясь, что это Dialogflow).

В консоли Dialogflow щелкните Выполнения ➤ Diagnostic Info.

Скопируйте и вставьте содержимое вкладки Запрос выполнения.

Сохраните это в локальном файле: stub.json.

Этот JSON-код уникален для вашего агента, так как он включает идентификаторы сеансов, имена проектов и идентификаторы намерений и ответов, уникальные для вашего агента.

Как только у вас есть файл stub.json, вы можете сделать вызов Curl POST из вашей командной строки, который использует файл-заглушку в качестве POST-сообщения:

curl -X POST -H "Content-Type: application/json" -d @stub.json http://localhost:8080/fulfillment

Это выполнит ваш локальный код.

Защита веб-перехватчиков

Возможно защитить ваш веб-перехватчик выполнения так, чтобы только вы или ваш агент Dialogflow были авторизованы для выполнения запросов. Поддерживаются следующие механизмы аутентификации:

  • Базовая аутентификация с логином и паролем
  • Аутентификация с заголовками аутентификации
  • Взаимная аутентификация TLS

Примечание: Dialogflow — это разговорный инструмент для NLP, сопоставления намерений и заполнения слотов параметров. Он не поставляется с CMS и поэтому не будет иметь системы входа пользователя или реализации OAuth.

Базовая аутентификация

Базовая аутентификация доступа — это метод для HTTP-агента пользователя (например, веб-браузера) для предоставления пользователя и пароля при выполнении запроса. При базовой аутентификации HTTP запрос содержит поле заголовка в следующем формате: Authorization: Basic , где credentials — это кодировка Base64 идентификатора и пароля, соединенных одним двоеточием.

Она не обеспечивает защиту конфиденциальности передаваемых учетных данных. Они просто кодируются с помощью Base64 при передаче, но не шифруются и не хешируются каким-либо образом. Поэтому базовая аутентификация обычно используется в сочетании с HTTPS для обеспечения конфиденциальности. К счастью, оба варианта работают из коробки с Dialogflow. Веб-перехватчики Dialogflow разрешают только URL-адреса HTTPS.

Вы можете настроить базовую аутентификацию на странице Выполнение Dialogflow; см. Рисунок 10-17. Помимо URL-адреса, вам нужно будет указать идентификатор пользователя и пароль.


Рисунок 10-17. Безопасный веб-перехватчик в Dialogflow

Использование опций базовой аутентификации в Dialogflow также требует от вас обработки контроля доступа в вашем веб-перехватчике выполнения. Давайте предположим, что вы создали бэкэнд с помощью Node.js и Express. Express имеет плагин промежуточного ПО для базовой аутентификации. Промежуточное ПО будет проверять входящие запросы на наличие заголовка базовой аутентификации (Authorization), разбирать его и проверять, действительны ли учетные данные. Если запрос не авторизован, он ответит HTTP 401 и настраиваемым телом (по умолчанию пустым).

В Листинге 10-18 промежуточное ПО будет проверять входящие запросы на соответствие учетным данным admin:supersecret, которые были переданы в веб-перехватчик Dialogflow, поля базовой аутентификации.

Листинг 10-18. Базовая аутентификация в вашем веб-перехватчике

const app = require('express')()
const basicAuth = require('express-basic-auth')

app.use(basicAuth({
 users: { 'admin': 'supersecret' }
}));

Аутентификация с помощью заголовков аутентификации

HTTP предоставляет общую структуру для контроля доступа и аутентификации. Наиболее распространенная аутентификация HTTP основана на схеме «Basic Auth», как вы видели в предыдущем разделе. Но существуют и другие схемы, такие как Bearer JWT Tokens или Digest. Dialogflow позволяет вам указывать свои собственные заголовки, которые вы можете использовать для аутентификации или, например, для установки кодировки символов.

Вы можете настроить пользовательские заголовки на странице Выполнение Dialogflow. Помимо URL-адреса, вам нужно будет указать ключ и значение заголовка.

Листинг 10-19 показывает пример проверки наличия заголовка в коде веб-перехватчика выполнения. Пользовательский заголовок с ключом x-auth и значением true был установлен в консоли Dialogflow. Этот код проверяет, присутствует ли этот заголовок; если нет, он отправляет ошибку HTTP 403, что означает, что код веб-перехватчика понимает запрос, но отказывается его авторизовать.

Листинг 10-19. Аутентификация по заголовку в вашем веб-перехватчике

app.use(function(req, res, next) {
 if (!req.headers['x-auth']) {
  return res.status(403).json({ error: 'No auth headers sent!'});
 }
 next();
});

Рисунок 10-18 показывает, как настроить веб-перехватчик Dialogflow для работы с кодом из Листинга 10-19. Обратите внимание на заголовок x-auth.


Рисунок 10-18. Безопасный веб-перехватчик с заголовками

Совет: Если вы хотите работать с токенами Bearer JWT, вы можете задаться вопросом, как установить заголовки, так как токен JWT должен меняться постоянно.

Обычно заголовки выглядят как Authorization - Token token=’yourtokenhere.

Устанавливать их в консоли Dialogflow не будет иметь особого смысла. Вы, скорее всего, захотите сделать это программно. С помощью SDK Dialogflow возможно вызвать метод updateFulfillment, который позволяет вам передать Fulfillment с GenericWebservice.

Как бы вы получили новый токен/перенаправление JWT? Возможно, поместив вашу интеграцию чат-бота за веб-логин? Это также означает, что ваша реализация кода должна будет проверять токены JWT и предоставлять токены JWT, так как Dialogflow этого не сделает.

Взаимная аутентификация TLS

Сетевой трафик, инициируемый Dialogflow для запросов веб-перехватчика, отправляется по общедоступной сети. Чтобы гарантировать, что трафик является безопасным и доверенным в обоих направлениях, Dialogflow опционально поддерживает взаимную аутентификацию TLS (mTLS). С mTLS и клиент (Dialogflow), и сервер (ваш сервер веб-перехватчика) представляют сертификат во время рукопожатия TLS, что взаимно доказывает идентичность.

Вам не нужно будет настраивать mTLS в консоли Dialogflow, но вам нужно будет сделать это на сервере веб-перехватчика выполнения. Обычно вы не будете настраивать это в своем коде приложения Node.js, так как приложения Node.js в производственной среде всегда будут находиться за сервером, таким как Apache, NGINX, или, возможно, облачным балансировщиком нагрузки. Именно там вы настроите свой сервер, и когда рукопожатие TLS докажет взаимность, тогда перенаправите запрос на ваш код приложения Node.js на другой порт (по HTTP).

Конфигурация будет иметь следующие требования:

  • Ваш код приложения должен будет иметь действительный безопасный SSL-сертификат.
  • Вам нужно будет загрузить корневой ЦС, который использует Dialogflow.
Действительный безопасный SSL-сертификат

Веб-страницы и приложения, работающие по HTTPS, требуют действительный SSL-сертификат. Есть два способа получить их бесплатно, но есть некоторые оговорки:

  • Вы можете генерировать самоподписанные запросы на сертификаты с помощью OpenSSL; однако ЦС не будет доверенным. Вы можете добавить сертификат в хранилище сертификатов вашего браузера или ОС, но всем, кто использует этот общедоступный IP, придется выполнить эти шаги. Возможно подписать ваш сертификат доверенным центром сертификации, таким как GlobalSign; это будет стоить денег. Dialogflow mTLS не будет работать без доверенного ЦС.
  • Используйте инструмент, такой как Let’s Encrypt. Он может генерировать доверенные сертификаты с доверенными ЦС бесплатно; однако вам нужно будет подключить домен к вашему общедоступному IP.

Рисунок 10-19 показывает, как это выглядит, если ваш SSL-сертификат безопасен.


Рисунок 10-19. Безопасный веб-сайт с SSL-сертификатом
Корневой ЦС

Выполните следующие две команды в вашей командной строке, чтобы загрузить корневой ЦС, который будет использоваться Dialogflow. Это добавит GTS101.crt и GSR2.crt в локальный файл: ca-crt.pem:

curl https://pki.goog/gsr2/GTS1O1.crt | openssl x509 -inform der >> ca-crt.pem

curl https://pki.goog/gsr2/GSR2.crt | openssl x509 -inform der >> ca-crt.pem

Вот пример того, как включить mTLS для Apache2. Эти настройки относятся к ssl.conf:

SSLVerifyClient require
SSLVerifyDepth 2
SSLCACertificateFile "ca-crt.pem"

И это контроль доступа, который гарантирует, что только Dialogflow.com может вызывать веб-перехватчик выполнения:


 Require all denied

Настройка аутентификации HTTPS с помощью Apache

Вместо настройки аутентификации HTTP в вашем коде приложения (Node.js), вы исправите это на стороне сервера. Чтобы защитить каталог паролем на сервере Apache, вам понадобятся файлы .htaccess и .htpasswd. Файл .htaccess обычно выглядит так:

AuthType Basic AuthName "Access to the staging site"
AuthUserFile /path/to/.htpasswd Require valid-user

Файл .htaccess ссылается на файл .htpasswd, в котором каждая строка состоит из имени пользователя и пароля, разделенных двоеточием («:»). Вы не можете видеть фактические пароли, так как они зашифрованы (md5 в данном случае). Обратите внимание, что вы можете назвать свой файл .htpasswd по-другому, если хотите, но имейте в виду, что этот файл не должен быть доступен никому. (Apache обычно настроен так, чтобы предотвращать доступ к файлам .ht*).

Например:

admin:$apr1$ZjTqBB3f$IF9gdYAGlMrs2fuINjHsz.

Полный пример настройки взаимной аутентификации TLS

HTTPS — это защищенная версия HTTP (HyperText Transfer Protocol). HTTP — это протокол, используемый вашим браузером и веб-серверами для связи и обмена информацией. Когда этот обмен данными зашифрован с помощью SSL/TLS, мы называем это HTTPS. «S» означает Secure (Безопасный).

Каждый раз, когда вы используете веб-браузер для подключения к безопасному сайту (https://что-то), вы используете Transport Layer Security (TLS). TLS является преемником SSL, и это отличный стандарт со многими функциями. TLS гарантирует идентичность сервера клиенту и обеспечивает двусторонний зашифрованный канал между сервером и клиентом.

Но для приложения веб-перехватчика, такого как Dialogflow, этого недостаточно, так как ваше приложение является сервером, и вы хотите подтвердить идентичность клиента, Dialogflow. Какое решение?

Взаимный TLS спешит на помощь! Это необязательная функция для TLS. Она позволяет серверу аутентифицировать идентичность клиента.

Развернутые приложения Cloud Run (HTTPS или gRPC) и бессерверные облачные функции предоставят действительный «сертификат сервера», доверенный корневыми хранилищами ЦС. Cloud Run и Cloud Functions не поддерживают использование собственного сертификата. URL-адреса *.run.app Cloud Run предоставляют свои собственные действительные сертификаты сервера TLS.

Предположим, для вашего веб-перехватчика требуется аутентификация mTLS. В этом случае вы можете рассмотреть одно из следующих решений Google Cloud: Compute Engine (ВМ), Google Kubernetes Engine (Контейнеры) или Cloud Run на Anthos (контейнер Cloud Run на GKE с Istio).

Вот как настроить виртуальную машину выполнения Dialogflow в Compute Engine с включенным mTLS.

Создание виртуальной машины Node.js в Compute Engine

Создайте виртуальную машину Node.js, выполнив следующие шаги:

В меню Cloud Console выберите Marketplace.

Выберите Node.js by Google Click to deploy image (VM) (см. Рисунок 10-20).


Рисунок 10-20. Виртуальная машина Node.js Compute в Google Cloud

Выберите регион. Убедитесь, что HTTP и HTTPS отмечены.

В меню Cloud Console выберите VPC Network ➤ Firewall ➤ Создать новое правило брандмауэра (см. Рисунок 10-21).

Цели: Все экземпляры в сети

Диапазоны IP-адресов источника: 0.0.0.0/0

Указанные порты: tcp > 3000

Нажмите Создать.


Рисунок 10-21. Открытие брандмауэра для порта 3000

Прикрепление доменного имени к вашей виртуальной машине

В меню Cloud Console выберите VPC Networks ➤ Внешние IP-адреса.

Вы должны увидеть свой новый экземпляр виртуальной машины. Теперь мы зарезервируем статический IP-адрес, чтобы мы могли привязать его позже к домену.

Измените тип с Ephemeral на Static.

Это зарезервирует статический IP-адрес, что означает, что вы не потеряете свой текущий IP-адрес после перезагрузки вашей виртуальной машины. Запишите этот адрес.

Чтобы этот учебник сработал, вам понадобится действительный SSL-сертификат, предоставленный действительным центром сертификации. В противном случае Chrome и, вероятно, другие приложения, такие как Dialogflow, заблокируют ваш веб-сайт; как только ваш веб-сайт будет заблокирован, Dialogflow не сможет достичь вашего URL-адреса выполнения, даже если он доступен по HTTPS.

Мы будем использовать Certbot с Let’s Encrypt, бесплатный инструмент для получения бесплатного действительного SSL-сертификата. Однако вам понадобится доменное имя, к которому вы можете его прикрепить. Если вы вместо этого прикрепите сертификат к IP-адресу, вы можете создать самоподписанный сертификат через OpenSSL; однако вам все равно понадобится действительный центр сертификации, который вам, скорее всего, придется заказать. Поэтому вместо этого мы выберем вариант с доменом.

Вы можете купить домен через https://domains.google.com/.

Я использовал следующие настройки в моем регистраторе доменов, mtls.conv.dev, для создания поддомена mtls:

Mtls A 1h <внешний IP-адрес compute>

Как только это будет связано, мы вернемся в консоль Google Cloud.

В меню Cloud Console выберите Compute Engine и SSH в только что созданную виртуальную машину.

Как только вы войдете в систему, выполните эту команду в командной строке, чтобы установить Certbot:

sudo apt-get install certbot python-certbot-apache

Выполните эту команду, чтобы получить сертификат и позволить Certbot автоматически отредактировать вашу конфигурацию Apache для его обслуживания, включив доступ по HTTPS одним шагом:

sudo certbot --apache

Перезапустите сервер Apache:

sudo /etc/init.d/apache2 restart

Теперь откройте новую вкладку браузера и протестируйте свое доменное имя. Он должен привести вас к настройке Apache по умолчанию (см. Рисунок 10-22).


Рисунок 10-22. Запуск безопасного Apache

Настройка вашего приложения Node

Когда инфраструктура настроена, давайте напишем код приложения. Как было показано ранее в этой главе, мы создадим реализацию Node.js Express, точно так же, как мы делали раньше.

sudo mkdir /var/www/projects
sudo chown $USER /var/www/projects
cd /var/www/projects
nano index.js

Используйте следующее содержимое Листинга 10-20:

Листинг 10-20. Настройка кода приложения mTLS index.js

'use strict';
// ... (код аналогичен Листингу 10-11, но порт может быть другим, например 3000) ...

const express = require('express');
const app = express(); // Инициализируем приложение Express
const bodyParser = require('body-parser');
// const basicAuth = require('express-basic-auth'); // Базовая аутентификация не используется в примере mTLS
const fs = require('fs'); // Потребуется для чтения файлов сертификатов, если HTTPS обрабатывается Node.js (но в этом примере Apache)

const { WebhookClient, Card, Suggestion } =
 require('dialogflow-fulfillment');

process.env.DEBUG = 'dialogflow:debug'; // включает отладочные операторы библиотеки

// Код выполнения Dialogflow (функции welcome, fallback, yourFunctionHandler как в Листинге 10-3)
function welcome(agent) { agent.add('Welcome to my agent!'); }
function fallback(agent) { agent.add(`I didn't understand`); agent.add(`I'm sorry, can you try again?`); }
function yourFunctionHandler(agent) { /* ... (как в Листинге 10-3) ... */ }


app.use(bodyParser.json()); // Используем body-parser для разбора JSON

app.post('/fulfillment', (request, response) => {
 const agent = new WebhookClient({ request, response });
 console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
 console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
 // Запустить правильный обработчик функции на основе сопоставленного имени намерения Dialogflow
 let intentMap = new Map();
 intentMap.set('Default Welcome Intent', welcome);
 intentMap.set('Default Fallback Intent', fallback);
 intentMap.set('Buy product regex', yourFunctionHandler);
 agent.handleRequest(intentMap);
});

app.get('/', (req, res) => {
 res.send('OK - mTLS Fulfillment Server'); // Ответ для корневого пути
});

const port = process.env.PORT || 3000; // Используем порт 3000, как настроено в Apache
app.listen(port, () => {
 console.log('Dialogflow Fulfillment listening on port', port);
});

nano package.json

Используйте следующее содержимое Листинга 10-21:

Листинг 10-21. Настройка файла mTLS package.json

{
 "name": "dialogflow-fulfillment-mtls",
 "description": "This is the default fulfillment for a Dialogflow agents using Compute with mTLS",
 "version": "0.0.1",
 "private": true,
 "license": "Apache Version 2.0",
 "author": "Lee Boonstra",
 "engines": {
  "node": "8" // Или более новая версия Node.js
 },
 "dependencies": {
  "actions-on-google": "^2.5.0", // Или более новая версия
  "body-parser": "^1.19.0",
  "dialogflow-fulfillment": "^0.6.1", // Использовать с осторожностью
  "express": "^4.17.1",
  "express-basic-auth": "^1.2.0" // Этот пакет не нужен для mTLS, но может остаться, если используется в других частях
 }
}

npm install

Нам нужно будет включить модули прокси:

sudo a2enmod proxy

sudo a2enmod proxy_http

Теперь давайте изменим конфигурацию Apache:

sudo nano /etc/apache2/sites-available/000-default-le-ssl.conf

Используйте следующий 000-default-le-ssl.conf, это будет использовать следующую конфигурацию для Apache. Используйте следующее содержимое Листинга 10-22.

Листинг 10-22. Настройка файла mTLS 000-default-le-ssl.conf



 # Директива ServerName устанавливает схему запроса, имя хоста и порт
 # которые сервер использует для идентификации себя. Это используется при создании
 # URL-адресов перенаправления. В контексте виртуальных хостов ServerName
 # указывает, какое имя хоста должно появиться в заголовке Host: запроса, чтобы
 # соответствовать этому виртуальному хосту. Для виртуального хоста по умолчанию (этот файл) это
 # значение не является решающим, так как оно используется в качестве хоста последнего средства независимо.
 # Однако вы должны установить его для любого дальнейшего виртуального хоста явно.
 #ServerName www.example.com

 ServerAdmin webmaster@localhost
 #DocumentRoot /var/www/projects # Закомментировано, так как проксируем на Node.js

 # Доступные уровни журнала: trace8, ..., trace1, debug, info, notice, warn,
 # error, crit, alert, emerg.
 # Также возможно настроить уровень журнала для конкретных
 # модулей, например:
 LogLevel info ssl:debug

 ErrorLog /var/www/projects/error.log
 CustomLog /var/www/projects/access.log combined

 ServerName mtls.conv.dev
 SSLCertificateFile /etc/letsencrypt/live/mtls.conv.dev/fullchain.pem
 SSLCertificateKeyFile /etc/letsencrypt/live/mtls.conv.dev/privkey.pem
 Include /etc/letsencrypt/options-ssl-apache.conf

 # Требовать проверку клиента TLS
 SSLVerifyClient require
 # Глубина проверки цепочки сертификатов клиента
 SSLVerifyDepth 2
 # Путь к файлу с доверенными корневыми сертификатами ЦС Dialogflow
 SSLCACertificateFile "/var/www/projects/ca-crt.pem"

 # контроль доступа, разрешать подключение только dialogflow.com
 
  Require all denied
 

 # Включить модули прокси Apache
 # sudo a2enmod proxy
 # sudo a2enmod proxy_http

 # Проксировать запросы на локальный Node.js сервер
 ProxyPass / http://localhost:3000/
 ProxyPassReverse / http://localhost:3000/


Настройка mTLS

Сначала убедитесь, что в Dialogflow вы указываете на вашу новую виртуальную машину с экрана выполнения.

Выполните следующие две команды для загрузки корневого ЦС, который будет использоваться Dialogflow. Это добавит GTS101.crt и GSR2.crt в локальный файл ca-crt.pem:

curl https://pki.goog/gsr2/GTS1O1.crt | openssl x509 -inform der >> ca-crt.pem

curl https://pki.goog/gsr2/GSR2.crt | openssl x509 -inform der >> ca-crt.pem

Эти настройки в /etc/apache2/sites-available/000-default-le-ssl.conf специфичны для настройки mTLS:

SSLVerifyClient require
SSLVerifyDepth 2
SSLCACertificateFile "/var/www/projects/ca-crt.pem"

И это контроль доступа, который гарантирует, что только Dialogflow.com может вызывать веб-перехватчик выполнения:


 Require all denied

Теперь вы можете это протестировать. Мы разрешим проходить только Dialogflow с сертификатом CN: *.dialogflow.com для доступа к выполнению. Когда ЦС Dialogflow не совпадает с ЦС, который мы ожидаем, статус выполнения Dialogflow будет НЕИЗВЕСТЕН.

Журнал ошибок Apache показывает (tail error.log).

[Fri Jun 26 11:45:54.732961 2020] [authz_core:error] [pid 10152:tid 139735259326208] [client 64.233.172.238:36290] AH01630: client denied by server configuration: proxy:http://localhost:3000/fulfillment

В противном случае он пройдет. Вы можете протестировать это в симуляторе Dialogflow. Я использовал намерение Buy product regex с высказыванием пользователя: Buy ms12345678 для отображения результатов.

(Не верите? Поменяйте != на == в контроле доступа: 000-default-le-ssl.conf на что-то другое, чем *.dialogflow.com.)

Рисунок 10-23 показывает скриншот того, как выглядит сертификат ЦС Dialogflow.


Рисунок 10-23. Сертификат ЦС Dialogflow

Резюме

Эта глава содержит информацию о выполнениях. Вы узнаете следующие задачи:

  • Вы хотите получить данные из веб-сервиса и написать свой код выполнения во встроенном редакторе.
  • Вы хотите получить данные из веб-сервиса и написать свой код выполнения в вашей собственной (облачной) среде, такой как Cloud Run.
  • Вы хотите получить данные из веб-сервиса, но ваш агент Dialogflow имеет многоязычную поддержку, так как вы можете организовать свой веб-перехватчик?
  • Вы хотите получить данные из локального источника данных для целей тестирования с помощью ngrok.
  • Вы хотите убедиться, что трафик к и от Dialogflow безопасен, используя аутентификацию через базовую аутентификацию, заголовки аутентификации и взаимный TLS.

Если вы хотите создать этот пример, исходный код для этой книги доступен на GitHub через страницу продукта книги, расположенную по адресу www.apress.com/978-1-4842-7013-4. Ищите папки webhook-fulfillment-lib, webhook-aog, webhook-manual, webhook-cloudrun, webhook-localized, secure-auth и secure-compute-mtls.

Дополнительные материалы

Предыдущая глава    Следующая глава

Другие статьи по этой теме: