はじめに
現在、Firebase Genkitをバックエンドに採用し、スケジュール管理や目標設定をサポートするパーソナルAIエージェントアプリを開発しています。
AIアプリにおいて「ユーザーにいかに継続して使ってもらうか」が課題になってきますが、その解決策の一つとして 「ユーザーが寝ている間に、AIがその日の会話履歴を読み込み、感情を分析して『今日の日記(ジャーナル)』を自動生成する」 という機能を実装しました。
ユーザーは朝起きてアプリを開くと、AIからの振り返りメッセージと昨日の日記を読むことができ、体験価値が大きく向上します。
この記事では、Firebase GenkitとCloud Functions(Cloud Scheduler)を組み合わせ、「夜間バッチでFirestoreの会話ログをLLMに分析させ、コンテンツを自動生成するアーキテクチャ」 の実装方法を紹介します。
※なお、AIの出力品質を左右する「ユーザープロファイルの構築ロジック」などのコアなプロンプトは簡略化して記載しています。
対象読者
- Firebase Genkitを活用した非同期(バッチ)処理の実装方法を知りたい方
- LLMを使ってユーザーの定着率(UX)を向上させるアイデアを探している方
- Cloud Functions (v2) のスケジュール実行の実践的な使い方を知りたい方
アーキテクチャの概要
この機能は、以下の3つのステップで構成される夜間バッチ処理です。
- トリガー: Cloud Schedulerが毎日深夜(例:午前2時)にCloud Functionsを起動
- データ収集: Firestoreから、各ユーザーの「その日のAIとのチャット履歴」を取得
-
Genkitによる分析・生成:
- チャット履歴から「何があったか」「どんな感情だったか(ポジティブ/ネガティブ)」を分析
- ユーザーの性格プロファイル(別で構築済みのもの)に合わせて文体を調整し、日記を生成
- 結果をFirestoreの「日記コレクション」に保存
ユーザーの操作をブロックすることなく、LLMの重い処理を夜間に分散できるのが最大のメリットです。
実装例
実際のバックエンド構成をベースに、要点を絞って解説します。
1. 日記を生成するGenkit Flowの定義
まずは、チャット履歴を受け取って日記を生成するGenkit Flowを作成します。
(抜粋)
import { z } from 'genkit';
import { ai, db } from '../core/bootstrap';
import { vertexAI } from '@genkit-ai/google-genai';
// 1. 入出力スキーマの定義
const GenerateDiaryInputSchema = z.object({
userId: z.string(),
date: z.string(), // 例: "2026-03-19"
chatHistory: z.string(), // その日のチャット履歴のテキスト
userProfile: z.string(), // ユーザーの性格や口調のプロファイル(事前に構築・要約済みのもの)
});
const GenerateDiaryOutputSchema = z.object({
diaryContent: z.string(),
emotionScore: z.number().describe("-1.0 (ネガティブ) 〜 1.0 (ポジティブ) の感情スコア"),
});
// 2. 日記生成フロー
export const generateDailyDiaryFlow = ai.defineFlow({
name: 'generateDailyDiaryFlow',
inputSchema: GenerateDiaryInputSchema,
outputSchema: GenerateDiaryOutputSchema,
}, async ({ userId, date, chatHistory, userProfile }) => {
if (!chatHistory) {
return { diaryContent: "今日はあまりお話ししませんでしたね。明日はどんな一日になるか楽しみです!", emotionScore: 0 };
}
// ★ポイント: ユーザーのプロファイル(性格)に合わせて出力のトーンを変える
const prompt = `
あなたはユーザーを深く理解する専属のエージェントです。
以下のチャット履歴を分析し、今日の出来事と感情を要約した「短い日記」を作成してください。
[ユーザープロファイル]
${userProfile}
[今日のチャット履歴]
${chatHistory}
[出力ルール]
- ユーザーに寄り添った、温かいトーンで書くこと。
- プロファイルに合わせた口調(フランク、丁寧など)を意識すること。
- 全体の感情スコアを -1.0 〜 1.0 で評価すること。
`;
// 夜間バッチなので、推論コストと精度のバランスが良いモデルを選択
const response = await ai.generate({
prompt,
model: vertexAI.model('gemini-3-flash-preview'),
output: { schema: GenerateDiaryOutputSchema },
});
const result = response.output;
if (result) {
// 3. 生成された日記をFirestoreに保存
await db.collection('users').doc(userId).collection('diaries').doc(date).set({
content: result.diaryContent,
emotion: result.emotionScore,
createdAt: new Date(),
});
}
return result || { diaryContent: "日記の生成に失敗しました。", emotionScore: 0 };
});
2. Cloud Functionsでのスケジュール(バッチ)実行
次に、作成したGenkit Flowを全ユーザーに対して実行するCloud Functionsのスケジュールトリガーを定義します。
(抜粋)
import { onSchedule } from 'firebase-functions/v2/scheduler';
import { generateDailyDiaryFlow } from './dailyAgent';
import { db } from './firebaseConfig';
// 毎日 深夜2時に実行
export const dailyDiaryBatch = onSchedule({
region: 'asia-northeast1',
schedule: "0 2 * * *", // cron書式
memory: '2GiB',
timeZone: 'Asia/Tokyo',
timeoutSeconds: 540, // 複数ユーザーのLLM処理が走るためタイムアウトを長めに設定
}, async (event) => {
console.log('[Batch] Starting daily diary generation...');
const today = new Date().toISOString().split('T')[0];
try {
// 1. 全ユーザーを取得(実運用ではアクティブユーザーのみに絞るなどの工夫が必要)
const usersSnapshot = await db.collection('users').get();
// 2. 各ユーザーごとに処理を実行
// ※注意: ユーザー数が多い場合は Promise.all() で並列実行するか、キュー(Cloud Tasks)に分割する
for (const userDoc of usersSnapshot.docs) {
const userId = userDoc.id;
const userProfile = userDoc.data().profileSummary || "";
// Firestoreからその日のチャット履歴を結合して取得(関数化している想定)
const chatHistory = await fetchTodayChatHistory(userId, today);
// Genkit Flowを呼び出して日記を生成・保存
await generateDailyDiaryFlow.run({
userId,
date: today,
chatHistory,
userProfile
});
console.log(`[Batch] Diary generated for user: ${userId}`);
}
console.log('[Batch] Daily diary generation completed.');
} catch (error) {
console.error('[Batch] Failed to generate diaries:', error);
}
});
得られた効果と運用上のポイント
1. UX(ユーザー体験)の向上
ユーザー自身が「日記を書く」という労力を払うことなく、アプリを開くだけで自分の日々の記録が自動で蓄積されていきます。
また、感情スコア(emotionScore)も一緒に保存しているため、フロントエンド(React Native)側で「今週は少しお疲れ気味ですね」といった メンタルヘルスのトラッキング表示 にも応用できています。
2. レイテンシを気にせず重い処理ができる
ユーザーがボタンを押して数秒〜十数秒待たされるのは苦痛ですが、夜間バッチであればLLMの推論時間を気にする必要がありません。
プロンプトに十分なコンテキスト(過去の記憶やプロファイル)を詰め込んで、質の高い文章を生成させることができます。
3. バッチ処理のタイムアウトとスケーリングに注意
ユーザー数が増えてくると、Cloud Functionsの最大タイムアウト時間(通常は540秒など)や、LLM APIのレートリミット(1分あたりのリクエスト上限)に引っかかる可能性があります。
スケールアウトを見据える場合は、上記のコードのように for ループで同期的に回すのではなく、 Cloud Tasks(タスクキュー)を使ってユーザーごとに非同期ワーカーを立ち上げる構成 への移行をおすすめします。
おわりに
Firebase Genkitは、ユーザーのリアルタイムな入力を処理するチャットボット用途だけでなく、今回のような「データの裏側での分析・生成パイプライン」としても非常に扱いやすいフレームワークです。