使用 Dialogflow Essentials 建構 Android 專用 Voice 機器人飄逸袖

1. 事前準備

在本程式碼研究室中,您將瞭解如何將簡單的 Dialogflow Essentials (ES) 文字和語音機器人整合至 Flutter 應用程式。Dialogflow ES 是一套開發套件,可用於建構對話式 UI。因此,聊天機器人、語音機器人、電話閘道。大家可以使用相同的工具建構,甚至支援超過 20 種語言的多個管道。Dialogflow 已與多個常見的對話平台相互整合,例如 Google 助理、Slack 和 Facebook Messenger。如果要為上述平台建構代理程式,建議您從眾多整合選項中擇一使用。不過,如要建立行動裝置專用的聊天機器人,則必須建立自訂整合項目。您將指定訓練詞組來訓練基礎機器學習模型,藉此建立意圖。

本實驗室包含多項練習,順序與常見雲端開發作業類似:

  1. 環境設定
  • Dialogflow:建立新的 Dialogflow ES 代理程式
  • Dialogflow:設定 Dialogflow
  • Google Cloud:建立服務帳戶
  1. Flutter:建構即時通訊應用程式
  • 建立 Flutter 專案
  • 設定設定和權限
  • 新增依附元件
  • 連結至服務帳戶。
  • 在虛擬裝置或實體裝置上執行應用程式
  1. Flutter:建構支援語音轉文字功能的對話介面
  • 建立即時通訊介面
  • 連結對話介面
  • 將 Dialogflow gRPC 套件整合至應用程式
  1. Dialogflow:建立 Dialogflow 代理程式模型
  • 設定歡迎意圖和備用意圖
  • 善用常見問題知識庫

修課條件

  • 具備 Dart/Flutter 基礎知識
  • 具備 Google Cloud Platform 基礎經驗
  • Dialogflow ES 的基本使用經驗

建構項目

本程式碼研究室將說明如何建構行動常見問題機器人,回答有關 Dialogflow 工具的常見問題。使用者可以透過文字介面互動,或透過行動裝置的內建麥克風串流語音來取得答案。

課程內容

  • 如何使用 Dialogflow Essentials 建立聊天機器人
  • 如何使用 Dialogflow gRPC 套件,將 Dialogflow 整合至 Flutter 應用程式
  • 如何使用 Dialogflow 偵測文字意圖
  • 如何透過麥克風將語音串流傳送至 Dialogflow
  • 如何使用知識庫連接器匯入公開常見問題
  • 透過虛擬或實體裝置的文字和語音介面測試聊天機器人

軟硬體需求

  • 您必須擁有 Google 身分 / Gmail 地址,才能建立 Dialogflow 虛擬服務專員。
  • 您必須有權存取 Google Cloud Platform,才能下載服務帳戶
  • Flutter 開發環境

設定 Flutter 開發環境

  1. 選取要安裝 Flutter 的作業系統。
  1. 您可以搭配使用任何文字編輯器和指令列工具,透過 Flutter 建構應用程式。不過,本研討會將使用 Android Studio。Android Studio 適用的 Flutter 和 Dart 外掛程式提供程式碼完成、語法醒目顯示、小工具編輯輔助、執行和偵錯支援等功能。請按照 https://flutter.dev/docs/get-started/editor 的步驟操作

2. 環境設定

Dialogflow:建立新的 Dialogflow ES 代理程式

  1. 開啟
  2. 在左側列的標誌下方,從下拉式選單中選取「建立新代理程式」。(請注意,請勿點選顯示「全球」的下拉式選單,我們需要使用全球 Dialogflow 執行個體,才能使用常見問題知識庫)。
  3. 指定代理名稱 yourname-dialogflow (使用自己的名稱)
  4. 選擇「英文 - en」做為預設語言。
  5. 選擇最接近您所在位置的時區做為預設時區。
  6. 不要選取超級代理程式。(有了這項功能,您就能建立統籌代理,在「子」代理之間協調運作。我們現在不需要這個。)
  7. 點選「建立」

建立新專案畫面

設定 Dialogflow

  1. 在左選單中,點選專案名稱旁的齒輪圖示。

建立新的專案下拉式選單

  1. 輸入下列代理程式說明:Dialogflow FAQ Chatbot
  2. 啟用 Beta 版功能,然後切換開關。

Dialogflow Essentials V2Beta1

  1. 按一下「語音」分頁,並確認「自動語音調整功能」方塊已啟用。
  2. 您也可以選擇開啟第一個切換鈕,這會改善語音模型,但只有在升級 Dialogflow 試用版後才能使用。
  3. 按一下「儲存」

Google Cloud:取得服務帳戶

在 Dialogflow 中建立代理程式後,Google Cloud 控制台應會建立 Google Cloud 專案。

  1. 開啟 Google Cloud 控制台:
  2. 確認您登入的 Google 帳戶與 Dialogflow 相同,然後選取頂端藍色列中的專案:yourname-dialogflow
  3. 接著,在頂端工具列中搜尋 Dialogflow API,然後點選下拉式選單中的「Dialogflow API」結果。

啟用 Dialogflow API

  1. 按一下藍色的「管理」按鈕,然後點選左側選單列中的「憑證」。(如果尚未啟用 Dialogflow,請先點選「啟用」)

憑證 GCP 控制台

  1. 按一下畫面頂端的「建立憑證」,然後選擇「服務帳戶」

建立憑證

  1. 指定服務帳戶名稱:flutter_dialogflow、ID 和說明,然後按一下「建立」

建立服務帳戶

  1. 在步驟 2 中,您需要選取角色:Dialogflow API Admin,然後按一下「繼續」和「完成」
  2. 按一下 flutter_dialogflow 服務帳戶,點選「金鑰」分頁標籤,然後依序點選「新增金鑰」>「建立新的金鑰」

建立金鑰

  1. 建立 JSON 金鑰。將檔案重新命名為 credentials.json,並儲存在硬碟的安全位置。稍後會用到這個名稱。

JSON 金鑰

太棒了,所有必要工具都已正確設定。現在可以開始在應用程式中整合 Dialogflow!

3. Flutter:建構即時通訊應用程式

建立樣板應用程式

  1. 開啟 Android Studio,然後選取「Start a new Flutter project」
  2. 選取「Flutter Application」做為專案類型。然後按一下「下一步」
  3. 確認 Flutter SDK 路徑指定 SDK 的位置 (如果文字欄位空白,請選取「安裝 SDK...」)。
  4. 輸入專案名稱 (例如 flutter_dialogflow_agent),然後點選「Next」(下一步)
  5. 修改套件名稱,然後按一下「完成」

建立新的 Flutter 應用程式

這會建立含有 Material Components 的範例應用程式。

等待 Android Studio 安裝 SDK 並建立專案。

設定與權限

  1. 我們將使用的錄音機程式庫 sound_stream 至少需要 minSdk 21。因此,請在 defaultConfig 區塊中,於 android/app/build.gradle 變更這項設定。(請注意,android 資料夾中有 2 個 build.gradle 檔案,但應用程式資料夾中的才是正確的檔案)。
defaultConfig {
   applicationId "com.myname.flutter_dialogflow_agent"
   minSdkVersion 21
   targetSdkVersion 30
   versionCode flutterVersionCode.toInteger()
   versionName flutterVersionName
}
  1. 如要授予麥克風權限,並讓應用程式連線至雲端執行的 Dialogflow 代理程式,我們必須將 INTERNET 和 RECORD_AUDIO 權限新增至 app/src/main/AndroidManifest.xml 檔案。Flutter 專案中有多個 AndroidManifest.xml 檔案,但您需要的是主資料夾中的檔案。您可以在資訊清單標記中直接新增這些行。
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECORD_AUDIO" />

新增依附元件

我們會使用 sound_streamrxdartdialogflow_grpc 套件。

  1. 新增 sound_stream 依附元件
$ flutter pub add sound_stream
Resolving dependencies...
  async 2.8.1 (2.8.2 available)
  characters 1.1.0 (1.2.0 available)
  matcher 0.12.10 (0.12.11 available)
+ sound_stream 0.3.0
  test_api 0.4.2 (0.4.5 available)
  vector_math 2.1.0 (2.1.1 available)
Downloading sound_stream 0.3.0...
Changed 1 dependency!
  1. 新增 dialogflow_grpc 依附元件
flutter pub add dialogflow_grpc
Resolving dependencies...
+ archive 3.1.5
  async 2.8.1 (2.8.2 available)
  characters 1.1.0 (1.2.0 available)
+ crypto 3.0.1
+ dialogflow_grpc 0.2.9
+ fixnum 1.0.0
+ googleapis_auth 1.1.0
+ grpc 3.0.2
+ http 0.13.4
+ http2 2.0.0
+ http_parser 4.0.0
  matcher 0.12.10 (0.12.11 available)
+ protobuf 2.0.0
  test_api 0.4.2 (0.4.5 available)
+ uuid 3.0.4
  vector_math 2.1.0 (2.1.1 available)
Downloading dialogflow_grpc 0.2.9...
Downloading grpc 3.0.2...
Downloading http 0.13.4...
Downloading archive 3.1.5...
Changed 11 dependencies!
  1. 新增 rxdart 依附元件
$ flutter pub add rxdart
Resolving dependencies...
  async 2.8.1 (2.8.2 available)
  characters 1.1.0 (1.2.0 available)
  matcher 0.12.10 (0.12.11 available)
+ rxdart 0.27.2
  test_api 0.4.2 (0.4.5 available)
  vector_math 2.1.0 (2.1.1 available)
Downloading rxdart 0.27.2...
Changed 1 dependency!

載入服務帳戶和 Google Cloud 專案資訊

  1. 在專案中建立目錄,並命名為:assets
  2. 將從 Google Cloud 控制台下載的 credentials.json 檔案移到 assets 資料夾。
  3. 開啟 pubspec.yaml,然後將服務帳戶新增至 Flutter 區塊。
flutter:
  uses-material-design: true
  assets:
    - assets/credentials.json

在實體裝置上執行應用程式

如果您使用 Android 裝置,可以透過 USB 傳輸線連接手機,並在裝置上進行偵錯。如要在 Android 裝置上透過「開發人員選項」畫面設定這項功能,請按照這些步驟操作。

在虛擬裝置上執行應用程式

如要在虛擬裝置上執行應用程式,請按照下列步驟操作:

  1. 依序點選「Tools」>「AVD Manager」。(或從頂端工具列選取 AVD 管理工具,如下圖以粉紅色醒目顯示)

Android Studio 頂端工具列

  1. 我們會建立目標 Android 虛擬裝置,以便在沒有實體裝置的情況下測試應用程式。詳情請參閱「管理 AVD」。選取新的虛擬裝置後,連按兩下即可啟動。

管理 AVD

虛擬裝置

  1. 在 Android Studio 主工具列中,選取 Android 裝置做為目標 (透過下拉式選單),並確認已選取 main.dart。然後按下「執行」按鈕 (綠色三角形)。

IDE 底部的控制台會顯示記錄。您會發現系統正在安裝 Android 和入門 Flutter 應用程式。這需要一分鐘的時間,虛擬裝置準備就緒後,變更速度就會非常快。完成後,系統會開啟入門 Flutter 應用程式。

樣板應用程式

  1. 讓我們為聊天機器人應用程式啟用麥克風。按一下虛擬裝置的「選項」按鈕,開啟選項。在「麥克風」分頁中,啟用所有 3 個切換按鈕。

AVD 選項

  1. 讓我們試試「熱重載」,示範如何快速進行變更。

lib/main.dart 中,將 MyApp 類別中的 MyHomePage title 變更為:Flutter Dialogflow Agent。並將 primarySwatch 變更為 Colors.orange。

第一個代碼

儲存檔案,或按一下 Android Studio 工具列中的閃電圖示。您應該會看到虛擬裝置直接進行的變更。

4. Flutter:建構支援語音轉文字的即時通訊介面

建立即時通訊介面

  1. lib 資料夾中建立新的 Flutter 小工具檔案。(在 lib 資料夾上按一下滑鼠右鍵,然後依序點選「New」>「Flutter Widget」>「Stateful widget」),將這個檔案命名為:chat.dart

將下列程式碼貼入這個檔案。這個 Dart 檔案會建立即時通訊介面。Dialogflow 尚未運作,目前只有所有元件的版面配置,以及麥克風元件的整合,可允許串流。檔案中的註解會指出稍後要整合 Dialogflow 的位置。

// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rxdart/rxdart.dart';
import 'package:sound_stream/sound_stream.dart';

// TODO import Dialogflow


class Chat extends StatefulWidget {
  Chat({Key key}) : super(key: key);

  @override
  _ChatState createState() => _ChatState();
}

class _ChatState extends State<Chat> {
  final List<ChatMessage> _messages = <ChatMessage>[];
  final TextEditingController _textController = TextEditingController();

  bool _isRecording = false;

  RecorderStream _recorder = RecorderStream();
  StreamSubscription _recorderStatus;
  StreamSubscription<List<int>> _audioStreamSubscription;
  BehaviorSubject<List<int>> _audioStream;

  // TODO DialogflowGrpc class instance

  @override
  void initState() {
    super.initState();
    initPlugin();
  }

  @override
  void dispose() {
    _recorderStatus?.cancel();
    _audioStreamSubscription?.cancel();
    super.dispose();
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlugin() async {
    _recorderStatus = _recorder.status.listen((status) {
      if (mounted)
        setState(() {
          _isRecording = status == SoundStreamStatus.Playing;
        });
    });

    await Future.wait([
      _recorder.initialize()
    ]);



    // TODO Get a Service account

  }

  void stopStream() async {
    await _recorder.stop();
    await _audioStreamSubscription?.cancel();
    await _audioStream?.close();
  }

  void handleSubmitted(text) async {
    print(text);
    _textController.clear();

    //TODO Dialogflow Code

  }

  void handleStream() async {
    _recorder.start();

    _audioStream = BehaviorSubject<List<int>>();
    _audioStreamSubscription = _recorder.audioStream.listen((data) {
      print(data);
      _audioStream.add(data);
    });


    // TODO Create SpeechContexts
    // Create an audio InputConfig

    // TODO Make the streamingDetectIntent call, with the InputConfig and the audioStream
    // TODO Get the transcript and detectedIntent and show on screen

  }

  // The chat interface
  //
  //------------------------------------------------------------------------------------
  @override
  Widget build(BuildContext context) {
    return Column(children: <Widget>[
      Flexible(
          child: ListView.builder(
            padding: EdgeInsets.all(8.0),
            reverse: true,
            itemBuilder: (_, int index) => _messages[index],
            itemCount: _messages.length,
          )),
      Divider(height: 1.0),
      Container(
          decoration: BoxDecoration(color: Theme.of(context).cardColor),
          child: IconTheme(
            data: IconThemeData(color: Theme.of(context).accentColor),
            child: Container(
              margin: const EdgeInsets.symmetric(horizontal: 8.0),
              child: Row(
                children: <Widget>[
                  Flexible(
                    child: TextField(
                      controller: _textController,
                      onSubmitted: handleSubmitted,
                      decoration: InputDecoration.collapsed(hintText: "Send a message"),
                    ),
                  ),
                  Container(
                    margin: EdgeInsets.symmetric(horizontal: 4.0),
                    child: IconButton(
                      icon: Icon(Icons.send),
                      onPressed: () => handleSubmitted(_textController.text),
                    ),
                  ),
                  IconButton(
                    iconSize: 30.0,
                    icon: Icon(_isRecording ? Icons.mic_off : Icons.mic),
                    onPressed: _isRecording ? stopStream : handleStream,
                  ),
                ],
              ),
            ),
          )
      ),
    ]);
  }
}


//------------------------------------------------------------------------------------
// The chat message balloon
//
//------------------------------------------------------------------------------------
class ChatMessage extends StatelessWidget {
  ChatMessage({this.text, this.name, this.type});

  final String text;
  final String name;
  final bool type;

  List<Widget> otherMessage(context) {
    return <Widget>[
      new Container(
        margin: const EdgeInsets.only(right: 16.0),
        child: CircleAvatar(child: new Text('B')),
      ),
      new Expanded(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Text(this.name,
                style: TextStyle(fontWeight: FontWeight.bold)),
            Container(
              margin: const EdgeInsets.only(top: 5.0),
              child: Text(text),
            ),
          ],
        ),
      ),
    ];
  }

  List<Widget> myMessage(context) {
    return <Widget>[
      Expanded(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.end,
          children: <Widget>[
            Text(this.name, style: Theme.of(context).textTheme.subtitle1),
            Container(
              margin: const EdgeInsets.only(top: 5.0),
              child: Text(text),
            ),
          ],
        ),
      ),
      Container(
        margin: const EdgeInsets.only(left: 16.0),
        child: CircleAvatar(
            child: Text(
              this.name[0],
              style: TextStyle(fontWeight: FontWeight.bold),
            )),
      ),
    ];
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.symmetric(vertical: 10.0),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: this.type ? myMessage(context) : otherMessage(context),
      ),
    );
  }
}

在 chat.dart 檔案中搜尋 Widget build。這會建構聊天機器人介面,其中包含:

  • ListView,內含使用者和聊天機器人的所有對話泡泡。這會使用 ChatMessage 類別,建立含有大頭貼和文字的即時通訊訊息。
  • 輸入文字查詢的 TextField
  • 具有傳送圖示的 IconButton,用於將文字查詢傳送至 Dialogflow
  • IconButton,內含麥克風,可將音訊串流傳送至 Dialogflow,按下後會變更狀態。

連結對話介面

  1. 開啟 main.dart 並變更 Widget build,以便只例項化 Chat() 介面。其他所有示範程式碼都可以移除。
import 'package:flutter/material.dart';
import 'chat.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.orange,
      ),
      home: MyHomePage(title: 'Flutter Dialogflow Agent'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Chat())
    );
  }
}
  1. 執行應用程式。(如果應用程式先前已啟動,停止虛擬裝置,然後重新執行 main.dart)。首次使用聊天介面執行應用程式時。系統會顯示權限彈出式視窗,詢問是否要允許使用麥克風。按一下「使用應用程式時」

權限

  1. 請試用文字區域和按鈕。輸入文字查詢並按下 Enter 鍵或輕觸傳送按鈕後,您會在 Android Studio 的「Run」分頁中看到記錄的文字查詢。輕觸麥克風按鈕並停止錄音後,您會在「執行」分頁中看到記錄的音訊串流。

音訊串流記錄

太棒了,現在可以將應用程式與 Dialogflow 整合!

將 Flutter 應用程式與 Dialogflow_gRPC 整合

  1. 開啟 chat.dart 並新增下列匯入項目:
import 'package:dialogflow_grpc/dialogflow_grpc.dart';
import 'package:dialogflow_grpc/generated/google/cloud/dialogflow/v2beta1/session.pb.dart';
  1. 在檔案頂端,緊接在 // TODO DialogflowGrpcV2Beta1 class instance 下方新增下列程式碼,以保留 Dialogflow 類別例項:
DialogflowGrpcV2Beta1 dialogflow;
  1. 搜尋 initPlugin() 方法,並在 TODO 註解下方新增下列程式碼:
    // Get a Service account
    final serviceAccount = ServiceAccount.fromString(
        '${(await rootBundle.loadString('assets/credentials.json'))}');
    // Create a DialogflowGrpc Instance
    dialogflow = DialogflowGrpcV2Beta1.viaServiceAccount(serviceAccount);

這會建立已透過服務帳戶授權給 Google Cloud 專案的 Dialogflow 執行個體。(請確認 assets 資料夾中有 credentials.json!)

再次提醒,為了示範如何使用 Dialogflow gRPC,將 credentials.json 檔案儲存在 assets 資料夾中是可以的,但對於正式版應用程式,您不應將該檔案儲存在 assets 資料夾中,因為這樣並不安全。

發出 detectIntent 呼叫

  1. 現在請找出 handleSubmitted() 方法,這就是神奇之處。在 TODO 註解下方新增下列程式碼。這段程式碼會將使用者輸入的訊息新增至 ListView:
ChatMessage message = ChatMessage(
 text: text,
 name: "You",
 type: true,
);

setState(() {
 _messages.insert(0, message);
});
  1. 現在,我們要在先前的程式碼正下方呼叫 detectIntent,並傳入輸入內容中的文字和 languageCode。- 結果 (位於 data.queryResult.fulfillment 內) 會列印在 ListView 中:
DetectIntentResponse data = await dialogflow.detectIntent(text, 'en-US');
String fulfillmentText = data.queryResult.fulfillmentText;
if(fulfillmentText.isNotEmpty) {
  ChatMessage botMessage = ChatMessage(
    text: fulfillmentText,
    name: "Bot",
    type: false,
  );

  setState(() {
    _messages.insert(0, botMessage);
  });
}
  1. 啟動虛擬裝置,並測試偵測意圖呼叫。類型:hi系統應會顯示預設歡迎訊息。如果輸入其他內容,系統會傳回預設備援。

撥打 streamingDetectIntent 電話

  1. 現在請找出 handleStream() 方法,這就是音訊串流的魔法所在。首先,在第一個 TODO 下方,建立具有 biasList 的音訊 InputConfigV2beta1,以偏向語音模型。由於我們使用的是手機 (虛擬裝置),因此 sampleHertz 為 16000,編碼為 Linear 16。這取決於您使用的電腦硬體 / 麥克風。就我的內部 MacBook 麥克風而言,16000 是不錯的選擇。(請參閱 https://pub.dev/packages/sound_stream 套件的資訊)
var biasList = SpeechContextV2Beta1(
    phrases: [
      'Dialogflow CX',
      'Dialogflow Essentials',
      'Action Builder',
      'HIPAA'
    ],
    boost: 20.0
);

    // See: https://cloud.google.com/dialogflow/es/docs/reference/rpc/google.cloud.dialogflow.v2#google.cloud.dialogflow.v2.InputAudioConfig
var config = InputConfigV2beta1(
    encoding: 'AUDIO_ENCODING_LINEAR_16',
    languageCode: 'en-US',
    sampleRateHertz: 16000,
    singleUtterance: false,
    speechContexts: [biasList]
);
  1. 接著,我們會對 dialogflow 物件呼叫 streamingDetectIntent 方法,該物件會保留 Dialogflow 工作階段:
final responseStream = dialogflow.streamingDetectIntent(config, _audioStream);
  1. 有了 responseStream,我們終於可以監聽傳入的轉錄稿、偵測到的使用者查詢,以及偵測到的相符意圖回應。我們會在螢幕上以 ChatMessage 列印這項資訊:
// Get the transcript and detectedIntent and show on screen
responseStream.listen((data) {
  //print('----');
  setState(() {
    //print(data);
    String transcript = data.recognitionResult.transcript;
    String queryText = data.queryResult.queryText;
    String fulfillmentText = data.queryResult.fulfillmentText;

    if(fulfillmentText.isNotEmpty) {

      ChatMessage message = new ChatMessage(
        text: queryText,
        name: "You",
        type: true,
      );

      ChatMessage botMessage = new ChatMessage(
        text: fulfillmentText,
        name: "Bot",
        type: false,
      );

      _messages.insert(0, message);
      _textController.clear();
      _messages.insert(0, botMessage);

    }
    if(transcript.isNotEmpty) {
      _textController.text = transcript;
    }

  });
},onError: (e){
  //print(e);
},onDone: () {
  //print('done');
});

這樣就完成了,請啟動應用程式,在虛擬裝置上測試,按下麥克風按鈕,然後說出「Hello」

這會觸發 Dialogflow 預設的歡迎意圖。螢幕上會顯示結果。Flutter 現在可與 Dialogflow 整合,因此我們可以開始處理聊天機器人對話。

5. Dialogflow:模擬 Dialogflow 代理程式

Dialogflow Essentials 是一套開發套件,可用來建構對話式 UI。因此,聊天機器人、語音機器人、電話閘道。大家可以使用相同的工具建構,甚至支援超過 20 種語言的多個管道。Dialogflow UX 設計師 (代理程式建模師、語言學家) 或開發人員會指定訓練詞組,藉此訓練基礎機器學習模型,進而建立意圖。

意圖會將使用者的意圖分類。您可以為每個 Dialogflow ES 代理程式定義多項意圖,好讓這些意圖能夠合併起來處理完整的對話。每個意圖都可以包含參數和回應。

比對意圖的作業也稱為「意圖分類」或「意圖比對」。這是 Dialogflow ES 的主要概念。意圖比對完成後,系統可以傳回回應、收集參數 (實體擷取) 或觸發 Webhook 程式碼 (執行要求),例如從資料庫擷取資料。

當使用者在聊天機器人中輸入或說出某項內容時 (稱為「使用者表達內容」或「語音」),Dialogflow ES 會根據訓練詞組,將表達內容與 Dialogflow 代理程式中最相符的意圖進行比對。Dialogflow ES 機器學習模型會根據這些訓練詞組進行訓練。

Dialogflow ES 採用「情境」的概念。就像人類一樣,Dialogflow ES 可以在第 2 和第 3 輪對話中記住情境。這樣一來,系統就能追蹤先前的使用者語音輸入內容。

進一步瞭解 Dialogflow 意圖

修改預設的歡迎意圖

建立新的 Dialogflow 代理程式時,系統會自動建立兩個預設意圖。預設歡迎意圖是您與代理程式展開對話時,首先會遇到的流程。如果代理程式無法理解您說的內容,或無法比對您剛才說的內容與意圖,就會進入「Default Fallback Intent」(預設備用意圖) 流程。

以下是預設歡迎意圖的歡迎訊息:

使用者

Agent

您好

「你好,我是 Dialogflow 常見問題機器人,可以回答有關 Dialogflow 的問題。」

「你想知道什麼?」

  1. 依序點選「Intents」>「Default Welcome Intent」
  2. 向下捲動至「Responses」
  3. 清除所有文字回覆。
  4. 在預設分頁中,建立下列 2 個回應:
  • 你好,我是 Dialogflow 常見問題機器人,可以回答 Dialogflow 相關問題。你想瞭解什麼?
  • 你好,我是 Dialogflow 常見問題機器人,請問你對 Dialogflow 有任何疑問嗎?我該如何協助您?

設定應與這個螢幕截圖類似。

編輯預設的歡迎意圖

  1. 按一下「儲存」
  2. 讓我們測試這個意圖。首先,我們可以在 Dialogflow 模擬工具中測試。輸入「Hello」,系統應會傳回下列其中一則訊息:
  • 你好,我是 Dialogflow 常見問題機器人,可以回答 Dialogflow 相關問題。你想瞭解什麼?
  • 你好,我是 Dialogflow 常見問題機器人,請問你對 Dialogflow 有任何疑問嗎?我該如何協助您?

修改預設備用意圖

  1. 依序點選「意圖」>「預設的備用意圖」
  2. 向下捲動至「Responses」
  3. 清除所有文字回覆。
  4. 在預設分頁中,建立下列回應:
  • 很抱歉,我不知道這個問題的答案。你是否已查看我們的網站?http://www.dialogflow.com?
  1. 按一下「儲存」

連線至線上知識庫

知識連接器可與已定義的意圖彼此互補。知識連接器會剖析知識文件,找出自動化回應。(例如常見問題或文章,來源可以是 CSV 檔案、線上網站,甚至是 PDF 檔案!)如要設定知識連接器,您必須定義一或多個知識庫 (即知識文件集合)。

進一步瞭解知識連接器。

我們來試試看。

  1. 在選單中選取「知識」 (Beta 版)。

知識庫

  1. 按一下右側的藍色按鈕「建立知識庫」
  2. 輸入知識庫名稱「Dialogflow 常見問題」,然後按一下「儲存」
  3. 按一下「建立第一個」連結

知識庫第一項

  1. 系統會開啟視窗。

請使用下列設定:

文件名稱:DialogflowFAQ 知識類型:FAQ MIME 類型:text/html

  1. 我們載入資料的網址為:

https://www.leeboonstra.dev/faqs/

  1. 按一下「Create」(建立)

知識庫已建立:

已建立知識庫

  1. 向下捲動至「Responses」部分,然後按一下「Add Response」

建立下列答案,然後按一下「儲存」

$Knowledge.Answer[1]
  1. 按一下「查看詳細資料」

查看詳細資料

  1. 選取「啟用自動重新載入」,在常見問題網頁更新時自動擷取變更,然後點選「儲存」

系統會顯示您在 Dialogflow 中導入的所有常見問題。就是這麼簡單!

請注意,您也可以指向含有常見問題的線上 HTML 網站,將常見問題匯入代理程式。您甚至可以上傳含有文字區塊的 PDF,Dialogflow 就會自行生成問題。

現在,常見問題應視為代理程式的「額外」內容,與意圖流程並列。知識庫常見問題無法訓練模型。因此,如果以完全不同的方式提問,系統可能無法找到相符結果,因為這類問題不會使用自然語言理解 (機器學習模型)。因此有時將常見問題轉換為意圖是值得的。

  1. 在右側的模擬工具中測試問題。
  2. 一切正常後,請返回 Flutter 應用程式,並使用這項新內容測試聊天和語音機器人!詢問您載入 Dialogflow 的問題。

結果

6. 恭喜

恭喜,您已成功建立第一個整合 Dialogflow 聊天機器人的 Flutter 應用程式!

涵蓋內容

  • 如何使用 Dialogflow Essentials 建立聊天機器人
  • 如何在 Flutter 應用程式中整合 Dialogflow
  • 如何使用 Dialogflow 偵測文字意圖
  • 如何透過麥克風將語音串流傳送至 Dialogflow
  • 如何使用知識庫連接器

後續步驟

喜歡這個程式碼研究室嗎?快來看看這些超棒的 Dialogflow 實驗室!

想瞭解我是如何為 Dart/Flutter 建構 Dialogflow gRPC 套件嗎?