Google Assistant
Google Assistant Service プログラミング事始め
前回の記事では Google Assistant Library ベースのプログラミングを通じて Google アシスタントの各機能を取り回す試みを行いました。今回はもうひとつの Google Assistant SDK である Google Assistant Service に目を向けてみます。
Google LLC は Google Assistant SDK for devices として「Google Assistant Library」「Google Assistant Service」の二種類のセットを提供しています。前者は高水準で稼働環境は狭め、後者は低水準で広範な稼働環境に対応しており、この周到な構成に Google の本気度が窺えます。現時点では一日あたりのリクエスト数に制限はあるものの個人でも無償で利用できることが大きな魅力です。
Google Assistant Service と稼働環境
前回も引用した Google Assistant Library と Google Assistant Service の比較表を再掲します。(2018年12月時点の公式記事より)
Google Assistant Service の最大の特長は、このように gRPC 対応のプラットフォーム+対応言語環境全般で利用可能であることです。現時点では Google Assistant Library ほどには多機能ではないものの、稼働環境が広く Windows PC や Mac はもとより小振りで消費電力が小さく IoT フロントエンドとして有用な Raspberry Pi Zero / Zero W 系ボードも Google アシスタントクライアントとして利用できることに関心を誘われます。
Google Assistant Service と Google Assistant Library の機能面での違いは SDK 全体のリリースノートを追うことで手早く俯瞰できます。
両者の機能差は今後変化する可能性がありますが、個人的には 2018年12月時点で Google Assistant Service 側にはない要素のうち実用面とのかねあいにおいて以下の三点に留意しています。
- Google Assistant Library とは異なりウェイクワードの待機・検知に対応していない
- ニュースの読み上げや Google Podcasts に未対応
- アラームに未対応 (リマインダは設定可能)
なお、上記 1. は外部の Hotword Detector との連携(後述)によりある程度補うことが可能です。
※余談ながら、Google Home デバイス実機とは異なり今のところ Google Assistant SDK ベースのプログラムの所作に google-home-notifier を絡ませることはできません。たしかに 8009 番ポートを使ったキャスト機構にはハードウェア側要件としての印象が強いものの、google-home-notifier を あち こち で利用しているファンとしてはちょっと残念です。
pushtotalk.py プログラムのこと
Google Assistant Service をセットアップ後の googlesamples/assistant/grpc/ フォルダには「pushtotalk.py」プログラムが配置されます。
このプログラムは名前の示すように Google Assistant Library に含まれる「hotword.py」とは異なり、ウェイクワード(ホットワード)の音声ではなく 'Press Enter to send a new request...' の CUI メッセージを添えた click.pause() API で物理的なトリガーを待って Google アシスタントとの対話を開始する内容で実装されています。
セットアップずみの SDK ディレクトリ下での pushtotalk.py の実行方法は以下の要領です。引数で指定したプロジェクト ID とモデル ID はホスト側へ記憶され、変更の必要がなければ次回以降は省略可能です。
$ pwd
/home/t/wk/GoogleAssistant
$ ls
env
$ source env/bin/activate
(env) $ googlesamples-assistant-pushtotalk --project-id [設定ずみのプロジェクトID] --device-model-id [設定すみのモデルID]
pushtotalk.py を手元の複数の環境で実行した様子の動画を以下に示します。
Windows |
Mac |
Linux (32bit) |
Raspberry Pi 3 Model B+ |
Raspberry Pi Zero W |
![]() |
![]() |
上の動画のRaspberry Pi 3 Model B+ / Zero W には手持ちの以下の以下のマイクとスピーカーを写真の要領で接続しています。
|
今回は、前回の hotword.py と対照的なこの pushtotalk.py を試作の下敷きとします。
Hotword Detector について
前掲の Google Assistant Service 側未対応の機能のうち、実用上の影響がもっとも大きいのはウェイクワードの待機・検知ができないことでしょう。Google Assistant Library プログラムにおいては Google Home デバイスと同様にハンズフリーでアシスタントとの応酬が可能であることを考えあわせると淋しく感じられますが、この点は外部の Hotword Detection ソフトウェアを併用することで補うことができます。
ただし、外部の Hotword Detector との連携は Google アシスタントネイティブでのウェイクワードサポートではなく、あくまでも Assistant API を呼び出すためのトリガーを外側に用意する手立てに他ならないため以下の注意が必要です。
- ウェイクワード設定の柔軟性や認識精度はすべて Detector 側の要件である
- Google アシスタントの発話中にウェイクワードで介入することはできない
こういった事情を理解した上で Hotword Detector を併用すれば、間口の広い Google Assistant Service を様々な環境でより便利に利用することができるでしょう。
Porcupine と Snowboy
現時点でのスマートスピーカー向きの代表的な Hotword Detector として、Picovoice (カナダ系)による Porcupine と、 KITT.AI (中国系) による Snowboy が挙げられます。後者はすでに随所で取り上げられていますね。どちらも機械学習に基づく精度の高さ・低負荷・マルチプラットフォーム対応・カスタマイズの柔軟性をアピールポイントとしており、後発の Picovoice は両者の比較記事を公開しています。
手元ではどちらも使い始めてまだ日が浅いのですが、今のところウェイクワードを認識する能力そのものに際立った性能差は感じておらず負荷の度合いについては未検証です。今の時点で把握している両者の一長一短を挙げてみます。
対応プラットフォームの広さ: Porcupine ◎
カスタムウェイクワードへの対応: Snowboy ◎
$ pwd
/home/t/wk/Porcupine
$ tools/optimizer/linux/i386/pv_porcupine_optimizer -r resources/ -p linux -o . -w "OK google"
手元では今のところ PC では Porcupine、Raspberry Pi では Snowboy の要領で使い分けています。下の動画はそれぞれの動作の様子です。
※ ふたつの動画でのウェイクワード検知時・対話終了時の応答音はいずれも Snowboy の resource/ ディレクトリ下の wave ファイルによるものです。これらが耳に馴染んだため Porcupine との連携においても同じ要領で使用しています
試作
前回記事での hotword.py (Google Assistant Library) ベースの試作に続き、今回は Google Assistant Service の pushtotalk.py を下敷きにプログラムを作成します。作業上の便宜から開発は PC で行いましたが、Google Assistant Service ベースなので指先に乗るサイズの Raspberry Pi Zero W ボードをはじめ広範な環境で実行可能であることに夢が広がります。オールインワンのダンボールキットも製品化されていますね。
今回はシンプルな切り口として前回の Google Assistant Library 版の各プログラムと同じ動きをするものを作ってみることにしました。Google Assistant Serivice プログラミングは奥が深くまだまだ習作の段階ではありますが、事始めとして取り組んだ内容を以下に掲載します。
1. 指定テキストに基づく音声合成と読み上げ
テーマ: Google アシスタントの音声合成機能を単体で利用する。いわゆる Text to Speech。
[前回 Google Assistant Libray 版試作へのリンク]
内容
デモ: 動画 27秒
(前回分)
考えたことなど
(env)t@PC-533:~/wk/GoogleAssistant$ ./textinput.sh
INFO:root:Connecting to embeddedassistant.googleapis.com
: こんにちは
<you> こんにちは
<@assistant> こんにちは、TAnabeさん
どうしましたか?
: 今何時?
<you> 今何時?
<@assistant> 時刻は、17:25です。
: 今晩、雨降る?
<you> 今晩、雨降る?
<@assistant> 夜は、雨の心配はないでしょう
今夜の山口は雨ではないでしょう。 気温10度、晴れるでしょう。
---
(weather.com でもっと見る)
:
083| def iter_assist_requests():
084| config = embedded_assistant_pb2.AssistConfig(
085| audio_out_config=embedded_assistant_pb2.AudioOutConfig(
086| encoding='LINEAR16',
087| sample_rate_hertz=16000,
088| volume_percentage=0,
089| ),
090| dialog_state_in=embedded_assistant_pb2.DialogStateIn(
091| language_code=self.language_code,
092| conversation_state=self.conversation_state,
093| is_new_conversation=self.is_new_conversation,
094| ),
095| device_config=embedded_assistant_pb2.DeviceConfig(
096| device_id=self.device_id,
097| device_model_id=self.device_model_id,
098| ),
099| text_query=text_query,
100| )
109| text_response = None
110| html_response = None
111| for resp in self.assistant.Assist(iter_assist_requests(),
112| self.deadline):
113| assistant_helpers.log_assist_response_without_audio(resp)
114| if resp.screen_out.data:
115| html_response = resp.screen_out.data
116| if resp.dialog_state_out.conversation_state:
117| conversation_state = resp.dialog_state_out.conversation_state
118| self.conversation_state = conversation_state
119| if resp.dialog_state_out.supplemental_display_text:
120| text_response = resp.dialog_state_out.supplemental_display_text
121| return text_response, html_response
149| if len(resp.audio_out.audio_data) > 0:
150| if not self.conversation_stream.playing:
151| self.conversation_stream.stop_recording()
152| self.conversation_stream.start_playback()
153| logging.info('Playing assistant response.')
: 今何時?
<you> 今何時?
0
1600
1600
(引用中略)
1600
1600
244
<@assistant> 時刻は、18:43です。
(mp3 形式へ変換したもの)
import sounddevice as sd
:
text_response = None
html_response = None
s = sd.RawStream(
samplerate=audio_helpers.DEFAULT_AUDIO_SAMPLE_RATE,
dtype='int16',
channels=1,
blocksize=audio_helpers.DEFAULT_AUDIO_DEVICE_BLOCK_SIZE)
for resp in self.assistant.Assist(iter_assist_requests(),
self.deadline):
assistant_helpers.log_assist_response_without_audio(resp)
s.write(resp.audio_out.audio_data)
s.start()
if resp.screen_out.data:
html_response = resp.screen_out.data
if resp.dialog_state_out.conversation_state:
conversation_state = resp.dialog_state_out.conversation_state
self.conversation_state = conversation_state
if resp.dialog_state_out.supplemental_display_text:
text_response = resp.dialog_state_out.supplemental_display_text
return text_response, html_response
:
ソースコード
- pushtotalk_tts.py - github.com/mkttanabe
2. 利用者の発話内容をテキストへ変換
テーマ: Google アシスタントの音声認識機能を単体で利用する。いわゆる Speech to Text。
[前回 Google Assistant Libray 版試作へのリンク]
内容
デモ: 動画 41秒
(前回分)
考えたことなど
138| for resp in self.assistant.Assist(iter_log_assist_requests(),
139| self.deadline):
140| assistant_helpers.log_assist_response_without_audio(resp)
141| if resp.event_type == END_OF_UTTERANCE:
142| logging.info('End of audio request detected.')
143| logging.info('Stopping recording.')
144| self.conversation_stream.stop_recording()
145| if resp.speech_results:
146| logging.info('Transcript of user request: "%s".',
147| ' '.join(r.transcript
148| for r in resp.speech_results))
ソースコード
- PicovoiceWithGoogleAssitantService_input.py - github.com/mkttanabe
- Porcupine をインストールしたディレクトリ下へ配置して実行のこと
- Porcupine/resource ディレクトリ下に前掲の ding.wav, dong.wav が必要
- デモ動画ではパラメータに "--keyword_file_paths resources/keyword_files/picovoice_linux.ppn" を指定
3. 利用者の発話内容を他言語へ連続翻訳
テーマ: Google アシスタントの 音声認識 / 文意解釈 / 言語翻訳 / 応答文生成 / 音声合成 の各機能を利用する
[前回 Google Assistant Libray 版試作へのリンク]
内容
デモ: 動画 2分30秒
(前回分)
ソースコード
- PicovoiceWithGoogleAssitantService_translate.py - github.com/mkttanabe
- Porcupine をインストールしたディレクトリ下へ配置して実行のこと
- Porcupine/resource ディレクトリ下に前掲の ding.wav, dong.wav が必要
- デモ動画ではパラメータに "--keyword_file_paths resources/keyword_files/picovoice_linux.ppn" を指定
4. 利用者の発話内容を復唱
テーマ: Google アシスタントの音声認識 / 音声合成機能を利用する
[前回 Google Assistant Libray 版試作へのリンク]
内容
デモ: 動画 39秒
(前回分)
ソースコード
- PicovoiceWithGoogleAssitantService_echo.py - github.com/mkttanabe
- Porcupine をインストールしたディレクトリ下へ配置して実行のこと
- Porcupine/resource ディレクトリ下に前掲の ding.wav, dong.wav が必要
- デモ動画ではパラメータに "--keyword_file_paths resources/keyword_files/picovoice_linux.ppn" を指定
しりとり
テーマ: Google アシスタントの音声認識 / 音声合成機能を利用する
[前回 Google Assistant Libray 版試作へのリンク]
内容
デモ: 動画 60秒
(前回分)
現時点ではプログラミングを行うための実践的な情報をあまり目にすることのない Google Assistant Library と Google Assistant Service を題材に手元で行った試みを二度に分けて紹介しました。幼児の年齢を迎えたばかりの Google アシスタントはこれからこれらの SDK とともに成長を重ねて行くことでしょう。未来へ向かう道すがらの愉しみがまたひとつ増えた思いです。
(tanabe)
Google Assistant Library プログラミングを楽しむ
Google LLC は Google Assistant SDK for devices として 「Google Assistant Library」「Google Assistant Service」 の二種類のセットを提供しています。前者は高水準で稼働環境は狭め、後者は低水準で広範な稼働環境に対応しており、この周到な構成に Google の本気度が窺えます。現時点では一日あたりのリクエスト数に制限はあるものの個人でも無償で利用できることが大きな魅力です。
Compatibility and feature support
The following table summarizes the platform compatibility requirements and the
supported features for the Google Assistant Library and the Google
Assistant Service:
Library Service
Supported architectures linux-armv7l and linux-x86_64 All gRPC platforms
Supported languages Python All gRPC languages
Hands-free activation
(Ok Google)Yes No
Audio capture and playback Built in Reference code is provided
Conversation state management Built in Reference code is provided
Timers and alarms Yes No
Playback of podcasts and news Yes No
Broadcast voice messages Yes No
Visual output (HTML5) of Assistant responses No Yes
Google Home デバイスやスマートフォンアプリを通じて Google アシスタントの存在感はどんどん大きくなっています。その一方で現時点では Google Assistant SDK プログラミングのための実践的な情報を国内外を通じてあまり見かけないことが残念に思われました。ネットの奥深くに息づいている Google アシスタント陣営の一連の機能を本 SDK ごしに柔軟に呼び出すことができればこの強力で魅力的なサービスをさらに活用できるのではないでしょうか。
- 音声認識
- 文意の解釈
- 応答文の生成
- 音声合成
- 言語翻訳
そんなわけで手元ではここしばらく Google Assistant SDK に向き合っています。今回はまず Google Assistant Library ベースでこれまでに行った調査と実験の内容を紹介します。
Google Assistant Library と稼働環境
前掲の表にも記載のあるように、今のところ Google Assistant Library の稼働環境は Python を利用可能な linux-armv7l または linux-x86_64 なプラットフォームに絞られます。周辺機器としてマイクとスピーカが必須。セットアップは下記ページからの説明にそって行えば問題ないでしょう。導入手順の詳細は随所で紹介されているためここでは省略します。
(※ スペックの上では Raspberry Pi 2 Model B/B+ も適合するかもしれません)
手元では作業用に 64bit lubuntu 環境の PC と Raspberry Pi 3 Model B+ を使っています。
hotword.py プログラムのこと
Google Assistant Library をセットアップ後の googlesamples/assistant/library/ フォルダには「hotword.py」プログラムが配置されます。実質 100行ほどの短い内容ですが、`sample` と言いながらこのプログラムを利用すればウェイクワードの検知を含め Google アシスタントとのいつものやりとりをそのまま実現することができます。抽象度が高くフロントエンドをこのようにシンプルに実装できることが Google Assistant Library の大きな特長です。
セットアップずみの SDK ディレクトリ下での hotword.py の実行方法は以下の要領です。引数で指定したプロジェクト ID とモデル ID はホスト側に記憶され、変更の必要がなければ次回以降は省略できます。
$ pwd
/home/t/wk/GoogleAssistant
$ ls
env
$ source env/bin/activate
(env) $ googlesamples-assistant-hotword --project-id [設定ずみのプロジェクトID] --device-model-id [設定すみのモデルID]
hotword.py を PC / Raspberry Pi 3 で実行した様子の動画です。ここでは個人的な好みからウェイクワードへの反応音「ポコッ」の再生をコードに加えています。
![]() |
上の動画のラズパイには手持ちの以下のマイク (USB 接続) とスピーカー (3.5mm オーディオジャック接続) を写真の要領でつないでいます。
|
一連の試作においてはこの hotword.py を下敷きにすることにしました。コンパクトで見通しのよい内容でありながらアシスタントとの応酬に必要な要素の一式が収められているためカスタマイズを試みるための土台としてはまさに好適でしょう。この記事の最後の項目に Google Assistant Library プログラミングを行う上での基本的な作法をまとめています。あわせて参照して下さい。
試作
今回手がけた試作をデモ動画と素のままのソースコードを添えて以下に掲載します。
1. 指定テキストに基づく音声合成と読み上げ
テーマ: Google アシスタントの音声合成機能を単体で利用する。いわゆる Text to Speech。
内容
デモ: 動画 48秒
考えたことなど
send_text_query(query)
Sends |query| to the Assistant as if it were spoken by the user.
This will behave the same as a user speaking the hotword and making a query OR speaking the answer to a follow-on query.
Parameters: query (str) - The text query to send to the Assistant.
ソースコード
- hotword_tts.py - github.com/mkttanabe
2. 利用者の発話内容をテキストへ変換
テーマ: Google アシスタントの音声認識機能を単体で利用する。いわゆる Speech to Text。
内容
デモ: 動画 41秒
考えたことなど
ON_CONVERSATION_TURN_STARTED
ON_END_OF_UTTERANCE
ON_RECOGNIZING_SPEECH_FINISHED:
{'text': '今何時'}
ON_RESPONDING_STARTED:
{'is_error_response': False}
ON_RESPONDING_FINISHED
ON_CONVERSATION_TURN_FINISHED:
{'with_follow_on_turn': False}
61 | if (event.type == EventType.ON_CONVERSATION_TURN_FINISHED and
62 | event.args and not event.args['with_follow_on_turn']):
63 | print()
ソースコード
- hotword_input.py - github.com/mkttanabe
3. 利用者の発話内容を他言語へ連続翻訳
テーマ: Google アシスタントの 音声認識 / 文意解釈 / 言語翻訳 / 応答文生成 / 音声合成 の各機能を利用する
内容
デモ: 動画 52秒
考えたことなど
ソースコード
- hotword_translate.py - github.com/mkttanabe
4. 利用者の発話内容を復唱
テーマ: Google アシスタントの音声認識 / 音声合成機能を利用する
内容
デモ: 動画 42秒
考えたことなど
ソースコード
- hotword_echo.py - github.com/mkttanabe
しりとり
テーマ: Google アシスタントの音声認識 / 音声合成機能を利用する
内容
デモ: 動画 58秒
考えたことなど
ソースコード
- 別途
メモ: Google Assistant Library プログラミングの基本
hotword.py を通じて
前述のように Google Assistant Library のインターフェイスは抽象度が高くシンプルに扱うことができる。実ロジック 100 行程度の hotoword.py が Google Home デバイスとほぼ同等に Google Assistant と連携可能であることは興味深い。hotoword.py の内容にあらためて目を向けると、デバイス ID 等の管理情報の取り回し以外の実質的な処理は以下のごく短い内容のみであることが見てとれる。
- hotword.py#L122-L145 より
122 | with Assistant(credentials, device_model_id) as assistant: 123 | events = assistant.start() : 144 | for event in events: 145 | process_event(event)
(リファレンスより)- class google.assistant.library.Assistant(credentials, device_model_id) - developers.google.com
Client for the Google Assistant Library.
Provides basic control functionality and lifecycle handling for the Google Assistant. It is best practice to use the Assistant as a ContextManager:
with Assistant(credentials, device_model_id) as assistant:
This allows the underlying native implementation to properly handle memory management.
Once start() is called, the Assistant generates a stream of Events relaying the various states the Assistant is currently in, for example:
ON_CONVERSATION_TURN_STARTED ON_END_OF_UTTERANCE ON_RECOGNIZING_SPEECH_FINISHED: {'text': 'what time is it'} ON_RESPONDING_STARTED: {'is_error_response': False} ON_RESPONDING_FINISHED ON_CONVERSATION_TURN_FINISHED: {'with_follow_on_turn': False}
See EventType for details on all events and their arguments.
- class google.assistant.library.Assistant(credentials, device_model_id) - developers.google.com
- hotword.py#L47-L66 より
47 | def process_event(event): : 56 | if event.type == EventType.ON_CONVERSATION_TURN_STARTED: 57 | print() 58 | 59 | print(event) 60 | 61 | if (event.type == EventType.ON_CONVERSATION_TURN_FINISHED and 62 | event.args and not event.args['with_follow_on_turn']): 63 | print() 64 | if event.type == EventType.ON_DEVICE_ACTION: 65 | for command, params in event.actions: 66 | print('Do command', command, 'with params', str(params))
(リファレンスより)- ON_CONVERSATION_TURN_STARTED - developers.google.com
Indicates a new turn has started.
The Assistant is currently listening, waiting for a user query. This could be the result of hearing the hotword or start_conversation() being called on the Assistant.- start_conversation() - developers.google.com
Manually starts a new conversation with the Assistant.
Starts both recording the user’s speech and sending it to Google, similar to what happens when the Assistant hears the hotword.
This method is a no-op if the Assistant is not started or has been muted.- send_text_query(query)
Sends query to the Assistant as if it were spoken by the user.
- send_text_query(query)
- stop_conversation() - developers.google.com
Stops any active conversation with the Assistant.
The Assistant could be listening to the user’s query OR responding. If there is no active conversation, this is a no-op.
- start_conversation() - developers.google.com
- ON_CONVERSATION_TURN_FINISHED - developers.google.com
The Assistant finished the current turn.
This includes both processing a user’s query and speaking the full response, if any.
- ON_CONVERSATION_TURN_STARTED - developers.google.com
ポイント
- Google Assistant との一連の応酬を開始するには google.assistant.library.Assistant クラスのインスタンスを生成し start() をコールする
- 応酬中の状況はイベントベースで捕捉可能
- 対話開始: ON_CONVERSATION_TURN_STARTED
- 対話終了: ON_CONVERSATION_TURN_FINISHED
- 後は EventType ごとに必要な処理を記述
- start_conversation() によりウェイクワードなしで Assistant との対話を開始
- send_text_query() により発話に代え任意のテキストを Assistant へ送出可能
- stop_conversation() により Assistant との対話を任意に終了
(tanabe)