使用 Dialogflow 基本功能构建 Android 语音聊天机器人Flutter

1. 准备工作

在此 Codelab 中,您将学习如何将简单的 Dialogflow Essentials (ES) 文本和语音聊天机器人集成到 Flutter 应用中。Dialogflow ES 是一套用于构建对话式界面的开发套件。因此,聊天机器人、语音机器人、电话网关。您可以使用同一工具构建应用,甚至可以支持 20 多种不同语言的多个渠道。Dialogflow 可以与 Google 助理、Slack 和 Facebook Messenger 等许多热门对话平台集成。如果您想要为上述某个平台构建代理,建议从众多集成方案中择一使用。不过,如需为移动设备创建聊天机器人,您必须创建自定义集成。您将通过指定训练短语来训练底层机器学习模型,从而创建 intent。

本实验的顺序旨在再现常见的云开发者体验:

  1. 环境设置
  • Dialogflow:创建新的 Dialogflow ES 代理
  • Dialogflow:配置 Dialogflow
  • Google Cloud:创建服务账号
  1. Flutter:构建聊天应用
  • 创建一个 Flutter 项目
  • 配置设置和权限
  • 添加依赖项
  • 与服务账号相关联。
  • 在虚拟设备或实体设备上运行应用
  1. Flutter:构建支持语音转文字的聊天界面
  • 创建聊天界面
  • 关联聊天界面
  • 将 Dialogflow gRPC 软件包集成到应用中
  1. Dialogflow:对 Dialogflow 代理进行建模
  • 配置欢迎 intent 和后备 intent
  • 使用常见问题解答知识库

前提条件

  • 具备基本的 Dart/Flutter 经验
  • 具备 Google Cloud Platform 基础知识
  • 具备 Dialogflow ES 的基本使用经验

构建内容

此 Codelab 将向您展示如何构建一个移动版常见问题解答机器人,该机器人可以回答有关 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. 选择 English - en 作为默认语言。
  5. 选择离您最近的时区作为默认时区。
  6. 选择“超级代理”。(借助此功能,您可以创建可协调“子”代理的总体代理。我们现在不需要此功能。)
  7. 点击创建

“创建新项目”界面

配置 Dialogflow

  1. 在左侧菜单中,点击项目名称旁边的齿轮图标。

创建新项目下拉菜单

  1. 输入以下代理说明:Dialogflow 常见问题解答聊天机器人
  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 的位置(如果文本字段为空,请选择“Install SDK…”)。
  4. 输入项目名称(例如 flutter_dialogflow_agent),然后点击下一步
  5. 修改软件包名称,然后点击 Finish

创建新的 Flutter 应用

这会创建一个包含 Material Components 的示例应用。

等待 Android Studio 安装相应 SDK 并创建项目。

设置和权限

  1. 我们将使用的音频录制器库 sound_stream 要求 minSdk 至少为 21。因此,我们可以在 android/app/build.gradle 的 defaultConfig 代码块中更改此设置。(请注意,android 文件夹中有 2 个 build.gradle 文件,但 app 文件夹中的那个才是正确的。)
defaultConfig {
   applicationId "com.myname.flutter_dialogflow_agent"
   minSdkVersion 21
   targetSdkVersion 30
   versionCode flutterVersionCode.toInteger()
   versionName flutterVersionName
}
  1. 为了向麦克风授予权限,并让应用能够与在云端运行的 Dialogflow 代理进行通信,我们必须向 app/src/main/AndroidManifest.xml 文件添加 INTERNET 和 RECORD_AUDIO 权限。您的 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 管理器。(或者从顶部工具栏中选择 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 标题更改为:Flutter Dialogflow Agent。并将 primarySwatch 更改为 Colors.orange。

第一个代码

保存文件,或点击 Android Studio 工具栏中的闪电图标。您应该会在虚拟设备中直接看到所做的更改。

4. Flutter:构建支持 STT 的聊天界面

创建聊天界面

  1. lib 文件夹中创建一个新的 Flutter widget 文件。(右键点击 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。此 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 实例。(请确保 credentials.json 位于 assets 文件夹中!)

同样,为了演示如何使用 Dialogflow gRPC,这样做是可以的,但对于正式版应用,您不应将 credentials.json 文件存储在 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. 启动虚拟设备,并测试检测 intent 调用。类型: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 对象(其中包含我们的 Dialogflow 会话)调用 streamingDetectIntent 方法:
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');
});

这样就完成了,启动您的应用,在虚拟设备上对其进行测试,按下麦克风按钮,然后说:“你好”

这会触发 Dialogflow 默认欢迎意图。结果将显示在屏幕上。现在,Flutter 可以很好地与 Dialogflow 集成,我们可以开始处理聊天机器人对话了。

5. Dialogflow:对 Dialogflow 代理进行建模

Dialogflow Essentials 是一款用于构建对话式界面的开发套件。因此,聊天机器人、语音机器人、电话网关。您可以使用同一工具构建应用,甚至可以支持 20 多种不同语言的多个渠道。Dialogflow 用户体验设计师(代理建模师、语言学家)或开发者通过指定训练短语来训练底层机器学习模型,从而创建意图。

意图用于对用户的意向进行分类。您可以为每个 Dialogflow ES 代理定义多个意图,组合意图可以处理一段完整的对话。每个意图都可以包含参数和回答。

匹配意图也称为意图分类或意图匹配。这是 Dialogflow ES 中的主要概念。匹配到意图后,系统可以返回响应、收集参数(实体提取)或触发 webhook 代码(fulfillment),例如从数据库中提取数据。

当最终用户在聊天机器人中输入文字或说出话语(称为“用户表述”或“话语”)时,Dialogflow ES 会根据训练短语将该表述与 Dialogflow 代理的最佳意图进行匹配。Dialogflow ES 机器学习模型在后台使用这些训练短语进行训练。

Dialogflow ES 采用一种称为“上下文”的概念。与人类一样,Dialogflow ES 可以在第二轮和第三轮对话中记住上下文。这样一来,它就可以跟踪之前的用户话语。

点击此处可详细了解 Dialogflow 意图

修改默认欢迎意图

创建新的 Dialogflow 代理时,系统会自动创建两个默认意图。默认欢迎意图是您与代理开始对话时首先进入的流程。默认后备意图是当代理无法理解您或无法将您刚才所说的话与意图匹配时,您将获得的流程。

以下是“默认欢迎意图”的欢迎辞:

用户

Agent

您好!

“您好,我是 Dialogflow 常见问题解答机器人,可以回答有关 Dialogflow 的问题。”

“您想了解什么?”

  1. 依次点击意图 > 默认欢迎意图
  2. 向下滚动到回答
  3. 清除所有文字回答。
  4. 在默认标签页中,创建以下 2 个回答:
  • 您好,我是 Dialogflow 常见问题解答机器人,我可以回答有关 Dialogflow 的问题。您想了解哪些方面的信息?
  • 您好,我是 Dialogflow 常见问题解答机器人,您对 Dialogflow 有疑问吗?您需要什么帮助?

配置应与此屏幕截图类似。

修改默认欢迎意图

  1. 点击保存
  2. 我们来测试一下此意图。首先,我们可以在 Dialogflow 模拟器中进行测试。输入:Hello。它应该会返回以下某条消息:
  • 您好,我是 Dialogflow 常见问题解答机器人,我可以回答有关 Dialogflow 的问题。您想了解哪些方面的信息?
  • 您好,我是 Dialogflow 常见问题解答机器人,您对 Dialogflow 有疑问吗?您需要什么帮助?

修改默认后备意图

  1. 依次点击意图 > 默认后备意图
  2. 向下滚动到回答
  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. 点击创建

知识库已创建:

知识库已创建

  1. 向下滚动到“响应”部分,然后点击添加响应

创建以下答案,然后点击保存

$Knowledge.Answer[1]
  1. 点击查看详情

查看详细信息

  1. 选择启用自动重新加载,以便在常见问题解答网页更新时自动获取更改,然后点击保存

这样一来,您在 Dialogflow 中实现的所有常见问题解答都会显示出来。是不是很简单?

请注意,您还可以指向包含常见问题的在线 HTML 网站,以便将常见问题导入到代理中。您甚至可以上传包含一段文字的 PDF,然后让 Dialogflow 自己生成问题。

现在,常见问题解答应被视为可添加到代理中的“额外”内容,与意图流程并列。知识库常见问题解答无法训练模型。因此,以完全不同的方式提问可能无法获得匹配结果,因为系统没有使用自然语言理解(机器学习模型)。因此,有时将常见问题解答转换为意图是值得的。

  1. 在右侧的模拟器中测试问题。
  2. 一切正常后,返回到 Flutter 应用,并使用此新内容测试聊天机器人和语音机器人!提出您已加载到 Dialogflow 中的问题。

结果

6. 恭喜

恭喜,您已成功创建第一个集成了 Dialogflow 聊天机器人的 Flutter 应用,做得好!

所学内容

  • 如何使用 Dialogflow Essentials 创建聊天机器人
  • 如何将 Dialogflow 集成到 Flutter 应用中
  • 如何使用 Dialogflow 检测文本意图
  • 如何通过麦克风将语音流式传输到 Dialogflow
  • 如何使用知识库连接器

接下来怎么做?

喜欢这个 Codelab 吗?快来看看这些出色的 Dialogflow 实验!

想知道我是如何为 Dart/Flutter 构建 Dialogflow gRPC 软件包的吗?