レコメンデーション × AIエージェント = 収益の自動最大化
アプリの収益化において、「適切なユーザーに、適切なタイミングで、適切なオファーを提示する」ことは最も重要であり、同時に最も難しい課題です。従来のルールベースのレコメンデーション(全員に同じ広告を表示する、登録7日後に割引を送る等)ではユーザーの多様な行動パターンに対応しきれません。
AIエージェントを使えば、ユーザーの行動データをリアルタイムに分析し、個々人に最適化された課金提案を自動的に生成・実行できます。ここではRorkアプリにおけるパーソナライズされたレコメンデーション収益エンジンの設計と実装を詳しく解説します。
パーソナライズ収益エンジンの仕組み
3つのコンポーネント
- 行動分析エンジン — ユーザーのアプリ内行動を追跡・スコアリング
- レコメンデーションAIエージェント — 行動データに基づき最適な提案を生成
- 課金実行レイヤー — Stripeと連携してオファーの提示・決済処理を実行
データフロー
ユーザー行動 → 行動分析 → AIエージェント判断 → レコメンデーション表示
↑ ↓
└────────── 結果フィードバック ←──── 課金/非課金の結果
行動分析エンジンの実装
// services/behavior-tracker.ts
// ユーザー行動分析エンジン
interface UserBehavior {
userId: string;
sessionCount: number;
avgSessionDuration: number; // 秒
featuresUsed: string[];
lastActiveAt: Date;
purchaseHistory: {
amount: number;
date: Date;
product: string;
}[];
premiumFeatureAttempts: number; // 有料機能のタップ回数
referralCount: number;
}
interface EngagementScore {
overall: number; // 0-100
purchaseIntent: number; // 0-100(購入意欲スコア)
churnRisk: number; // 0-100(解約リスク)
optimalTiming: string; // レコメンデーション表示の最適タイミング
}
function calculateEngagementScore(
behavior: UserBehavior
): EngagementScore {
// エンゲージメントスコアの計算
const sessionScore = Math.min(behavior.sessionCount * 2, 30);
const durationScore = Math.min(behavior.avgSessionDuration / 60 * 5, 20);
const featureScore = Math.min(behavior.featuresUsed.length * 3, 20);
const premiumScore = Math.min(behavior.premiumFeatureAttempts * 10, 30);
const overall = sessionScore + durationScore + featureScore + premiumScore;
// 購入意欲スコア(有料機能へのタップが多いほど高い)
const purchaseIntent = Math.min(
behavior.premiumFeatureAttempts * 15 +
(behavior.sessionCount > 10 ? 20 : 0) +
(behavior.avgSessionDuration > 300 ? 15 : 0),
100
);
// 解約リスク(最終アクティブからの経過日数で判定)
const daysSinceActive = Math.floor(
(Date.now() - behavior.lastActiveAt.getTime()) / 86400000
);
const churnRisk = Math.min(daysSinceActive * 10, 100);
// 最適タイミングの判定
let optimalTiming = "session_end"; // デフォルト
if (purchaseIntent > 70) {
optimalTiming = "premium_feature_tap"; // 意欲が高い→即座に提案
} else if (purchaseIntent > 40) {
optimalTiming = "session_mid"; // 中程度→セッション途中で
}
return { overall, purchaseIntent, churnRisk, optimalTiming };
}
// 使用例
const score = calculateEngagementScore({
userId: "user_123",
sessionCount: 15,
avgSessionDuration: 420,
featuresUsed: ["search", "favorites", "share", "compare"],
lastActiveAt: new Date(),
purchaseHistory: [],
premiumFeatureAttempts: 5,
referralCount: 2
});
console.log(score);
// 出力例:
// {
// overall: 82,
// purchaseIntent: 75,
// churnRisk: 0,
// optimalTiming: "premium_feature_tap"
// }AIエージェントによるレコメンデーション生成
// services/recommendation-agent.ts
// AIエージェントによるパーソナライズレコメンデーション
import { GoogleGenerativeAI } from "@google/generative-ai";
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);
const model = genAI.getGenerativeModel({ model: "gemini-2.5-pro" });
interface Recommendation {
type: "subscription" | "one_time" | "upgrade" | "retention";
plan: string;
message_ja: string;
message_en: string;
discount?: number;
urgency: "low" | "medium" | "high";
displayTiming: string;
}
async function generateRecommendation(
behavior: UserBehavior,
score: EngagementScore
): Promise<Recommendation> {
const prompt = `あなたはモバイルアプリの収益最適化エージェントです。
以下のユーザーデータに基づいて、最適な課金提案を生成してください。
ユーザーデータ:
- セッション数: ${behavior.sessionCount}
- 平均セッション時間: ${behavior.avgSessionDuration}秒
- 使用機能: ${behavior.featuresUsed.join(", ")}
- 有料機能タップ回数: ${behavior.premiumFeatureAttempts}
- 購入履歴: ${behavior.purchaseHistory.length}件
- 紹介ユーザー数: ${behavior.referralCount}
スコア:
- エンゲージメント: ${score.overall}/100
- 購入意欲: ${score.purchaseIntent}/100
- 解約リスク: ${score.churnRisk}/100
- 最適タイミング: ${score.optimalTiming}
利用可能なプラン:
- Tip: ¥150 / $1.50(1回限り)
- Pro月額: ¥380 / $3
- Premium永久: ¥1,480 / $10
以下のJSON形式で回答してください:
{
"type": "subscription" | "one_time" | "upgrade" | "retention",
"plan": "プラン名",
"message_ja": "日本語のレコメンデーションメッセージ(50文字以内)",
"message_en": "English recommendation message (under 50 words)",
"discount": 0-30,
"urgency": "low" | "medium" | "high",
"displayTiming": "表示タイミング"
}`;
const response = await model.generateContent(prompt);
const text = response.response.text();
// JSONの抽出とパース
const jsonMatch = text.match(/\{[\s\S]*\}/);
if (!jsonMatch) throw new Error("Failed to parse recommendation");
return JSON.parse(jsonMatch[0]);
}
// 期待する出力例:
// {
// "type": "subscription",
// "plan": "Pro月額",
// "message_ja": "お気に入り機能を無制限で使えるProプラン、今なら20%オフ",
// "message_en": "Unlock unlimited favorites with Pro — 20% off today",
// "discount": 20,
// "urgency": "high",
// "displayTiming": "premium_feature_tap"
// }Rorkアプリでのレコメンデーション表示
// components/SmartOffer.tsx
// AIが生成したレコメンデーションを表示するコンポーネント
import React, { useEffect, useState } from 'react';
import {
View, Text, TouchableOpacity,
Animated, StyleSheet
} from 'react-native';
interface OfferProps {
userId: string;
currentScreen: string;
trigger: "session_end" | "premium_feature_tap" | "session_mid";
}
export default function SmartOffer({
userId, currentScreen, trigger
}: OfferProps) {
const [offer, setOffer] = useState<any>(null);
const [visible, setVisible] = useState(false);
const fadeAnim = useState(new Animated.Value(0))[0];
useEffect(() => {
fetchOffer();
}, [trigger]);
const fetchOffer = async () => {
try {
const res = await fetch(
'https://api.your-app.com/recommendations',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer TOKEN'
},
body: JSON.stringify({
userId,
currentScreen,
trigger,
}),
}
);
const data = await res.json();
if (data.recommendation) {
setOffer(data.recommendation);
setVisible(true);
Animated.timing(fadeAnim, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}).start();
}
} catch (e) {
// サイレントフェイル(レコメンデーション失敗でUXを妨げない)
}
};
if (!visible || !offer) return null;
return (
<Animated.View style={[styles.container, { opacity: fadeAnim }]}>
<Text style={styles.title}>
{offer.message_ja}
</Text>
{offer.discount > 0 && (
<Text style={styles.discount}>
{offer.discount}% OFF
</Text>
)}
<TouchableOpacity
style={styles.button}
onPress={() => {
// Stripe Checkoutへ遷移
// handlePurchase(offer.plan, offer.discount);
}}
>
<Text style={styles.buttonText}>
詳しく見る
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => setVisible(false)}>
<Text style={styles.dismiss}>後で</Text>
</TouchableOpacity>
</Animated.View>
);
}
const styles = StyleSheet.create({
container: {
position: 'absolute', bottom: 20, left: 16, right: 16,
backgroundColor: '#FFF', borderRadius: 16, padding: 20,
shadowColor: '#000', shadowOpacity: 0.15, shadowRadius: 10,
elevation: 5,
},
title: { fontSize: 16, fontWeight: '600', marginBottom: 8 },
discount: {
fontSize: 24, fontWeight: 'bold', color: '#E91E63',
marginBottom: 12
},
button: {
backgroundColor: '#2196F3', padding: 14, borderRadius: 10,
alignItems: 'center',
},
buttonText: { color: '#FFF', fontSize: 16, fontWeight: '600' },
dismiss: {
textAlign: 'center', color: '#999', marginTop: 12, fontSize: 14,
},
});効果測定とフィードバックループ
レコメンデーションの効果を継続的に改善するには、結果データをAIエージェントにフィードバックする点が肝心です。
提示されたオファーに対するユーザーの反応(クリック率、購入率、却下率)をトラッキングし、次回のレコメンデーション生成時にその履歴をプロンプトに含めます。これにより、エージェントは「このタイプのユーザーには割引よりも機能訴求の方が効果的」といったパターンを学習し、レコメンデーションの精度が継続的に向上します。
全体を振り返って — 「売り込まない」収益化の実現
AIエージェントによるレコメンデーション収益化の本質は、ユーザー体験を損なわずに収益を最大化することです。ユーザーが本当に必要としているタイミングで、本当に価値のあるオファーを提示する——この「売り込まない収益化」こそが、長期的に持続可能なビジネスモデルです。
まずはシンプルな行動スコアリングから始め、AIエージェントによるパーソナライズを段階的に導入してみてください。
アプリの収益化についてさらに詳しく知りたい方は、Rork サブスクリプション収益モデル設計やRork Maxマネタイズ完全ガイドもぜひご参考にしてください。
12年の個人開発で見えてきたこと
リリース直後にチェックしているもの
- クラッシュレポートの上位エラーを24時間ごとに確認しているか
- AdMob/StoreKit の収益ダッシュボードに異常検知が出ていないか
- ユーザーレビューに技術的な指摘が含まれていないか