取り組みの背景:Claude APIの高度な機能があなたのアプリを変える
Rork Maxで構築したAIアプリが、気づいたらAPI費用で予算を圧迫していたり、思ったより回答品質が低かったりという経験はないでしょうか。Anthropicが提供するClaude APIには、こうした課題を根本から解決する強力な機能が存在します。それがPrompt CachingとExtended Thinkingです。
Prompt Cachingは、繰り返し送信されるシステムプロンプトや大量のコンテキストをキャッシュすることで、APIコストを最大90%削減し、レスポンス速度を大幅に改善できる機能です。Extended Thinkingは、Claude 3.7 Sonnetから導入された機能で、回答を返す前に内部で深い思考プロセスを走らせることで、複雑な推論・数学的問題・多段階の意思決定などのタスクで格段に高い精度を実現します。
ここで扱うのはRork Maxで開発するモバイルアプリにこれらの機能を組み込む実践的な方法を、動作するコードと共に詳しく解説します。対象読者は、すでにClaude APIの基本的な使い方を理解している方(rork-ai/claude-api-ai-assistant-app参照)で、さらに一歩進んだ実装を目指している開発者の方々です。
Prompt Cachingの仕組みと効果
なぜPrompt Cachingが重要か
通常のClaude APIリクエストでは、リクエストのたびに入力トークン全体を処理します。長いシステムプロンプトや、RAGで取得した大量のドキュメントを毎回送信する設計では、トークン消費が膨大になります。
Prompt Cachingを使うと、Claude APIサーバーが入力の一部をキャッシュします。次回以降のリクエストでキャッシュヒットすれば、そのトークン分の処理をスキップできるため、次のような効果が得られます。
- コスト削減: キャッシュヒット時の入力トークン単価がbase priceの約10%に
- 速度向上: キャッシュヒット時のTime to First Tokenが大幅短縮(場合により80%以上)
- 実用的なコンテキスト活用: 長いドキュメントや詳細なシステムプロンプトを気にせず使える
キャッシュが有効な典型パターン
Prompt Cachingが特に効果を発揮するユースケースを整理しておきましょう。
- チュートリアルアプリ: 数万文字のマニュアル・教科書をコンテキストに持つAIアシスタント
- カスタマーサポートBot: 詳細なFAQ・製品仕様をシステムプロンプトに埋め込むBot
- 法律・医療系アプリ: 長大な規約・診断基準をコンテキストとして保持する専門アプリ
- コード解析ツール: 大規模なコードベースをコンテキストとして渡す開発支援ツール
Extended Thinkingの仕組みと活用場面
「考える」AIアシスタントを作る
Extended Thinkingは、Claude 3.7 Sonnetが持つ特別な能力です。通常のClaudeは入力を受け取って即座に回答しますが、Extended Thinkingを有効にすると、内部で思考の連鎖(思考トークン)を生成してから最終的な回答を返します。この思考プロセスはAPIレスポンスに含まれるため、アプリ上で「考え中...」としてユーザーに示したり、デバッグに活用したりできます。
Extended Thinkingが特に有効なシナリオです。
- 数学・論理パズル解法: 複数ステップの計算や証明が必要なシナリオ
- コード生成: アーキテクチャを考慮した高品質なコードの生成
- 比較分析・意思決定支援: 多数の選択肢をトレードオフ込みで評価
- 長文のプランニング: 旅行計画・事業計画・学習カリキュラム設計
- 科学的・工学的問題: 物理・化学・エンジニアリングの複雑な計算
注意点: Extended Thinkingはトークンを多く消費するため、「何でもExtended Thinkingで」は得策ではありません。簡単な質疑応答や雑談には通常モードで十分です。複雑さに応じて動的に切り替える設計がベストプラクティスです。
環境準備とRork Maxでの実装設定
前提条件
この記事の実装例を試すには、以下が必要です。
- Rork Max アカウント(Claudeコードモードが利用可能)
- Anthropic APIキー(有料プラン推奨)
- Expo SDK 52以上
- Node.js 18以上(ローカル確認用)
APIキーの安全な管理
APIキーは絶対にソースコードにハードコードしてはいけません。Rork Maxでは、Supabase Edge Functionsまたは自前のバックエンドを介してClaude APIを呼び出す設計を推奨します。
// ❌ 絶対にやってはいけない — APIキーの直書き
const response = await fetch('https://api.anthropic.com/v1/messages', {
headers: {
'x-api-key': 'YOUR_API_KEY', // ← プレースホルダー。実際のキーをここに書かない
},
});
// ✅ 正しい設計 — バックエンド(Supabase Edge Function等)経由
const response = await fetch('https://your-project.supabase.co/functions/v1/claude-proxy', {
headers: {
'Authorization': `Bearer ${supabaseAnonKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ userMessage: message }),
});
セキュアなClaude APIプロキシの実装については、Supabase Edge Functionsを活用したRLSとAPIデザイン完全ガイドも参考にしてください。
Supabase Edge Functionのベースセットアップ
// supabase/functions/claude-proxy/index.ts
import Anthropic from 'npm:@anthropic-ai/sdk@0.39.0';
const client = new Anthropic({
apiKey: Deno.env.get('ANTHROPIC_API_KEY'),
});
Deno.serve(async (req) => {
if (req.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
const { userMessage, conversationHistory, enableExtendedThinking } = await req.json();
// ここでPrompt Cachingを設定(後述)
// ...
});
Prompt Cachingの実装
cache_controlフィールドの設定方法
Prompt Cachingを有効にするには、APIリクエストの特定の箇所にcache_control: { type: "ephemeral" }を追加します。現在サポートされているキャッシュポイントは最大4箇所で、1回のリクエストで複数設定できます。
// supabase/functions/claude-proxy/index.ts(Prompt Caching対応版)
const SYSTEM_PROMPT_BASE = `
あなたはRorkアプリ開発の専門家AIアシスタントです。
ユーザーのモバイルアプリ開発に関する質問に、具体的で実践的な回答をします。
## 専門知識
- React Native / Expo によるクロスプラットフォーム開発
- Supabase / Firebase によるバックエンド構築
- App Store / Google Play の審査・公開プロセス
- Stripe によるサブスクリプション課金の実装
- UI/UX設計とパフォーマンス最適化
## 回答スタイル
- コード例を必ず含める
- エラーが起きやすい箇所を事前に警告する
- 初心者にも理解しやすい平易な言葉を使う
(...長大なシステムプロンプトが続く場合、ここに全文を含める...)
`.trim();
// 長大なドキュメントやFAQをコンテキストとして追加する場合
const KNOWLEDGE_BASE = `
## Extended Thinkingの実装
### thinking設定でClaudeを「考えさせる」
Extended Thinkingを有効にするには、`thinking`パラメータを追加します。`budget_tokens`で思考トークンの上限を指定できます。
```typescript
// Extended Thinking対応のEdge Function
async function callClaudeWithThinking(
userMessage: string,
thinkingBudget: number = 8000
) {
const response = await client.messages.create({
model: 'claude-sonnet-4-6', // Extended ThinkingはSonnet系推奨
max_tokens: thinkingBudget + 4096, // 思考トークン + 回答トークンの合計
thinking: {
type: 'enabled',
budget_tokens: thinkingBudget, // 思考に使うトークンの上限(1024〜100000)
},
system: [
{
type: 'text',
text: SYSTEM_PROMPT_BASE,
cache_control: { type: 'ephemeral' }, // キャッシュとExtended Thinkingの併用も可能
},
],
messages: [{ role: 'user', content: userMessage }],
});
// レスポンスには思考ブロックと回答ブロックが含まれる
const thinkingBlocks = response.content.filter(b => b.type === 'thinking');
const textBlocks = response.content.filter(b => b.type === 'text');
return {
thinking: thinkingBlocks.map(b => (b as any).thinking).join('\n'),
answer: textBlocks.map(b => (b as any).text).join('\n'),
usage: response.usage,
};
}
複雑さに応じた動的切り替え
すべてのメッセージにExtended Thinkingを使うと不必要にコストが増加します。質問の複雑さを判定して動的に切り替えるパターンが実用的です。
// 質問の複雑さを簡易判定するユーティリティ
function assessComplexity(message: string): 'simple' | 'moderate' | 'complex' {
const complexKeywords = [
'設計', 'アーキテクチャ', '比較', '最適化', '数学', '計算',
'why', 'explain', 'compare', 'optimize', 'design', 'architecture',
'どちらが', 'どのように', 'なぜ', '分析', 'evaluate',
];
const complexityScore = complexKeywords.filter(kw =>
message.toLowerCase().includes(kw)
).length;
const isLong = message.length > 200; // 長い質問は複雑な可能性が高い
if (complexityScore >= 2 || (complexityScore >= 1 && isLong)) return 'complex';
if (complexityScore >= 1 || isLong) return 'moderate';
return 'simple';
}
// 複雑さに応じて設定を変える統合関数
async function callClaude(userMessage: string, history: any[]) {
const complexity = assessComplexity(userMessage);
switch (complexity) {
case 'complex':
// Extended Thinking + Prompt Cachingの両方を使用
return callClaudeWithThinking(userMessage, 16000); // 思考に16Kトークン
case 'moderate':
// Extended Thinkingなし、Prompt Cachingのみ
return callClaudeWithCache(userMessage, history);
case 'simple':
default:
// 通常リクエスト(最速・最安)
return client.messages.create({
model: 'claude-haiku-4-5-20251001', // 簡単な質問はHaikuで十分
max_tokens: 1024,
messages: [...history, { role: 'user', content: userMessage }],
});
}
}
Rorkアプリ側:思考プロセスの表示UI
Extended Thinkingの「考え中」状態をユーザーに示す、エンゲージメントの高いUIを実装しましょう。
// app/components/ThinkingIndicator.tsx
import React, { useEffect, useRef } from 'react';
import { View, Text, Animated, StyleSheet } from 'react-native';
interface ThinkingIndicatorProps {
isThinking: boolean;
thinkingText?: string; // 思考内容を途中で表示する場合(上級者向け)
}
export function ThinkingIndicator({ isThinking, thinkingText }: ThinkingIndicatorProps) {
const dotOpacity = useRef(new Animated.Value(0)).current;
useEffect(() => {
if (!isThinking) return;
const animation = Animated.loop(
Animated.sequence([
Animated.timing(dotOpacity, { toValue: 1, duration: 600, useNativeDriver: true }),
Animated.timing(dotOpacity, { toValue: 0.3, duration: 600, useNativeDriver: true }),
])
);
animation.start();
return () => animation.stop();
}, [isThinking]);
if (!isThinking) return null;
return (
<View style={styles.container}>
<Animated.View style={[styles.dot, { opacity: dotOpacity }]} />
<Text style={styles.label}>深く考えています...</Text>
{thinkingText && (
<Text style={styles.thinkingPreview} numberOfLines={2}>
{thinkingText}
</Text>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
padding: 12,
backgroundColor: '#F0F4FF',
borderRadius: 12,
marginVertical: 8,
gap: 8,
},
dot: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: '#7C6AF4',
},
label: {
fontSize: 14,
color: '#5B52B5',
fontWeight: '500',
},
thinkingPreview: {
fontSize: 11,
color: '#888',
fontStyle: 'italic',
flex: 1,
},
});
ストリーミングUIの最適化
ストリーミングでUXを劇的に改善する
Extended Thinkingと組み合わせる場合、最初のトークンが返ってくるまでに数秒かかることがあります。ストリーミングAPIを使うと、思考中は思考トークンが、回答生成が始まると即座にテキストが流れるため、体感速度が大きく改善します。
// Edge Function — ストリーミング対応版
Deno.serve(async (req) => {
const { userMessage, enableExtendedThinking } = await req.json();
const stream = await client.messages.stream({
model: 'claude-sonnet-4-6',
max_tokens: enableExtendedThinking ? 20000 : 4096,
...(enableExtendedThinking && {
thinking: { type: 'enabled', budget_tokens: 10000 },
}),
system: [
{
type: 'text',
text: SYSTEM_PROMPT_BASE,
cache_control: { type: 'ephemeral' },
},
],
messages: [{ role: 'user', content: userMessage }],
});
// SSE(Server-Sent Events)形式でクライアントにストリーム
const readable = new ReadableStream({
async start(controller) {
const encoder = new TextEncoder();
for await (const chunk of stream) {
if (chunk.type === 'content_block_delta') {
if (chunk.delta.type === 'thinking_delta') {
// 思考中のイベント
controller.enqueue(encoder.encode(
`data: ${JSON.stringify({ type: 'thinking', text: chunk.delta.thinking })}\n\n`
));
} else if (chunk.delta.type === 'text_delta') {
// 回答テキストのイベント
controller.enqueue(encoder.encode(
`data: ${JSON.stringify({ type: 'text', text: chunk.delta.text })}\n\n`
));
}
}
}
controller.enqueue(encoder.encode('data: [DONE]\n\n'));
controller.close();
},
});
return new Response(readable, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
},
});
});
// app/hooks/useStreamingChat.ts — Rork側のストリーミング受信
import { useState, useCallback } from 'react';
export function useStreamingChat() {
const [streamingText, setStreamingText] = useState('');
const [thinkingText, setThinkingText] = useState('');
const [isThinking, setIsThinking] = useState(false);
const [isStreaming, setIsStreaming] = useState(false);
const sendMessage = useCallback(async (userMessage: string, enableThinking: boolean) => {
setStreamingText('');
setThinkingText('');
setIsThinking(enableThinking);
setIsStreaming(true);
try {
const response = await fetch(
`${process.env.EXPO_PUBLIC_SUPABASE_URL}/functions/v1/claude-stream`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ userMessage, enableExtendedThinking: enableThinking }),
}
);
const reader = response.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n').filter(line => line.startsWith('data: '));
for (const line of lines) {
const data = line.replace('data: ', '');
if (data === '[DONE]') {
setIsThinking(false);
setIsStreaming(false);
return;
}
const parsed = JSON.parse(data);
if (parsed.type === 'thinking') {
setThinkingText(prev => prev + parsed.text);
} else if (parsed.type === 'text') {
setIsThinking(false); // 回答が始まったら「考え中」を解除
setStreamingText(prev => prev + parsed.text);
}
}
}
} catch (err) {
console.error('Streaming error:', err);
setIsStreaming(false);
setIsThinking(false);
}
}, []);
return { streamingText, thinkingText, isThinking, isStreaming, sendMessage };
}
コスト最適化とレート制限対策
APIコストの計算と最適化指針
Prompt Cachingを活用した場合のコスト比較を確認しておきましょう(2026年4月時点の概算。最新の価格はAnthropicの公式サイトで確認してください)。
Claude Sonnet系の概算コスト構造(参考値):
- 通常入力トークン($3/Mトークン): 毎回のリクエストで全入力トークンを課金
- キャッシュ書き込みトークン($3.75/Mトークン): キャッシュ作成時のみ(初回は若干高め)
- キャッシュ読み込みトークン($0.30/Mトークン): 通常の10%のコスト!
たとえば、5,000トークンのシステムプロンプトを1日100回リクエストするアプリの場合:
- キャッシュなし: 5,000 × 100 × $3/M = $1.50/日
- キャッシュあり(1回書き込み + 99回ヒット): 5,000 × $3.75/M + 5,000 × 99 × $0.30/M = $0.167/日
コスト削減効果は約89%。1か月換算で$45が$5に削減されます。
レート制限への対応
本番アプリではAPIのレート制限も考慮が必要です。実用的なリトライ処理を実装しましょう。
// lib/claudeRetry.ts — エクスポネンシャルバックオフ付きリトライ
async function callClaudeWithRetry<T>(
fn: () => Promise<T>,
maxRetries: number = 3
): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error: any) {
const isRateLimitError = error.status === 429;
const isServerError = error.status >= 500;
if (attempt === maxRetries || (!isRateLimitError && !isServerError)) {
throw error;
}
// レート制限の場合はRetry-Afterヘッダーを優先
const retryAfter = error.headers?.['retry-after'];
const waitMs = retryAfter
? parseInt(retryAfter) * 1000
: Math.min(1000 * Math.pow(2, attempt), 30000); // 最大30秒
console.log(`Rate limited. Retrying after ${waitMs}ms (attempt ${attempt + 1}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, waitMs));
}
}
throw new Error('Max retries exceeded');
}
// 使用例
const result = await callClaudeWithRetry(() =>
callClaudeWithCache(userMessage, conversationHistory)
);
実践的なユースケース:ビジネスへの応用
ユースケース1:AIプログラミングメンター
Rorkで作ったアプリ開発チュートリアルアプリに、大量のドキュメントを持つAIメンターを実装する場合、Prompt Cachingが威力を発揮します。Expo/React Nativeの公式ドキュメント全文(数十万トークン)をキャッシュすれば、APIコストを抑えながら正確な技術サポートが提供できます。
ユースケース2:AIビジネスプランアドバイザー
起業家向けのアプリで、「この事業計画を評価してください」というリクエストに対してExtended Thinkingを使うと、多角的な分析(市場性・競合・財務リスク)を経て、格段に質の高いフィードバックが得られます。ユーザーはAIが「本気で考えてくれている」実感を持てるため、有料プランへのコンバージョン率向上も期待できます。
ユースケース3:法律・規約チェックBot
利用規約や契約書の確認を行うアプリでは、法的な正確さが重要です。Extended Thinkingで慎重な解釈をさせることで、見落とし率を大幅に下げられます。また、法律ドキュメント(民法・各種規制)をキャッシュしておくことで、低コストかつ高品質な回答が実現します。
ビジネス活用のさらなる戦略については、AIエージェントを活用したアプリ内推薦・収益化の高度な設計もご覧ください。
まとめ
ここで扱うのはClaude APIの高度な機能であるPrompt CachingとExtended Thinkingを、Rorkアプリに組み込む実践的な手法を解説しました。
重要なポイントをまとめます。
- Prompt Cachingはシステムプロンプト・知識ベースに
cache_control: { type: "ephemeral" }を追加するだけで有効になり、APIコストを最大90%削減できる
- Extended Thinkingは
thinking: { type: "enabled", budget_tokens: N }で有効化し、複雑な推論を要するシナリオで劇的に品質が向上する
- 複雑さに応じた動的切り替えにより、コストと品質のバランスを最適化できる
- ストリーミングUIを組み合わせることで、思考時間が長くてもユーザー体験を損なわない
これらの機能を組み合わせることで、「コストが高くて実用に耐えない」「AI回答の質が低い」という2大課題を同時に解決できます。ぜひご自身のRorkアプリに取り入れてみてください。
さらに深いAI実装を学びたい方には、Claude API × Function Callingでインテリジェントなアシスタントアプリを作る完全ガイドと、RAGとベクトル検索でAIチャットアプリを構築するもおすすめです。また、AIアプリ実装の全体像を体系的に学ぶなら「Claude/GPT APIで作るAIアプリ開発入門」(著者: 岡田拓也 著)が体系的にまとまっており、実践の参考になります。