取り組みの背景:なぜ今 Responses API なのか
2025年3月、OpenAI は従来の Assistants API を刷新した Responses API を正式リリースしました。これは Chat Completions API のシンプルさと、Assistants API のステートフル性・ツール統合能力を融合させた次世代 API です。
Rork Max でアプリ開発をする個人開発者にとって、この API の登場は大きな転換点です。これまで「AIチャット」「ファイル検索」「Web検索」を個別に実装していた複雑なバックエンドが、単一の API 呼び出しで実現できるようになりました。
ここで扱うのはResponses API を Rork Max アプリに組み込み、ステートフルな AI エージェント を構築する完全な手順を解説します。バックエンドは Cloudflare Workers、フロントエンドは Rork の React Native UIという構成で、実際にリリースできるレベルのアプリを作り上げることを目指します。
Responses API の核心:Chat Completions との違い
Responses API が革新的な理由は、会話スレッドの状態をサーバー側で保持 できる点にあります。従来の Chat Completions API では、毎回の API 呼び出しに全会話履歴を送信する必要がありましたが、Responses API では previous_response_id を渡すだけで文脈が継続します。
主要な違いを整理します:
ステートフル管理 : response.id を保存し、次回呼び出し時に previous_response_id として渡すだけで会話が継続する
組み込みツール : web_search_preview(Web検索)、file_search(ベクトルストア検索)、code_interpreter(コード実行)がネイティブに利用可能
ストリーミング : Server-Sent Events によるリアルタイムストリーミングに対応
マルチモーダル入力 : テキスト・画像・音声の複合入力をサポート
個人開発者にとって最も嬉しい変更は、Assistants API の複雑なスレッド管理・Run管理が不要になった ことです。シンプルなコードで高度な AI 機能が実現できます。
アーキテクチャ設計:Rork + Cloudflare Workers + OpenAI
全体構成
Rork Max プロジェクトでは、Next.js App Router を使ったフロントエンドと Cloudflare Workers のエッジバックエンドが統合されています。AI エージェントのバックエンドは以下の構成で設計します:
Rork Max App (React Native / Expo)
│
▼
Cloudflare Workers (Edge Backend)
│ ├── /api/agent/chat ← メイン会話エンドポイント
│ ├── /api/agent/files ← ファイルアップロード
│ └── /api/agent/history ← 会話履歴取得
│
▼
OpenAI Responses API
│ ├── web_search_preview ← Web検索
│ ├── file_search ← ベクトルストア検索
│ └── code_interpreter ← コード実行
│
▼
Cloudflare KV (スレッドID・ユーザーセッション管理)
KV スキーマ設計
会話スレッドの管理には Cloudflare KV を活用します。
// スレッドIDの保存キー形式
// key: thread:{userId}:{agentType}
// value: { responseId: string, createdAt: number, messageCount: number }
interface ThreadState {
responseId : string ; // 最新の response_id
createdAt : number ; // Unix timestamp
messageCount : number ; // 累計メッセージ数(コスト管理用)
agentType : 'general' | 'search' | 'analyst' ;
}
Step 1:Cloudflare Workers エンドポイントの実装
パッケージのインストール
# Rork プロジェクトのルートで実行
npm install openai zod
メイン会話エンドポイント
// src/app/api/agent/chat/route.ts
import OpenAI from 'openai' ;
import { getCloudflareContext } from '@opennextjs/cloudflare' ;
interface ChatRequest {
message : string ;
agentType ?: 'general' | 'search' | 'analyst' ;
resetThread ?: boolean ;
}
export async function POST ( request : Request ) {
const { env } = await getCloudflareContext ();
const openai = new OpenAI ({ apiKey: env. OPENAI_API_KEY });
// 認証チェック(Stripe Premium ユーザーのみ)
const authHeader = request.headers. get ( 'Authorization' );
if ( ! authHeader || !await verifyPremiumToken (authHeader, env)) {
return Response. json ({ error: 'Premium membership required' }, { status: 403 });
}
const userId = await getUserIdFromToken (authHeader);
const body : ChatRequest = await request. json ();
// KV からスレッド状態を取得
const threadKey = `thread:${ userId }:${ body . agentType || 'general'}` ;
const threadStateStr = await env. KV . get (threadKey);
const threadState : ThreadState | null = threadStateStr
? JSON . parse (threadStateStr)
: null ;
// スレッドリセット処理
const previousResponseId = body.resetThread ? undefined : threadState?.responseId;
// エージェントタイプ別のシステムプロンプト
const systemPrompt = getSystemPrompt (body.agentType || 'general' );
// ツール設定(agentTypeに応じて変更)
const tools = getToolsForAgent (body.agentType || 'general' );
try {
// Responses API 呼び出し(ストリーミング)
const stream = await openai.responses. stream ({
model: 'gpt-4o' ,
input: body.message,
previous_response_id: previousResponseId,
instructions: systemPrompt,
tools: tools,
stream: true ,
});
// ストリーミングレスポンスの処理
const encoder = new TextEncoder ();
const readable = new ReadableStream ({
async start ( controller ) {
let finalResponseId : string | undefined ;
let fullText = '' ;
for await ( const event of stream) {
if (event.type === 'response.output_text.delta' ) {
// テキストチャンクをSSEで送信
const chunk = `data: ${ JSON . stringify ({
type: 'text_delta' ,
delta: event . delta ,
}) } \n\n ` ;
controller. enqueue (encoder. encode (chunk));
fullText += event.delta;
}
if (event.type === 'response.done' ) {
finalResponseId = event.response.id;
// ツール使用情報を送信
const toolUses = event.response.output
. filter ( item => item.type === 'tool_use' )
. map ( item => ({ type: item.type, name: (item as any ).name }));
if (toolUses. length > 0 ) {
const toolChunk = `data: ${ JSON . stringify ({
type: 'tool_uses' ,
tools: toolUses ,
}) } \n\n ` ;
controller. enqueue (encoder. encode (toolChunk));
}
// 完了イベント送信
const doneChunk = `data: ${ JSON . stringify ({
type: 'done' ,
responseId: finalResponseId ,
}) } \n\n ` ;
controller. enqueue (encoder. encode (doneChunk));
}
}
// KV にスレッド状態を保存(TTL: 7日間)
if (finalResponseId) {
const newState : ThreadState = {
responseId: finalResponseId,
createdAt: threadState?.createdAt || Date. now (),
messageCount: (threadState?.messageCount || 0 ) + 1 ,
agentType: body.agentType || 'general' ,
};
await env. KV . put (threadKey, JSON . stringify (newState), {
expirationTtl: 60 * 60 * 24 * 7 , // 7日
});
}
controller. close ();
},
});
return new Response (readable, {
headers: {
'Content-Type' : 'text/event-stream' ,
'Cache-Control' : 'no-cache' ,
'Connection' : 'keep-alive' ,
},
});
} catch (error) {
console. error ( 'OpenAI Responses API error:' , error);
return Response. json ({ error: 'AI service error' }, { status: 500 });
}
}
// エージェントタイプ別のツール設定
function getToolsForAgent ( agentType : string ) {
const baseTools = [];
if (agentType === 'search' || agentType === 'general' ) {
baseTools. push ({ type: 'web_search_preview' });
}
if (agentType === 'analyst' ) {
baseTools. push (
{ type: 'code_interpreter' , container: { type: 'auto' } }
);
}
return baseTools;
}
// エージェントタイプ別のシステムプロンプト
function getSystemPrompt ( agentType : string ) : string {
const prompts = {
general: `あなたはRork Labのプレミアムアシスタントです。
ユーザーのアプリ開発に関する質問に、具体的かつ実践的なアドバイスを提供してください。
必要に応じてコード例を含め、Rorkの特性(React Native / Expo / Cloudflare Workers)を考慮した回答を心がけてください。` ,
search: `あなたは最新情報に強いリサーチアシスタントです。
Web検索ツールを活用して、ユーザーの質問に最新かつ正確な情報を提供してください。
情報源のURLを明示し、情報の鮮度も伝えてください。` ,
analyst: `あなたはデータ分析の専門家です。
コードインタプリタを使って、ユーザーが提供するデータを分析し、
チャートや統計情報を生成して洞察を提供してください。` ,
};
return prompts[agentType as keyof typeof prompts] || prompts.general;
}
Step 2:Rork フロントエンドの実装
カスタムフック:useAgentChat
// hooks/useAgentChat.ts
import { useState, useCallback, useRef } from 'react' ;
interface Message {
id : string ;
role : 'user' | 'assistant' ;
content : string ;
toolUses ?: { type : string ; name : string }[];
timestamp : Date ;
}
interface UseAgentChatOptions {
agentType ?: 'general' | 'search' | 'analyst' ;
onError ?: ( error : Error ) => void ;
}
export function useAgentChat ({ agentType = 'general' , onError } : UseAgentChatOptions = {}) {
const [ messages , setMessages ] = useState < Message []>([]);
const [ isStreaming , setIsStreaming ] = useState ( false );
const [ currentStreamText , setCurrentStreamText ] = useState ( '' );
const abortControllerRef = useRef < AbortController | null >( null );
const sendMessage = useCallback ( async ( content : string ) => {
if (isStreaming) return ;
// ユーザーメッセージを追加
const userMessage : Message = {
id: `user-${ Date . now () }` ,
role: 'user' ,
content,
timestamp: new Date (),
};
setMessages ( prev => [ ... prev, userMessage]);
setIsStreaming ( true );
setCurrentStreamText ( '' );
const controller = new AbortController ();
abortControllerRef.current = controller;
try {
const response = await fetch ( '/api/agent/chat' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'Authorization' : `Bearer ${ await getAuthToken () }` ,
},
body: JSON . stringify ({ message: content, agentType }),
signal: controller.signal,
});
if ( ! response.ok) throw new Error ( 'API request failed' );
const reader = response.body ! . getReader ();
const decoder = new TextDecoder ();
let accumulatedText = '' ;
let toolUses : { type : string ; name : string }[] = [];
while ( true ) {
const { done , value } = await reader. read ();
if (done) break ;
const chunk = decoder. decode (value, { stream: true });
const lines = chunk. split ( ' \n ' );
for ( const line of lines) {
if ( ! line. startsWith ( 'data: ' )) continue ;
const data = JSON . parse (line. slice ( 6 ));
if (data.type === 'text_delta' ) {
accumulatedText += data.delta;
setCurrentStreamText (accumulatedText);
} else if (data.type === 'tool_uses' ) {
toolUses = data.tools;
} else if (data.type === 'done' ) {
// 完了 — アシスタントメッセージを確定
const assistantMessage : Message = {
id: `assistant-${ Date . now () }` ,
role: 'assistant' ,
content: accumulatedText,
toolUses: toolUses. length > 0 ? toolUses : undefined ,
timestamp: new Date (),
};
setMessages ( prev => [ ... prev, assistantMessage]);
setCurrentStreamText ( '' );
}
}
}
} catch (error) {
if (error instanceof Error && error.name !== 'AbortError' ) {
onError ?.(error);
}
} finally {
setIsStreaming ( false );
abortControllerRef.current = null ;
}
}, [isStreaming, agentType, onError]);
const stopStreaming = useCallback (() => {
abortControllerRef.current?. abort ();
setIsStreaming ( false );
setCurrentStreamText ( '' );
}, []);
const resetThread = useCallback ( async () => {
setMessages ([]);
// バックエンドのスレッドもリセット
await fetch ( '/api/agent/chat' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'Authorization' : `Bearer ${ await getAuthToken () }` ,
},
body: JSON . stringify ({ message: '---reset---' , agentType, resetThread: true }),
});
}, [agentType]);
return {
messages,
isStreaming,
currentStreamText,
sendMessage,
stopStreaming,
resetThread,
};
}
チャット UI コンポーネント
// components/AgentChatScreen.tsx
import React, { useState, useRef, useEffect } from 'react' ;
import {
View,
ScrollView,
TextInput,
TouchableOpacity,
Text,
StyleSheet,
ActivityIndicator,
Animated,
} from 'react-native' ;
import { useAgentChat } from '../hooks/useAgentChat' ;
interface AgentChatScreenProps {
agentType ?: 'general' | 'search' | 'analyst' ;
title ?: string ;
}
export function AgentChatScreen ({ agentType = 'general' , title = 'AI アシスタント' } : AgentChatScreenProps ) {
const [ inputText , setInputText ] = useState ( '' );
const scrollViewRef = useRef < ScrollView >( null );
const cursorOpacity = useRef ( new Animated. Value ( 1 )).current;
const { messages , isStreaming , currentStreamText , sendMessage , stopStreaming } = useAgentChat ({
agentType,
onError : ( err ) => console. error ( 'Chat error:' , err),
});
// カーソルアニメーション
useEffect (() => {
if (isStreaming) {
Animated. loop (
Animated. sequence ([
Animated. timing (cursorOpacity, { toValue: 0 , duration: 500 , useNativeDriver: true }),
Animated. timing (cursorOpacity, { toValue: 1 , duration: 500 , useNativeDriver: true }),
])
). start ();
} else {
cursorOpacity. setValue ( 1 );
}
}, [isStreaming]);
// 新メッセージ時にスクロール
useEffect (() => {
setTimeout (() => {
scrollViewRef.current?. scrollToEnd ({ animated: true });
}, 100 );
}, [messages, currentStreamText]);
const handleSend = async () => {
if ( ! inputText. trim () || isStreaming) return ;
const text = inputText;
setInputText ( '' );
await sendMessage (text);
};
return (
< View style = { styles.container } >
< ScrollView ref = { scrollViewRef } style = { styles.messageList } contentContainerStyle = { styles.messageListContent } >
{ messages. map (( msg ) => (
< View key = { msg.id } style = { [styles.messageBubble, msg.role === 'user' ? styles.userBubble : styles.assistantBubble] } >
{ msg.toolUses && msg.toolUses. length > 0 && (
< View style = { styles.toolBadgeContainer } >
{ msg.toolUses. map (( tool , i ) => (
< View key = { i } style = { styles.toolBadge } >
< Text style = { styles.toolBadgeText } >
{ tool.name === 'web_search_preview' ? '🔍 Web検索' :
tool.name === 'code_interpreter' ? '💻 コード実行' : tool.name }
</ Text >
</ View >
)) }
</ View >
) }
< Text style = { [styles.messageText, msg.role === 'user' ? styles.userText : styles.assistantText] } >
{ msg.content }
</ Text >
</ View >
)) }
{ /* ストリーミング中のメッセージ */ }
{ isStreaming && currentStreamText ? (
< View style = { [styles.messageBubble, styles.assistantBubble] } >
< Text style = { [styles.messageText, styles.assistantText] } >
{ currentStreamText }
< Animated.Text style = { { opacity: cursorOpacity } } >▌</ Animated.Text >
</ Text >
</ View >
) : null }
{ /* 読み込み中インジケーター */ }
{ isStreaming && ! currentStreamText ? (
< View style = { [styles.messageBubble, styles.assistantBubble, styles.loadingBubble] } >
< ActivityIndicator size = "small" color = "#666" />
</ View >
) : null }
</ ScrollView >
< View style = { styles.inputContainer } >
< TextInput
style = { styles.textInput }
value = { inputText }
onChangeText = { setInputText }
placeholder = "メッセージを入力..."
multiline
maxLength = { 2000 }
editable = { ! isStreaming }
/>
< TouchableOpacity
style = { [styles.sendButton, isStreaming && styles.stopButton] }
onPress = { isStreaming ? stopStreaming : handleSend }
>
< Text style = { styles.sendButtonText } >
{ isStreaming ? '⏹ 停止' : '送信' }
</ Text >
</ TouchableOpacity >
</ View >
</ View >
);
}
const styles = StyleSheet. create ({
container: { flex: 1 , backgroundColor: '#f8f9fa' },
messageList: { flex: 1 },
messageListContent: { padding: 16 , gap: 12 },
messageBubble: {
maxWidth: '85%' ,
borderRadius: 16 ,
padding: 12 ,
},
userBubble: {
alignSelf: 'flex-end' ,
backgroundColor: '#007AFF' ,
},
assistantBubble: {
alignSelf: 'flex-start' ,
backgroundColor: '#ffffff' ,
shadowColor: '#000' ,
shadowOffset: { width: 0 , height: 1 },
shadowOpacity: 0.1 ,
shadowRadius: 4 ,
elevation: 2 ,
},
loadingBubble: { padding: 16 },
messageText: { fontSize: 15 , lineHeight: 22 },
userText: { color: '#ffffff' },
assistantText: { color: '#1a1a1a' },
toolBadgeContainer: { flexDirection: 'row' , flexWrap: 'wrap' , gap: 4 , marginBottom: 8 },
toolBadge: {
backgroundColor: '#e8f4f8' ,
borderRadius: 8 ,
paddingHorizontal: 8 ,
paddingVertical: 3 ,
},
toolBadgeText: { fontSize: 11 , color: '#0066cc' , fontWeight: '600' },
inputContainer: {
flexDirection: 'row' ,
padding: 12 ,
backgroundColor: '#ffffff' ,
borderTopWidth: 1 ,
borderTopColor: '#e0e0e0' ,
gap: 8 ,
alignItems: 'flex-end' ,
},
textInput: {
flex: 1 ,
borderWidth: 1 ,
borderColor: '#e0e0e0' ,
borderRadius: 20 ,
paddingHorizontal: 16 ,
paddingVertical: 10 ,
maxHeight: 120 ,
fontSize: 15 ,
backgroundColor: '#f8f9fa' ,
},
sendButton: {
backgroundColor: '#007AFF' ,
borderRadius: 20 ,
paddingHorizontal: 16 ,
paddingVertical: 10 ,
justifyContent: 'center' ,
},
stopButton: { backgroundColor: '#FF3B30' },
sendButtonText: { color: '#ffffff' , fontWeight: '600' , fontSize: 15 },
});
Step 3:エージェントタイプ別の活用戦略
1. 検索エージェント(search):最新情報を必要とするユースケース
Web検索ツールを組み込んだ検索エージェントは、以下のようなアプリに最適です:
最新ニュース収集アプリ : 特定トピックの最新情報を AI が要約して提供
価格比較アプリ : ECサイトの価格情報をリアルタイムで比較・分析
競合調査ツール : App Store のレビューや競合アプリ情報を自動収集
実装例(検索エージェントのプロンプト調整):
// 特定ドメインに限定した検索エージェント
const restrictedSearchPrompt = `
あなたはApp Store最適化(ASO)の専門アシスタントです。
Web検索を使って以下の情報のみを調査してください:
1. App Storeのカテゴリランキング変動
2. 競合アプリの最新レビュートレンド
3. ASO関連の最新ベストプラクティス
個人情報や機密情報の収集は行わないでください。
` ;
2. 分析エージェント(analyst):データ分析ユースケース
コードインタプリタを活用した分析エージェントは、以下のシナリオで威力を発揮します:
収益分析ダッシュボード : AdMob / Stripe の収益データを CSV でアップロードし、AI が自動分析・グラフ生成
ユーザー行動分析 : Firebase Analytics のエクスポートデータを自然言語で質問・分析
A/B テスト結果評価 : 統計的有意性の計算を AI が自動実行
// ファイルアップロード + 分析エージェントの連携
const analyzeRevenue = async ( csvData : string ) => {
// まずファイルをアップロード
const uploadResponse = await fetch ( '/api/agent/files' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'Authorization' : `Bearer ${ authToken }` ,
},
body: JSON . stringify ({ content: csvData, filename: 'revenue.csv' }),
});
const { fileId } = await uploadResponse. json ();
// ファイルIDを含めてメッセージ送信
await sendMessage (
`アップロードしたCSV(fileId: ${ fileId })の収益データを分析してください。
月次トレンド、上位収益源、異常値を特定し、改善提案を含めてください。`
);
};
Step 4:コスト管理とレート制限
トークン消費量のモニタリング
Responses API はトークン単位での課金です。モバイルアプリで商用運用する場合、コスト管理は不可欠です。
// src/app/api/agent/usage/route.ts
export async function GET ( request : Request ) {
const { env } = await getCloudflareContext ();
const userId = await getUserIdFromToken (request.headers. get ( 'Authorization' ) ! );
// 当月の使用量をKVから取得
const month = new Date (). toISOString (). slice ( 0 , 7 ); // "2026-04"
const usageKey = `usage:${ userId }:${ month }` ;
const usageStr = await env. KV . get (usageKey);
const usage = usageStr ? JSON . parse (usageStr) : { inputTokens: 0 , outputTokens: 0 , requests: 0 };
// プランごとの制限チェック(Proは月500リクエスト、Premiumは無制限)
const plan = await getUserPlan (userId, env);
const limit = plan === 'premium' ? Infinity : 500 ;
return Response. json ({
usage,
limit,
remaining: Math. max ( 0 , limit - usage.requests),
plan,
});
}
レート制限の実装
// ユーザーごとのレート制限(1分あたり10リクエスト)
async function checkRateLimit ( userId : string , env : CloudflareEnv ) : Promise < boolean > {
const key = `ratelimit:${ userId }:${ Math . floor ( Date . now () / 60000 ) }` ;
const current = parseInt ( await env. KV . get (key) || '0' );
if (current >= 10 ) return false ; // 制限超過
await env. KV . put (key, String (current + 1 ), { expirationTtl: 120 });
return true ;
}
Step 5:Stripe 連携によるプレミアム AI 機能のゲーティング
Responses API の高度なツール(Web検索・コードインタプリタ)は、Stripe のサブスクリプション課金と組み合わせることで、強力な収益化モデルを構築できます。
プランごとのツールアクセス制御
// エージェントタイプをプランで制御
async function getAvailableAgentTypes ( userId : string , env : CloudflareEnv ) : Promise < string []> {
const plan = await getUserPlan (userId, env);
switch (plan) {
case 'premium' :
case 'pro' :
return [ 'general' , 'search' , 'analyst' ]; // 全ツール利用可能
case 'article' : // 記事単体購入
return [ 'general' , 'search' ]; // Web検索まで利用可能
default : // 無料ユーザー
return [ 'general' ]; // 基本会話のみ
}
}
収益化モデルの設計
個人開発者が Responses API を軸に月収 100 万円超を目指すための収益構造:
Freeプラン : 基本 AI チャット(general エージェント、月 20 リクエスト)
Pro プラン(¥580/月) : Web 検索対応(search エージェント、月 500 リクエスト)
Premium プラン(¥2,480/永続) : 全機能無制限(analyst エージェント含む)
この構造により、OpenAI API コストを課金収益でカバーしながら、プレミアムユーザーの LTV を最大化できます。
よくあるエラーと対処法
エラー 1:previous_response_id が無効になる
原因 : Responses API のレスポンス ID は 30 日間しか有効ではありません。また、モデルのバージョン変更でも無効になります。
// 対処:KV 保存時に有効期限チェックを追加
const isValidThread = ( state : ThreadState ) : boolean => {
const thirtyDaysAgo = Date. now () - 30 * 24 * 60 * 60 * 1000 ;
return state.createdAt > thirtyDaysAgo;
};
const previousResponseId = threadState && isValidThread (threadState)
? threadState.responseId
: undefined ; // 期限切れは新規スレッドとして開始
エラー 2:Web 検索で料金が想定以上に発生する
原因 : web_search_preview ツールは呼び出しごとに追加料金が発生します(2026 年現在、約 $0.03/呼び出し)。
// 対処:Web検索の呼び出し回数をKVで追跡
const trackSearchUsage = async ( userId : string , env : CloudflareEnv ) => {
const today = new Date (). toISOString (). slice ( 0 , 10 );
const key = `search_count:${ userId }:${ today }` ;
const count = parseInt ( await env. KV . get (key) || '0' );
if (count >= 50 ) { // 1日50回制限
throw new Error ( 'Daily search limit reached' );
}
await env. KV . put (key, String (count + 1 ), { expirationTtl: 86400 });
};
エラー 3:Cloudflare Workers でのストリーミングタイムアウト
原因 : Workers のリクエストは最大 30 秒の CPU 時間制限があります。長いストリーミングレスポンスで問題が発生することがあります。
// 対処:チャンクを定期的に flush して接続を維持
// wrangler.toml に以下を追加
// [limits]
// cpu_ms = 30000
//
// さらに、長文生成の場合はmax_output_tokensで制限
const stream = await openai.responses. stream ({
model: 'gpt-4o' ,
input: message,
max_output_tokens: 1500 , // Workersのタイムアウト対策
// ...
});
個人開発者の視点から(実体験メモ)
まとめ
OpenAI Responses API は、モバイル AI アプリ開発における「複雑さ」と「機能性」のトレードオフを大きく改善した API です。Rork Max と Cloudflare Workers の組み合わせにより、以下のことが個人開発者でも実現可能になりました:
会話文脈を保持するステートフル AI エージェント
Web 検索・コード実行などの組み込みツールを活用した高機能アシスタント
Stripe 連携によるプレミアム AI 機能の収益化
関連する実装パターンとして、Rork × AI Function Calling で構築するインテリジェントアシスタントアプリ — コンテキスト管理・ツール統合・会話メモリの実装パターン や Rork × マルチAIオーケストレーション完全ガイド — Claude・Gemini・GPT-4oを統合するインテリジェントアプリ設計 も参考にしてください。
AI エージェント技術は日々進化しています。本記事のコードは GitHub で管理し、API の変更に追随していく姿勢が個人開発者として長く戦い続けるための鍵です。
AI API の本質的な仕組みを理解することで、Responses API の活用がさらに広がります。