GEMINI LABEN
NANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定ですNANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定です
記事一覧/API / SDK
API / SDK/2026-05-15中級

累計5,000万DLの壁紙アプリで、Gemini Function Calling をレコメンドエンジンに使ってみた

2014年から12年間運営してきた壁紙アプリに、Gemini Function Calling を組み込んでレコメンドロジックを刷新した実装メモ。スキーマ設計からコスト試算、Claude/GPT との使い分け判断まで。

gemini-api279function-calling20個人開発91レコメンドエンジン壁紙アプリ7

壁紙アプリのレコメンド処理が「惰性」になっていると気づいたのは、2025年末のことでした。

2014年から個人で壁紙アプリを開発・運営し、累計5,000万DLを超えた今でも、ユーザーが「次に何を見たいか」を予測するロジックはほぼ変わっていませんでした。タグベースのフィルタリングと、DL数に基づく人気順の組み合わせ。シンプルで安定していますが、個々のユーザーの好みに応じてパーソナライズするには、明らかに限界がありました。

Function Calling をレコメンドエンジンに組み込もうと決めたのは、Gemini 2.5 Flash のコストパフォーマンスを確認してからです。タグ付けや分類に使えるかどうかではなく、「ユーザーの行動ログを構造化データとして渡して、好みを推定させる」という使い方が現実的かどうか、実際に試してみました。

12年越しの「惰性」に気づいたきっかけ

壁紙アプリに限らず、個人開発のアプリは「動いているから触らない」という状態になりやすいです。特にバックエンドのロジックは、ユーザーが直接見るものではないので後回しになりがちでした。

今回、アプリのレビューを読み返していたところ、「おすすめが毎回同じに見える」という声がいくつか目に留まりました。データを確認すると、確かにトップDL数の壁紙が何度も同じユーザーに表示されていて、一度DLしたものと類似したカテゴリの壁紙が優先されていませんでした。

ルールを書き直すことは難しくありませんが、「どんなルールにするか」が問題でした。ユーザーが好む壁紙の傾向は多様で、自然・都市・抽象・アニマル等の大分類だけでは粒度が足りません。かといって細かいタグを全て手動で管理するのは現実的ではなく、ここで Function Calling を使う発想が浮かびました。

Function Calling を選んだ理由 — 他の方法との比較

最初に考えたのは、ユーザー行動のログをそのままプロンプトに貼って「次に何を見せるか答えて」という方法でした。実際に試してみると、出力形式が不安定で、アプリ側でのパースが難しいことがすぐに分かりました。

次に試したのは Structured Output(JSON モード)です。これは安定していましたが、レコメンドのロジックが「出力を受け取るだけ」になってしまい、必要なデータを能動的に取りに来る動作が作れませんでした。

Function Calling の場合、モデルが「このユーザーの過去DL履歴を取得したい」「現在のカテゴリ分布を教えてほしい」と自分から問い合わせを行う設計にできます。レコメンドには「今何が在庫にあるか」「ユーザーが最近DLしたもの」「ユーザーが長く見ていた壁紙」の3つが揃って初めて判断できるため、この能動的な取得の仕組みが合っていると判断しました。

Claude 3.7 Sonnet や GPT-4o でも同様の実装ができますが、レコメンドの呼び出しは1セッションで数百〜数千回になることがあります。Gemini 2.5 Flash の入力コストは Claude Sonnet の約1/10程度(2026年5月時点の試算)で、レコメンドのような高頻度処理では差が大きくなります。精度よりもコストとスループットを優先したいケースで、Flash は合理的な選択でした。

コスト面の詳細はGemini API の文脈キャッシュでコストを80%削減した実装例も参考にしてください。

スキーマ設計 — ユーザー行動をどう渡すか

Function Calling のスキーマ設計で一番時間がかかったのは、「どのデータをどの関数で渡すか」の分割でした。

最終的に定義した関数は3つです。

  • get_user_download_history: 過去30日間のDL履歴(壁紙ID・カテゴリ・DL日時)
  • get_user_view_behavior: 閲覧時間が長かった壁紙のID一覧(スクロール停止時間から算出)
  • get_available_wallpapers: 現在表示可能な壁紙の一覧(カテゴリ・タグ・人気スコア含む)

最初は1つの大きな関数で全てのデータを渡していましたが、モデルが必要なデータだけを選んで取りに来る設計の方が、不要なトークン消費を避けられました。特に get_available_wallpapers は壁紙数が多い場合にレスポンスが大きくなるため、カテゴリでフィルタリングした部分集合を返すように引数を設けています。

import google.generativeai as genai
 
# 関数スキーマ定義
tools = [
    {
        "function_declarations": [
            {
                "name": "get_user_download_history",
                "description": "ユーザーの過去30日間の壁紙DL履歴を取得する",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "user_id": {
                            "type": "string",
                            "description": "ユーザーの識別ID"
                        },
                        "limit": {
                            "type": "integer",
                            "description": "取得件数の上限(デフォルト20)"
                        }
                    },
                    "required": ["user_id"]
                }
            },
            {
                "name": "get_available_wallpapers",
                "description": "現在配信中の壁紙一覧を取得する(カテゴリフィルタあり)",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "category": {
                            "type": "string",
                            "description": "絞り込むカテゴリ(nature/city/abstract/animal 等)"
                        },
                        "limit": {
                            "type": "integer",
                            "description": "取得件数の上限(デフォルト50)"
                        }
                    },
                    "required": []
                }
            }
        ]
    }
]
 
# Function Calling の実行ループ
def run_recommendation(user_id: str) -> list[str]:
    """ユーザーIDを渡して推薦壁紙IDのリストを返す"""
    model = genai.GenerativeModel("gemini-2.5-flash-preview-05-20")
    
    chat = model.start_chat()
    response = chat.send_message(
        f"ユーザー {user_id} に次に見せる壁紙を5枚推薦してください。"
        f"必要なデータを関数で取得してから判断してください。"
        f"最終的な回答はJSON形式の壁紙IDリストだけにしてください。",
        tools=tools
    )
    
    # Function Calling ループ(モデルが必要とするデータを順次取得)
    while response.candidates[0].content.parts[0].function_call.name:
        fc = response.candidates[0].content.parts[0].function_call
        func_name = fc.name
        func_args = dict(fc.args)
        
        # ローカルDB から実際のデータを取得
        if func_name == "get_user_download_history":
            result = fetch_download_history(func_args.get("user_id"), func_args.get("limit", 20))
        elif func_name == "get_available_wallpapers":
            result = fetch_wallpapers(func_args.get("category"), func_args.get("limit", 50))
        else:
            result = {}
        
        response = chat.send_message(
            genai.protos.Content(
                parts=[genai.protos.Part(
                    function_response=genai.protos.FunctionResponse(
                        name=func_name,
                        response={"result": result}
                    )
                )]
            )
        )
    
    # 最終出力からIDリストを抽出
    import json
    raw = response.text.strip()
    return json.loads(raw)

fetch_download_historyfetch_wallpapers はそれぞれ自前のDBを参照する関数です。Function Calling のポイントは、モデルがどの関数を何回呼ぶかを自律的に決める点にあります。試してみると、ほとんどのケースで get_user_download_historyget_available_wallpapers(カテゴリ指定あり)の順に呼ばれていました。

実際に動かして見えた3つのポイント

1. モデルが「関数を呼ばない」ケースの対処

初回ユーザー(DL履歴がゼロ)に対してモデルが get_user_download_history を呼ばず、いきなり推薦を返そうとするケースがありました。プロンプトに「必ず get_user_download_history を最初に呼ぶこと」と明示することで解消しましたが、これは Function Calling 特有のハマりポイントです。Function Calling が正しく動作しないときの対処法に詳しい解説があります。

2. スキーマのフィールド名が精度に直結する

wallpaper_idid では、モデルの応答精度が若干変わりました。型名も同様で、integer より string の方が IDとして扱われやすかったです。人間が読んで自明なフィールド名を付けると、モデルも迷わずに扱ってくれる印象があります。

3. 並列呼び出しでスループットを上げる

ユーザー数が多い場合、逐次処理では間に合わないことが分かりました。Gemini API は並列 Function Calling をサポートしているため、バッチ処理ではユーザーを複数並列で処理するように変更しています。詳細はGemini API の並列 Function Calling 実装パターンをご覧ください。

Claude・OpenAI との使い分けをどう判断したか

今回の実装を通じて、個人的に整理できた使い分けの基準を書いておきます。

高頻度・コスト重視の処理(今回のようなレコメンド、分類、スコアリング)には Gemini 2.5 Flash が合っています。呼び出し回数が多いほどコスト差が出るため、月あたりの推定コストを先に試算してからモデルを選ぶのが現実的です。

品質を優先したい処理(ユーザーへの長文回答生成、複雑なマルチステップ推論)では、Gemini 2.5 Pro や Claude Sonnet の方が安定しています。個人開発のスケールでは「プレミアムモデルを使う場面を絞る」という設計が予算を守るうえで重要です。

OpenAI の Function Calling は仕様が安定していて、エラーハンドリングのエコシステムが充実しています。新しいプロジェクトで試行錯誤する場合のリファレンス実装として参照することはありますが、コスト面で Gemini Flash に軍配が上がるケースが多くなりました。

全体を振り返って

累計5,000万DLの壁紙アプリに Gemini Function Calling を組み込んだ今回の実装で、特に学びが多かったのはスキーマ設計の部分でした。「どのデータをどの粒度で渡すか」がそのままレコメンドの精度とコストに直結します。

まず試してみるなら、既存のルールベースロジックを1つだけ関数にしてモデルに判断を委ねるところから始めるとハードルが低いです。全てを一度に置き換えようとせず、比較検証しながら範囲を広げていく方が、本番環境での安心感が高いと感じています。

同じ方向で悩んでいる個人開発者の方に、少しでも参考になれば幸いです。

シェア

お読みいただきありがとうございます

Gemini Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

API / SDK2026-05-18
Gemini Vision で壁紙アプリの自動カテゴリ分類を実装した話
個人開発の壁紙アプリで Gemini Vision API を使い、画像の自動カテゴリ分類を実装した実体験です。精度改善のプロセスと、公式ドキュメントには載っていない落とし穴、GPT-4o Vision とのコスト比較までまとめました。
API / SDK2026-05-13
AdMob + Gemini APIで個人開発iOS壁紙アプリの収益を最大化する設計パターン
AdMob収益とGemini APIコストのバランスを個人開発10年の経験から解説。iOS壁紙アプリにAI機能を追加する際の設計思想、コスト制御パターン、Freemium設計の実装例を紹介します。
API / SDK2026-07-14
並行して回す実験が共有予算を食い尽くす前に — AI Studio 費用上限をプロジェクト隔離の境界にする設計
個人開発で複数の Gemini 実験を同じ請求アカウントで並行させると、ひとつの暴走が他の全部を巻き添えにします。AI Studio に入ったプロジェクト単位の費用上限を隔離境界として使い、クライアント側のソフト天井と月次照合まで含めた設計を実装込みでまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →