はじめに
初めましての方は初めまして、久しぶりの方はお久しぶりです。佐藤佑哉です。
先日、春のインターンに参加した時に出会った同期の方々と、2023年6月17日(土)に福岡で開催された、技育 CAMP キャラバン2023 vol.2 in福岡に出場し、株式会社CyberAgent様から企業賞をいただきました。
その振り返りを本記事を通して残したいと思います。
技育CAMPキャラバンとは
サポーターズ主催のオフライン型ハッカソンです。以前まで、技育ハッカソンはオンラインでの開催が主でしたが、、ついに対面でハッカソンをする貴重なハッカソンとなりました。
作成物
私たちは献立お助けアプリ「レシピLite」を作成しました。
(発表資料から抜粋)
役割・やったこと
役割は、「UI班・機械学習班」が3人、「データ班」は1人で分けました。
今回、私は「データ班」だったので、画面の作成をしておらず、データのアクセス・ビジネスロジックと画面の状態管理を担当したので、そちらを中心的にお話しできればと思います🙇♂️
アーキテクチャの選定
今回のアーキテクチャは、アプリの推奨アーキテクチャであるUIレイヤ・ドメインレイヤ・データレイヤの3層を用いたレイヤードアーキテクチャとクリーンアーキテクチャを採用しました!
単体テストやテストダブル
今回、Flutterのテストを実装してみました(してみたかった)。データレイヤからの実装が大半だったので、エラー時の対処を早くするためにテストダブルや単体テストを行いました。
難しかったことは、isarのデータベースを使用した単体テストでした。getApplicationSupportDirectory()
を使用し、パスを取得したかったのですが、 MissingPluginException
というエラーが出てしまうため、このような実装にいたしました。
テストのセットアップ
setUpAll(() async {
final name = Random().nextInt(pow(2, 32) as int);
dir = Directory(
path.join(
Directory.current.path,
'.dart_tool',
'test',
'application_support_$name',
),
);
//パスが存在しない場合は、再帰的にディレクトリを作成する
await dir.create(recursive: true);
//Isarを開く
isar = await Isar.open(
[RecipeSchema],
directory: dir.path,
);
recipeService = RecipeService(isar);
});
実際にテスト
test("レシピを保存できているかどうかを判断する単体テスト", () async {
//レシピを保存する
await recipeService.insertRecipe(recipe[0]).then((value) => expect(0, 0));
await recipeService.insertRecipe(recipe[1]).then((value) => expect(1, 1));
//保存されているレシピを取得する
//全てのプロパティが一致するかを検証する
});
tearDownAll(
() async {
//Isarを閉じる
await isar.close(deleteFromDisk: true);
//テスト用のディレクトリを削除する
if (dir.existsSync()) {
await dir.delete(recursive: true);
}
},
);
ChatGPTを用いたチャット機能からPaLM APIへの移行
チームから6/15(木)の深夜1:00くらいに連絡がありました。
「Maker Suite使えるようになった!」
「レスポンス早い!」
「精度が良い!」
Googleが提供している大規模言語モデルAPIであるPaLMがWeb上で使用できるようになったそうなのです。また、PaLMのAPIキーを取得できるようになりました。また、ChatGPTで実装している都合上、このような問題がありました。
- レスポンスが単純に遅い
- 献立の精度を上げる都合上、ChatGPTに最初にメッセージを送る必要があるため、無駄なポストが増える
この2点を踏まえて、PaLM APIに移行しようと考えました。
実際に単体テストを行った結果、PaLM APIの方が約3秒早い結果となりました🥳
移行において難しかったこと
難しかったことは、PaLM APIが返してくるメッセージ中身がMarkDownで送られてくることでした。そのため、必要なデータをJson形式で取得していたのですが、返してきてからの処理がとても大変でした。
//
//{ "candidates" [ "output": {...}]
//}
//→outputプロパティはこのようになっている
//```json
//{
// "recipe": "ポテトはおいしいです"
//...
//}
//
//レスポンスボディをパース
final parseByJsonToString = json
.decode(chatResponse.body)['candidates'][0]["output"]
.toString();
final cuttedMessage = parseByJsonToString.substring(
parseByJsonToString.indexOf('{'), //最初の{の位置までを切り取る
parseByJsonToString.length - 3); //最後の[''']を切り取る
//パースしたものをまたパースして、Messageデータクラスに格納する
final recipe = Message.fromJson(
json.decode(cuttedMessage),
);
これにより、各データを取得することができました!!
挙動は下記のようになります。
当日の発表会
当日の発表会までに3.5時間の開発期間が設けられていましたが、2徹していた私は、ほぼ進捗がなく終わってしまいました🙃
発表は1チーム3分でした。前日までの発表時間は原則2分でしたが、サポーターズさんのご協力もあって3分まで伸ばしていただきました。ありがとうございます。🙏
懇親会
全チームの発表会が終わってから、各企業さんやチームの方々と懇親会がありました。Twitter上で知り合っていた方々や、受賞された方々などたくさんお話をしました。
懇親会の途中で、各賞の発表がありました。私たちは、株式会社CyberAgent様から企業賞をいただきました。高い技術力を評価していただいたみたいです。本当にありがとうございます。
そして、春のインターンに一緒に参加していただいた同期の方々も全員が受賞していました。
感想
今回のハッカソンで賞を初めていただきました。1週間、全力で走り抜けた結果が結びついてとても嬉しかったです。他にも新しく技術的な知見を得ることも多くありました。
他の方々のプロダクトも発表を通じてとても完成度が高いなぁと感じました。特に自分では思いつかないアイデア力や発想力には、驚くものもありました。
最後に、一緒に開発していただいたチームメンバーの方々、企画してくださったサポーターズの方々、交流していただいた皆さんに心より感謝申し上げます。