トレタ開発者ブログ

飲食店向け予約/顧客台帳サービス「トレタ」、モバイルオーダー「トレタO/X」などを運営するトレタの開発メンバーによるブログです。

GitHub ActionsとtblsでDBスキーマ変更に対応するER図の自動生成する

はじめに

こんにちは、サーバーサイドエンジニアの @shiroemons です。

プルリクエストにDBスキーマの変更が含まれた場合、ER図を自動生成するために、tblsとGitHub Actionsを組み合わせた設定を行いました。

DBスキーマの変更は開発プロセスにおいて頻繁に発生しますが、手動でER図やドキュメントを更新することは煩雑で効率が悪い作業です。 そこで、GitHub Actionsとtblsを使用することで、ER図の自動生成と更新を容易に実現できました。

今回は、設定したGitHub Actionsの設定ファイルを紹介します。

必要なツールと環境

今回紹介するツールと環境はこちらです。

  • CI: GitHub Actions
  • DB: PostgreSQL
  • マイグレーションツール: psqldef
  • テーブル定義書作成: tbls

tbls について

github.com

tblsは、データベースのスキーマ情報をドキュメント化するためのコマンドラインツールです。データベースからスキーマ情報を取得し、MarkdownやPlantUMLなどの形式で出力することができます。

このツールを使用することで、データベースのスキーマ情報をわかりやすく、自動化された形でドキュメント化することができます。tblsは、PostgreSQL, MySQL, SQLite, Microsoft SQL Server など、複数のデータベース管理システムに対応しています。

ディレクトリ構成と設定ファイルの説明

ディレクトリ構成

  • .github/workflows/tbls.yml
    • 今回解説するGitHubActionsのファイル
  • db/schema/*.sql
    • スキーマファイル群
  • db/dbdoc/
    • tblsのER図自動生成先
  • db/tbls.yml
    • tblsの設定ファイル
    • 設定ファイルの中身は後述

設定内容

.github/workflows/tbls.yml

こちらが今回作成したGitHub Actionsの内容です。

name: tbls
on:
  pull_request:
    types:
      - opened
      - synchronize
      - reopened
    paths:
      - db/schema/*.sql
      - db/tbls.yml
    branches-ignore:
      - production
      - staging

jobs:
  tbls:
    name: generate-and-push
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    services:
      postgres:
        image: postgres:15-alpine
        ports:
          - 5432:5432
        env:
          POSTGRES_HOST_AUTH_METHOD: trust
          POSTGRES_DB: sampledbname
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    env:
      TBLS_DSN: "postgres://postgres:@127.0.0.1:5432/sampledbname?sslmode=disable"
      TBLS_DOC_PATH: "db/dbdoc"
    steps:
      - uses: actions/checkout@v3
        with:
          repository: ${{ github.event.pull_request.head.repo.full_name }}
          ref: ${{ github.event.pull_request.head.ref }}
      - uses: actions/setup-go@v3
        with:
          go-version: ^1.20
      - name: Execute migration
        run: |
          go install github.com/k0kubun/sqldef/cmd/psqldef@latest
          psqldef --file schema.sql sampledbname
        working-directory: ./db/schema
      - name: Remove dbdocs
        run: |
          rm -rf ./*.md ./*.svg
        working-directory: ./db/dbdoc
      - uses: k1low/setup-tbls@v1
      - name: Run tbls for generate database document
        run: |
          tbls doc -c ./db/tbls.yml -f
      - name: Count uncommit files
        id: check_diff
        run: |
          git status --porcelain | wc -l
          file_count=$(git status --porcelain | wc -l)
          echo "file_count=$file_count" >> $GITHUB_OUTPUT
        working-directory: ./db
      - name: Commit ER graph
        if: ${{ steps.check_diff.outputs.file_count != '0' }}
        uses: EndBug/add-and-commit@v9
        with:
          default_author: github_actions
          message: "CI: ER diagram and markdown update triggered by actions"
      - name: Report commit on pull request
        if: ${{ steps.check_diff.outputs.file_count != '0' }}
        uses: actions/github-script@v6
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: 'CI: ER diagrams and markdown auto-committed by Actions 🤖'
            })

docs/tbls.yml

tblsのER図の自動生成ではデフォルトでコメントを出力しない設定となっています。

コメントも出力されるように設定ファイルに記載しています。

er:
  # Add table/column comment to ER diagram
  # Default is false
  comment: true

GitHub Actionsワークフローの解説

ワークフローのトリガーを設定する

プルリクエストが開かれた、同期された、再開された場合に、このGitHub Actionsがトリガーされます。また、db/schema/*.sql および db/tbls.yml の内容が変更されたときにのみ実行されます。

マージ先がstaging、productionブランチの場合、発火しないようにしています。

on:
  pull_request:
    types:
      - opened
      - synchronize
      - reopened
    paths:
      - db/schema/*.sql
      - db/tbls.yml
    branches-ignore:
      - production
      - staging

パーミッションを設定する

  • contents: write: リポジトリのコンテンツに対して書き込み権限を持ちます。これにより、github-actions[bot] がコミットおよびプッシュすることができます。
  • pull-requests: write: プルリクエストに対して書き込み権限を持ちます。これにより、プルリクエストにコメントを追加することができます。
    permissions:
      contents: write
      pull-requests: write

データベースを事前に起動する

今回は、以下のように宣言し、PostgreSQLを起動しておきます。

    services:
      postgres:
        image: postgres:15-alpine
        ports:
          - 5432:5432
        env:
          POSTGRES_HOST_AUTH_METHOD: trust
          POSTGRES_DB: sampledbname
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

環境変数を設定する

プロセスに必要な環境変数が設定されています。

データベース接続用のDSNやドキュメントのパスを指定しています。

    env:
      TBLS_DSN: "postgres://postgres:@127.0.0.1:5432/sampledbname?sslmode=disable"
      TBLS_DOC_PATH: "db/dbdoc"

コードをクローンしてGitHubの設定をする

      - uses: actions/checkout@v3
        with:
          repository: ${{ github.event.pull_request.head.repo.full_name }}
          ref: ${{ github.event.pull_request.head.ref }}

マイグレーションを実行する

  • Go言語をセットアップします。
    • マイグレーションツールとして、psqldefを使用しています。
    • psqldefはGo言語で作成されているため、Go言語をセットアップしておきます。
      - uses: actions/setup-go@v3
        with:
          go-version: ^1.20
  • マイグレーションを実行します。
    • psqldef を go install でインストールします。
    • その後にマイグレーションを実行します。
      - name: Execute migration
        run: |
          go install github.com/k0kubun/sqldef/cmd/psqldef@latest
          psqldef --file schema.sql sampledbname
        working-directory: ./db/schema

ER図関連のファイルを削除

スキーマの変更でテーブルの削除が含まれる場合は、ファイルを削除していないとそのまま残り続けることとなります。そのため、ER図を自動生成する前にファイルを削除しておきます。

      - name: Remove dbdocs
        run: |
          rm -rf ./*.md ./*.svg
        working-directory: ./db/dbdoc

tblsには、--rm-distというオプションが用意してあります。 今回、tblsには生成の責務に集中してもらうためオプションは使用せずに明示的に削除するようにしました。

tblsのセットアップと実行

  • tblsをセットアップする
    • tblsには、GitHub Actionsで簡単に使用できるためのActionが用意されています。
    • この一文を記載することで tbls を使用できるようになります。
      - uses: k1low/setup-tbls@v1

参照: k1low.hatenablog.com

  • tblsの実行し、ER図を生成する
    • tblsを実行する際、ER図にコメントを含めるためにtblsの設定ファイルを指定します。
      - name: Run tbls for generate database document
        run: |
          tbls doc -c ./db/tbls.yml -f

ER図に差分がある場合、プルリクエストに対してその差分をコミットする

  • 差分ファイル数を取得する
      - name: Count uncommit files
        id: check_diff
        run: |
          git status --porcelain | wc -l
          file_count=$(git status --porcelain | wc -l)
          echo "file_count=$file_count" >> $GITHUB_OUTPUT
        working-directory: ./db
  • 差分ファイルをコミットする
    • 差分ファイルがある場合のみコミットされるように条件を設定しています。
    • GitHub Actionで簡単にAdd と Commit ができる EndBug/add-and-commitを使用しました。 github.com
      - name: Commit ER graph
        if: ${{ steps.check_diff.outputs.file_count != '0' }}
        uses: EndBug/add-and-commit@v9
        with:
          default_author: github_actions
          message: "CI: ER diagram and markdown update triggered by actions"

プルリクエストにER図を更新した旨をコメントする

こちらも同様に、差分ファイルがある場合のみプルリクエストにコメントするように条件を設定しています。

      - name: Report commit on pull request
        if: ${{ steps.check_diff.outputs.file_count != '0' }}
        uses: actions/github-script@v6
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: 'CI: ER diagrams and markdown auto-committed by Actions 🤖'
            })

まとめ

ER図を自動生成してくれるGitHub Actionsの設定ファイルを紹介しました。

今回は、紹介した設定はGoプロジェクトですが、マイグレーションの設定部分を変更することで Ruby on Railsのプロジェクトでも応用が効きます。

GitHub Actionsとtblsの組み合わせによる自動化は、開発効率向上、品質保持、そして情報共有の容易化に貢献します。

また、柔軟なカスタマイズ性を持つため、チームのニーズに応じて機能拡張や改善が容易に実現可能です。

参考記事

大変参考になりました。ありがとうございます。

tblsとGitHub Actionsを使ってDBマイグレーションを含むPRには自動更新したER図を追加する - BASEプロダクトチームブログ

Cloud Buildの結果をSlackに通知する

この記事はトレタ Advent Calendar 2022の22日目の記事です。

はじめに

こんにちは、3年連続で22日に記事を書くサーバーサイドエンジニアの @shiroemons です。

前回は、Cloud Buildでpsqldefを使用してCloud SQLにマイグレーションする方法を紹介しました。

tech.toreta.in

Cloud Buildは便利ですが、結果を確認しに行かないといけないのが少々手間です。

そこで今回は、Cloud Buildの結果をSlackに通知する方法を紹介したいと思います。

今回のゴール

  • 成功の場合の通知画面

    • 成功ステータス(SUCCESS)と ✅ が表示されている
  • 失敗の場合の通知画面

    • 成功以外のステータス(FAILUREなど)と ❌ が表示されている

Slackアプリ

アプリを新規作成

専用のSlackがあるわけではないので、以下より新規作成してください。

api.slack.com

アプリ名とアイコンは各自で設定してください。

ここでは、アプリ名を「Cloud Build 結果通知」としています。

アイコンは、アーキテクチャ図用のアイコンのライブラリ - Google Cloud PlatformのSVGとPNGのアイコンのものを設定しています。

Webhook URLを生成

  1. 作成したSlackアプリのIncoming Webhooksにアクセスする。
  2. [Add New Webhook to Workspace]ボタンをクリックする。
  3. 通知するチャンネルを選択する。
  4. [許可する]ボタンをクリックする。
  5. Webhook URLが生成される。

Google Cloud 側の作業

cloud.google.com

上記の資料を元に作業を行います。

必要なAPIを有効化

始める前に の[API を有効にする]ボタンから有効にできます。

Secret Manager

  • Webhook URLを保存します。
    • ここでは、シークレット名を cloudbuild_slack_notifier_webhook_url で保存しました。

IAM

  • <プロジェクト番号>-compute@developer.gserviceaccount.com に対して、
    • ロール Secret Manager のシークレット アクセサー を付与します。
    • ロール Storage オブジェクト閲覧者 を付与します。

Google Cloud側の作業は以上です。

ローカル環境での作業

macOSを対象として記載しております。

事前準備

  1. Google Cloud SDKをインストールする。

     brew install --cask google-cloud-sdk
     # Homebrew のバージョンが 2.6 以前の場合
     # brew cask install google-cloud-sdk
    
  2. アカウント連携する。

     gcloud auth login
    
  3. gcloud configの設定する。
    • 対象プロジェクトに設定されているか確認します。
     gcloud config list
    
  4. プロジェクトを設定する。
    • 対象プロジェクトを設定します。
     gcloud config set project <プロジェクトID>
    
  5. デフォルトのリージョンを設定する。
    • リージョンをasia-northeast1(東京)に設定します。
     gcloud config set run/region asia-northeast1
    

Slack Notifier の clone

公式がGitHubで公開している GoogleCloudPlatform/cloud-build-notifiers をcloneします。

git clone git@github.com:GoogleCloudPlatform/cloud-build-notifiers.git

clone したディレクトリへ移動します。

cd cloud-build-notifiers

config ファイル(slack.yaml)の作成

  • exampleファイルからコピーして作成します。

      cp ./slack/slack.yaml.example ./slack/slack.yaml
    
  • configファイル(slack.yaml)をカスタマイズします。

    • カスタマイズの差分は、こちらです。
# Copyright 2020 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.

apiVersion: cloud-build-notifiers/v1
kind: SlackNotifier
metadata:
-  name: example-slack-notifier
+  name: cloudbuild-slack-notifier
spec:
  notification:
-    filter: build.status == Build.Status.SUCCESS
+    filter: build.status in [Build.Status.SUCCESS, Build.Status.FAILURE, Build.Status.INTERNAL_ERROR, Build.Status.TIMEOUT]
    params:
      buildStatus: $(build.status)
+      buildTriggerName: $(build.substitutions['TRIGGER_NAME'])
+      buildRepository: $(build.substitutions['REPO_NAME'])
+      buildBranch: $(build.substitutions['BRANCH_NAME'])
+      buildCommit: $(build.substitutions['SHORT_SHA'])
    delivery:
      webhookUrl:
        secretRef: webhook-url
    template:
      type: golang
-      uri: gs://example-gcs-bucket/slack.json
+      uri: gs://<プロジェクトID>-notifiers-config/slack.json

  secrets:
  - name: webhook-url
-    value: projects/example-project/secrets/example-slack-notifier-webhook-url/versions/latest
+    value: projects/<プロジェクトID>/secrets/<シークレット名>/versions/<バージョン>
  • カスタマイズの内容は以下の通りです。
    • metadata.name
      • example から cloudbuild に変更します。
    • spec.notification.filter
      • デフォルトのままだと成功しか通知しないため、失敗やタイムアウトも通知するように変更します。
    • spec.notification.params
      • Slack通知時に情報が足りないのため以下の情報を追加します。
        • トリガー名、リポジトリ名、ブランチ名、短いコミットハッシュ
    • spec.notification.template.uri
      • プロジェクトごとに変更が必要
      • Slack Notifierのデプロイ時に自動で配置されるパスに変更します。
      • ※※※注意事項: ここの設定はSlack Notifier の デプロイ後に使用する値です。デプロイ時には使用されないことに注意※※※
    • secrets.value
      • プロジェクトごとに変更が必要
      • WebhookURLを設定したSecret Managerのパスを設定します。
      • バージョンは、基本的に latest のままで良いと思います。

Slackテンプレートファイル(slack.json) をカスタマイズ

  • デフォルトのSlackテンプレートのままだと味気なく情報量も少ないのでカスタマイズします。
  • 以下のslack.jsonの内容を ./slack/slack.json に上書きする。
[
  {
    "type": "section",
    "text": {
      "type": "mrkdwn",
      "text": "Cloud Build build state *{{.Params.buildStatus}}*. {{ if eq .Params.buildStatus `SUCCESS` }}✅{{ else }}❌{{ end }}"
    }
  },
  {
    "type": "section",
    "text": {
      "type": "mrkdwn",
      "text": "*Trigger:* {{.Params.buildTriggerName}}"
    }
  },
  {
    "type": "section",
    "fields": [
      {
        "type": "mrkdwn",
        "text": "*ProjectId:*\n{{.Build.ProjectId}}"
      },
      {
        "type": "mrkdwn",
        "text": "*Repository:*\n{{.Params.buildRepository}}"
      },
      {
        "type": "mrkdwn",
        "text": "*Branch:*\n{{.Params.buildBranch}}"
      },
      {
        "type": "mrkdwn",
        "text": "*Commit:*\n{{.Params.buildCommit}}"
      }
    ]
  },
  {
    "type": "divider"
  },
  {
    "type": "actions",
    "elements": [
      {
        "type": "button",
        "text": {
          "type": "plain_text",
          "text": "View Build Logs"
        },
        "value": "click_me_123",
        "url": "{{.Build.LogUrl}}"
      }
    ]
  }
]

Setup シェル(setup.sh)の修正

  • Cloud Storageのロケーションを設定する。
    • デフォルトのままだと、マルチリージョンのusが設定されます。
    • upload_config 関数を修正し、デュアルリージョンのasia1に設定しています。
upload_config() {
  # We allow this `mb` command to error since we rely on the `cp` command hard-
  # erroring if there's an actual problem (since `mb` fails if the bucket
  # already exists).
-  gsutil mb "${DESTINATION_BUCKET_URI}"
+  gsutil mb -l asia1 "${DESTINATION_BUCKET_URI}"

  gsutil cp "${SOURCE_CONFIG_PATH}" "${DESTINATION_CONFIG_PATH}" ||
    fail "failed to copy config to GCS"

  if [ ! -z "${SOURCE_TEMPLATE_PATH}" ]; then
    gsutil cp "${SOURCE_TEMPLATE_PATH}" "${DESTINATION_TEMPLATE_PATH}" ||
      fail "failed to copy template to GCS"
  fi

}
  • スケールできる最大数を設定する。
    • deploy_notifier 関数を修正し、最大1台までに設定しています。
deploy_notifier() {
  gcloud run deploy "${SERVICE_NAME}" \
    --image="${IMAGE_PATH}" \
    --no-allow-unauthenticated \
+    --max-instances=1 \
    --update-env-vars="CONFIG_PATH=${DESTINATION_CONFIG_PATH},PROJECT_ID=${PROJECT_ID}" ||
    fail "failed to deploy notifier service -- check service logs for configuration error"
}
  • サブスクリプションの有効期限を無期限に設定する。
create_pubsub_subscription() {
  gcloud pubsub subscriptions create "${SUBSCRIPTION_NAME}" \
    --topic=cloud-builds \
    --push-endpoint="${SERVICE_URL}" \
-    --push-auth-service-account="${INVOKER_SA}"
+    --push-auth-service-account="${INVOKER_SA}" \
+    --expiration-period="never"
}

Slack Notifier の デプロイ

すべての設定が完了したのでsetupシェルを実行します。

./setup.sh slack ./slack/slack.yaml -t ./slack/slack.json

以下のように最後に出力されれば成功です。

+ echo '** NOTIFIER SETUP COMPLETE **'
** NOTIFIER SETUP COMPLETE **
  • デプロイで実行される内容
    1. Cloud Storageに「<project id>-notifiers-config」を作成
    2. slack.yamlとslack.jsonを作成したCloud Storageにアップロード
    3. Cloud Runに「slack-notifier」をデプロイ
    4. 必要なIAMの設定
    5. サービスアカウント「Cloud Run Pub/Sub Invoker」を作成
    6. 必要なIAMの設定
    7. Cloud Pub/Subトピックに「cloud-builds」を作成
    8. Cloud Pub/Subサブスクリプションに「slack-subscription」を作成

失敗した場合は、ログやCloud Runのログを確認してください。

2回目以降の実行の場合は、作成済みのものは ERROR と表示されますがとくに問題ありません。

動作確認

Slack Notifierのデプロイ成功後は、実際にCloud Buildを動かし、動作確認を行いましょう。

以下のような通知がSlackに届いていれば成功/完成です。

苦労したところ

Google Cloudに公式ガイドがあるのは嬉しいですね。Slack通知ようのアプリもあるのもありがたいです。

ただ既存のままだと情報量が少なくそっけなかったので、slack.jsonをカスタマイズして必要そうな情報も表示することができました。

カスタマイズを行うにあたり参照に記載したリンクからCloud BuildやSlackについて調べながら進めました。

苦労したところは、成功や失敗のステータスを絵文字(✅や❌)で表現するところです。

"Cloud Build build state *{{.Params.buildStatus}}*. {{ if eq .Params.buildStatus `SUCCESS` }}✅{{ else }}❌{{ end }}"

Go Templateの記載で解決することができました。

さいごに

今回は、Cloud Buildの結果をSlackに通知するための設定方法を紹介しました。

トレタでは、Cloud Buildを使用しているプロジェクトに今回の設定方法を共有し、 Cloud Buildの結果がSlack通知で届くように設定しています。

Cloud Buildの結果をSlack通知したいと悩んでいる方の助けになれば幸いです。

なお、トレタではエンジニアの募集を全方位で行なっております。

コロナ禍を乗り越えた飲食店の新しい姿を探求する仲間をお待ちしております。

corp.toreta.in

参考

過去の記事

tech.toreta.in

トレタ予約番を支える技術

こんにちは、トレタ予約番事業で開発リーダーをしている北川です。

トレタ2022年アドベントカレンダー19日目の記事として、トレタ予約番サービスについて今まであまり公表していなかった裏側の仕組みについて紹介したいと思います。

トレタ予約番とは

まずトレタ予約番というサービスの紹介です。

トレタ予約番は、飲食店の電話に自動応答ロボットが24時間応答し、予約の受付/変更/キャンセルを対応してくれるサービスです。

飲食店にとっては今まで営業時間外にかかってきて取りこぼしていた予約電話を受けられるようになったり、多忙な営業時間の電話応答はトレタ予約番に任せつつ、スタッフ対応が必要な電話だけトレタ予約番からスタッフに繋ぐことで、業務負荷の軽減に役立てていただいています。

自動応答の電話というと宅配便の受取時間変更の電話のようなIVRをイメージされるかと思いますが、大きな違いとしてはユーザーはプッシュボタンでの操作ではなく、音声で操作を行う点です。

それを実現するために、音声認識や音声合成などの技術要素を色々と利用しているので、その仕組みについて紹介していきます。

toreta.in

具体的なトレタ予約番に電話したときの様子はこちらのサービスサイトに動画があるので、是非見て聞いてみてください。

会話の仕組み

電話サービス

まず、電話のサービスにはAWSのAmazon Connectを利用しています。

Amazon Connectはコールセンター向けの電話サービスです。

電話を受電した際にどのコールスタッフに繋ぐかなど応答時のフローを柔軟に設定することができ、AWS Lambdaを使うことでプログラマブルな制御を入れることができるのも特徴です。

Amazon ConnectにはCCPというコントロールパネルのSDKや、CCPと連携するためのAmazon Connect Streamsというライブラリが提供されています。これらを使い、電話の応答・切断・転送などをプログラムで制御します。

aws.amazon.com

音声認識

通話中の音声データから、音声認識を行いテキストデータに変換します。

音声認識にはGCPのSpeech-to-Textを利用しています。Speech-to-TextのAPIに音声データを入力してほぼリアルタイムに音声認識結果のテキストデータを取得することができます。

もちろん日本語に対応していますし、GCP以外の音声認識エンジンをいくつか精度検証を行いましたが現時点ではGCPが頭ひとつ抜けて精度がよい印象です。

後述していますが、専用の学習モデルに切り替えられたりチューニングなどで音声認識の向上を行うことも可能です。

cloud.google.com

自然言語処理

音声認識で取得したテキストデータは文字起こしされた文章なので、そこから意味を解釈して必要なデータを抽出するのが自然言語処理です。

例えば、予約の人数について「2名です」という発話であれば「2」という数値データを抽出します。

人によって「2名」や「2人」など語彙の揺れがあるので、自然言語処理によってそれらの揺れを吸収します。

自然言語処理についてはトレタ予約番を共同開発しているユニロボット株式会社のunirobot cloudを使用しています。

www.unirobot.com

会話シナリオ

予約電話の会話は基本的にシナリオがあり、大まかに以下のステップを踏みながら会話を進めます。

  1. 日時の質問
  2. 人数の質問
  3. 連絡先の確認
  4. 予約内容の確認

日時の質問であればユーザーが日時を答えることが期待値であり、自然言語処理を行なった結果が日時として認識できれば次のステップに進みます。日時以外の内容であれば再度日時を言い直すように応答します。

また、ユーザーの発話内容をサーバー側の処理を通すことでシステムの応答内容を分岐させる場合もあります。

例えばユーザーが「23時」と答えた場合、日時としては正しいですが店舗の営業時間外であれば正しく日時として、正しくないことを伝え再度日時を質問します。

最後に予約確認まで進めば、トレタの予約台帳のAPIに対して予約登録を行い、登録完了を伝えて通話終了となります。

音声合成

シナリオに沿ってシステムの回答を音声合成サービスを通して音声データとして作成します。大体は決まった文言ではありますが、ユーザーの発話内容を復唱したりするため毎会話ごとに音声合成を行なっています。

現在は株式会社エーアイのAITalkを利用しています。日本語がとても自然なのと、イントネーションや感情値など細かいチューニングが行えるので、明るく活発な印象にすることができています。

www.ai-j.jp

サービス向上のとりくみ

基本的な動きとしては前述の通りですが、より自然に、よりユーザーにストレスがないように細かいチューニングを日々行なっています。

サービスの質に直結する音声認識精度の向上に向けた取り組みについていくつか紹介します。

学習モデルのチューニング

現在使用しているGCPのSpeechToTextには認識精度を向上させるためのチューニング方法がいくつか提供されています。

例えば、音声文字変換モデルで電話の通話に最適化された phone_call モデルがあります。通話音声は音の帯域が狭く音質が悪いためこのモデルを利用するのは有効的です。

cloud.google.com

その他にも PhraseSet を設定することで任意の単語の精度を上げることができます。例えば「はい」「いいえ」の回答を要求する会話の部分において、「いいえ」を「家(いえ)」と誤認識してしまうことがありましたが、「はい」「いいえ」をフレーズ設定することで正しく認識する確率が上がりました。

意味を汲み取る

正しく音声認識をしても文脈で意味が異なるケースがあります。

例えば、予約の日時を聞いた時の回答が「7時」であった場合、予約の文脈であれば「午前7時」ではなく「午後7時(19時)」が正しいと考えるのが一般的です。

厳密にはその店舗の営業時間を参照し、7時なのか19時なのかを判別し正しいと思われる方で音声認識の結果を補正します。

他にも、例えば予約の日時を「7月(しちがつ)」と認識した場合、その時の日付が1月であれば「1月(いちがつ)」を「7月(しちがつ)」と誤認識してしまった可能性が高いです。

「2月(にがつ)」「4月(しがつ)」などの様に、日時としては正しいが聞き間違い(誤認識)をしてしまうことは多々あります。 その場合はその時の日付や時間帯なども考慮して、音声認識した結果を改めるようにしています。

もっと難しい判定としては「大丈夫」などのどちらにも捉えられるワードがあります。

「この内容でよろしいですか?」「大丈夫です」の場合、おそらくYesと思われます。

「店舗にお繋ぎしますか?」「大丈夫です」の場合、おそらくNoと思われます。

このように曖昧な表現は各会話ごとにどう解釈させるかを定義として入れて対応しています。

さいごに

トレタ予約番は正式ローンチして1年以上経つサービスですが、AIの業界は日進月歩の世界なのでその時その時でよりよい音声認識エンジンであったり合成音声エンジンを切り替えたり、多くの試行錯誤やチューニングを行なっています。

いろいろと知見も溜まり新しい機能やシステムのリプレイスを考えたり、やりたいことはまだまだあるので興味を持たれたエンジニアの方は是非話を聞きにいらしてみてください。

corp.toreta.in

© Toreta, Inc.

Powered by Hatena Blog