【12章】オリジナルアプリ制作の振り返り

【12章】オリジナルアプリ制作の振り返り

公開: 2025年12月07日

学習振り返り
要約
  • AI、RAG、楽天APIを活用したパーソナライズドガジェット推薦システム「Gadget Concierge」を開発しました。
  • 構築中にAI応答の一貫性、埋め込み生成のコスト、楽天APIのレート制限といった課題に直面し、解決策を実装しました。
  • プロジェクトを通じて得られた知見は、モダンなWebアプリケーションの開発やセキュリティ対策に役立つものとなりました。
音声で記事を再生

オリジナルアプリ制作振り返り:AI × RAG × 楽天APIで実現したパーソナライズドガジェット推薦システム

はじめに

Gadget Concierge」は、ユーザーの回答に基づいて最適なガジェットを推薦するWebアプリケーションです。AI、RAG、楽天市場APIを組み合わせて構築しました。本記事では、開発を通じて得た知見と課題を共有します!

プロジェクト概要

技術スタック

  • フロントエンド: Next.js 14 (App Router), React, TypeScript, Tailwind CSS

  • バックエンド: Next.js API Routes, Server Actions

  • データベース: PostgreSQL (Supabase) + Prisma ORM

  • 認証: Supabase Auth (メール/パスワード + Google OAuth)

  • AI: Google Gemini 2.0 Flash, Google Embeddings API

  • AIエージェント: Mastra

  • 外部API: 楽天市場商品検索API

  • ベクター検索: pgvector

  • レート制限: Upstash Ratelimit + Vercel KV

主な機能

1. カテゴリ別質問システム: カテゴリごとの動的質問生成

2. AI推薦エンジン: ユーザー回答を分析し、最適な3製品を推薦

3. RAG検索: ベクター埋め込みによるセマンティック検索

4. 楽天市場連携: 商品情報の自動同期とアフィリエイト対応

5. ユーザー履歴管理: 過去の診断結果と推薦履歴の保存

技術的な挑戦と学び

1. RAG(Retrieval-Augmented Generation)の実装

課題

  • テキスト検索では「コスパ重視」「初心者向け」などの意図を捉えにくい

  • 製品数が増えると精度が低下

解決策

Google Embeddings APIとpgvectorを組み合わせて実装。

typescript
// 製品の埋め込み生成
const { embedding } = await embed({
 model: google.textEmbeddingModel("text-embedding-004"),
 value: productContent,
});

// pgvectorによる類似度検索
const vectorResults = await prisma.$queryRaw`
 SELECT
 p.id,
 p.name,
 1 - (p.content_embedding <=> ${JSON.stringify(embedding)}::vector) as similarity
 FROM products p
 WHERE 1 - (p.content_embedding <=> ${JSON.stringify(embedding)}::vector) > 0.7
 ORDER BY similarity DESC
 LIMIT 20
`;

学び

  • 埋め込み生成のコストと速度のバランスが重要

  • フォールバック(テキスト検索)を用意することで可用性を確保

  • 類似度閾値(0.7)は実データで調整が必要

2. Mastraエージェントフレームワークの活用

課題

  • 構造化出力の一貫性

  • エラーハンドリングとフォールバック

解決策

Mastraの構造化出力機能を活用。

typescript
const agent = mastra.getAgent("gadgetRecommendationAgent");
 const aiResponse = await agent.generate(
 [{ role: "user", content: aiPrompt }], {
structuredOutput: {
 schema: recommendationSchema,
 errorStrategy: "fallback",
 fallbackValue: { recommendations: [] },},
});

学び

  • スキーマ定義(Zod)で型安全性とバリデーションを両立

  • errorStrategy: "fallback"でエラー時の挙動を制御

  • プロンプト設計が出力品質に大きく影響

3. 楽天市場APIとの連携

課題

  • レート制限(1秒1リクエスト)

  • 大量商品の同期

  • エラーハンドリング

解決策

並列処理対応のレート制限管理とCronジョブを実装。

typescript
// レート制限管理(並列処理対応)
let lastRakutenRequestTime = 0;
let rateLimitLock: Promise<void> = Promise.resolve();
async function waitForRateLimit(): Promise<void> {
 await rateLimitLock;
 let releaseLock: () => void;
 rateLimitLock = new Promise((resolve) => {
 releaseLock = resolve;
});
const timeSinceLastRequest = Date.now() - lastRakutenRequestTime;
 if (timeSinceLastRequest < 1000) {
 await new Promise((resolve) =>
 setTimeout(resolve, 1000 - timeSinceLastRequest));
}
 lastRakutenRequestTime = Date.now();
 releaseLock();
}

学び

  • レート制限はロック機構で並列リクエストを制御

  • リトライと指数バックオフで安定性を向上

  • バッチ処理はCronで定期実行

4. セキュリティ対策

実装した対策

1. タイミング攻撃対策: ハッシュ化比較

typescript
const tokenHash = crypto.createHash("sha256").update(token).digest();
const secretHash = crypto.createHash("sha256").update(cronSecret).digest();
if (!crypto.timingSafeEqual(tokenHash, secretHash)) {
 await delayOnFailure();
 return { isValid: false };
}


2. レート制限: Vercel KV + Upstash Ratelimit

typescript
export const cronRatelimit = new Ratelimit({
 redis: kv,
 limiter: Ratelimit.slidingWindow(10, "60 s"),
 prefix: "ratelimit:cron",
});

学び

  • サーバーレス環境では共有ストレージ(KV)が必要

  • タイミング攻撃対策はハッシュ化で実現

  • セキュリティは段階的に強化

5. データベース設計の工夫

階層カテゴリの実装

prisma
model Category {
 id String @id @default(cuid())
 name String
 parentId String?
 parentCategory Category? @relation("CategorySubCategory", fields: [parentId], references: [id])
 subCategories Category[] @relation("CategorySubCategory")
}
// ベクターカラムの追加
prisma
model Product {
 content_embedding Unsupported("vector")?
 content_for_embedding String?
 embeddings_generated_at DateTime?
}

学び

  • 階層構造は自己参照リレーションで表現

  • pgvector拡張でベクター検索を実現

  • インデックス戦略が検索性能に影響

苦労した点と解決方法

1. AI応答の一貫性

問題: 構造化出力が時々不正な形式で返る

解決:

  • Zodスキーマで厳密にバリデーション

  • errorStrategy: "fallback"でエラー時の挙動を制御

  • プロンプトに「製品名は完全一致」を明記

2. 埋め込み生成のコスト

問題: 全製品の埋め込み生成に時間とコストがかかる

解決:

  • バッチ処理で段階的に生成

  • 新規製品のみ生成

  • 非同期処理でユーザー体験を維持

3. レート制限の管理

問題: 楽天APIのレート制限を守りつつ効率的に同期

解決:

  • ロック機構で並列リクエストを制御

  • リトライロジックの実装

  • Cronジョブで定期同期

技術選定の振り返り

良かった選択

1. Next.js 14 App Router: Server Actionsでシンプルな実装

2. Prisma: 型安全性とマイグレーション管理

3. Google Gemini 2.0 Flash: 日本語理解と構造化出力

4. Supabase: 認証とデータベースを統合管理

改善の余地があった選択

1. Mastra: 学習コストがやや高い(代替案も検討)

2. Vercel KV: コスト面で要検討(Redis代替も検討)

3. 楽天API: レート制限が厳しい(代替データソースも検討)

まとめ

本プロジェクトでは、AI、RAG、外部API連携を組み合わせた推薦システムを構築しました。特にRAGの実装とセキュリティ対策に注力し、実用的なシステムに仕上げました。

今後、継続的な最適化が必要です。特にRAGの精度向上とコスト最適化が重要!!

このプロジェクトを通じて、モダンなWebアプリケーション開発、AI統合、セキュリティ対策について多くの知見を得られました。同じようなプロジェクトに取り組む方の参考になれば幸いです。


技術スタックまとめ:

  • Next.js 14 + TypeScript

  • Supabase + Prisma

  • Google Gemini 2.0 Flash + Embeddings API

  • Mastra

  • pgvector

  • 楽天市場API

主要な成果物:

  • パーソナライズド推薦システム

  • RAG検索機能

  • セキュアなCronジョブ実装

  • スケーラブルなデータベース設計

シェア!

XThreads
ShiftB Logo
icon
三嶋雅幸
SES企業でエンジニアをしています。今後のキャリアアップのために、頑張っていきたいと思います。
Loading...
記事一覧に戻る
XThreads
0