{"id":13172,"date":"2026-03-16T18:39:11","date_gmt":"2026-03-16T18:39:11","guid":{"rendered":"https:\/\/withcode.tech\/media\/?p=13172"},"modified":"2026-07-14T11:53:46","modified_gmt":"2026-07-14T11:53:46","slug":"vercel-ai-sdk-streaming-ui","status":"publish","type":"post","link":"https:\/\/withcode.tech\/media\/vercel-ai-sdk-streaming-ui\/","title":{"rendered":"Vercel AI SDK\u3067\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0UI\u5b9f\u88c5\uff5cNext.js\u00d7LLM\u30a2\u30d7\u30ea\u306eUX\u6539\u5584\u3010\u5b8c\u5168\u30ac\u30a4\u30c9\u3011"},"content":{"rendered":"\n<div class=\"wp-block-group swl-box swl-box--border\"><div class=\"wp-block-group__inner-container\">\n\n<h3 class=\"wp-block-heading\">\u3053\u306e\u8a18\u4e8b\u3067\u308f\u304b\u308b\u3053\u3068<\/h3>\n\n\n<ul class=\"wp-block-list\">\n<li>Vercel AI SDK\u306e\u6982\u8981\u3068\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0UI\u304c\u6570\u5341\u884c\u3067\u5b9f\u88c5\u3067\u304d\u308b\u7406\u7531<\/li>\n<li>SSE\uff08Server-Sent Events\uff09\u306e\u4ed5\u7d44\u307f\u3068\u30ce\u30f3\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u3068\u306e\u9055\u3044<\/li>\n<li>Next.js App Router\u3067\u306eAPI\u30eb\u30fc\u30c8\u30fbuseChat Hook\u306e\u5b9f\u88c5\u5168\u30b3\u30fc\u30c9<\/li>\n<li>\u30c4\u30fc\u30eb\u547c\u3073\u51fa\u3057\uff08Function Calling\uff09\u30fb\u30de\u30eb\u30c1\u30e2\u30fc\u30c0\u30eb\u30fb\u69cb\u9020\u5316\u30c7\u30fc\u30bf\u751f\u6210\u306e\u5b9f\u88c5\u65b9\u6cd5<\/li>\n<li>Edge Runtime\u6d3b\u7528\u30fb\u30ec\u30fc\u30c8\u5236\u9650\u30fb\u30c8\u30fc\u30af\u30f3\u7ba1\u7406\u306a\u3069\u672c\u756a\u74b0\u5883\u306e\u30d9\u30b9\u30c8\u30d7\u30e9\u30af\u30c6\u30a3\u30b9<\/li>\n<\/ul>\n\n<\/div><\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u7d50\u8ad6\u304b\u3089\u8a00\u3046\u3068\u3001Vercel AI SDK\u3092\u4f7f\u3048\u3070ChatGPT\u306e\u3088\u3046\u306a\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0UI\u3092\u6570\u5341\u884c\u306e\u30b3\u30fc\u30c9\u3067\u5b9f\u88c5\u3067\u304d\u307e\u3059\u3002<\/strong><code>useChat<\/code> Hook\u306bSSE\u306e\u30d1\u30fc\u30b9\u30fb\u72b6\u614b\u7ba1\u7406\u30fb\u30a8\u30e9\u30fc\u30cf\u30f3\u30c9\u30ea\u30f3\u30b0\u304c\u3059\u3079\u3066\u62bd\u8c61\u5316\u3055\u308c\u3066\u304a\u308a\u3001OpenAI\u30fbAnthropic\u30fbAmazon Bedrock\u306a\u3069\u8907\u6570\u30d7\u30ed\u30d0\u30a4\u30c0\u30fc\u306b\u3082\u5bfe\u5fdc\u3057\u3066\u3044\u307e\u3059\u3002Claude Sonnet 4\u306e\u3088\u3046\u306a\u9ad8\u6027\u80fd\u30e2\u30c7\u30eb\u306710\u79d2\u4ee5\u4e0a\u304b\u304b\u308b\u5fdc\u7b54\u3082\u3001\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u5b9f\u88c5\u3067\u300c\u6700\u521d\u306e\u6587\u5b57\u304c0.3\u301c1\u79d2\u3067\u8868\u793a\u3055\u308c\u308b\u300d\u4f53\u9a13\u306b\u5909\u308f\u308a\u307e\u3059\u3002<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Vercel AI SDK\u3068\u306f\uff5c\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0UI\u304c\u6570\u5341\u884c\u3067\u5b9f\u88c5\u3067\u304d\u308b\u7406\u7531<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Vercel AI SDK\u306f\u3001<strong>LLM\u3092\u6d3b\u7528\u3057\u305fWeb\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u7c21\u5358\u306b\u69cb\u7bc9\u3059\u308b\u305f\u3081\u306e\u30aa\u30fc\u30d7\u30f3\u30bd\u30fc\u30b9\u30e9\u30a4\u30d6\u30e9\u30ea<\/strong>\u3067\u3059\u3002Server-Sent Events\uff08SSE\uff09\u3092\u4f7f\u3063\u305f\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u51e6\u7406\u30fbReact Hooks\u306b\u3088\u308b\u72b6\u614b\u7ba1\u7406\u30fb\u8907\u6570AI\u30d7\u30ed\u30d0\u30a4\u30c0\u30fc\u3078\u306e\u5bfe\u5fdc\u3092\u63d0\u4f9b\u3057\u307e\u3059\u3002Vercel\u793e\u304c2023\u5e74\u306b\u516c\u958b\u3057\u30012024\u301c2026\u5e74\u306b\u304b\u3051\u3066\u6025\u901f\u306b\u666e\u53ca\u3057\u3001Next.js\u516c\u5f0f\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u3067\u3082\u63a8\u5968\u3055\u308c\u3066\u3044\u307e\u3059\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u5f93\u6765\u306f\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0UI\u306e\u5b9f\u88c5\u306b\u300cSSE\u306e\u30ec\u30b9\u30dd\u30f3\u30b9\u3092\u624b\u52d5\u3067\u30d1\u30fc\u30b9\u300d\u300c\u30c6\u30ad\u30b9\u30c8\u306e\u5897\u5206\u3092state\u3067\u7ba1\u7406\u300d\u300c\u30a8\u30e9\u30fc\u6642\u306e\u30ea\u30c8\u30e9\u30a4\u30ed\u30b8\u30c3\u30af\u3092\u81ea\u524d\u3067\u5b9f\u88c5\u300d\u306a\u3069\u591a\u304f\u306e\u30dc\u30a4\u30e9\u30fc\u30d7\u30ec\u30fc\u30c8\u30b3\u30fc\u30c9\u304c\u5fc5\u8981\u3067\u3057\u305f\u3002Vercel AI SDK\u306f\u3053\u308c\u3089\u3092\u3059\u3079\u3066\u5185\u90e8\u306b\u62bd\u8c61\u5316\u3057\u3001<code>useChat<\/code>\u3068\u3044\u3046\u4e00\u3064\u306eHook\u3067\u5b8c\u5168\u306a\u30c1\u30e3\u30c3\u30c8UI\u6a5f\u80fd\u3092\u63d0\u4f9b\u3057\u307e\u3059\u3002<\/p>\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-none\" data-lang=\"Code\"><code>&gt;\u3010Vercel AI SDK\u306e3\u3064\u306e\u7279\u5fb4\u3011\n\n\u2705 \u7c21\u5358\u306a\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u5b9f\u88c5\n\u2192 streamText() + toDataStreamResponse() \u306e2\u884c\u3067\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u5bfe\u5fdc\u5b8c\u4e86\n\n\u2705 \u62bd\u8c61\u5316\u3055\u308c\u305f\u30d5\u30ed\u30f3\u30c8\u30a8\u30f3\u30c9\n\u2192 useChat() Hook \u3067\u9001\u4fe1\u30fb\u53d7\u4fe1\u30fb\u5c65\u6b74\u7ba1\u7406\u3092\u3059\u3079\u3066\u7ba1\u7406\n\u2192 \u81ea\u524d\u3067\u72b6\u614b\u7ba1\u7406\u3092\u66f8\u304f\u5fc5\u8981\u304c\u307b\u307c\u306a\u3044\n\n\u2705 \u30de\u30eb\u30c1\u30d7\u30ed\u30d0\u30a4\u30c0\u30fc\u5bfe\u5fdc\n\u2192 OpenAI, Anthropic, Google AI, Amazon Bedrock, Mistral etc.\n\u2192 \u30d7\u30ed\u30d0\u30a4\u30c0\u30fc\u3092\u5909\u66f4\u3057\u3066\u3082UI\u30b3\u30fc\u30c9\u306f\u305d\u306e\u307e\u307e<\/code><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0UI\u3068\u30ce\u30f3\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0UI\u306e\u6bd4\u8f03<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>\u89b3\u70b9<\/th><th>\u30ce\u30f3\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0<\/th><th>\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0<\/th><\/tr><\/thead><tbody><tr><td>\u4f53\u611f\u901f\u5ea6<\/td><td>\u5168\u30c6\u30ad\u30b9\u30c8\u751f\u6210\u307e\u3067\u5f85\u6a5f\uff0810\u301c30\u79d2\uff09<\/td><td>\u751f\u6210\u958b\u59cb\u3068\u540c\u6642\u306b\u8868\u793a\uff080.3\u301c1\u79d2\u3067\u6700\u521d\u306e\u6587\u5b57\uff09<\/td><\/tr><tr><td>\u30e6\u30fc\u30b6\u30fc\u4f53\u9a13<\/td><td>\u300c\u56fa\u307e\u3063\u305f\u306e\u304b\uff1f\u300d\u3068\u3044\u3046\u4e0d\u5b89\u611f<\/td><td>AI\u304c\u8003\u3048\u3066\u3044\u308b\u3053\u3068\u304c\u30ea\u30a2\u30eb\u30bf\u30a4\u30e0\u3067\u308f\u304b\u308b<\/td><\/tr><tr><td>\u5b9f\u88c5\u8907\u96d1\u5ea6<\/td><td>\u30b7\u30f3\u30d7\u30eb\uff08fetch\u2192JSON\u89e3\u6790\uff09<\/td><td>Vercel AI SDK\u4f7f\u7528\u3067\u30b7\u30f3\u30d7\u30eb\u306b\u5b9f\u73fe<\/td><\/tr><tr><td>\u30b5\u30fc\u30d0\u30fc\u30ea\u30bd\u30fc\u30b9<\/td><td>\u5fdc\u7b54\u5b8c\u4e86\u307e\u3067\u30e1\u30e2\u30ea\u306b\u4fdd\u6301\u304c\u5fc5\u8981<\/td><td>\u30c1\u30e3\u30f3\u30af\u3092\u9806\u6b21\u9001\u4fe1\u3057\u3066\u30e1\u30e2\u30ea\u52b9\u7387\u304c\u9ad8\u3044<\/td><\/tr><tr><td>\u30a8\u30e9\u30fc\u30cf\u30f3\u30c9\u30ea\u30f3\u30b0<\/td><td>\u4e00\u5ea6\u306b\u30a8\u30e9\u30fc\u304c\u5224\u660e<\/td><td>\u30b9\u30c8\u30ea\u30fc\u30e0\u9014\u4e2d\u3067\u30a8\u30e9\u30fc\u306b\u306a\u308b\u5834\u5408\u304c\u3042\u308b<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u306e\u4ed5\u7d44\u307f\uff1aServer-Sent Events\uff08SSE\uff09<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Vercel AI SDK\u306e\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u306fHTTP\u306e\u300cServer-Sent Events\uff08SSE\uff09\u300d\u3092\u4f7f\u3063\u3066\u3044\u307e\u3059\u3002SSE\u306fHTTP\u63a5\u7d9a\u3092\u7dad\u6301\u3057\u305f\u307e\u307e\u30b5\u30fc\u30d0\u30fc\u304b\u3089\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u3078\u4e00\u65b9\u5411\u306b\u30c7\u30fc\u30bf\u3092\u30d7\u30c3\u30b7\u30e5\u3067\u304d\u308bWebAPI\u3067\u3059\u3002WebSocket\u3068\u6bd4\u3079\u3066\u30b7\u30f3\u30d7\u30eb\u3067\u3001HTTP\u306e\u6a19\u6e96\u6a5f\u80fd\u306e\u307f\u3067\u52d5\u4f5c\u3059\u308b\u305f\u3081CDN\u30fb\u30d7\u30ed\u30ad\u30b7\u30fb\u30d5\u30a1\u30a4\u30a2\u30a6\u30a9\u30fc\u30eb\u74b0\u5883\u3067\u3082\u5b89\u5b9a\u3057\u3066\u3044\u307e\u3059\u3002<\/p>\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-none\" data-lang=\"Code\"><code>&gt;\u3010LLM\u304c\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u3067\u9001\u308b\u30c7\u30fc\u30bf\u306e\u5b9f\u969b\u306e\u5f62\u5f0f\u3011\n\nf:{\"messageId\":\"msg-OuigLUo9Lxefa0EudVrcsnaz\"}\n0:\"Vercel AI\"\n0:\" SDK\u306f\u3001\"\n0:\"AI\u6a5f\u80fd\u3092\"\n0:\"Web\u30a2\u30d7\u30ea\"\n0:\"\u30b1\u30fc\u30b7\u30e7\u30f3\u306b\"\n0:\"\u7c21\u5358\u306b\u7d71\"\n0:\"\u5408\u3059\u308b\u305f\u3081\u306e\"\n0:\"\u30aa\u30fc\u30d7\u30f3\u30bd\u30fc\u30b9\"\n0:\"\u30e9\u30a4\u30d6\u30e9\u30ea\u3067\u3059\"\n...\ne:{\"finishReason\":\"stop\",\"usage\":{\"promptTokens\":468,\"completionTokens\":570}}\nd:{\"finishReason\":\"stop\"}\n\n\u2192 \u30c6\u30ad\u30b9\u30c8\u304c\u751f\u6210\u3055\u308c\u308b\u305f\u3073\u306b\u5c0f\u3055\u306a\u30c1\u30e3\u30f3\u30af\u304c\u30ea\u30a2\u30eb\u30bf\u30a4\u30e0\u3067\u9001\u4fe1\u3055\u308c\u308b\n\u2192 \u30e6\u30fc\u30b6\u30fc\u306f\u751f\u6210\u306e\u904e\u7a0b\u3092\u6587\u5b57\u5358\u4f4d\u3067\u30ea\u30a2\u30eb\u30bf\u30a4\u30e0\u306b\u78ba\u8a8d\u3067\u304d\u308b\n\u2192 HTTP\u63a5\u7d9a\u3092\u7dad\u6301\u3057\u306a\u304c\u3089\u30b5\u30fc\u30d0\u30fc\u304b\u3089\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u3078\u4e00\u65b9\u5411\u306b\u9001\u4fe1\uff08SSE\uff09\n\u2192 \u5148\u982d\u306e\u6570\u5b57\u306f\u30c7\u30fc\u30bf\u306e\u7a2e\u985e\u3092\u793a\u3059\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\uff080=\u30c6\u30ad\u30b9\u30c8\u3001f=\u30e1\u30bf\u30c7\u30fc\u30bf\u3001e=\u5b8c\u4e86\u60c5\u5831\uff09<\/code><\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u74b0\u5883\u69cb\u7bc9\u3068\u5b9f\u88c5\u30b3\u30fc\u30c9\u5168\u6587\uff5cNext.js App Router\u7248<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. \u30d1\u30c3\u30b1\u30fc\u30b8\u306e\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb<\/h3>\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-bash\" data-lang=\"Bash\"><code># Next.js \u30d7\u30ed\u30b8\u30a7\u30af\u30c8\u3092\u4f5c\u6210\nnpx create-next-app@latest ai-streaming-app --typescript --tailwind --app\n\n# Vercel AI SDK \u3068\u4f7f\u7528\u3059\u308b\u30d7\u30ed\u30d0\u30a4\u30c0\u30fc\u306e\u30d1\u30c3\u30b1\u30fc\u30b8\u3092\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\n# OpenAI \u3092\u4f7f\u3046\u5834\u5408\npnpm add ai @ai-sdk\/openai\n\n# Amazon Bedrock \u3092\u4f7f\u3046\u5834\u5408\npnpm add ai @ai-sdk\/amazon-bedrock @aws-sdk\/credential-providers\n\n# Anthropic \u3092\u4f7f\u3046\u5834\u5408\npnpm add ai @ai-sdk\/anthropic<\/code><\/pre><\/div>\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-none\" data-lang=\"Code\"><code># .env.local \u306b API\u30ad\u30fc\u3092\u8a2d\u5b9a\uff08Git\u306b\u542b\u3081\u306a\u3044\u3053\u3068\uff09\nOPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxx\n\n# Amazon Bedrock \u3092\u4f7f\u3046\u5834\u5408\nAWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXX\nAWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nAWS_REGION=ap-northeast-1<\/code><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">2. \u30d0\u30c3\u30af\u30a8\u30f3\u30c9\u5b9f\u88c5\uff1aAPI\u30eb\u30fc\u30c8\uff08OpenAI\u7248\uff09<\/h3>\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-javascript\" data-lang=\"JavaScript\"><code>&gt;\/\/ src\/app\/api\/chat\/route.ts\n\nimport { streamText } from 'ai'\nimport { openai } from '@ai-sdk\/openai'\n\n\/\/ \u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u5fdc\u7b54\u3092\u6700\u592730\u79d2\u307e\u3067\u8a31\u53ef\u3059\u308b\uff08Vercel Edge Function\u306e\u30bf\u30a4\u30e0\u30a2\u30a6\u30c8\uff09\nexport const maxDuration = 30\n\nexport async function POST(req: Request) {\n  const { messages } = await req.json()\n\n  \/\/ \u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u3067\u30c6\u30ad\u30b9\u30c8\u3092\u751f\u6210\n  const result = streamText({\n    model: openai('gpt-4o-mini'),\n    system: '\u3042\u306a\u305f\u306f\u89aa\u5207\u306aAI\u30a2\u30b7\u30b9\u30bf\u30f3\u30c8\u3067\u3059\u3002\u65e5\u672c\u8a9e\u3067\u56de\u7b54\u3057\u3066\u304f\u3060\u3055\u3044\u3002',\n    messages,\n  })\n\n  \/\/ \u30c7\u30fc\u30bf\u30b9\u30c8\u30ea\u30fc\u30e0\u30ec\u30b9\u30dd\u30f3\u30b9\u3068\u3057\u3066\u8fd4\u5374\n  \/\/ \u2192 Server-Sent Events \u5f62\u5f0f\u3067\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u306b\u9001\u4fe1\u3055\u308c\u308b\n  return result.toDataStreamResponse()\n}<\/code><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">3. \u30d5\u30ed\u30f3\u30c8\u30a8\u30f3\u30c9\u5b9f\u88c5\uff1a\u30c1\u30e3\u30c3\u30c8UI\uff08useChat Hook\uff09<\/h3>\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-markup\" data-lang=\"HTML\"><code>&gt;\/\/ src\/app\/page.tsx\n'use client'\n\nimport { useChat } from '@ai-sdk\/react'\n\nexport default function Chat() {\n  \/\/ useChat Hook \u306b\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u51e6\u7406\u304c\u5b8c\u5168\u306b\u62bd\u8c61\u5316\u3055\u308c\u3066\u3044\u308b\n  const {\n    messages,    \/\/ \u30c1\u30e3\u30c3\u30c8\u5c65\u6b74\uff08{ id, role, content, parts }[]\uff09\n    input,       \/\/ \u30c6\u30ad\u30b9\u30c8\u5165\u529b\u306e\u73fe\u5728\u5024\n    handleInputChange,  \/\/ input \u306e onChange \u30cf\u30f3\u30c9\u30e9\n    handleSubmit,       \/\/ \u30d5\u30a9\u30fc\u30e0\u306e onSubmit \u30cf\u30f3\u30c9\u30e9\n    isLoading,   \/\/ \u5fdc\u7b54\u5f85\u3061\u4e2d\u306f true\n    stop,        \/\/ \u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u3092\u4e2d\u65ad\u3059\u308b\u95a2\u6570\n    reload,      \/\/ \u6700\u5f8c\u306e\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u518d\u751f\u6210\u3059\u308b\u95a2\u6570\n    error,       \/\/ \u30a8\u30e9\u30fc\u304c\u3042\u308c\u3070 Error \u30aa\u30d6\u30b8\u30a7\u30af\u30c8\n  } = useChat({\n    api: '\/api\/chat',  \/\/ \u30c7\u30d5\u30a9\u30eb\u30c8\u5024\u306a\u306e\u3067\u7701\u7565\u53ef\n    onFinish: (message) =&gt; {\n      console.log('\u751f\u6210\u5b8c\u4e86:', message.content.length, '\u6587\u5b57')\n    },\n    onError: (error) =&gt; {\n      console.error('\u30a8\u30e9\u30fc\u767a\u751f:', error.message)\n    }\n  })\n\n  return (\n    &lt;div className=\"flex flex-col w-full max-w-2xl mx-auto h-screen py-8\"&gt;\n      {\/* \u30c1\u30e3\u30c3\u30c8\u5c65\u6b74\u30a8\u30ea\u30a2 *\/}\n      &lt;div className=\"flex-1 overflow-y-auto space-y-4 mb-4 px-4\"&gt;\n        {messages.length === 0 &amp;&amp; (\n          &lt;p className=\"text-center text-gray-400 mt-20\"&gt;\n            \u8cea\u554f\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\n          &lt;\/p&gt;\n        )}\n        {messages.map(message =&gt; (\n          &lt;div\n            key={message.id}\n            className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}\n          &gt;\n            &lt;div className={`max-w-[80%] rounded-2xl px-4 py-3 ${\n              message.role === 'user'\n                ? 'bg-blue-500 text-white'\n                : 'bg-gray-100 text-gray-900'\n            }`}&gt;\n              {\/* \u30c6\u30ad\u30b9\u30c8\u30d1\u30fc\u30c4\u3092\u8868\u793a\uff08\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u4e2d\u3082\u968f\u6642\u66f4\u65b0\u3055\u308c\u308b\uff09 *\/}\n              {message.parts?.map((part, i) =&gt; {\n                if (part.type === 'text') {\n                  return (\n                    &lt;p key={i} className=\"whitespace-pre-wrap text-sm\"&gt;\n                      {part.text}\n                    &lt;\/p&gt;\n                  )\n                }\n                return null\n              })}\n            &lt;\/div&gt;\n          &lt;\/div&gt;\n        ))}\n\n        {\/* \u30ed\u30fc\u30c7\u30a3\u30f3\u30b0\u30a4\u30f3\u30b8\u30b1\u30fc\u30bf\u30fc *\/}\n        {isLoading &amp;&amp; (\n          &lt;div className=\"flex justify-start\"&gt;\n            &lt;div className=\"bg-gray-100 rounded-2xl px-4 py-3\"&gt;\n              &lt;span className=\"inline-block animate-pulse text-gray-500\"&gt;\u258d&lt;\/span&gt;\n            &lt;\/div&gt;\n          &lt;\/div&gt;\n        )}\n      &lt;\/div&gt;\n\n      {\/* \u30a8\u30e9\u30fc\u8868\u793a *\/}\n      {error &amp;&amp; (\n        &lt;div className=\"mx-4 mb-2 p-3 bg-red-50 border border-red-200 rounded-lg\"&gt;\n          &lt;p className=\"text-red-600 text-sm\"&gt;{error.message}&lt;\/p&gt;\n          &lt;button\n            onClick={reload}\n            className=\"mt-2 text-xs text-red-500 underline\"\n          &gt;\n            \u518d\u8a66\u884c\u3059\u308b\n          &lt;\/button&gt;\n        &lt;\/div&gt;\n      )}\n\n      {\/* \u5165\u529b\u30d5\u30a9\u30fc\u30e0 *\/}\n      &lt;form onSubmit={handleSubmit} className=\"flex gap-2 px-4\"&gt;\n        &lt;input\n          value={input}\n          onChange={handleInputChange}\n          placeholder=\"\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u5165\u529b...\"\n          disabled={isLoading}\n          className=\"flex-1 border border-gray-300 rounded-xl px-4 py-3 text-sm\n                     focus:outline-none focus:ring-2 focus:ring-blue-500\n                     disabled:opacity-50\"\n        \/&gt;\n        {isLoading ? (\n          &lt;button\n            type=\"button\"\n            onClick={stop}\n            className=\"bg-red-500 text-white rounded-xl px-5 py-3 text-sm font-medium\n                       hover:bg-red-600\"\n          &gt;\n            \u505c\u6b62\n          &lt;\/button&gt;\n        ) : (\n          &lt;button\n            type=\"submit\"\n            disabled={!input.trim()}\n            className=\"bg-blue-500 text-white rounded-xl px-5 py-3 text-sm font-medium\n                       hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed\"\n          &gt;\n            \u9001\u4fe1\n          &lt;\/button&gt;\n        )}\n      &lt;\/form&gt;\n    &lt;\/div&gt;\n  )\n}<\/code><\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u30c4\u30fc\u30eb\u547c\u3073\u51fa\u3057\uff08Function Calling\uff09\u306e\u5b9f\u88c5<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Vercel AI SDK\u306e\u3082\u3063\u3068\u3082\u5f37\u529b\u306a\u6a5f\u80fd\u306e\u4e00\u3064\u304c\u300c\u30c4\u30fc\u30eb\u547c\u3073\u51fa\u3057\u300d\u3067\u3059\u3002LLM\u304c\u81ea\u5f8b\u7684\u306b\u5916\u90e8API\u3084\u95a2\u6570\u3092\u547c\u3073\u51fa\u3057\u3066\u60c5\u5831\u3092\u53d6\u5f97\u3057\u3001\u305d\u306e\u7d50\u679c\u3092\u3082\u3068\u306b\u56de\u7b54\u3092\u751f\u6210\u3067\u304d\u307e\u3059\u3002ChatGPT\u306e\u30d7\u30e9\u30b0\u30a4\u30f3\u6a5f\u80fd\u306b\u76f8\u5f53\u3059\u308b\u4ed5\u7d44\u307f\u3067\u3059\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\u30d0\u30c3\u30af\u30a8\u30f3\u30c9\uff1a\u30c4\u30fc\u30eb\u306e\u5b9a\u7fa9<\/h3>\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-javascript\" data-lang=\"JavaScript\"><code>&gt;\/\/ src\/app\/api\/chat\/route.ts\n\/\/ \u30c4\u30fc\u30eb\u547c\u3073\u51fa\u3057\u3092\u4f7f\u3063\u305f\u4f8b\uff1a\u5929\u6c17\u60c5\u5831\u3092\u53d6\u5f97\u3057\u3066\u56de\u7b54\u3059\u308b\n\nimport { streamText, tool } from 'ai'\nimport { openai } from '@ai-sdk\/openai'\nimport { z } from 'zod'\n\nexport const maxDuration = 30\n\nexport async function POST(req: Request) {\n  const { messages } = await req.json()\n\n  const result = streamText({\n    model: openai('gpt-4o-mini'),\n    system: '\u3042\u306a\u305f\u306f\u30a2\u30b7\u30b9\u30bf\u30f3\u30c8\u3067\u3059\u3002\u5929\u6c17\u306b\u3064\u3044\u3066\u805e\u304b\u308c\u305f\u3089\u5fc5\u305a getWeather \u30c4\u30fc\u30eb\u3092\u4f7f\u3063\u3066\u56de\u7b54\u3057\u3066\u304f\u3060\u3055\u3044\u3002',\n    messages,\n    tools: {\n      \/\/ \u30c4\u30fc\u30eb\u540d: tool() \u3067\u5b9a\u7fa9\n      getWeather: tool({\n        description: '\u6307\u5b9a\u3057\u305f\u90fd\u5e02\u306e\u73fe\u5728\u306e\u5929\u6c17\u60c5\u5831\u3092\u53d6\u5f97\u3059\u308b',\n        parameters: z.object({\n          city: z.string().describe('\u90fd\u5e02\u540d\uff08\u4f8b: Tokyo, Osaka\uff09'),\n          unit: z.enum(['celsius', 'fahrenheit']).default('celsius'),\n        }),\n        \/\/ execute \u95a2\u6570\u3067\u5b9f\u969b\u306e\u51e6\u7406\u3092\u5b9f\u88c5\uff08\u975e\u540c\u671fOK\uff09\n        execute: async ({ city, unit }) =&gt; {\n          \/\/ \u5b9f\u969b\u306fAPI\u3092\u53e9\u304f\uff08\u3053\u3053\u3067\u306f\u30e2\u30c3\u30af\u30c7\u30fc\u30bf\uff09\n          const mockData = {\n            tokyo: { temp: 18, condition: '\u6674\u308c', humidity: 55 },\n            osaka: { temp: 20, condition: '\u66c7\u308a', humidity: 60 },\n          }\n          const data = mockData[city.toLowerCase()] ?? { temp: 15, condition: '\u4e0d\u660e', humidity: 50 }\n\n          return {\n            city,\n            temperature: unit === 'celsius' ? data.temp : data.temp * 9\/5 + 32,\n            unit,\n            condition: data.condition,\n            humidity: data.humidity,\n          }\n        },\n      }),\n\n      \/\/ \u8907\u6570\u306e\u30c4\u30fc\u30eb\u3092\u5b9a\u7fa9\u3067\u304d\u308b\n      searchWeb: tool({\n        description: 'Web\u3092\u691c\u7d22\u3057\u3066\u6700\u65b0\u60c5\u5831\u3092\u53d6\u5f97\u3059\u308b',\n        parameters: z.object({\n          query: z.string().describe('\u691c\u7d22\u30af\u30a8\u30ea'),\n        }),\n        execute: async ({ query }) =&gt; {\n          \/\/ \u5b9f\u969b\u306fBing Search API\u7b49\u3092\u547c\u3073\u51fa\u3059\n          return { results: `\u300c${query}\u300d\u306e\u691c\u7d22\u7d50\u679c...` }\n        },\n      }),\n    },\n    maxSteps: 5, \/\/ \u30c4\u30fc\u30eb\u547c\u3073\u51fa\u3057\u306e\u6700\u5927\u30b9\u30c6\u30c3\u30d7\u6570\uff08\u9023\u9396\u7684\u306a\u547c\u3073\u51fa\u3057\u306b\u5bfe\u5fdc\uff09\n  })\n\n  return result.toDataStreamResponse()\n}<\/code><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">\u30d5\u30ed\u30f3\u30c8\u30a8\u30f3\u30c9\uff1a\u30c4\u30fc\u30eb\u5b9f\u884c\u7d50\u679c\u306e\u8868\u793a<\/h3>\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-markup\" data-lang=\"HTML\"><code>&gt;\/\/ src\/app\/page.tsx\n\/\/ \u30c4\u30fc\u30eb\u547c\u3073\u51fa\u3057\u306e\u7d50\u679c\u3092\u8868\u793a\u3059\u308b\n\n'use client'\nimport { useChat } from '@ai-sdk\/react'\n\nexport default function ChatWithTools() {\n  const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat()\n\n  return (\n    &lt;div className=\"max-w-2xl mx-auto p-4\"&gt;\n      &lt;div className=\"space-y-4 mb-4\"&gt;\n        {messages.map(message =&gt; (\n          &lt;div key={message.id} className=\"space-y-2\"&gt;\n            &lt;div className={`font-semibold text-sm ${\n              message.role === 'user' ? 'text-blue-600' : 'text-gray-600'\n            }`}&gt;\n              {message.role === 'user' ? '\u3042\u306a\u305f' : 'AI'}\n            &lt;\/div&gt;\n\n            {\/* \u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u30d1\u30fc\u30c4\u3092\u7a2e\u985e\u3054\u3068\u306b\u8868\u793a *\/}\n            {message.parts?.map((part, i) =&gt; {\n              switch (part.type) {\n                case 'text':\n                  return &lt;p key={i} className=\"text-gray-800\"&gt;{part.text}&lt;\/p&gt;\n\n                case 'tool-invocation':\n                  \/\/ \u30c4\u30fc\u30eb\u547c\u3073\u51fa\u3057\u4e2d \/ \u5b8c\u4e86\u3092\u8868\u793a\n                  return (\n                    &lt;div key={i} className=\"bg-blue-50 border border-blue-200 rounded-lg p-3\"&gt;\n                      &lt;div className=\"text-xs font-medium text-blue-700 mb-1\"&gt;\n                        \ud83d\udd27 \u30c4\u30fc\u30eb: {part.toolInvocation.toolName}\n                      &lt;\/div&gt;\n                      {part.toolInvocation.state === 'call' &amp;&amp; (\n                        &lt;p className=\"text-xs text-blue-500\"&gt;\u5b9f\u884c\u4e2d...&lt;\/p&gt;\n                      )}\n                      {part.toolInvocation.state === 'result' &amp;&amp; (\n                        &lt;pre className=\"text-xs text-blue-800 overflow-auto\"&gt;\n                          {JSON.stringify(part.toolInvocation.result, null, 2)}\n                        &lt;\/pre&gt;\n                      )}\n                    &lt;\/div&gt;\n                  )\n\n                default:\n                  return null\n              }\n            })}\n          &lt;\/div&gt;\n        ))}\n      &lt;\/div&gt;\n\n      &lt;form onSubmit={handleSubmit} className=\"flex gap-2\"&gt;\n        &lt;input\n          value={input}\n          onChange={handleInputChange}\n          placeholder=\"\u300c\u6771\u4eac\u306e\u5929\u6c17\u306f\uff1f\u300d\u306a\u3069\u8a66\u3057\u3066\u307f\u3066\u304f\u3060\u3055\u3044\"\n          className=\"flex-1 border rounded-lg px-4 py-2\"\n        \/&gt;\n        &lt;button type=\"submit\" disabled={isLoading} className=\"bg-blue-500 text-white px-4 py-2 rounded-lg\"&gt;\n          \u9001\u4fe1\n        &lt;\/button&gt;\n      &lt;\/form&gt;\n    &lt;\/div&gt;\n  )\n}<\/code><\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u30de\u30eb\u30c1\u30e2\u30fc\u30c0\u30eb\u5bfe\u5fdc\uff5c\u753b\u50cf\u5165\u529b\u3068Vision\u30e2\u30c7\u30eb\u306e\u6d3b\u7528<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">GPT-4o\u30fbClaude 3.5 Sonnet\u30fbGoogle Gemini\u306a\u3069\u306e\u30de\u30eb\u30c1\u30e2\u30fc\u30c0\u30eb\u30e2\u30c7\u30eb\u306b\u753b\u50cf\u3092\u6e21\u3057\u3066\u5206\u6790\u3055\u305b\u308b\u3053\u3068\u3082\u3001Vercel AI SDK\u3092\u4f7f\u3048\u3070\u7c21\u5358\u306b\u5b9f\u88c5\u3067\u304d\u307e\u3059\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\u30d0\u30c3\u30af\u30a8\u30f3\u30c9\uff1a\u753b\u50cf\u5165\u529b\u306e\u51e6\u7406<\/h3>\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-javascript\" data-lang=\"JavaScript\"><code>&gt;\/\/ src\/app\/api\/vision\/route.ts\n\nimport { streamText } from 'ai'\nimport { openai } from '@ai-sdk\/openai'\n\nexport const maxDuration = 30\n\nexport async function POST(req: Request) {\n  const formData = await req.formData()\n  const image = formData.get('image') as File\n  const question = formData.get('question') as string\n\n  \/\/ File \u3092 ArrayBuffer \u306b\u5909\u63db\n  const imageBytes = await image.arrayBuffer()\n  const imageBase64 = Buffer.from(imageBytes).toString('base64')\n\n  const result = streamText({\n    model: openai('gpt-4o'),  \/\/ Vision\u5bfe\u5fdc\u30e2\u30c7\u30eb\u3092\u4f7f\u7528\n    messages: [\n      {\n        role: 'user',\n        content: [\n          {\n            type: 'image',\n            image: imageBase64,  \/\/ Base64\u30a8\u30f3\u30b3\u30fc\u30c9\u3055\u308c\u305f\u753b\u50cf\n            mimeType: image.type as 'image\/jpeg' | 'image\/png' | 'image\/webp',\n          },\n          {\n            type: 'text',\n            text: question || '\u3053\u306e\u753b\u50cf\u306b\u3064\u3044\u3066\u8a73\u3057\u304f\u8aac\u660e\u3057\u3066\u304f\u3060\u3055\u3044\u3002',\n          },\n        ],\n      },\n    ],\n  })\n\n  return result.toDataStreamResponse()\n}<\/code><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">\u30d5\u30ed\u30f3\u30c8\u30a8\u30f3\u30c9\uff1a\u753b\u50cf\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9UI<\/h3>\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-markup\" data-lang=\"HTML\"><code>&gt;\/\/ src\/app\/vision\/page.tsx\n'use client'\n\nimport { useState, useRef } from 'react'\nimport { useCompletion } from '@ai-sdk\/react'\n\nexport default function VisionPage() {\n  const [imagePreview, setImagePreview] = useState&lt;string | null&gt;(null)\n  const [question, setQuestion] = useState('')\n  const fileInputRef = useRef&lt;HTMLInputElement&gt;(null)\n\n  const { completion, isLoading, complete } = useCompletion({\n    api: '\/api\/vision',\n  })\n\n  const handleImageChange = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {\n    const file = e.target.files?.[0]\n    if (!file) return\n\n    const reader = new FileReader()\n    reader.onload = () =&gt; setImagePreview(reader.result as string)\n    reader.readAsDataURL(file)\n  }\n\n  const handleSubmit = async (e: React.FormEvent) =&gt; {\n    e.preventDefault()\n    const file = fileInputRef.current?.files?.[0]\n    if (!file) return\n\n    const formData = new FormData()\n    formData.append('image', file)\n    formData.append('question', question)\n\n    await complete('', {\n      body: formData,\n      headers: {}, \/\/ Content-Type\u306f\u30d6\u30e9\u30a6\u30b6\u304cFormData\u3092\u81ea\u52d5\u8a2d\u5b9a\n    })\n  }\n\n  return (\n    &lt;div className=\"max-w-2xl mx-auto p-6\"&gt;\n      &lt;h1 className=\"text-2xl font-bold mb-6\"&gt;\u753b\u50cf\u5206\u6790AI&lt;\/h1&gt;\n\n      &lt;form onSubmit={handleSubmit} className=\"space-y-4\"&gt;\n        &lt;div className=\"border-2 border-dashed border-gray-300 rounded-xl p-6 text-center\"&gt;\n          &lt;input\n            ref={fileInputRef}\n            type=\"file\"\n            accept=\"image\/*\"\n            onChange={handleImageChange}\n            className=\"hidden\"\n            id=\"image-upload\"\n          \/&gt;\n          &lt;label htmlFor=\"image-upload\" className=\"cursor-pointer\"&gt;\n            {imagePreview ? (\n              &lt;img src={imagePreview} alt=\"\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u753b\u50cf\" className=\"max-h-48 mx-auto rounded-lg\" \/&gt;\n            ) : (\n              &lt;div className=\"text-gray-400\"&gt;\n                &lt;p className=\"text-4xl mb-2\"&gt;\ud83d\udcf7&lt;\/p&gt;\n                &lt;p&gt;\u30af\u30ea\u30c3\u30af\u3057\u3066\u753b\u50cf\u3092\u9078\u629e&lt;\/p&gt;\n              &lt;\/div&gt;\n            )}\n          &lt;\/label&gt;\n        &lt;\/div&gt;\n\n        &lt;input\n          value={question}\n          onChange={(e) =&gt; setQuestion(e.target.value)}\n          placeholder=\"\u753b\u50cf\u306b\u3064\u3044\u3066\u8cea\u554f\u3057\u3066\u304f\u3060\u3055\u3044\uff08\u4f8b\uff1a\u3053\u306e\u6599\u7406\u306e\u30ab\u30ed\u30ea\u30fc\u306f\uff1f\uff09\"\n          className=\"w-full border rounded-xl px-4 py-3\"\n        \/&gt;\n\n        &lt;button\n          type=\"submit\"\n          disabled={!imagePreview || isLoading}\n          className=\"w-full bg-blue-500 text-white rounded-xl py-3 font-medium disabled:opacity-50\"\n        &gt;\n          {isLoading ? '\u5206\u6790\u4e2d...' : '\u753b\u50cf\u3092\u5206\u6790\u3059\u308b'}\n        &lt;\/button&gt;\n      &lt;\/form&gt;\n\n      {completion &amp;&amp; (\n        &lt;div className=\"mt-6 p-4 bg-gray-50 rounded-xl\"&gt;\n          &lt;h2 className=\"font-semibold mb-2\"&gt;\u5206\u6790\u7d50\u679c\uff1a&lt;\/h2&gt;\n          &lt;p className=\"whitespace-pre-wrap text-gray-700\"&gt;{completion}&lt;\/p&gt;\n        &lt;\/div&gt;\n      )}\n    &lt;\/div&gt;\n  )\n}<\/code><\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Vercel AI SDK\u306e\u4fbf\u5229\u6a5f\u80fd3\u9078<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u6a5f\u80fd1\uff1a\u69cb\u9020\u5316\u30c7\u30fc\u30bf\u306e\u751f\u6210\uff08Zod\u30b9\u30ad\u30fc\u30de\u6307\u5b9a\uff09<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>generateObject<\/code>\u3092\u4f7f\u3046\u3068\u3001LLM\u306e\u51fa\u529b\u3092Zod\u30b9\u30ad\u30fc\u30de\u3067\u578b\u5b9a\u7fa9\u3057\u305fJSON\u3068\u3057\u3066\u53d7\u3051\u53d6\u308c\u307e\u3059\u3002\u300c\u8a18\u4e8b\u306e\u30e1\u30bf\u30c7\u30fc\u30bf\u751f\u6210\u300d\u300c\u30d5\u30a9\u30fc\u30e0\u306e\u81ea\u52d5\u5165\u529b\u300d\u300c\u30c7\u30fc\u30bf\u62bd\u51fa\u300d\u306a\u3069\u306e\u30e6\u30fc\u30b9\u30b1\u30fc\u30b9\u3067\u7279\u306b\u6709\u52b9\u3067\u3059\u3002<\/p>\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-javascript\" data-lang=\"JavaScript\"><code>&gt;\/\/ \u51fa\u529b\u7d50\u679c\u306e\u69cb\u9020\u3092Zod\u3067\u5b9a\u7fa9\u3057\u3066JSON\u5f62\u5f0f\u3067\u53d6\u5f97\u3067\u304d\u308b\nimport { generateObject } from 'ai'\nimport { openai } from '@ai-sdk\/openai'\nimport { z } from 'zod'\n\nconst result = await generateObject({\n  model: openai('gpt-4o-mini'),\n  schema: z.object({\n    title: z.string().describe('\u8a18\u4e8b\u30bf\u30a4\u30c8\u30eb'),\n    summary: z.string().describe('100\u6587\u5b57\u4ee5\u5185\u306e\u8981\u7d04'),\n    tags: z.array(z.string()).max(5).describe('\u95a2\u9023\u30bf\u30b0\uff08\u6700\u59275\u3064\uff09'),\n    difficulty: z.enum(['beginner', 'intermediate', 'advanced']).describe('\u96e3\u6613\u5ea6'),\n    readingTime: z.number().describe('\u8aad\u4e86\u6642\u9593\uff08\u5206\uff09'),\n  }),\n  prompt: '\u6b21\u306e\u6280\u8853\u8a18\u4e8b\u306b\u3064\u3044\u3066\u30e1\u30bf\u30c7\u30fc\u30bf\u3092\u751f\u6210\u3057\u3066\u304f\u3060\u3055\u3044\uff1a\u300cTypeScript\u306e\u578b\u63a8\u8ad6\u5165\u9580\u300d',\n})\n\nconsole.log(result.object)\n\/\/ \u2192 { title: 'TypeScript\u306e\u578b\u63a8\u8ad6\u5165\u9580', summary: '...', tags: ['TypeScript', ...], difficulty: 'beginner', readingTime: 8 }<\/code><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">\u6a5f\u80fd2\uff1a\u81ea\u52d5\u30ea\u30c8\u30e9\u30a4\u51e6\u7406<\/h3>\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-javascript\" data-lang=\"JavaScript\"><code>&gt;\/\/ LLM\u306e\u51e6\u7406\u306f\u69d8\u3005\u306a\u7406\u7531\u3067\u5931\u6557\u3059\u308b\u3053\u3068\u304c\u3042\u308b\n\/\/ maxRetries \u3067\u30ea\u30c8\u30e9\u30a4\u56de\u6570\u3092\u8a2d\u5b9a\u3067\u304d\u308b\nimport { generateText } from 'ai'\nimport { openai } from '@ai-sdk\/openai'\n\nconst result = await generateText({\n  model: openai('gpt-4o-mini'),\n  maxRetries: 3,  \/\/ \u5931\u6557\u6642\u306b\u6700\u59273\u56de\u81ea\u52d5\u30ea\u30c8\u30e9\u30a4\uff08\u6307\u6570\u30d0\u30c3\u30af\u30aa\u30d5\uff09\n  prompt: '\u65e5\u672c\u306e\u9996\u90fd\u306f\u3069\u3053\u3067\u3059\u304b\uff1f',\n})\n\nconsole.log(result.text)\n\/\/ \u2192 \u30ea\u30c8\u30e9\u30a4\u6642\u306e\u30ed\u30b0: Retrying after 1s \/ 2s \/ 4s...\n\/\/ \u2192 \u6700\u7d42\u7684\u306a\u6210\u529f\u7d50\u679c\u3092\u8fd4\u3059<\/code><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">\u6a5f\u80fd3\uff1a\u8907\u6570\u30d7\u30ed\u30d0\u30a4\u30c0\u30fc\u306e\u5207\u308a\u66ff\u3048<\/h3>\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-javascript\" data-lang=\"JavaScript\"><code>&gt;\/\/ \u30d7\u30ed\u30d0\u30a4\u30c0\u30fc\u3092\u5909\u3048\u3066\u3082UI\u30b3\u30fc\u30c9\u306f\u305d\u306e\u307e\u307e\nimport { streamText } from 'ai'\nimport { openai } from '@ai-sdk\/openai'\nimport { anthropic } from '@ai-sdk\/anthropic'\nimport { createAmazonBedrock } from '@ai-sdk\/amazon-bedrock'\n\n\/\/ \u74b0\u5883\u5909\u6570\u3067\u30d7\u30ed\u30d0\u30a4\u30c0\u30fc\u3092\u5207\u308a\u66ff\u3048\u308b\nconst getModel = () =&gt; {\n  switch (process.env.AI_PROVIDER) {\n    case 'anthropic':\n      return anthropic('claude-3-5-sonnet-20241022')\n    case 'bedrock':\n      const bedrock = createAmazonBedrock({ region: 'ap-northeast-1' })\n      return bedrock('apac.anthropic.claude-sonnet-4-20250514-v1:0')\n    default:\n      return openai('gpt-4o-mini')\n  }\n}\n\nexport async function POST(req: Request) {\n  const { messages } = await req.json()\n  const result = streamText({ model: getModel(), messages })\n  return result.toDataStreamResponse()\n}<\/code><\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u30d1\u30d5\u30a9\u30fc\u30de\u30f3\u30b9\u6700\u9069\u5316\u3068\u30d9\u30b9\u30c8\u30d7\u30e9\u30af\u30c6\u30a3\u30b9<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Edge Runtime\u3067\u306e\u30ec\u30a4\u30c6\u30f3\u30b7\u524a\u6e1b<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Vercel Edge Functions\u3092\u4f7f\u3046\u3053\u3068\u3067\u3001\u30e6\u30fc\u30b6\u30fc\u306e\u5730\u7406\u7684\u306b\u6700\u3082\u8fd1\u3044\u30b5\u30fc\u30d0\u30fc\u3067AI API\u30ea\u30af\u30a8\u30b9\u30c8\u3092\u51e6\u7406\u3067\u304d\u307e\u3059\u3002\u65e5\u672c\u30e6\u30fc\u30b6\u30fc\u304c\u6771\u4eac\u30ea\u30fc\u30b8\u30e7\u30f3\u306eEdge\u304b\u3089\u547c\u3073\u51fa\u3059\u3053\u3068\u3067\u3001\u7c73\u56fd\u7d4c\u7531\u306e\u30ea\u30af\u30a8\u30b9\u30c8\u3068\u6bd4\u3079\u3066\u30ec\u30a4\u30c6\u30f3\u30b7\u309230\u301c50%\u524a\u6e1b\u3067\u304d\u308b\u5834\u5408\u304c\u3042\u308a\u307e\u3059\u3002<\/p>\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-javascript\" data-lang=\"JavaScript\"><code>&gt;\/\/ src\/app\/api\/chat\/route.ts\n\/\/ Edge Runtime\u3092\u6709\u52b9\u306b\u3059\u308b\n\nexport const runtime = 'edge'\nexport const maxDuration = 30\n\nimport { streamText } from 'ai'\nimport { openai } from '@ai-sdk\/openai'\n\nexport async function POST(req: Request) {\n  const { messages } = await req.json()\n  const result = streamText({\n    model: openai('gpt-4o-mini'),\n    messages,\n  })\n  return result.toDataStreamResponse()\n}\n\n\/\/ \u26a0\ufe0f Edge Runtime\u3067\u306ffetch\u306a\u3069\u4e00\u90e8\u306eNode.js API\u304c\u4f7f\u3048\u306a\u3044\u306e\u3067\u6ce8\u610f\n\/\/ \u26a0\ufe0f \u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30a2\u30af\u30bb\u30b9\u306b\u306fEdge\u5bfe\u5fdc\u306e\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\uff08Neon HTTP driver\u7b49\uff09\u304c\u5fc5\u8981<\/code><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">\u30b7\u30b9\u30c6\u30e0\u30d7\u30ed\u30f3\u30d7\u30c8\u306e\u6700\u9069\u5316<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u30b7\u30b9\u30c6\u30e0\u30d7\u30ed\u30f3\u30d7\u30c8\u306f\u6bce\u56de\u306e\u30ea\u30af\u30a8\u30b9\u30c8\u3067\u30c8\u30fc\u30af\u30f3\u3092\u6d88\u8cbb\u3057\u307e\u3059\u3002\u9577\u3059\u304e\u308b\u30b7\u30b9\u30c6\u30e0\u30d7\u30ed\u30f3\u30d7\u30c8\u306f\u30b3\u30b9\u30c8\u3068\u9045\u5ef6\u306e\u4e21\u65b9\u306b\u5f71\u97ff\u3057\u307e\u3059\u3002\u4e00\u822c\u7684\u306a\u30d9\u30b9\u30c8\u30d7\u30e9\u30af\u30c6\u30a3\u30b9\u3068\u3057\u3066\u3001200\u301c500\u30c8\u30fc\u30af\u30f3\uff08\u65e5\u672c\u8a9e\u3067400\u301c1000\u6587\u5b57\uff09\u7a0b\u5ea6\u306b\u53ce\u3081\u308b\u3053\u3068\u3092\u63a8\u5968\u3057\u307e\u3059\u3002<\/p>\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-javascript\" data-lang=\"JavaScript\"><code>&gt;\/\/ \u30b7\u30b9\u30c6\u30e0\u30d7\u30ed\u30f3\u30d7\u30c8\u306e\u6700\u9069\u5316\u4f8b\n\n\/\/ \u274c \u60aa\u3044\u4f8b\uff1a\u9577\u3059\u304e\u3066\u6bce\u56de\u30c8\u30fc\u30af\u30f3\u3092\u7121\u99c4\u9063\u3044\nconst badSystem = `\n\u3042\u306a\u305f\u306fWithCode\u3068\u3044\u3046\u30d7\u30ed\u30b0\u30e9\u30df\u30f3\u30b0\u30b9\u30af\u30fc\u30eb\u306eAI\u30a2\u30b7\u30b9\u30bf\u30f3\u30c8\u3067\u3059\u3002\nWithCode\u306f2018\u5e74\u306b\u8a2d\u7acb\u3055\u308c\u3001\u6771\u4eac\u3092\u62e0\u70b9\u306b...\uff08200\u884c\u7d9a\u304f\uff09\n`\n\n\/\/ \u2705 \u826f\u3044\u4f8b\uff1a\u8981\u70b9\u3092\u7d5e\u3063\u305f\u7c21\u6f54\u306a\u30d7\u30ed\u30f3\u30d7\u30c8\nconst goodSystem = `\n\u3042\u306a\u305f\u306fWithCode\uff08\u30d7\u30ed\u30b0\u30e9\u30df\u30f3\u30b0\u30b9\u30af\u30fc\u30eb\uff09\u306eAI\u30a2\u30b7\u30b9\u30bf\u30f3\u30c8\u3067\u3059\u3002\n\u4e3b\u306a\u696d\u52d9\uff1a\u53d7\u8b1b\u76f8\u8ac7\u3001\u30ab\u30ea\u30ad\u30e5\u30e9\u30e0\u8aac\u660e\u3001\u6280\u8853\u7684\u306aQ&amp;A\u5bfe\u5fdc\u3002\n\u56de\u7b54\u306f200\u6587\u5b57\u4ee5\u5185\u3067\u7c21\u6f54\u306b\u3002\u5c02\u9580\u7528\u8a9e\u306b\u306f\u5fc5\u305a\u8aac\u660e\u3092\u4ed8\u3051\u308b\u3002\n`<\/code><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">\u4f1a\u8a71\u5c65\u6b74\u306e\u30c8\u30fc\u30af\u30f3\u7ba1\u7406\uff08\u30b9\u30e9\u30a4\u30c7\u30a3\u30f3\u30b0\u30a6\u30a3\u30f3\u30c9\u30a6\uff09<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u30c1\u30e3\u30c3\u30c8\u306e\u4f1a\u8a71\u304c\u9577\u304f\u306a\u308b\u306b\u3064\u308c\u3001\u6e21\u3059\u30e1\u30c3\u30bb\u30fc\u30b8\u5c65\u6b74\u306e\u30c8\u30fc\u30af\u30f3\u6570\u304c\u5897\u52a0\u3057\u307e\u3059\u3002\u30b3\u30b9\u30c8\u3068\u901f\u5ea6\u306e\u89b3\u70b9\u304b\u3089\u3001\u4e00\u5b9a\u4ef6\u6570\u4ee5\u4e0a\u306e\u53e4\u3044\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u5207\u308a\u6368\u3066\u308b\u300c\u30b9\u30e9\u30a4\u30c7\u30a3\u30f3\u30b0\u30a6\u30a3\u30f3\u30c9\u30a6\u300d\u6226\u7565\u304c\u6709\u52b9\u3067\u3059\u3002<\/p>\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-typescript\" data-lang=\"TypeScript\"><code>&gt;\/\/ \u30e1\u30c3\u30bb\u30fc\u30b8\u5c65\u6b74\u306e\u30c8\u30fc\u30af\u30f3\u6570\u3092\u5236\u9650\u3059\u308b\u30e6\u30fc\u30c6\u30a3\u30ea\u30c6\u30a3\n\nfunction trimMessageHistory(\n  messages: Message[],\n  maxMessages: number = 20\n): Message[] {\n  if (messages.length &lt;= maxMessages) return messages\n\n  \/\/ \u6700\u521d\u306e\u30b7\u30b9\u30c6\u30e0\u30e1\u30c3\u30bb\u30fc\u30b8\u306f\u5e38\u306b\u4fdd\u6301\n  const systemMessages = messages.filter(m =&gt; m.role === 'system')\n  const conversationMessages = messages.filter(m =&gt; m.role !== 'system')\n\n  \/\/ \u6700\u65b0N\u4ef6\u306e\u307f\u4fdd\u6301\n  const trimmed = conversationMessages.slice(-maxMessages)\n\n  return [...systemMessages, ...trimmed]\n}\n\n\/\/ API\u30eb\u30fc\u30c8\u3067\u4f7f\u7528\nexport async function POST(req: Request) {\n  const { messages } = await req.json()\n  const trimmedMessages = trimMessageHistory(messages, 20)\n\n  const result = streamText({\n    model: openai('gpt-4o-mini'),\n    messages: trimmedMessages,\n  })\n\n  return result.toDataStreamResponse()\n}<\/code><\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u30c7\u30d7\u30ed\u30a4\u3068\u672c\u756a\u74b0\u5883\u306e\u8a2d\u5b9a<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Vercel\u3078\u306e\u30c7\u30d7\u30ed\u30a4<\/h3>\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-bash\" data-lang=\"Bash\"><code># Vercel CLI\u3067\u30c7\u30d7\u30ed\u30a4\nnpm i -g vercel\nvercel\n\n# \u74b0\u5883\u5909\u6570\u3092\u8a2d\u5b9a\uff08Vercel\u30c0\u30c3\u30b7\u30e5\u30dc\u30fc\u30c9 or CLI\u3067\uff09\nvercel env add OPENAI_API_KEY production\n\n# \u30d7\u30ec\u30d3\u30e5\u30fc\u30c7\u30d7\u30ed\u30a4\uff08develop\/feature\u30d6\u30e9\u30f3\u30c1\uff09\nvercel --env OPENAI_API_KEY=sk-xxx\n\n# \u672c\u756a\u30c7\u30d7\u30ed\u30a4\nvercel --prod<\/code><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">\u30ec\u30fc\u30c8\u5236\u9650\u306e\u5b9f\u88c5\uff08\u672c\u756a\u74b0\u5883\u5fc5\u9808\uff09<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u672c\u756a\u74b0\u5883\u3067\u306f\u60aa\u610f\u3042\u308b\u30e6\u30fc\u30b6\u30fc\u306b\u3088\u308b\u5927\u91cf\u30ea\u30af\u30a8\u30b9\u30c8\uff08DoS\u653b\u6483\u30fb\u30b3\u30b9\u30c8\u7206\u767a\uff09\u3092\u9632\u3050\u30ec\u30fc\u30c8\u5236\u9650\u304c\u5fc5\u9808\u3067\u3059\u3002Vercel KV\uff08Redis\u4e92\u63db\uff09\u3092\u4f7f\u3063\u305f\u30b7\u30f3\u30d7\u30eb\u306a\u5b9f\u88c5\u4f8b\u3092\u793a\u3057\u307e\u3059\u3002<\/p>\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-javascript\" data-lang=\"JavaScript\"><code>&gt;\/\/ src\/app\/api\/chat\/route.ts\n\/\/ \u30ec\u30fc\u30c8\u5236\u9650\u4ed8\u304dAPI\u30eb\u30fc\u30c8\n\nimport { streamText } from 'ai'\nimport { openai } from '@ai-sdk\/openai'\nimport { kv } from '@vercel\/kv'\nimport { headers } from 'next\/headers'\n\nconst RATE_LIMIT = 20  \/\/ 1\u5206\u3042\u305f\u308a\u306e\u6700\u5927\u30ea\u30af\u30a8\u30b9\u30c8\u6570\nconst RATE_LIMIT_WINDOW = 60  \/\/ \u30a6\u30a3\u30f3\u30c9\u30a6\u30b5\u30a4\u30ba\uff08\u79d2\uff09\n\nexport async function POST(req: Request) {\n  \/\/ IP\u30a2\u30c9\u30ec\u30b9\u3067\u30ec\u30fc\u30c8\u5236\u9650\n  const headersList = await headers()\n  const ip = headersList.get('x-forwarded-for') ?? 'unknown'\n  const key = `rate_limit:${ip}`\n\n  const count = await kv.incr(key)\n  if (count === 1) {\n    await kv.expire(key, RATE_LIMIT_WINDOW)\n  }\n\n  if (count &gt; RATE_LIMIT) {\n    return new Response(\n      JSON.stringify({ error: 'Too Many Requests' }),\n      {\n        status: 429,\n        headers: {\n          'Content-Type': 'application\/json',\n          'Retry-After': String(RATE_LIMIT_WINDOW),\n        },\n      }\n    )\n  }\n\n  const { messages } = await req.json()\n  const result = streamText({\n    model: openai('gpt-4o-mini'),\n    messages,\n  })\n\n  return result.toDataStreamResponse()\n}<\/code><\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u3088\u304f\u3042\u308b\u8cea\u554f<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u8d77\u304d\u305f\u5834\u5408\u3069\u3046\u306a\u308a\u307e\u3059\u304b\uff1f<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>useChat<\/code>\u306e<code>error<\/code>\u30d7\u30ed\u30d1\u30c6\u30a3\u306b\u30a8\u30e9\u30fc\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u304c\u683c\u7d0d\u3055\u308c\u307e\u3059\u3002<code>onError<\/code>\u30b3\u30fc\u30eb\u30d0\u30c3\u30af\u3092\u8a2d\u5b9a\u3059\u308b\u3053\u3068\u3067\u30a8\u30e9\u30fc\u767a\u751f\u6642\u306e\u30ed\u30b0\u9001\u4fe1\u30fb\u30e6\u30fc\u30b6\u30fc\u3078\u306e\u901a\u77e5\u51e6\u7406\u3092\u8a18\u8ff0\u3067\u304d\u307e\u3059\u3002<code>maxRetries<\/code>\u3067\u81ea\u52d5\u30ea\u30c8\u30e9\u30a4\u3092\u8a2d\u5b9a\u3059\u308b\u3053\u3068\u3067\u4e00\u6642\u7684\u306a\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u969c\u5bb3\u3078\u306e\u8010\u6027\u3092\u9ad8\u3081\u3089\u308c\u307e\u3059\u3002<code>reload()<\/code>\u95a2\u6570\u3067\u6700\u5f8c\u306e\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u518d\u9001\u4fe1\u3059\u308b\u3053\u3068\u3082\u53ef\u80fd\u3067\u3059\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Vercel\u4ee5\u5916\uff08AWS\u30fbGCP\u30fbRender\u306a\u3069\uff09\u306b\u30c7\u30d7\u30ed\u30a4\u3067\u304d\u307e\u3059\u304b\uff1f<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u3067\u304d\u307e\u3059\u3002Vercel AI SDK\u306fVercel\u30d7\u30e9\u30c3\u30c8\u30d5\u30a9\u30fc\u30e0\u306b\u4f9d\u5b58\u3057\u3066\u3044\u307e\u305b\u3093\u3002\u305f\u3060\u3057<code>export const maxDuration = 30<\/code>\u306fVercel Edge Function\u306e\u8a2d\u5b9a\u3067\u3042\u308a\u3001\u4ed6\u306e\u74b0\u5883\u3067\u306f\u30b5\u30fc\u30d0\u30fc\u306e\u30bf\u30a4\u30e0\u30a2\u30a6\u30c8\u8a2d\u5b9a\u3092\u5225\u9014\u8abf\u6574\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002AWS\u306eApp Runner\u30fbECS\u30fbLambda\u3084GCP\u306eCloud Run\u3067\u3082\u554f\u984c\u306a\u304f\u52d5\u4f5c\u3057\u307e\u3059\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\u30c1\u30e3\u30c3\u30c8\u5c65\u6b74\u3092\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306b\u4fdd\u5b58\u3059\u308b\u306b\u306f\u3069\u3046\u3059\u308c\u3070\u3044\u3044\u3067\u3059\u304b\uff1f<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>useChat<\/code>\u306e<code>onFinish<\/code>\u30b3\u30fc\u30eb\u30d0\u30c3\u30af\u3067\u4f1a\u8a71\u7d42\u4e86\u6642\u306bAPI\u3092\u53e9\u3044\u3066DB\u306b\u4fdd\u5b58\u3059\u308b\u306e\u304c\u6a19\u6e96\u7684\u306a\u30d1\u30bf\u30fc\u30f3\u3067\u3059\u3002<code>initialMessages<\/code>\u30d7\u30ed\u30d1\u30c6\u30a3\u3067DB\u304b\u3089\u53d6\u5f97\u3057\u305f\u904e\u53bb\u306e\u5c65\u6b74\u3092\u521d\u671f\u5024\u3068\u3057\u3066\u6e21\u3059\u3053\u3068\u3067\u4f1a\u8a71\u5c65\u6b74\u306e\u5fa9\u5143\u3082\u53ef\u80fd\u3067\u3059\u3002<\/p>\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-javascript\" data-lang=\"JavaScript\"><code>&gt;const { messages } = useChat({\n  initialMessages: await fetchMessagesFromDB(sessionId), \/\/ \u904e\u53bb\u306e\u5c65\u6b74\u3092\u521d\u671f\u5024\u3068\u3057\u3066\u6e21\u3059\n  onFinish: async (message) =&gt; {\n    \/\/ \u4f1a\u8a71\u5b8c\u4e86\u6642\u306bDB\u3078\u4fdd\u5b58\n    await fetch('\/api\/save-message', {\n      method: 'POST',\n      body: JSON.stringify({ sessionId, message }),\n    })\n  },\n})<\/code><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">useChat \u3068 useCompletion \u306e\u9055\u3044\u306f\u4f55\u3067\u3059\u304b\uff1f<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>useChat<\/code>\u306f\u30c1\u30e3\u30c3\u30c8\u5f62\u5f0f\uff08\u8907\u6570\u30bf\u30fc\u30f3\u306e\u3084\u308a\u53d6\u308a\u30fb\u4f1a\u8a71\u5c65\u6b74\u7ba1\u7406\uff09\u306b\u7279\u5316\u3057\u305fHook\u3067\u3059\u3002<code>useCompletion<\/code>\u306f\u5358\u767a\u306e\u30c6\u30ad\u30b9\u30c8\u751f\u6210\uff081\u3064\u306e\u30d7\u30ed\u30f3\u30d7\u30c8\u306b\u5bfe\u3057\u30661\u3064\u306e\u88dc\u5b8c\uff09\u306b\u7279\u5316\u3057\u3066\u3044\u307e\u3059\u3002\u300c\u7ffb\u8a33\u30c4\u30fc\u30eb\u300d\u300c\u8981\u7d04\u30c4\u30fc\u30eb\u300d\u300c\u30b3\u30fc\u30c9\u751f\u6210\u300d\u306a\u3069\u30b7\u30f3\u30b0\u30eb\u30bf\u30fc\u30f3\u306e\u30e6\u30fc\u30b9\u30b1\u30fc\u30b9\u306b\u306f<code>useCompletion<\/code>\u304c\u9069\u3057\u3066\u3044\u307e\u3059\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u3092\u9014\u4e2d\u3067\u505c\u6b62\u3067\u304d\u307e\u3059\u304b\uff1f<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u3067\u304d\u307e\u3059\u3002<code>useChat<\/code>\u304c\u8fd4\u3059<code>stop()<\/code>\u95a2\u6570\u3092\u547c\u3076\u3053\u3068\u3067\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u3092\u3044\u3064\u3067\u3082\u4e2d\u65ad\u3067\u304d\u307e\u3059\u3002\u4e2d\u65ad\u3057\u305f\u5834\u5408\u305d\u308c\u307e\u3067\u306b\u53d7\u4fe1\u3057\u305f\u30c6\u30ad\u30b9\u30c8\u306f<code>messages<\/code>\u306b\u6b8b\u308a\u307e\u3059\u3002<code>isLoading<\/code>\u306b\u5fdc\u3058\u3066\u300c\u9001\u4fe1\u30dc\u30bf\u30f3\u300d\u3068\u300c\u505c\u6b62\u30dc\u30bf\u30f3\u300d\u3092\u5207\u308a\u66ff\u3048\u308bUI\u30d1\u30bf\u30fc\u30f3\u304c\u4e00\u822c\u7684\u3067\u3059\u3002<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u307e\u3068\u3081<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Vercel AI SDK<\/strong>\uff1astreamText + toDataStreamResponse \u3067\u30b5\u30fc\u30d0\u30fc\u5074\u306e\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u304c2\u884c\u3067\u5b8c\u6210\u3002\u30ce\u30f3\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u3068\u6bd4\u3079\u3066\u30e6\u30fc\u30b6\u30fc\u4f53\u9a13\u304c\u5927\u5e45\u306b\u5411\u4e0a\u3059\u308b<\/li>\n\n\n<li><strong>useChat Hook<\/strong>\uff1a\u9001\u4fe1\u30fb\u53d7\u4fe1\u30fb\u30ed\u30fc\u30c7\u30a3\u30f3\u30b0\u30fb\u30a8\u30e9\u30fc\u30fb\u5c65\u6b74\u7ba1\u7406\u30fb\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u505c\u6b62\uff08stop\uff09\u30fb\u518d\u8a66\u884c\uff08reload\uff09\u3092\u3059\u3079\u3066\u62bd\u8c61\u5316\u3002\u72b6\u614b\u7ba1\u7406\u30b3\u30fc\u30c9\u304c\u307b\u307c\u4e0d\u8981<\/li>\n\n\n<li><strong>\u30c4\u30fc\u30eb\u547c\u3073\u51fa\u3057<\/strong>\uff1atool()\u95a2\u6570\u3068Zod\u30b9\u30ad\u30fc\u30de\u3067LLM\u304c\u81ea\u5f8b\u7684\u306b\u5916\u90e8API\u3092\u547c\u3073\u51fa\u3059\u30a8\u30fc\u30b8\u30a7\u30f3\u30c8\u6a5f\u80fd\u3092\u5b9f\u88c5\u3067\u304d\u308b\u3002maxSteps\u3067\u9023\u9396\u7684\u306a\u547c\u3073\u51fa\u3057\u3082\u5236\u5fa1\u53ef\u80fd<\/li>\n\n\n<li><strong>\u30de\u30eb\u30c1\u30e2\u30fc\u30c0\u30eb<\/strong>\uff1aGPT-4o\u7b49\u306eVision\u30e2\u30c7\u30eb\u306b\u753b\u50cf\u3092\u6e21\u3057\u3066\u5206\u6790\u3059\u308bUI\u3082formData\u3068ArrayBuffer\u3092\u4f7f\u3063\u3066\u7c21\u5358\u306b\u5b9f\u88c5\u3067\u304d\u308b<\/li>\n\n\n<li><strong>\u30de\u30eb\u30c1\u30d7\u30ed\u30d0\u30a4\u30c0\u30fc<\/strong>\uff1aOpenAI \/ Anthropic \/ Amazon Bedrock\u7b49\u3092\u307b\u307c\u540c\u3058API\u3067\u5207\u308a\u66ff\u3048\u53ef\u80fd\u3002\u672c\u756a\u74b0\u5883\u3067\u306f\u74b0\u5883\u5909\u6570\u3067\u30d7\u30ed\u30d0\u30a4\u30c0\u30fc\u3092\u5236\u5fa1\u3067\u304d\u308b<\/li>\n\n\n<li><strong>\u672c\u756a\u74b0\u5883\u5bfe\u7b56<\/strong>\uff1aEdge Runtime\u306b\u3088\u308b\u30ec\u30a4\u30c6\u30f3\u30b7\u524a\u6e1b\u30fbVercel KV\u3092\u4f7f\u3063\u305f\u30ec\u30fc\u30c8\u5236\u9650\u30fb\u30b9\u30e9\u30a4\u30c7\u30a3\u30f3\u30b0\u30a6\u30a3\u30f3\u30c9\u30a6\u3067\u30b3\u30b9\u30c8\u7ba1\u7406\u304c\u53ef\u80fd<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong><span class=\"swl-marker mark_yellow\">Vercel AI SDK\u3092\u4f7f\u3048\u3070\u3001LLM\u306e\u5fdc\u7b54\u5f85\u3061\u4f53\u9a13\u3092\u6839\u672c\u304b\u3089\u6539\u5584\u3059\u308b\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0UI\u304c\u6570\u5341\u884c\u306e\u30b3\u30fc\u30c9\u3067\u5b9f\u88c5\u3067\u304d\u307e\u3059\u3002\u30c4\u30fc\u30eb\u547c\u3073\u51fa\u3057\u30fb\u30de\u30eb\u30c1\u30e2\u30fc\u30c0\u30eb\u30fb\u30de\u30eb\u30c1\u30d7\u30ed\u30d0\u30a4\u30c0\u30fc\u306a\u3069\u9ad8\u5ea6\u306a\u6a5f\u80fd\u3082\u6a19\u6e96\u3067\u63d0\u4f9b\u3055\u308c\u3066\u304a\u308a\u3001Next.js\u3068TypeScript\u306e\u57fa\u790e\u304c\u3042\u308c\u3070\u4eca\u65e5\u304b\u3089\u59cb\u3081\u3089\u308c\u307e\u3059\u3002<\/span><\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<!-- withmedia-related -->\n<h2 class=\"wp-block-heading\">\u95a2\u9023\u8a18\u4e8b<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/withcode.tech\/media\/ai-era-freelance-work-style\/\">AI\u6642\u4ee3\u306e\u50cd\u304d\u65b9\uff5c\u30d5\u30ea\u30fc\u30e9\u30f3\u30b9\u304c\u300c\u4f7f\u308f\u308c\u308b\u5074\u300d\u304b\u3089\u300c\u4f7f\u3046\u5074\u300d\u306b\u306a\u308b\u305f\u3081\u306e\u6975\u610f\u3092\u5fb9\u5e95\u89e3\u8aac<\/a><\/li>\n<li><a href=\"https:\/\/withcode.tech\/media\/owasp-llm-top-10-guide\/\">OWASP Top 10 for LLM Applications \u89e3\u8aac\uff5c\u751f\u6210AI\u30a2\u30d7\u30ea\u306e\u4ee3\u8868\u7684\u30ea\u30b9\u30af\u3068\u5bfe\u7b56<\/a><\/li>\n<li><a href=\"https:\/\/withcode.tech\/media\/google-gemini-guide\/\">Google Gemini \u516c\u5f0f\u60c5\u5831\u307e\u3068\u3081\uff5c\u30e2\u30c7\u30eb\u30fb\u3067\u304d\u308b\u3053\u3068\u30fb\u6599\u91d1\u3092\u3084\u3055\u3057\u304f\u89e3\u8aac<\/a><\/li>\n<li><a href=\"https:\/\/withcode.tech\/media\/anthropic-claude-models-pricing\/\">Claude\u516c\u5f0f\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u3067\u898b\u308b\u30e2\u30c7\u30eb\u4ed5\u69d8\u3068\u6599\u91d1\uff5c\u696d\u52d9\u6d3b\u7528\u306e\u57fa\u790e\u30ac\u30a4\u30c9<\/a><\/li>\n<li><a href=\"https:\/\/withcode.tech\/media\/openai-models-pricing-guide\/\">OpenAI\u516c\u5f0f\u3067\u5b66\u3076\u30e2\u30c7\u30eb\u30fb\u6599\u91d1\u30fb\u5229\u7528\u898f\u7d04\uff5cChatGPT\/API\u6d3b\u7528\u306e\u57fa\u790e<\/a><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<!-- withmedia-references -->\n<h2 class=\"wp-block-heading\">\u53c2\u8003\u30ea\u30f3\u30af\uff08\u516c\u5f0f\u30fb\u4e00\u6b21\u60c5\u5831\uff09<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/developer.mozilla.org\/ja\/docs\/Web\/JavaScript\" target=\"_blank\" rel=\"noopener noreferrer\">MDN Web Docs \u2014 JavaScript<\/a><\/li>\n<li><a href=\"https:\/\/ja.react.dev\/\" target=\"_blank\" rel=\"noopener noreferrer\">React \u516c\u5f0f\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8<\/a><\/li>\n<li><a href=\"https:\/\/www.typescriptlang.org\/docs\/\" target=\"_blank\" rel=\"noopener noreferrer\">TypeScript \u516c\u5f0f\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8<\/a><\/li>\n<li><a href=\"https:\/\/platform.openai.com\/docs\/overview\" target=\"_blank\" rel=\"noopener noreferrer\">OpenAI \u516c\u5f0f\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8<\/a><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">WithCode\u3092\u4f53\u9a13\u3067\u304d\u308b\u521d\u7d1a\u30b3\u30fc\u30b9\u516c\u958b\u4e2d\uff01<\/h2>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"alignleft size-full\"><img decoding=\"async\" width=\"600\" height=\"500\" src=\"https:\/\/withcode.tech\/media\/wp-content\/uploads\/2024\/04\/\u30d0\u30ca\u30fc_300\u00d7250_20240425.png\" alt=\"WithCode\u3092\u4f53\u9a13\u3067\u304d\u308b\u521d\u7d1a\u30b3\u30fc\u30b9\u516c\u958b\u4e2d\uff01\" class=\"wp-image-2650\"\/><\/figure>\n<\/div>\n\n\n<h3 class=\"wp-block-heading\">\u521d\u7d1a\u30b3\u30fc\u30b9(\u00a549,800)\u304c\u5b8c\u5168\u7121\u6599\u306b\uff01<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>\u671f\u9593\uff1a1\u9031\u9593<\/strong><\/li>\n<li><strong>\u5b66\u7fd2\u5185\u5bb9\uff1a<\/strong><br><strong>\u30ed\u30fc\u30c9\u30de\u30c3\u30d7\/\u57fa\u790e\u77e5\u8b58\/\u74b0\u5883\u69cb\u7bc9\/HTML\/CSS\/LP\u30fb\u30dd\u30fc\u30c8\u30d5\u30a9\u30ea\u30aa\u4f5c\u6210<\/strong><br><strong>\u2192 <span class=\"swl-marker mark_yellow\">\u6b63\u3057\u3044\u5b66\u7fd2\u65b9\u6cd5\u3067\u300c\u78ba\u304b\u306a\u6210\u9577\u300d\u3092\u5b9f\u611f\u3067\u304d\u308b\u30ab\u30ea\u30ad\u30e5\u30e9\u30e0\u3002<\/span><\/strong><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">\u526f\u696d\u30fb\u30d5\u30ea\u30fc\u30e9\u30f3\u30b9\u304c\u4e3b\u6d41\u306b\u306a\u3063\u3066\u3044\u308b\u4eca\u3053\u305d\u3001\u81ea\u3089\u306e\u30b9\u30ad\u30eb\u3067\u7a3c\u3052\u308b\u4eba\u6750\u3092\u76ee\u6307\u3057\u3066\u307f\u307e\u305b\u3093\u304b\uff1f<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u672a\u7d4c\u9a13\u3067\u3082\u5fc3\u914d\u3059\u308b\u3053\u3068\u306f\u3042\u308a\u307e\u305b\u3093\u3002\u521d\u7d1a\u30b3\u30fc\u30b9\u3092\u53d7\u8b1b\u3055\u308c\u308b\u65b9\u306e\u5927\u591a\u6570\u306f\u30d7\u30ed\u30b0\u30e9\u30df\u30f3\u30b0\u672a\u7d4c\u9a13\u3067\u3059\u3002\u307e\u305a\u306f\u7121\u6599\u30ab\u30a6\u30f3\u30bb\u30ea\u30f3\u30b0\u3067\u3001\u60a9\u307f\u3084\u4e0d\u5b89\u3092\u304a\u805e\u304b\u305b\u304f\u3060\u3055\u3044\uff01<\/p>\n\n\n\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link has-white-color has-text-color has-background has-link-color wp-element-button\" href=\"https:\/\/withcode.tech\/reservation\/\" style=\"background-color:#ffbf00\"><strong>\u516c\u5f0f\u30b5\u30a4\u30c8\u304b\u3089\u7121\u6599\u30ab\u30a6\u30f3\u30bb\u30ea\u30f3\u30b0\u306b\u7533\u3057\u8fbc\u3080 \u2192<\/strong><\/a><\/div>\n<\/div>\n\n\n\n<!-- withmedia-faq-schema -->\n<script type=\"application\/ld+json\">\n{\n  \"@context\": \"https:\/\/schema.org\",\n  \"@type\": \"FAQPage\",\n  \"mainEntity\": [\n    {\n      \"@type\": \"Question\",\n      \"name\": \"\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u8d77\u304d\u305f\u5834\u5408\u3069\u3046\u306a\u308a\u307e\u3059\u304b\uff1f\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"useChat\u306eerror\u30d7\u30ed\u30d1\u30c6\u30a3\u306b\u30a8\u30e9\u30fc\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u304c\u683c\u7d0d\u3055\u308c\u307e\u3059\u3002onError\u30b3\u30fc\u30eb\u30d0\u30c3\u30af\u3092\u8a2d\u5b9a\u3059\u308b\u3053\u3068\u3067\u30a8\u30e9\u30fc\u767a\u751f\u6642\u306e\u30ed\u30b0\u9001\u4fe1\u30fb\u30e6\u30fc\u30b6\u30fc\u3078\u306e\u901a\u77e5\u51e6\u7406\u3092\u8a18\u8ff0\u3067\u304d\u307e\u3059\u3002maxRetries\u3067\u81ea\u52d5\u30ea\u30c8\u30e9\u30a4\u3092\u8a2d\u5b9a\u3059\u308b\u3053\u3068\u3067\u4e00\u6642\u7684\u306a\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u969c\u5bb3\u3078\u306e\u8010\u6027\u3092\u9ad8\u3081\u3089\u308c\u307e\u3059\u3002reload()\u95a2\u6570\u3067\u6700\u5f8c\u306e\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u518d\u9001\u4fe1\u3059\u308b\u3053\u3068\u3082\u53ef\u80fd\u3067\u3059\u3002\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Vercel\u4ee5\u5916\uff08AWS\u30fbGCP\u30fbRender\u306a\u3069\uff09\u306b\u30c7\u30d7\u30ed\u30a4\u3067\u304d\u307e\u3059\u304b\uff1f\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"\u3067\u304d\u307e\u3059\u3002Vercel AI SDK\u306fVercel\u30d7\u30e9\u30c3\u30c8\u30d5\u30a9\u30fc\u30e0\u306b\u4f9d\u5b58\u3057\u3066\u3044\u307e\u305b\u3093\u3002\u305f\u3060\u3057export const maxDuration = 30\u306fVercel Edge Function\u306e\u8a2d\u5b9a\u3067\u3042\u308a\u3001\u4ed6\u306e\u74b0\u5883\u3067\u306f\u30b5\u30fc\u30d0\u30fc\u306e\u30bf\u30a4\u30e0\u30a2\u30a6\u30c8\u8a2d\u5b9a\u3092\u5225\u9014\u8abf\u6574\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002AWS\u306eApp Runner\u30fbECS\u30fbLambda\u3084GCP\u306eCloud Run\u3067\u3082\u554f\u984c\u306a\u304f\u52d5\u4f5c\u3057\u307e\u3059\u3002\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"\u30c1\u30e3\u30c3\u30c8\u5c65\u6b74\u3092\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306b\u4fdd\u5b58\u3059\u308b\u306b\u306f\u3069\u3046\u3059\u308c\u3070\u3044\u3044\u3067\u3059\u304b\uff1f\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"useChat\u306eonFinish\u30b3\u30fc\u30eb\u30d0\u30c3\u30af\u3067\u4f1a\u8a71\u7d42\u4e86\u6642\u306bAPI\u3092\u53e9\u3044\u3066DB\u306b\u4fdd\u5b58\u3059\u308b\u306e\u304c\u6a19\u6e96\u7684\u306a\u30d1\u30bf\u30fc\u30f3\u3067\u3059\u3002initialMessages\u30d7\u30ed\u30d1\u30c6\u30a3\u3067DB\u304b\u3089\u53d6\u5f97\u3057\u305f\u904e\u53bb\u306e\u5c65\u6b74\u3092\u521d\u671f\u5024\u3068\u3057\u3066\u6e21\u3059\u3053\u3068\u3067\u4f1a\u8a71\u5c65\u6b74\u306e\u5fa9\u5143\u3082\u53ef\u80fd\u3067\u3059\u3002\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"useChat \u3068 useCompletion \u306e\u9055\u3044\u306f\u4f55\u3067\u3059\u304b\uff1f\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"useChat\u306f\u30c1\u30e3\u30c3\u30c8\u5f62\u5f0f\uff08\u8907\u6570\u30bf\u30fc\u30f3\u306e\u3084\u308a\u53d6\u308a\u30fb\u4f1a\u8a71\u5c65\u6b74\u7ba1\u7406\uff09\u306b\u7279\u5316\u3057\u305fHook\u3067\u3059\u3002useCompletion\u306f\u5358\u767a\u306e\u30c6\u30ad\u30b9\u30c8\u751f\u6210\uff081\u3064\u306e\u30d7\u30ed\u30f3\u30d7\u30c8\u306b\u5bfe\u3057\u30661\u3064\u306e\u88dc\u5b8c\uff09\u306b\u7279\u5316\u3057\u3066\u3044\u307e\u3059\u3002\u300c\u7ffb\u8a33\u30c4\u30fc\u30eb\u300d\u300c\u8981\u7d04\u30c4\u30fc\u30eb\u300d\u300c\u30b3\u30fc\u30c9\u751f\u6210\u300d\u306a\u3069\u30b7\u30f3\u30b0\u30eb\u30bf\u30fc\u30f3\u306e\u30e6\u30fc\u30b9\u30b1\u30fc\u30b9\u306b\u306fuseCompletion\u304c\u9069\u3057\u3066\u3044\u307e\u3059\u3002\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u3092\u9014\u4e2d\u3067\u505c\u6b62\u3067\u304d\u307e\u3059\u304b\uff1f\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"\u3067\u304d\u307e\u3059\u3002useChat\u304c\u8fd4\u3059stop()\u95a2\u6570\u3092\u547c\u3076\u3053\u3068\u3067\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u3092\u3044\u3064\u3067\u3082\u4e2d\u65ad\u3067\u304d\u307e\u3059\u3002\u4e2d\u65ad\u3057\u305f\u5834\u5408\u305d\u308c\u307e\u3067\u306b\u53d7\u4fe1\u3057\u305f\u30c6\u30ad\u30b9\u30c8\u306fmessages\u306b\u6b8b\u308a\u307e\u3059\u3002isLoading\u306b\u5fdc\u3058\u3066\u300c\u9001\u4fe1\u30dc\u30bf\u30f3\u300d\u3068\u300c\u505c\u6b62\u30dc\u30bf\u30f3\u300d\u3092\u5207\u308a\u66ff\u3048\u308bUI\u30d1\u30bf\u30fc\u30f3\u304c\u4e00\u822c\u7684\u3067\u3059\u3002\"\n      }\n    }\n  ]\n}\n<\/script>\n\n\n\n<!-- withmedia-article-schema -->\n<script type=\"application\/ld+json\">\n{\n  \"@context\": \"https:\/\/schema.org\",\n  \"@graph\": [\n    {\n      \"@type\": \"Organization\",\n      \"@id\": \"https:\/\/withcode.tech\/media\/#organization\",\n      \"name\": \"WithCode\",\n      \"url\": \"https:\/\/withcode.tech\/\",\n      \"description\": \"\u300c\u76ee\u6307\u3059\u306a\u3089\u7a3c\u3052\u308b\u4eba\u6750\u300d\u3092\u30d3\u30b8\u30e7\u30f3\u306b\u7d2f\u8a08400\u540d\u4ee5\u4e0a\u306e\u30d5\u30ea\u30fc\u30e9\u30f3\u30b9\u3092\u8f29\u51fa\u3057\u305f\u5b9f\u8df5\u578b\u30d7\u30ed\u30b0\u30e9\u30df\u30f3\u30b0\u30b9\u30af\u30fc\u30eb\u3002150\u793e\u4ee5\u4e0a\u306e\u5b9f\u6848\u4ef6\u652f\u63f4\u306b\u57fa\u3065\u304dWeb\u5236\u4f5c\u30fbWeb\u30c7\u30b6\u30a4\u30f3\u306e\u60c5\u5831\u3092\u767a\u4fe1\u3002\",\n      \"logo\": {\n        \"@type\": \"ImageObject\",\n        \"url\": \"https:\/\/withcode.tech\/media\/wp-content\/uploads\/2023\/12\/WITHCODE_logo.png\"\n      },\n      \"sameAs\": [\n        \"https:\/\/withcode.tech\/\"\n      ]\n    },\n    {\n      \"@type\": \"WebSite\",\n      \"@id\": \"https:\/\/withcode.tech\/media\/#website\",\n      \"name\": \"WithMedia\",\n      \"url\": \"https:\/\/withcode.tech\/media\/\",\n      \"publisher\": {\n        \"@id\": \"https:\/\/withcode.tech\/media\/#organization\"\n      },\n      \"inLanguage\": \"ja\"\n    },\n    {\n      \"@id\": \"https:\/\/withcode.tech\/media\/author\/withcode-officialgmail-com\/#author\",\n      \"name\": \"WithCode \u7de8\u96c6\u90e8\",\n      \"url\": \"https:\/\/withcode.tech\/media\/author\/withcode-officialgmail-com\/\",\n      \"description\": \"WithCode\uff08\u30a6\u30a3\u30ba\u30b3\u30fc\u30c9\uff09\u306f\u300c\u76ee\u6307\u3059\u306a\u3089\u7a3c\u3052\u308b\u4eba\u6750\u300d\u3092\u30d3\u30b8\u30e7\u30f3\u306b\u3001\u7d2f\u8a08400\u540d\u4ee5\u4e0a\u306e\u30d5\u30ea\u30fc\u30e9\u30f3\u30b9\u3092\u8f29\u51fa\u3057\u3066\u304d\u305f\u8d85\u5b9f\u8df5\u578b\u30d7\u30ed\u30b0\u30e9\u30df\u30f3\u30b0\u30b9\u30af\u30fc\u30eb\u3067\u3059\u3002150\u793e\u4ee5\u4e0a\u306e\u5b9f\u6848\u4ef6\u652f\u63f4\u3092\u7279\u5fb4\u306bWeb\u5236\u4f5c\u30fbWeb\u30c7\u30b6\u30a4\u30f3\u306a\u3069\u306e\u5f79\u7acb\u3064\u60c5\u5831\u3092\u73fe\u5834\u306e\u30ce\u30a6\u30cf\u30a6\u306b\u57fa\u3065\u3044\u3066\u767a\u4fe1\u3057\u3066\u3044\u304d\u307e\u3059\u3002\",\n      \"worksFor\": {\n        \"@id\": \"https:\/\/withcode.tech\/media\/#organization\"\n      },\n      \"@type\": \"Organization\"\n    },\n    {\n      \"@type\": \"BlogPosting\",\n      \"@id\": \"https:\/\/withcode.tech\/media\/vercel-ai-sdk-streaming-ui\/#article\",\n      \"headline\": \"Vercel AI SDK\u3067\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0UI\u3092\u5b9f\u88c5\u3059\u308b\u65b9\u6cd5\u3010\u5b8c\u5168\u30ac\u30a4\u30c9\u3011\uff5cNext.js \u00d7 LLM\u30a2\u30d7\u30ea\u306eUX\u6539\u5584\u3092\u5fb9\u5e95\u89e3\u8aac\",\n      \"mainEntityOfPage\": {\n        \"@type\": \"WebPage\",\n        \"@id\": \"https:\/\/withcode.tech\/media\/vercel-ai-sdk-streaming-ui\/\"\n      },\n      \"datePublished\": \"2026-03-16T18:39:11Z\",\n      \"dateModified\": \"2026-07-06T14:36:49Z\",\n      \"author\": {\n        \"@id\": \"https:\/\/withcode.tech\/media\/author\/withcode-officialgmail-com\/#author\"\n      },\n      \"publisher\": {\n        \"@id\": \"https:\/\/withcode.tech\/media\/#organization\"\n      },\n      \"isPartOf\": {\n        \"@id\": \"https:\/\/withcode.tech\/media\/#website\"\n      },\n      \"inLanguage\": \"ja\",\n      \"description\": \"Vercel AI SDK\u3092\u4f7f\u3048\u3070ChatGPT\u306e\u3088\u3046\u306a\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0UI\u3092Next.js\u3067\u6570\u5341\u884c\u3067\u5b9f\u88c5\u3067\u304d\u307e\u3059\u3002useChat Hook\u30fb\u30c4\u30fc\u30eb\u547c\u3073\u51fa\u3057\u30fb\u30de\u30eb\u30c1\u30e2\u30fc\u30c0\u30eb\u30fb\u30ec\u30fc\u30c8\u5236\u9650\u307e\u3067\u672c\u756a\u5bfe\u5fdc\u306e\u5b9f\u88c5\u30b3\u30fc\u30c9\u3092\u5b8c\u5168\u89e3\u8aac\u3057\u307e\u3059\u3002\",\n      \"image\": \"https:\/\/withcode.tech\/media\/wp-content\/uploads\/2026\/07\/13172-eyecatch.jpg\"\n    },\n    {\n      \"@type\": \"BreadcrumbList\",\n      \"@id\": \"https:\/\/withcode.tech\/media\/vercel-ai-sdk-streaming-ui\/#breadcrumb\",\n      \"itemListElement\": [\n        {\n          \"@type\": \"ListItem\",\n          \"position\": 1,\n          \"name\": \"\u30db\u30fc\u30e0\",\n          \"item\": \"https:\/\/withcode.tech\/media\/\"\n        },\n        {\n          \"@type\": \"ListItem\",\n          \"position\": 2,\n          \"name\": \"AI\",\n          \"item\": \"https:\/\/withcode.tech\/media\/category\/ai\/\"\n        },\n        {\n          \"@type\": \"ListItem\",\n          \"position\": 3,\n          \"name\": \"Vercel AI SDK\u3067\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0UI\u3092\u5b9f\u88c5\u3059\u308b\u65b9\u6cd5\u3010\u5b8c\u5168\u30ac\u30a4\u30c9\u3011\uff5cNext.js \u00d7 LLM\u30a2\u30d7\u30ea\u306eUX\u6539\u5584\u3092\u5fb9\u5e95\u89e3\u8aac\",\n          \"item\": \"https:\/\/withcode.tech\/media\/vercel-ai-sdk-streaming-ui\/\"\n        }\n      ]\n    }\n  ]\n}\n<\/script>\n\n","protected":false},"excerpt":{"rendered":"<p>Vercel AI SDK\u3092\u4f7f\u3048\u3070ChatGPT\u306e\u3088\u3046\u306a\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0UI\u3092Next.js\u3067\u6570\u5341\u884c\u3067\u5b9f\u88c5\u3067\u304d\u307e\u3059\u3002useChat Hook\u30fb\u30c4\u30fc\u30eb\u547c\u3073\u51fa\u3057\u30fb\u30de\u30eb\u30c1\u30e2\u30fc\u30c0\u30eb\u30fb\u30ec\u30fc\u30c8\u5236\u9650\u307e\u3067\u672c\u756a\u5bfe\u5fdc\u306e\u5b9f\u88c5\u30b3\u30fc\u30c9\u3092\u5b8c\u5168\u89e3\u8aac\u3057\u307e\u3059\u3002<\/p>\n","protected":false},"author":2,"featured_media":14273,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"swell_btn_cv_data":"","footnotes":"","vk-ltc-link":"","vk-ltc-target":"0"},"categories":[408],"tags":[],"class_list":["post-13172","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/withcode.tech\/media\/wp-json\/wp\/v2\/posts\/13172","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/withcode.tech\/media\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/withcode.tech\/media\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/withcode.tech\/media\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/withcode.tech\/media\/wp-json\/wp\/v2\/comments?post=13172"}],"version-history":[{"count":17,"href":"https:\/\/withcode.tech\/media\/wp-json\/wp\/v2\/posts\/13172\/revisions"}],"predecessor-version":[{"id":17850,"href":"https:\/\/withcode.tech\/media\/wp-json\/wp\/v2\/posts\/13172\/revisions\/17850"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/withcode.tech\/media\/wp-json\/wp\/v2\/media\/14273"}],"wp:attachment":[{"href":"https:\/\/withcode.tech\/media\/wp-json\/wp\/v2\/media?parent=13172"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/withcode.tech\/media\/wp-json\/wp\/v2\/categories?post=13172"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/withcode.tech\/media\/wp-json\/wp\/v2\/tags?post=13172"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}