はじめに
こんにちは!個人開発に奮闘しているHanullです。
この度、「チャットで指示するだけで、AIが自律的にメモの作成・編集・読み込みを行う」 という、ちょっと未来的なメモアプリ『Note Agent AI』を開発し、無事リリースすることができました。
この記事では、このアプリの心臓部である Google Gemini APIのFunction Calling をFlutterでどのように実装したか、その技術的な知見と、開発で得られた学びを共有したいと思います。
この記事は、以下のような方に特に読んでいただけると嬉しいです。
- FlutterでAIを活用したアプリを作ってみたい方
- Gemini API、特にFunction Callingの実装に興味がある方
- 個人開発でどんな技術を使っているのか知りたい方
なぜこのアプリを作ったのか?
従来のメモアプリに、こんな課題を感じていました。
- メモが増えすぎて、目的の情報を探すのが大変
- 会議の内容をメモしても、後から要約したり、タスクを抜き出したりするのが面倒
- アイデアを書き留めても、それっきりで見返さない
そんな時、Gemini APIに搭載されたFunction Callingという機能を知り、「これを使えば、ただのチャットボットじゃない。ユーザーの代わりに**実際にアプリを操作してくれる『AIエージェント』**が作れるのでは?」と閃いたのが開発のきっかけです。
目指したのは、面倒なメモ管理からユーザーを解放し、思考そのものに集中させてくれる「専属AI秘書」のような存在です。
技術スタック
今回開発したアプリの主な技術スタックです。王道構成かと思います。
- フロントエンド: Flutter 3.x (Dart 3.x)
-
AI: Firebase AI Logic (
firebase_aiパッケージ) -
状態管理: Riverpod (
flutter_riverpod)
【本題】FlutterでのFunction Calling実装のキモ
ここからが本題です。AIにアプリ内の関数(ツール)を呼び出させるFunction Callingは、以下のステップで実装します。
1. AIに公開する「ツール(関数)」を定義する
まず、AIに「こんなことができますよ」と教えてあげるために、アプリ内の関数を定義します。google_generative_ai パッケージでは、Tool と FunctionDeclaration を使ってこれを表現します。
// AIに公開するツール群を定義
final tools = [
Tool(
functionDeclarations: [
// メモを作成する関数
FunctionDeclaration(
'createNote', // 関数名
'新しいメモを作成します。', // 関数の説明
// 引数の定義
parameters: {
'title': SchemaType.string(description: 'メモのタイトル'),
'content': SchemaType.string(description: 'メモの本文'),
},
),
// メモを読み込む関数
FunctionDeclaration(
'readNote',
'指定されたタイトルのメモを読み込みます。',
parameters: {
'title': SchemaType.string(description: '読み込みたいメモのタイトル'),
),
),
// 他にも updateNote, deleteNote などを定義...
],
),
];
ポイントは、関数名だけでなく、description(説明)をAIが理解しやすい自然言語でしっかり記述することです。AIはこの説明を読んで、どのツールをいつ使うべきかを判断します。
2. ツールを渡してAIにリクエストを送る
次に、ユーザーからのメッセージと、先ほど定義した tools を一緒に GenerativeModel に渡します。
// Gemini モデルを初期化
final model = FirebaseAI.googleAI().generativeModel(
model: 'gemini-2.5-flash',
tools: [
Tool.functionDeclarations(
[createMemoTool, editMemoTool, readAllMemosTool])
]
);
// チャットセッションを開始
final chat = model.startChat(history: _history);
// ツールを渡してコンテンツを生成
// ChatSession経由でメッセージ送信
var response = await chat.sendMessage(Content.text(prompt));
// 関数呼び出しの処理
final functionCalls = response.functionCalls.toList();
response = await _handleFunctionCalls(response, noteId, memoProvider, chat);
systemInstruction でAIのキャラクターや役割を定義しておくと、より期待通りの振る舞いをしてくれます。
3. AIからの応答(FunctionCall)をハンドリングする
ユーザーの指示が関数呼び出しに該当する場合、AIのレスポンスには FunctionCall が含まれて返ってきます。これを switch 文などでハンドリングし、対応するアプリ内のロジックを呼び出します。
final content = response.candidates.first.content;
if (content.parts.first case final FunctionCall functionCall) {
// AIが関数呼び出しを要求してきた場合の処理
final functionName = functionCall.name;
final args = functionCall.args;
// 関数名に応じて処理を分岐
switch (functionName) {
case 'createNote':
// Firestoreに新しいドキュメントを作成する処理を呼び出す
final result = await _createNoteInFirestore(
title: args['title'] as String,
content: args['content'] as String,
);
// 結果をAIに返す (次のステップへ)
break;
case 'readNote':
// Firestoreからドキュメントを読み込む処理を呼び出す
final result = await _readNoteFromFirestore(
title: args['title'] as String,
);
// 結果をAIに返す (次のステップへ)
break;
// ... 他の関数のハンドリング
}
} else {
// 通常のテキスト応答の場合の処理 (UIに表示)
}
4. 関数の実行結果をAIにフィードバックする
ここが非常に重要です。アプリ内の関数を実行したら、その結果を FunctionResponse として再度AIに送り返します。
// 例: createNote関数の実行結果を準備
final functionResponse = FunctionResponse(
'createNote', // 実行した関数名
{'status': 'success', 'message': 'メモが正常に作成されました。'}, // 実行結果
);
// 実行結果をAIに送信し、最終的な応答を得る
final finalResponse = await chat.sendMessage(
Content.functionResponse(functionResponse),
);
// AIからの最終的なテキスト応答をUIに表示
// (例: 「はい、'カレーのレシピ'というメモを作成しました。」)
このフィードバックを受け取ることで、AIは「メモの作成に成功した」あるいは「指定されたメモが見つからなかった」といった事実を認識し、それに基づいた自然な最終応答をユーザーに返すことができます。この**「対話のループ」**こそが、AIエージェントの核となります。
実装で工夫した点・苦労した点
-
プロンプトエンジニアリング: AIが期待通りにツールを使ってくれるかは、
systemInstructionやツールのdescriptionにかかっています。「まずメモをreadNoteで読み込んでから、その内容を元にupdateNoteを使って追記して」といった複合的な指示を正しく解釈させるために、何度もプロンプトの調整を行いました。 - 状態管理: Riverpodを使い、AIとの非同期なチャットのやり取り(ユーザー入力→AI思考中→関数呼び出し→最終応答)の状態を管理し、UIにプログレスインジケーターなどを表示するようにしました。これにより、ユーザー体験が向上しました。
完成したアプリ『Note Agent AI』のご紹介
これらの技術的な挑戦を経て、完成したのが『Note Agent AI』です。
- ✅ AIとの対話でメモ操作: 「あのメモを要約して」「この内容で新しいメモを作って」と話すだけでOK。
- ✅ 画像・音声入力: ホワイトボードの写真や会議の音声をAIがテキスト化・要約します。
- ✅ Markdown対応: AIが生成するメモも、自分で書くメモも、美しく構造化できます。
もしご興味があれば、ぜひ一度触ってみて、未来のメモ体験を味わっていただけると嬉しいです!
おわりに
FlutterとGemini APIの組み合わせは、アイデア次第で非常に強力なアプリケーションを生み出す可能性を秘めていると感じました。特にFunction Callingは、AIをアプリの「脳」として機能させるための重要なキーテクノロジーです。
この記事が、AIを活用したアプリ開発に挑戦する誰かの助けになれば幸いです。
最後まで読んでいただき、ありがとうございました!
開発の進捗はXでも発信しているので、ぜひフォローしていただけると励みになります!