RustとLambdaでなんか作る 前編
RustとLambdaでなんか作る 中編
はい。後編でございます。
作るアプリ考えました。
後編とはいうものの、ここまでHello worldしかやってないのでこれ一個で完結しちゃったりもします。
何を作る?
アプリ名:リクガメの食べもの
リクガメにあげてもいい餌を教えてくれるアプリ。
俺: リクガメの食べ物につないで
Home: はい、リクガメの食べ物です
俺: ピーマンあげていい?
Home: はい。ピーマンはあげていい食べ物です。カルシウムは少ないです。(終了)
みたいな、シンプルなものにします。
webhook側は一度リクエストを受け取ってレスポンスを返せばOK.
問いと回答は静的に用意しておく。
google home アプリのとこ
項目 | 内容 |
---|---|
アプリ名 | リクガメの食べもの |
project name | kamefood |
Category | Education & referrence |
Actions | Dialogflow |
Intents | askIntent, wellcome, fallback |
Entities | food |
askIntentというのがあり、例えば「小松菜上げていい?」と問いかけます。
すると小松菜がfoodというEntityになってRequestに乗っかってきます。
それはqueryResult.queryTextではなく、queryResult.Parametersの中にfoodとして入ってきます。
それを受けてそれに対するワードを返すところをRustで作る。
ソース
main.rsのみとなっていましたが、分けたらどうなるのかなと思ってfood.rsというのも作成してみました。
foods.rsは単にHashを保持して、キーに応じてvalueをリターンしてるだけ。
キーが食べ物でgoogleが投げてくるやつです。
そしてmain.rsはそれを投げる人で受け取る人でdialogflowに返す人です。
少し苦戦した箇所はあるけどそれは最後のまとめで。ひとまず動きました。
pub mod foods {
use std::collections::HashMap;
pub fn get(s:&str) -> String {
let mut f = HashMap::new();
f.insert("小松菜".to_string(), "<speak>はい。小松菜は、<prosody volume=loud>あげても大丈夫です。</prosody>カルシウムも豊富でおススメの食べ物です。ではまた。</speak>");
f.insert("水菜".to_string(), "<speak>はい。水菜は、<prosody volume=loud>あげても大丈夫です。</prosody>カルシウムも豊富でおススメの食べ物です。ではまた。</speak>");
f.insert("チンゲンサイ".to_string(), "<speak>はい。チンゲンサイは、<prosody volume=loud>あげても大丈夫です。</prosody>カルシウムも豊富でおススメの食べ物です。ではまた。</speak>");
...(略)...
return format!("{}", f.get(s).as_ref().unwrap());
}
}
use std::error::Error;
use lambda_http::{lambda, Request, Response, Body};
use lambda_runtime::{error::HandlerError, Context};
use log::{self, info};
use simple_logger;
mod foods;
#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;
#[derive(Debug,Deserialize,Default)]
struct Req {
#[serde(default)]
queryResult: QueryResult
}
#[derive(Debug,Deserialize,Default)]
struct QueryResult {
#[serde(default)]
queryText: String,
#[serde(default)]
parameters: Parameters
}
#[derive(Debug,Deserialize,Default)]
struct Parameters {
#[serde(default)]
food: String
}
fn main() -> Result<(), Box<dyn Error>> {
simple_logger::init_with_level(log::Level::Info).unwrap();
lambda!(my_handler);
Ok(())
}
fn my_handler(e: Request, c: Context) -> Result<Response<Body>, HandlerError> {
let query: Req = serde_json::from_slice(e.body().as_ref()).unwrap();
info!("{:#?}", query);
let food: String = query.queryResult.parameters.food;
info!("{}", foods::foods::get(&food));
Ok(
Response::builder()
.status(200)
.header("Content-Type", "application/json; charset=UTF-8")
.body(
format!(
r#"
{{
"fulfillmentText": "{}"
}}"#,
foods::foods::get(&food)
).into(),
)
.expect("none")
)
}
actions on google や dialog flowの設定
スクショなしで簡潔に書きますね。
基本的な手順は前回と同じです。
dialogflow
- askIntentを追加しました
- Traning phrasesには「小松菜は大丈夫」のようなパターンを羅列しています
- 「小松菜」といった食べ物の名前には@foodのEntitiesを設定しています
- webhookを有効にしています
- Set this intent of conversationを有効にしています
- @foodはREQUIREDなパラメータにして絶対入ってくるようにする
- fullfillment
- webhookurlをlabmda(api gateway)のURLにしています
actions on google
- アプリ名とかもろもろは前述
- 画像は192x192と1920x1080を用意して登録しないといけないです
- プライバシーポリシーはwordpress上で一枚用意してます。たったこんだけ->http://ikegami.tokyo/2019/01/21/kamefood-pp/
- simuraterでテストしてOKだったのでOverviewからReleaseを選択し、SUBMIT FOR PRODUCTION
まとめ
そんなわけで申請中です。
ちなみにgoogle home、alexa、clovaがスマートスピーカーの代表的なとこですが、googleが一番審査早く、審査自体も通りやすいと思ってます。リジェクトされても指摘箇所直せばサクッと通ります。逆にalexaが一番厳しいですかね。
さて、最後にRustを味見してみた感想。
まだ10%も使えてないと思うけどそれを加味して聞いてくださいませ。
- 思ったよりは使いやすい
- スクリプト言語出身な人でも、ミュータブルな変数もあるわけだし、意外と自由に書こうと思えば書ける(それが良いとは言ってない)。特にnodejsな人は馴染みやすそう。
- 独特な記法についてはまだよくわからない
- 調べてみるとawsのsdk的なの(rusoto)があるし、今回みたいな用途で使ってもいいのかも。
- コンパイラが親切。根気よくダメ出ししてくれる。
- Optionとかunwrapとか最初よくわかんなかったけど、慣れると恩恵大きそう
- 好きか嫌いかと言えば好き
そんなわけでもうしばらく触ってみようと思います。
今回のも正常系しかほぼ想定してなくて、アカンコードですしね。まずはここを直していこう。テストコードも書いておきたい。
そしてその次は何つくろ。・・仕事でこっそりバッチ作ってみようかなぁ。
##追記
https://assistant.google.com/services/a/uid/0000008de1f59b3d?hl=ja
半日くらいで公開されました。画像適当すぎたなあ。。
そんなわけで、スマートスピーカー、めっちゃ敷居低いっすよ。なんか思いついたら是非。なんか使いたい言語使ってみるとなお良いかも。