1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FlutterとGemini API (Function Calling) で、AIが自律的にメモを操作するAIエージェントメモアプリを個人開発した話

Posted at

はじめに

こんにちは!個人開発に奮闘している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 パッケージでは、ToolFunctionDeclaration を使ってこれを表現します。

// 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でも発信しているので、ぜひフォローしていただけると励みになります!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?