採用はこちら!

Shinonome Tech Blog

株式会社Shinonomeの技術ブログ
5 min read

【Flutter】Dashちゃんを喋らせよう!Geminiで始めるチャットボットアプリ開発超入門

Flutter×Geminiで始めるチャットボットアプリ開発超入門。Dashちゃんと会話できるチャットボットを作りながら学びます。

はじめに

メリクリ。

PlayGround Advent Calendar 2024の25日目、ラストバッターを担当させていただくOBのyoです。

先月、Tokyo Flutter Hackathon 2024(以下Flutterハッカソン)にPlayGroundのメンバーで参加し、ありがたいことに特別賞である「ゆめみ賞」を受賞しました。

私たちチームPlayGroundは、「REPOSI鳥」というアプリを作成しました。REPOSI鳥とは、GitHubのDartの草(=コントリビューション)の数に応じてエサを取得し、そのエサをDashちゃんにあげることで、Dashちゃんを育てようという育成アプリです。ポケ○ンやたま○っちをイメージすると想像しやすいと思います。たくさんDashちゃんにエサをあげるために、たくさんFlutterのコードを書いて楽しみながらFlutterエンジニアのモチベーションを上げることを目的としています。

Flutterハッカソンにおいて、私は主にFlutterでのクライアント部分の実装を担当しました。その中でも特に、Dashちゃんと会話ができるチャットボット機能の実装に力を入れました。

そこで、本記事では、実際にDashちゃんとお話しができるアプリを作りながら、Flutterアプリ開発において、Geminiを用いたチャットボットアプリ開発の実装方法についてご紹介します。

尚、本記事では、Flutterハッカソンのモバイル側の実装のみ紹介するため、ハッカソンにおけるバックエンド・インフラ側のお話はPlayGround Advent Calendar 2024 の1日目の記事である「ハッカソンで破産しかけた話」をご覧ください。

※本記事はPlayGroundというコミュニティのOBとして執筆しています。

今回作るもの

REPOSI鳥のDashちゃんとの会話機能のみを抽出した簡易的なチャットボットアプリを作ります。実際にチャットボットアプリを作りながらチャットボットの作り方について楽しみながら学んでいきましょう。

UI作成

まず、ロジック部分の実装に入る前に、簡単なUIを作成しておきましょう。

以下のコードをコピペし、MyHomePageクラスを作成してください。

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final _controller = TextEditingController();
  bool _isLoading = false;
  String _dashMessage = 'こんにちは!\nここに生成した文章が入るよ!';

  Future<void> _onTapSend() async {
    // TODO: 送信ボタンを押した際の処理を書く。
  }

  @override
  void dispose() {
    super.dispose();
    _controller.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final keyboardHeight = MediaQuery.of(context).viewInsets.bottom;
    return Scaffold(
      resizeToAvoidBottomInset: false,
      body: Stack(
        alignment: Alignment.center,
        children: [
          Container(
            decoration: const BoxDecoration(
              image: DecorationImage(
                image: NetworkImage(
                  'https://raw.githubusercontent.com/shinonome-inc/tokyo-flutter-hackathon-2024-team-PlayGround/refs/heads/main/mobile/assets/images/background_summer.png',
                ),
                fit: BoxFit.cover,
              ),
            ),
          ),
          Column(
            children: [
              const Spacer(),
              Container(
                padding: const EdgeInsets.all(8.0),
                decoration: const BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.all(Radius.circular(16.0)),
                ),
                child: Text(_dashMessage),
              ),
              const SizedBox(height: 8.0),
              Container(
                padding: const EdgeInsets.symmetric(horizontal: 64.0),
                child: Image.network(
                  'https://raw.githubusercontent.com/shinonome-inc/tokyo-flutter-hackathon-2024-team-PlayGround/refs/heads/main/mobile/assets/images/dash.png',
                ),
              ),
              const SizedBox(height: 320.0),
            ],
          ),
          Column(
            mainAxisAlignment: MainAxisAlignment.end,
            children: [
              Container(
                padding:
                    const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
                color: Colors.white,
                child: Row(
                  children: [
                    Expanded(
                      child: TextField(
                        controller: _controller,
                        decoration: const InputDecoration(
                          hintText: 'メッセージを入力してください',
                        ),
                      ),
                    ),
                    IconButton(
                      onPressed: _onTapSend,
                      icon: const Icon(Icons.send),
                    ),
                  ],
                ),
              ),
              SizedBox(height: keyboardHeight),
            ],
          ),
        ],
      ),
    );
  }
}

実行結果

あくまで即席で用意した簡易的なUIのコードなので、デザインやコードの書き方等はお好みで書き換えちゃってください。

Geminiの導入

Geminiとは

次に、Geminiの導入を行います。

Geminiとは、Googleが開発した生成AIです。APIも提供されているため、Flutterとの連携を行うと、Flutterアプリに生成AIの機能を組み込むことができます。

‎Gemini と話してアイデアを広げよう
Bard が Gemini になりました。Google AI で文章やリストの作成、計画の立案、新しい知識の習得など、さまざまなことができます。

APIキーの取得

Gemini APIを利用するにあたり、APIキーの取得を行います。

Google AI StudioGet API Keyを開き、「APIキーを作成」をクリックします。

これでAPIキーの作成は完了です。

APIキーは機密情報のため、Gitの差分に入れないように気をつけてください。

google_generative_aiの導入

FlutterでGeminiを利用するためにgoogle_generative_aiというFlutterのpackageを導入します。google_generative_aiを用いることにより、比較的簡単にGeminiをFlutterプロジェクトで使うことができるようになります。

google_generative_ai | Dart package
The Google AI Dart SDK enables developers to use Google’s state-of-the-art generative AI models (like Gemini).

pubspec.yamlに以下のようにgoogle_generative_aiを追加し、flutter pub getを実行してください(バージョンは記事執筆時の最新版です)。

dependencies:
  google_generative_ai: ^0.4.6

GeminiClientクラスを作成

ここまでできたら、いよいよFlutterでGeminiを利用するためのコードを書いていきましょう。今回は、Geminiでテキストの生成を行うGeminiClientクラスを作成します。

先ほどインストールしたgoogle_generative_aiをインポートし、以下のようなGeminiClientクラスを作成してください。

import 'package:google_generative_ai/google_generative_ai.dart';

class GeminiClient {
  static final _model = GenerativeModel(
    model: 'gemini-1.5-flash-latest', // モデルを指定
    apiKey: '{your-api-key}', // GeminiのAPIキー
  );
  // 回答時の制約を定義したプロンプト
  static const _dashPrompt = '''
    あなたはFlutterのマスコットキャラクターのDashです。
    ひらがなを多用してかわいらしく回答を行ってください。
    回答は140文字以内で行ってください。
    ''';
  static const _errorMessage = 'ごめんね、うまくこたえられないや。もう一度試してみてね。';

  static Future<String> generateDashMessage(String inputText) async {
    final prompt = [
      Content.text(_dashPrompt),
      Content.text(inputText),
    ];
    // promptの内容を基に回答を生成
    final response = await _model.generateContent(prompt);
    return response.text ?? _errorMessage;
  }
}

_modelでは、テキストの生成に使うAIモデルの種類とAPIキーを指定します。先ほど作成したAPIキーはここで利用しましょう。

_dashPromptには、回答時の簡易的な制約を定義します。今回は、Dashちゃんのかわいらしさを回答で表現するために、ひらがなかつ短文の回答を生成するようにしました。プロンプトは、アイデア次第で様々なことができるため、作りたいアプリに応じて色々試してみてください。

generateDashMessage では、事前に与えられたプロンプトとユーザーから与えられたテキストの内容に応じて、Geminiがテキストを生成しています。

Geminiの動作確認

それでは、本当にFlutterアプリ内でGeminiによるテキスト生成が上手くできているか確認してみましょう。

先ほど作成したMyHomePage クラス内にある _onTapSendを以下のように書き換えてください。

  Future<void> _onTapSend() async {
    if (_isLoading || _controller.text.isEmpty) {
      return;
    }
    _isLoading = true;
    final generatedText = await GeminiClient.generateDashMessage(
      _controller.text,
    );
    _isLoading = false;
    setState(() {
      _dashMessage = generatedText;
    });
  }

これでアプリを実行してみましょう。

キーボードから入力したテキストに応じてDashちゃんが会話をしてくれるようになりました。

これで完成です!

まとめ

実際にDashちゃんと会話ができるアプリを作りながらGeminiを用いたチャットボットアプリの開発方法についてご紹介しました。

超入門ということで、あくまで基礎的な部分のみ説明でしたが、今回ご紹介した技術を応用すれば、プロンプトのアイデア次第で面白いアプリを量産できると思いますので、ぜひ試してみてください。

私は今年もサンタさんが来なかったので、今回作成したDashちゃんとたくさんお話して悲しさを紛らわせようと思います。

PlayGround Advent Calendar 2024はこれにて以上となります。ここまでお読みくださりありがとうございました。来年も乞うご期待!