♊️

FlutterでAIアプリを作ってみた(google_generative_ai)

2024/06/25に公開

Google I/Oを視聴して、遅ればせながらFlutterでAIアプリを作ってみました!
https://www.youtube.com/watch?v=ddcZnW1HKUY

どうやったか

https://pub.dev/packages/google_generative_ai
を使用しました。
API_KEYの取得などの初期設定方法方法は公式チュートリアルを見れば一通り記載されているため割愛します🙏

まずは動かす

リポジトリ内部にFlutter用サンプルがあったので使ってみました。
https://github.com/google-gemini/generative-ai-dart/tree/main/samples/flutter_app
サンプルアプリではチャット方のアプリを作成できました。Assetにあらかじめ入れた画像を送ることも可能です。
最初に作った時は私の方でAsset入れ忘れたので画像説明はエラー出てました。
demoApp
このあと修正入れたら画像の説明も簡単にできました🎉
demoAppImagae

サンプルアプリの内容は、リポジトリを参照ください。
サンプルアプリ中で、google_generative_ai関連の内容はこんな感じです。

  • GenerativeModel: モデル種別やキーを指定することで今回のアプリで使用するモデルを作成するクラス。generateContent()でプロンプト送信が可能。画像とテキストをまとめて送ったりとかもできる
  • ChatSession: GenerativeModel.startChat() で返される。sendMessage()でテキストメッセージを送れる
    • generateContent()とは違い、そのセッションの会話の流れを把握している

モデルの比較

先ほどの章で触れた通り、自分のモデルの作成はGenerativeModelを使うことで簡単にできます。

GenerativeModel(
    model: "モデル名",
    apiKey: "自身のAPI_KEY",
);

この時に迷うのが、「どのモデルを使えばいいのか」です。
色々な場所で説明されてますが、簡単にモデル比較もしておきます。
全体を通じて、公式が一番わかりやすいし最新情報だと思いますので、冒頭にそれぞれのリンクを貼ってます。

各モデルの特徴

https://ai.google.dev/gemini-api/docs/models/gemini?hl=ja#model-variations

models

料金

https://ai.google.dev/pricing?hl=ja

無料枠だとこんな感じでした(※ 最新情報は上記リンクから確認ください)

モデル 1 分あたりのリクエスト数 1 分あたりのトークン数 1 日あたりのリクエスト数
Gemini 1.5 Pro 2 32,000 50
Gemini 1.5 Flash 15 100 万 1,500
Gemini 1.0 Pro 15 32,000 1,500

今回は、新しいモデルで、かつ無料でそれなりに使えそうなGemini 1.5 Flashを使用しました。

デモアプリの作成

今回は、ポモドーロタイマーの休憩時間をAIで提案する、というアプリを作成してみました。
このアプリの画面下部に、休憩時間になったら休憩内容を提案するイメージです。

pomo-proto

構成としては以下を考えてます。

  • GenerativeModel はProviderで提供する
  • 休憩アイディア取得用のFutureProviderを作成して、非同期にデータを取得するようにする

1. 休憩アイディア表示用のウィジェット作成

BreakIdeaウィジェットを作成しました。

break_idea.dart
class BreakIdea extends HookConsumerWidget {
  const BreakIdea({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final breakIdea = ref.watch(asyncBreakIdeaProvider);

    return SizedBox(
      height: 100,
      child: switch (breakIdea) {
        AsyncError(:final error) => Text('Error: $error'),
        AsyncData(:final value) => MarkdownBody(data: value.text!),
        _ => const CircularProgressIndicator(),
      },
    );
  }
}

2. モデル提供用のProvider作成

gemini_model_provider.dart

GenerativeModel geminiModel(GeminiModelRef ref) {
  const String apiKey = String.fromEnvironment('API_KEY');
  final model = GenerativeModel(
    model: 'gemini-1.5-flash',
    apiKey: apiKey,
  );
  return model;
}

3. 休憩アイディア取得用のFutureProvider作成

当初の予定では、イメージで記載した通りFutureProviderで作成しようとしたのですが、下記理由から、作成と更新を行えるようにAsyncNotifierProviderで作成しました

  • 前回提案したアイディアを保持してないので、同じ提案が連続しやすい
  • 毎回初期プロンプトを与える影響でトークン数を無駄に食う
break_idea_async_provider.dart

class AsyncBreakIdea extends _$AsyncBreakIdea {
  late final ChatSession _chat;

  
  FutureOr<GenerateContentResponse> build() async {
    final GenerativeModel model = ref.read(geminiModelProvider);
    _chat = model.startChat();
    const message = '''
        ポモドーロタイマーの休憩のアイディアを1つ、30文字以内でください。
        5分以内にできることを考えてください。
        例: お茶を飲む
        ''';

    final response = await _chat.sendMessage(Content.text(message));
    return response;
  }

  Future<void> nextIdea() async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(() async {
      final response = await _chat.sendMessage(Content.text('''
        ポモドーロタイマーの休憩のアイディアを1つ、30文字以内でください。
        先ほどとは異なるアイディアをお願いします。
        '''));
      return response;
    });
  }
}

完成系

今までの実装を繋ぎ合わせた結果がこちらです🎉

pomo-proto-goal

レイアウトとかは置いておいて、まあまあいい感じじゃないでしょうか。
個人的にはポモドーロの休憩時間の使い方って結構難しい(ついスマホ見ちゃう)ので結構役立ちそうだと思ってます☺️

今後に向けて

以下の二つの視点から、今後やってみたいことを考えてみます

  • google_generative_ai パッケージをどう使うか
  • 今回作ったポモドーロアプリの改善点

google_generative_ai パッケージをどう使うか

サーバーを介した呼び出し

公式チュートリアル の冒頭に記載に以下の記載があります。

こちらの通り、サーバーを介した呼び出しをしてより公開できるような作りにしてみたいです。
サーバーサイドDartで呼び出すことで全てDartで動く世界が理想形です。

他のモデルとの比較

今回はGemini 1.5 Flashしか触ってない、かつそこまで高性能なモデルを必要としない作りになってました。
もう少し高度なことをさせて、その時にモデル比較をしてみたいです。

ポモドーロアプリの改善点

こちらはざっと箇条書きで

  • ユーザーからのフィードバックを受けることで「専用の休憩提案」ができるようにする
    • 何回か休憩を繰り返したら、「今回の提案はどうでしたか?」みたいな質問を設けることで、個別最適な提案ができるかも
  • 今の会話形式だと、会話を終了させた後、提案内容はリセットしてしまう
    • 過去のデータをRAGみたいな感じで読み込ませることもきっとできるはず
  • トークン節約
    • 「前回の休憩を引き継ぐ」みたいなメニューがあるとトークンの節約にもつながる?

(余談)

Google I/Oの中でも触れられてましたが、Gemini APIを使用したアプリコンペがあるそうです。
https://ai.google.dev/competition?hl=ja

優勝者には賞金とEVデロリアンが送られるみたいなので、興味を持った方はぜひ!w

Discussion