🦔

ジェミニ統合アプリの開発

に公開

はじめに

話題になっているAIがどんどん活用されることになっている世の中、自分はどうやって個人のプロジェクトに導入できるかを考えて、Flutterのプロジェクトで、ジェミニとなら難しくないはずと思ったので、簡単なジェミニチャットアプリを作って見ることになりました。

ちょっとした歴史として、Googleがgoogle_generative_aiというパッケージを運用する事がありましたが、もう提供しておらず、Firebaseに移し、そこからLLMをプロジェクト内の導入するのが公式な方法になっているそうです。もし非公式な方法でFirebaseに繋がらない方がよろしければ、ジェミニAPIが使えるパッケージが存在しています例えばflutter_geminiでAPIキーを注入するだけでジェミニのAPIエンドポイントにアクセス出来ます。
この記事で、一からジェミニの導入を行い、実際のプロジェクトで使用出来るのかを考えます。

最低要件

Dart 3.2.0
Flutter 3.16.0
iOS 15.0 (iOS対応の場合)
Android APIレベル21 (Android対応の場合)

Flutterアプリを製作しましょう

セットアップ

まず最初にcliで下記のコマンドで新プロジェクトを作成しましょう。

flutter create my_gemini_app

次、Firebaseプロジェクトを建てましょう。FirebaseConsoleにサインインして、新しいプロジェクトを作成。

Firebaseプロジェクト内、サイドメニューからAI Logicを選択して、ページ内の指示に従って、Firebase AI Logicを有効にする。

Gemini Developer API(無料から使える)かVertex AI Gemini API(従量課金制プラン必要)か両方プロジェクトに有効にできますが、この記事はGemini Developer APIに集中します。
Gemini Developer APIは個人やデベロッパー向けの小さいプロジェクト用です。
Vertex AI Gemini APIの方はエンタープライズ用でスケールが大きい方です。

Flutterプロジェクトのpubspec.yamlにfirebase_aiとfirebase_coreを加えます。

flutter pub add firebase_ai
flutter pub add firebase_core

Firebaseのcliをインストール
macOSがWindowsより簡単な方法がありますが、両方を見ましょう。どっちもバイナリーインストールとnpmの選択肢がありますがmacOSにもう一つの簡単なオートインストールスクリプトがあります。

WindowsとmacOS

// npmがもうすでにインストールされている場合
npm install -g firebase-tools

macOS

// オートインストールスクリプト
curl -sL https://firebase.tools | bash

インストールが完了次第、次はcliからFirebaseにログインします。

firebase login

ログインが成功したら、テストとして全Firebaseプロジェクトをリストで表示します。

firebase projects:list

FlutterFireを起動します。

dart pub global activate flutterfire_cli

ローカルFlutterプロジェクトでfirebase_options.dartを生成します。

flutterfire configure --project=my-gemini-app(FirebaseプロジェクトのID)

これでローカルプロジェクトとFirebaseに繋いだはずです。

基本準備

Firebaseを初期化する

import 'firebase_options.dart';

await Firebase.initializeApp(
 options: DefaultFirebaseOptions.currentPlatform,
);

ジェミニのモデルをインスタンス化

  final _model = FirebaseAI.googleAI().generativeModel(
    model: 'gemini-2.5-flash',
  );

使えるモデルはいくつかありますが、2025年10月29日の日付で無料リクエスト数が1日250回gemini-2.5-flashを使います。
他のモデルの情報はFirebaseのドキュメンテーションで見つかれます。

チャットに関する変数を定義

final List<(bool, String)> _chatHistory = [];
final List<Content> _history = [];
late final _chat = _model.startChat(history: _history);

_chatHistoryはウィジェットツリーで使用します。
_model.startChat()を使用すると、historyパラメーターでセッション内の記憶を保って、続く会話が可能になります。

アプリのUIを設定しましょう。


Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Gemini Chat'),
        backgroundColor: Colors.grey.shade300,
      ),
      backgroundColor: Colors.grey.shade300,
      body: SafeArea(
        child: Column(
          children: [
            Expanded(
              child: Container(
                color: Colors.white,
                child: ListView.builder(
                  controller: _scrollController,
                  itemCount: _chatHistory.length,
                  itemBuilder: (context, index) {
                    final (isFromUser, text) = _chatHistory[index];
                    return MessageWidget(
                      isFromUser: isFromUser,
                      message: text,
                    );
                  },
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0) + EdgeInsets.only(left: 16),
              child: Row(
                children: [
                  Expanded(
                    child: TextField(
                      controller: _textController,
                      decoration: const InputDecoration(
                        hintText: 'Enter a prompt...',
                        border: OutlineInputBorder(),
                        filled: true,
                        fillColor: Colors.white,
                      ),
                      onSubmitted: (_) => _sendMessage(),
                    ),
                  ),
                  const SizedBox(width: 8),
                  IconButton(
                    icon: const Icon(Icons.send),
                    onPressed: _isLoading ? null : _sendMessage,
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

_sendMessage()を定義しましょう。
まずはユーザー入力メッセージを変数にし、それをチャット履歴に加えてtextControllerをclear()。
それからモデルに送って、返答をawaitします。
モデルの返答が届いた際に、チャット履歴に加えます。

final prompt = _textController.text;
if (prompt.isEmpty) return;

_isLoading = true;
_chatHistory.add((true, prompt)); // ユーザープロンプト
_textController.clear();


try{
final chatResponse = await _chat.sendMessage(Content.text(prompt));

_chatHistory.add((false, chatResponse.text ?? '')); // ジェミニの返答
_isLoading = false;
}catch(e){
//ネットワークやAPIエラーを処理
}

さらなる機能

時間を読める力を与えたまえ!

// getDateTime関数を定義します
 Map<String, Object> get getDateTime => {
    'date': DateFormat('EEEE, d MMMM, yyyy').format(DateTime.now()),
    'time': DateFormat('hh:mm a').format(DateTime.now()),
  };

// FunctionDeclarationに'name'と'description'を記入しないといけません
// この例に'name'を'getDateTime'として設定しています。
  final getCurrentDateTimeTool = FunctionDeclaration(
    'getDateTime',
    '日付と時間を取得する関数',
    parameters: {'schema': Schema.object(properties: {})},
  );

// モデルにツールとして呼び出しを入れる
final _model = FirebaseAI.googleAI().generativeModel(
    model: 'gemini-2.5-flash',
    tools: [
      Tool.functionDeclarations([getCurrentDateTimeTool]),
    ],
  );

その後、このツールをモデルに使ってもらうために、_sendMessage()を編集して、最終的な返答をチャットに入れます。

var chatResponse = await _chat.sendMessage(Content.text(prompt));

// モデルがどのツールを使うと決めたのを探る
final functionCalls = chatResponse.functionCalls.toList();
 if (functionCalls.isNotEmpty) {
  for (final functionCall in functionCalls) {
   if (functionCall.name == 'getCurrentDateTime') {
    final functionResult = getDateTime;

// 関数からの結果を使った返答をchatResponseに入れて、チャットに入れる
    chatResponse = await _chat.sendMessage(
    Content.functionResponse(functionCall.name, functionResult),
    );
   }
  }
 }

だがしかし、これだけでは、問題が発生します。

なんと!モデル内の情報がわからなくなってきて、時間だけを伝えるようになってしまった!モデルに呼び出しされた関数以外の知識を忘れているようですが、ただツール外の機能がないと思ってしまう。
対策するために、モデルのルールを決めて、systemInstructionのパラメーターに設定する必要があります。そこで色んなルールを設定して、モデルの挙動を制御する事ができます。

systemInstruction: Content('モデル', [
 TextPart('''
    ルール:
    1. 日付や時間を訪ねる時だけgetCurrentDateTimeを使って
  ''')]),

こうして問題挙動の修理が出来ました。

どんなふうに返答してもらいたいかも制御できます!
このルールを追加したら。。。

2. うずまきナルトのように答えて

こんな結果になります!

このようにsystemInstructionを使って、モデルの挙動、呼び出しの使い方、個性や性格まで制御が出来ます。

制限

この記事で使っているモデル(gemini-2.5-flash)は色んなインプット(テキスト、コード、PDF、イメージなど)が分かりますが、テキスト、コード、JSONしか生成出来ません。

LLMはもちろん万能ではない。toolsには、設定が3種あります:

Tools.functionDeclarations([])
Tools.codeExecution()
Tools.googleSearch()

現時点で、この中から同時に一つしか使えない。

functionDeclarations(関数呼び出し)にいくつかの関数をモデルに呼び出しすることが可能。モデルが自らプロンプトによって適切な関数を使って、返答を生成します。

googleSearch(グラウンディング)を使うと、モデルがGoogle検索の力で情報を見つけて返答を生成します。

codeExecution(コード実行)を使うとpythonコードを生成、実行します。

結論

プロジェクトセットアップ、ジェミニ有効化、Firebase統合、ジェミニAPIの初期化と使用を仕上げて、簡単なチャットアプリが完成です!
APIの基本操作に加えて、toolsパラメーター設定での関数呼び出し、Google検索グラウンディング、コード実行で機能を増やす事が出来ます。
無料のモデルでもテキスト、コード、PDF、イメージなどのインプットが対応していますが、この記事で簡単なテキストのみに焦点があてました。
systemInstructionでモデルの挙動と性格を制御出来る事とその方法も探りました。
生成AIアプリの導入としてジェミニは使いやすい、もっと複雑なアプリ内の使用は期待出来ます。

株式会社BALEEN STUDIO

Discussion