Flutter x Geminiで生成AI利用アプリを爆速で作ろう![ハッカソンにオススメ]
はじめに
ChatGPT 、Gemini など生成AIが世の中で話題になる中、
エンジニアとして生成AIを利用したアプリを作ることは一個のモチベーションになると思います。
特に1日、2日の時間の限られているハッカソンイベントで、
生成AIを利用したアプリを提出できたら、審査員の方々に大きなインパクトを与えられるでしょう。
「そんな短期間で作れるのか?難しいのでは?」
そう思う方がいるかもしれません。
実は、できるんです。
それも、凄腕エンジニアでなくても可能です。
この記事がそれを示します。
本記事の内容
本記事はFlutter と Google製生成AI、Geminiを利用したアプリを爆速で作る方法について解説します。
google_generative_ai パッケージを利用したサンプルアプリの作り方を通し、
生成AIとの繋ぎこみ方、
関数呼び出し(Function Calling)の方法についても解説します。
サンプルアプリ、コード
サンプルアプリとして以下のようなアプリケーションを作成します。
サンプルコードのGitHubはこちら。
対象読者
対象読者は以下になります。
- Flutterで簡単なUIが作成できる方
- Statefull Widgetを使ってTextField や ボタン動作を実装できる方
- Geminiを使ったアプリを作ってみたい方
- 簡単にインパクトのあるアプリをハッカソンに向けて作ってみたい方
それでは参りましょう!
Flutter,Geminiとは
Flutterとは
FlutterとはGoogleの開発したオープンソースのマルチプラットフォームなアプリUI作成フレームワークです。
スマホアプリとして iOS/iPadOSアプリ、 Androidアプリが作れるだけでなく、
Webアプリ、MacOSアプリ、Windowsアプリ、Linuxアプリと、多種多様なアプリケーションを作成できます。
Google 製ということから、
Google製のFirebaseやGeminiなど他のサービスとの連携のしやすさは特筆するものがあります。
色んなプラットフォームのアプリを、充実したサービス連携で爆速で作れるフレームワーク
としてハッカソンの技術選定として一考の余地があると思います。
Geminiとは
Google製の生成AIです。
Google検索、Googleドキュメントなど、Googleの様々なサービスとの連携がスムーズに行える、という点が強みとなっています。
こちらもGoogle製、ということはそう、Flutterとの連携がとてもしやすいんです。
具体的には、バックエンドサーバーを叩いたり、REST APIなどのAPIリクエストをコードに記載しなくても、
Gemini利用パッケージのメソッドを利用することで生成AIの利用が可能となります。
以下ではFlutterとGeminiを結びつける、Google製パッケージ、
google_generative_ai パッケージの使い方を中心に紹介します。
コード解説
Geminiとの繋ぎこみ
Geminiとの繋ぎこみ部の解説を行います。
APIキーの取得
まず、Gemini API を利用するため、APIキーを取得しましょう。
以下のページから取得が可能です。
パッケージのインストール
以下のコマンドをプロジェクトルートにて実行し、
google_generative_ai パッケージ
をFlutterプロジェクトにインストールします。
flutter pub add google_generative_ai
モデルの初期化
main.dartにて以下のようにし、
google_generative_aiパッケージで利用するmodelにAPIキー等を設定して初期化します。
void main() {
final model = GenerativeModel(
// geminiで使用するモデルを記載します。
model: 'gemini-1.5-flash-latest',
// APIキー(Stringを設定します)
apiKey: geminiApiKey,
// ...
);
// 今回はクラスのフィールドにて生成AIを利用するクラスまでmodelを届けます
runApp(GeminiForTokyoFlutterHackathon(model: model));
}
プロンプトの設定とAPIの呼び出し
プロンプトの設定とAPIの呼び出しは以下のように行います。
//...
class SuggestionPage extends StatefulWidget {
const SuggestionPage({super.key, required this.model});
final GenerativeModel model;
State<SuggestionPage> createState() => _SuggestionPageState();
}
class _SuggestionPageState extends State<SuggestionPage> {
final TextEditingController controller = TextEditingController();
//...
Future<void> _getAnswer() async {
//...
//プロンプト設定部
final contents = [
Content.text(geminiPrompt),
Content.text(controller.text),
];
GenerateContentResponse? response;
//...
//API呼び出し部
response = await widget.model.generateContent(contents);
//...
}
今回のアプリのプロンプトは
あなたは世界一のグルメ情報通です。今から地名を伝えます。その土地で有名なグルメの名前を返答してください。伝えられたものが地名でなければ、「地名ではありません。」と返答してください。
となります。
これが上記geminiPrompt
に設定されています。
controller.txt
はこのアプリのTextFieldに設定されているものです。
ユーザーの入力がここに入ってきます。
最後に、response = await widget.model.generateContent(contents);
とすることでGeminiのAPIを叩いた結果がresponse
に格納され、response.text
とすることでString
でgeminiのAPI結果を取得できます。
関数呼び出し(Function Calling)
生成AIを使ったアプリ作成で悩ましいのは、APIを利用した結果が文章で出力されることでしょう。
この主力を、単語やList
、Map
に設定するのが関数呼び出しと呼ばれる手法です。
今回は簡単に手法だけ解説します。
GeminiのAPIを実行した結果から呼び出す関数を設定する
// ...
String? setFood(
Map<String, Object?> arguments,
) =>
arguments['food'] as String?;
この際、Map<String, Object?>
型の引数を設定する必要があります。
Map
の中の後述する方法で設定したkey
に欲しい情報が入力されます。
上記関数を呼び出すための設定を行う
import 'package:google_generative_ai/google_generative_ai.dart';
final answerController = FunctionDeclaration(
// 上で定義した関数の名前を記述する
'setFood',
// Gemini API を叩いた結果の出力に対する指示を記述する
'質問の回答の食べ物の名前を一つ選んでセットしてください',
//出力の型(Map<String, Object?>)を定義する
Schema(
SchemaType.object,
properties: {
//ここに設定するfoodが上の関数のkeyになる
'food': Schema(
SchemaType.string,
description: '食べ物の名前',
),
},
//上記で設定したkeyを設定する
requiredProperties: [
'food',
],
),
);
modelへ上記設定を反映させる
void main() {
final model = GenerativeModel(
model: 'gemini-1.5-flash-latest',
apiKey: geminiApiKey,
// 上記設定を以下のように設定する
tools: [
Tool(functionDeclarations: [answerController]),
],
);
runApp(GeminiForTokyoFlutterHackathon(model: model));
}
API呼び出し結果から関数呼び出し結果を取り出す。
Future<void> _getAnswer() async {
// ...
final contents = [
Content.text(geminiPrompt),
Content.text(controller.text),
];
GenerateContentResponse? response;
try {
response = await widget.model.generateContent(contents);
} on Exception catch (e) {
answer = 'エラーが発生しました1。 $e';
setState(() {
isLoading = false;
});
return;
}
//関数呼び出しした結果を取り出す
final functionCalls = response.functionCalls.toList();
try {
if (functionCalls.isNotEmpty) {
final functionCall = functionCalls.first;
// *1
answer = switch (functionCall.name) {
'setFood' => setFood(functionCall.args),
_ => throw UnimplementedError(
'Function not implemented: ${functionCall.name}',
)
};
}
} on Exception catch (e) {
// ...
}
// ...
}
// ...
以上がfunctionCallingのやり方になります!
UI
UIのコードは以下となります。
コード
import 'package:flutter/material.dart';
import 'package:gemini_app_for_tokyo_flutter_hackathon/constants/function_calling_setting.dart';
import 'package:gemini_app_for_tokyo_flutter_hackathon/constants/gemini_prompt.dart';
import 'package:google_generative_ai/google_generative_ai.dart';
class SuggestionPage extends StatefulWidget {
const SuggestionPage({super.key, required this.model});
final GenerativeModel model;
State<SuggestionPage> createState() => _SuggestionPageState();
}
class _SuggestionPageState extends State<SuggestionPage> {
final TextEditingController controller = TextEditingController();
bool isLoading = false;
String? answer = '';
void dispose() {
controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
const Text('地名を入力してください。おすすめグルメを教えます。'),
SizedBox(
width: 320,
child: TextField(
decoration: InputDecoration(
hintText: '東京',
hintStyle: TextStyle(color: Theme.of(context).hintColor),
),
controller: controller,
),
),
ElevatedButton(
onPressed: _getAnswer,
child: const Text('Geminiに聞く'),
),
Container(
padding: const EdgeInsets.all(8),
width: 320,
height: 120,
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).primaryColor,
),
borderRadius: BorderRadius.circular(8),
),
child: isLoading
? const CircularProgressIndicator()
: Center(child: Text(answer!)),
),
],
),
),
);
}
Future<void> _getAnswer() async {
/// ...
}
}
本記事では詳細解説しませんが、
TextField,Button,結果出力部のみのシンプルなUIとなります。
以上がFlutter x Geminiのアプリの解説となります。
長々と説明しましたが、以下のリポジトリをクローンして、
色々と遊んでみてください!
このアプリがハッカソンでおすすめの理由
このアプリ、ハッカソンのような短期間のイベントでとても効力を発揮します。
なぜなら、プロンプトとUIをカスタマイズするだけで、色々なアプリに化けるからです。
例えばプロンプトを「おすすめのお寺を教えてください」にすればお寺検索アプリになりますし、
「おすすめのグッズを紹介してください」にすればグッズ検索アプリになります。
あなたのアイデアをプロンプトという文字列で設定するだけで、色々なアプリに早変わりするんです!
なので、このアプリを実行できるようにしといて、
ハッカソン期間中、プロンプトを色々試し、あとはUIを練る、
ということで、Geminiを使ったインパクトのあるアプリをハッカソンで提示できます。
ぜひ検討してみてください!
東京Flutterハッカソン
よし、ハッカソンに出てみよう!と考えたそこのあなたにおすすめなのが、
東京Flutterハッカソンです。
2024年11月2日、3日の2日間にかけて、優勝賞金30万円を用意したハッカソンイベントになります。
Flutterを使ったアプリを作っていただく、という条件なので、この記事の内容はピッタリです。
2024年9月27日(金)まで参加募集を行っていますので、ぜひチームメンバーを集めてご参加ください!
参考
Flutter Ninjas : Building a Gemini Al-Powered Emoji Movie-Guessing Feature in a Production-Ready App with Flutter
Discussion