Vercel AI SDK(npmパッケージ名は ai)は、LLMの呼び出しからチャット画面へのストリーミングまでをTypeScriptでつなぐツールキットです。2026年10月31日時点の最新は AI SDK 7(7.0.159)で、Node.js 22以上・ESM専用になりました。7月時点の解説で定番だった result.toUIMessageStreamResponse() や stepCountIs は、v7では非推奨または別名扱いです。
この記事では、インストール、テキスト生成、Next.jsのチャット画面、ツール呼び出し、@ai-sdk/langchain を使ったLangChain・LangGraphとの連携を、v7の書き方で順に示します。コード例はNode.js 26.5.0とTypeScript 7.0.2で型検査し、生成部分はAPIキーなしのモックモデルで実行して出力を確かめています。v6の新機能(ToolLoopAgentの登場など)を詳しく知りたい場合はVercel AI SDK v6とは?新機能(ToolLoopAgent・needsApproval)とv7との違い・移行【2026】を参照してください。
まとめ|AI SDK 7で押さえる要点
- 最新はAI SDK 7(7.0.159)。
npm install aiでv7が入り、Node.js 22以上・ESM専用。v6を使い続けるならai@ai-v6のdist-tagで固定する。 - Next.jsのルートハンドラは
toUIMessageStream({ stream: result.stream })をcreateUIMessageStreamResponseに渡す。結果オブジェクトのtoUIMessageStreamResponse()はv7で非推奨(動作はする)。 - システムプロンプトは
instructionsに書く。messagesにrole: 'system'を混ぜるとInvalidPromptErrorで止まる。 generateTextはstopWhenを省くと1ステップで止まる。最初の応答がツール呼び出しだけだとtextは空文字になるので、isStepCount(n)を指定する。- LangChain・LangGraphの出力は
@ai-sdk/langchainのtoUIMessageStreamでuseChatに流せる。streamModeに'messages'を入れないと本文が1文字も届かない。
AI SDK 7の現行バージョンと対応パッケージ
バージョン履歴と各パッケージの対応
AI SDKは本体の ai と、UIフック(@ai-sdk/react)・プロバイダ(@ai-sdk/openai など)・アダプタ(@ai-sdk/langchain)が別パッケージです。メジャー版ごとに組み合わせる版が決まっており、npmにはその組み合わせがdist-tagとして登録されています。
| 本体 ai | 初版の公開日 | @ai-sdk/react | @ai-sdk/openai | @ai-sdk/langchain | dist-tag |
|---|---|---|---|---|---|
| 7.0.159 | 2026-06-25 | 4.0.163 | 4.0.106 | 3.0.159 | latest |
| 6.0.320 | 2025-12-22 | 3.0.323 | 3.0.135 | 2.0.328 | ai-v6 |
| 5.0.283 | 2025-07-31 | 2.0.286 | 2.0.139 | 1.0.283 | ai-v5 |
版番号は2026年10月31日に npm view で取得した値です。v6系も10月30日に6.0.320が出ており、保守は続いています。v7で変わった主な点は、公式の移行ガイド(Migrate AI SDK 6.x to 7.0)によると、Node.js 22未満のサポート終了、CommonJSの廃止、system→instructions などの改称、ツール承認の設定場所の移動です。
インストール済みバージョンの確認とv6系の固定
手元のプロジェクトに入っている版は npm ls で、公開済みの最新版は npm view で確認できます。既存のv6アプリを今すぐ移行できない場合は、関連パッケージをまとめて ai-v6 タグで入れれば、組み合わせの食い違いを避けられます。
# 入っている版
npm ls ai @ai-sdk/react @ai-sdk/langchain
# 公開済みの最新版とdist-tag
npm view ai dist-tags
# v6系に固定する(本体・UI・プロバイダ・アダプタを同じタグで)
npm install ai@ai-v6 @ai-sdk/react@ai-v6 @ai-sdk/openai@ai-v6 @ai-sdk/langchain@ai-v6
インストールとプロバイダの設定
必要なパッケージと環境変数
Next.jsでチャットを作る最小構成は、本体・Reactフック・プロバイダ・スキーマ定義用のzodの4つです。AI SDK 7は "engines": { "node": ">=22" } を宣言しているため、Node.js 20以前の環境では先にランタイムを上げます。
npm install ai @ai-sdk/react @ai-sdk/openai zod
# .env.local
OPENAI_API_KEY=sk-xxxxxxxx
プロバイダの切り替えとモデルIDの指定
モデルは model 引数に渡すだけなので、プロバイダを替えても呼び出し側は変わりません。@ai-sdk/openai 4.0.106の型定義に載っているOpenAIの新しいモデルIDは gpt-6-astra・gpt-6.1-sol などです。公式のNext.jsクイックスタートは、Vercel AI Gateway経由で 'anthropic/claude-sonnet-5.5' のような文字列だけでモデルを指定する書き方を採っています(環境変数は AI_GATEWAY_API_KEY)。
// lib/model.ts
import { openai } from '@ai-sdk/openai';
export const model = openai('gpt-6-astra');
// AI Gatewayを使う場合は文字列で指定: 'anthropic/claude-sonnet-5.5'
テキストを生成する|generateText・streamText・Output.object
まとめて受け取る generateText
応答を一度に受け取るなら generateText です。v7ではシステムプロンプトの引数名が system から instructions に変わりました。モックモデルで実行すると、instructions の内容はプロバイダへ role: 'system' のメッセージとして渡っていました。
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
const { text } = await generateText({
model: openai('gpt-6-astra'),
instructions: '回答は3行以内で',
prompt: 'TypeScriptを採用する理由を説明して',
});
console.log(text);
過去の会話履歴を messages で渡すとき、その中に { role: 'system' } が残っていると、v7は AI_InvalidPromptError: Invalid prompt: System messages are not allowed in the prompt or messages fields. を投げます。DBに保存したv6時代の履歴を読み込むアプリで起きやすいエラーです。信頼できる履歴に限り allowSystemInMessages: true で旧挙動に戻せますが、ユーザーが編集できる履歴に付けるとシステムプロンプトを書き換えられるため、公式も推奨していません。
逐次表示する streamText
生成途中のトークンから順に表示するなら streamText を使います。textStream は非同期イテラブルなので for await で読めます。v7では全イベントを流す fullStream が stream に改称され、fullStream は非推奨になりました。
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
const result = streamText({
model: openai('gpt-6-astra'),
prompt: '自己紹介文を書いて',
});
for await (const delta of result.textStream) {
process.stdout.write(delta);
}
日本語の表示を滑らかにする smoothStream の分割設定は、Smooth Text Streamingとは?AI SDK smoothStreamの使い方と日本語の分割設定で扱っています。
構造化データで受け取る Output.object
JSONを型付きで受け取る用途の generateObject はv6で非推奨になり、generateText の output 引数に Output.object を渡す形へ移りました。結果は result.output に入ります。
import { generateText, Output } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
const { output } = await generateText({
model: openai('gpt-6-astra'),
output: Output.object({
schema: z.object({
title: z.string(),
tags: z.array(z.string()),
}),
}),
prompt: 'この記事のタイトルとタグ候補を生成して',
});
console.log(output.title, output.tags);
Next.jsでチャット画面を作る|ルートハンドラとuseChat
ルートハンドラでのUIメッセージストリーム返却
App Routerのルートハンドラで streamText を呼び、その stream を toUIMessageStream で変換して返します。v7の移行ガイドは、結果オブジェクトのメソッドだった toUIMessageStreamResponse() などを非推奨とし、次のメジャー版で削除すると明記しています。v7の間は警告付きで動きますが、新しく書くコードを関数版にしておけば、このメソッド削除に伴う修正は不要になります。
// app/api/chat/route.ts
import {
streamText,
convertToModelMessages,
toUIMessageStream,
createUIMessageStreamResponse,
type UIMessage,
} from 'ai';
import { openai } from '@ai-sdk/openai';
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model: openai('gpt-6-astra'),
instructions: 'あなたは社内ヘルプデスクの担当者です。',
messages: await convertToModelMessages(messages),
});
return createUIMessageStreamResponse({
stream: toUIMessageStream({ stream: result.stream }),
});
}
convertToModelMessages はPromiseを返すので await が要ります。このルートが返す中身を確かめるため、モックモデルに「こん」「にちは」の2断片を返させたところ、レスポンスは content-type: text/event-stream と x-vercel-ai-ui-message-stream: v1 を持つSSEで、本文は次の順に流れました。
data: {"type":"start"}
data: {"type":"start-step"}
data: {"type":"text-start","id":"t1"}
data: {"type":"text-delta","id":"t1","delta":"こん"}
data: {"type":"text-delta","id":"t1","delta":"にちは"}
data: {"type":"text-end","id":"t1"}
data: {"type":"finish-step"}
data: {"type":"finish","finishReason":"stop"}
data: [DONE]
レスポンスヘッダには x-accel-buffering: no も付きます。nginxを前段に置く構成で表示が一括になる場合は、この値がプロキシに届いているかを先に疑います。
useChatによる送信と描画
useChat は @ai-sdk/react から読み込みます。送信先は DefaultChatTransport で指定し、送信は sendMessage({ text }) です。メッセージ本文は message.content ではなく message.parts 配列に入るため、type === 'text' の要素を描画します。
// app/page.tsx
'use client';
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import { useState } from 'react';
export default function Chat() {
const [input, setInput] = useState('');
const { messages, sendMessage, status, stop } = useChat({
transport: new DefaultChatTransport({ api: '/api/chat' }),
});
return (
<div>
{messages.map((m) => (
<div key={m.id}>
<strong>{m.role}: </strong>
{m.parts.map((part, i) =>
part.type === 'text' ? <span key={i}>{part.text}</span> : null,
)}
</div>
))}
<form
onSubmit={(e) => {
e.preventDefault();
if (!input.trim()) return;
sendMessage({ text: input });
setInput('');
}}
>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button type="submit" disabled={status === 'submitted' || status === 'streaming'}>送信</button>
{status === 'streaming' && (
<button type="button" onClick={() => stop()}>停止</button>
)}
</form>
</div>
);
}
status は 'submitted'(送信直後)→'streaming'(受信中)→'ready' と変わり、失敗時は 'error' になります。ボタンを無効にするのは送信中と受信中だけにしておくと、'error' のあとも画面から送り直せます。毎ターンHTTPを張り直さずに1本の接続で会話を続けたい場合は、公式のTransportのページにある WebSocketChatTransport を使えます。ただしサーバー側もWebSocketのフレームを受けて返す実装に替える必要があり、上の route.ts のままでは動きません。
Svelte・Vue・Angularで使う場合
UI部分はフレームワークごとに別パッケージです。Reactは useChat フック、Svelteは @ai-sdk/svelte(5.0.159)の Chat クラス、Vueは @ai-sdk/vue(4.0.159)、Angularは @ai-sdk/angular(3.0.159)を使います。Vueはv7で Chat クラスが非推奨になり、useChat コンポーザブルへ移りました。サーバー側のルートは共通なので、上の route.ts の書き方はそのまま使えます。
ツール呼び出しとエージェントを組む|tool・isStepCount・ToolLoopAgent
toolの定義とisStepCountによるループ上限
ツールは tool() で定義し、入力の型は inputSchema にzodで書きます(v4までの parameters ではありません)。ツール実行→結果を踏まえた再推論のループは stopWhen で上限を決めます。v7で stepCountIs は isStepCount に改称されました。7.0.159では stepCountIs も同じ関数の別名として残っていますが、移行ガイドは改称として扱っています。
import { generateText, tool, isStepCount } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
export const weatherTool = tool({
description: '指定した都市の現在の天気を返す',
inputSchema: z.object({
city: z.string().describe('都市名(例: 東京)'),
}),
execute: async ({ city }) => {
// 実際は外部APIを呼ぶ。ここではダミー値
return { city, tempC: 21, condition: 'くもり' };
},
});
const result = await generateText({
model: openai('gpt-6-astra'),
tools: { weather: weatherTool },
stopWhen: isStepCount(5),
prompt: '東京と大阪、暖かいのはどっち?',
});
console.log(result.text, result.steps.length);
1回目にツール呼び出しだけ、2回目に文章を返すモックで試すと、stopWhen: isStepCount(5) ありでは2ステップで「東京は21度です」が返りました。stopWhen を省くと既定値の isStepCount(1) で止まり、ツールは実行されても result.text は空文字でした。モデルが同じ応答の中で文章も返した場合は、その文章は text に残ります。ループの止め方は公式のLoop Controlに条件の組み合わせ方が載っています。
ToolLoopAgentとツール承認(toolApproval)
同じ設定を複数のルートで使い回すなら ToolLoopAgent にまとめます。こちらは stopWhen を省いたときの既定が isStepCount(20) で、上のモックでも指定なしで2ステップ目まで進みました。
import { ToolLoopAgent, isStepCount } from 'ai';
import { openai } from '@ai-sdk/openai';
import { weatherTool } from './weather-tool';
const agent = new ToolLoopAgent({
model: openai('gpt-6-astra'),
instructions: 'あなたは天気アシスタント。ツールで調べてから答える。',
tools: { weather: weatherTool },
stopWhen: isStepCount(5),
toolApproval: { weather: 'user-approval' },
});
const { text } = await agent.generate({ prompt: '東京の天気は?' });
削除や送金のように人の確認を挟みたいツールは、v7では tool() 側の needsApproval(非推奨)ではなく、呼び出し側の toolApproval に書きます。値は 'user-approval'・'approved'・'denied'・'not-applicable' の4種類です。'user-approval' を付けたモックでは、ツールは実行されず、結果に tool-approval-request が入って finishReason が tool-calls で止まりました。画面側では useChat の addToolApprovalResponse で承認・却下を返します(公式のTool Approvals)。承認フローの設計そのものはヒューマンインザループ(HITL)とは?意味・HOTLとの違い・承認の実装と導入判断で詳しく扱っています。
LangChainとVercel AI SDKを併用する|@ai-sdk/langchain
LangChain・LangGraphとAI SDKの役割分担
LangChainとAI SDKは競合というより担当範囲が違います。LangChain・LangGraphはエージェントの分岐・状態・RAGの組み立てが中心で、画面側にもLangGraph用の useStream フックを用意しています。AI SDKはモデル呼び出しと useChat・UIメッセージストリームが中心です。この章では、LangChain側でロジックを書き、画面はAI SDKの useChat にそろえる組み合わせを扱います。
| やりたいこと | 向いている側 |
|---|---|
| 1〜数ステップのツール呼び出し付きチャット | AI SDKだけ |
| 分岐・再試行・中断再開のあるワークフロー | LangGraph+AI SDKのUI |
| 既存のLangChain資産(RAG・チェーン)の画面化 | LangChain+@ai-sdk/langchain |
| Pythonで書いたLangGraphをブラウザから呼ぶ | LangSmith Deployment+@ai-sdk/langchain |
判断の目安は、エージェントの状態を会話をまたいで保存・再開する必要があるかです。必要ならLangGraphのチェックポイントに任せ、不要なら依存を増やさずAI SDKだけで書く方が保守しやすくなります。LangChainとLangGraphの分担はLangChainとLangGraphの違いと使い分け|v1.0以降の関係をコードで確認【2026年10月】を、JavaScript版LangChainの基本はLangChain.jsとは?npm導入からcreateAgent・構造化出力・会話メモリまで【v1.5対応】を参照してください。
LangChainエージェントとuseChatのストリーム連携
連携用の @ai-sdk/langchain(3.0.159)は、@langchain/core 1.0以上と @langchain/langgraph 0.2以上をpeer依存に持ちます。toBaseMessages で useChat のメッセージをLangChainの形式に変え、エージェントのストリームを toUIMessageStream でAI SDKの形式に戻します(公式のLangChainアダプタ)。クライアント側は前章の page.tsx の送信先を /api/langchain に変えるだけです。
npm install @ai-sdk/langchain @langchain/core langchain @langchain/openai
// app/api/langchain/route.ts
import { createAgent, tool } from 'langchain';
import { ChatOpenAI } from '@langchain/openai';
import { toBaseMessages, toUIMessageStream } from '@ai-sdk/langchain';
import { createUIMessageStreamResponse, type UIMessage } from 'ai';
import { z } from 'zod';
const searchDocs = tool(
async ({ query }) => `検索デモです(実検索は行っていません): ${query}`,
{
name: 'search_docs',
description: '社内文書を検索する',
schema: z.object({ query: z.string() }),
},
);
const agent = createAgent({
model: new ChatOpenAI({ model: 'gpt-6-astra' }),
tools: [searchDocs],
});
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const stream = await agent.stream(
{ messages: await toBaseMessages(messages) },
{ streamMode: ['values', 'messages'] },
);
return createUIMessageStreamResponse({
stream: toUIMessageStream(stream),
});
}
このコードはlangchain 1.6.0・@langchain/openai 1.8.0の型定義で型検査を通しています。ツール実行の進み具合も画面に出したい場合は streamMode に 'tools' を、ツール内で config.writer() を使って独自データを送る場合は 'custom' を足します。
LangGraphのstreamMode別に見た本文イベントの有無
toUIMessageStream はLangGraphのストリームを自動判別しますが、本文のトークンを取り出すのは messages モードの出力からです。トークン単位でストリーミングできるフェイクモデル(FakeListChatModel)を1ノードのStateGraphに置き、streamMode だけを変えてUIメッセージストリームを比べた結果が次の表です。
| streamMode | 流れたイベント | 画面の本文 |
|---|---|---|
['values', 'messages'] |
start〜text-delta×5〜finish | 表示される |
['messages'] |
start〜text-delta×5〜finish | 表示される |
['values'] |
start・finishのみ | 空 |
['updates'] |
start・finishのみ | 空 |
values だけでもエラーは出ず、ステータス200で正常終了します。そのため「つながっているのに何も表示されない」という症状になり、原因に気づきにくくなります。LangGraphのストリーミングの説明でも、values と updates は状態の、messages はLLM出力をトークン単位で流すモードと区別されています。もう1つの落とし穴として、ストリーミングに対応しないモデル(LangChainのテスト用 fakeModel など)をノードに置くと、messages を指定していても本文のイベントは出ませんでした。テストでUIまで確かめるときは、トークンを流すフェイクを選びます。
デプロイ済みLangGraphへのブラウザ直接接続
LangGraphをLangSmith Deployment(旧LangGraph Platform)や langgraph dev のローカルサーバーで動かしている場合は、Next.js側にルートハンドラを書かずに LangSmithDeploymentTransport を useChat に渡せます。graphId の既定値は 'agent' です。
'use client';
import { useChat } from '@ai-sdk/react';
import { LangSmithDeploymentTransport } from '@ai-sdk/langchain';
import { useMemo } from 'react';
export default function LangGraphChat() {
const transport = useMemo(
() => new LangSmithDeploymentTransport({ url: 'http://localhost:2024' }),
[],
);
const { messages, sendMessage } = useChat({ transport });
// 描画は前章のpage.tsxと同じ
}
本番のデプロイ先に apiKey を渡すと、キーがブラウザに配られる点に注意が必要です。公開サイトではこの方式を避け、ルートハンドラ経由でサーバー側からキーを付けます。デプロイ環境と料金はLangGraph Platform(旧LangGraph Cloud)とは?現LangSmith Deploymentのデプロイ手順と料金【2026年10月】にまとめています。
旧LangChainAdapterからの移行
ai v3・v4の解説によく出てくる LangChainAdapter.toDataStreamResponse() は、本体の ai から削除されています。古いサンプルを動かす場合は、次のように @ai-sdk/langchain へ置き換えます。
// 旧: ai v3・v4(現在の ai には存在しない)
import { LangChainAdapter } from 'ai';
return LangChainAdapter.toDataStreamResponse(await chain.stream(input));
// 新: @ai-sdk/langchain
import { toUIMessageStream } from '@ai-sdk/langchain';
import { createUIMessageStreamResponse } from 'ai';
return createUIMessageStreamResponse({
stream: toUIMessageStream(await chain.stream(input)),
});
古いサンプルが動かないときの確認点
検索で見つかる解説の多くはv4〜v6の書き方です。v7で動かないとき、または非推奨の警告が出るときは、次の対応表で置き換え先を確かめます。
| 古い書き方 | AI SDK 7の書き方 | v7での扱い |
|---|---|---|
result.toUIMessageStreamResponse() |
toUIMessageStream+createUIMessageStreamResponse |
非推奨 |
stepCountIs(n) |
isStepCount(n) |
改称(別名で残存) |
system: '...' |
instructions: '...' |
改称 |
tool({ needsApproval }) |
toolApproval(呼び出し側) |
非推奨 |
result.fullStream |
result.stream |
非推奨 |
onFinish / onStepFinish |
onEnd / onStepEnd |
改称 |
require('ai') |
import 文(ESM) | 廃止 |
改称の多くは公式のcodemodで自動置換できます。プロジェクトのルートで npx @ai-sdk/codemod v7 を実行し、残った箇所を手で直す流れが移行ガイドの推奨です。generateObject の非推奨化と convertToModelMessages の非同期化はv5→v6で入った変更なので、v6から上げる場合は対象外です。
社内のチャットボットや業務エージェントをAI SDKやLangGraphで作る場合の設計・実装は、生成AI開発・AI受託開発で支援しています。
よくある質問
Vercel AI SDKの最新バージョンは?
2026年10月31日時点で7.0.159です。AI SDK 7の初版7.0.0は2026年6月25日に公開されました。v6系は6.0.320が ai-v6 タグで提供されています。最新版は npm view ai dist-tags で確認できます。
LangChainとVercel AI SDKは併用できますか?
できます。@ai-sdk/langchain の toBaseMessages と toUIMessageStream で、LangChain・LangGraphのエージェントをAI SDKの useChat にそのままつなげます。LangGraphから流すときは streamMode に 'messages' を含めます。
toUIMessageStreamResponseはもう使えませんか?
AI SDK 7でも動きますが、streamText の結果オブジェクトのメソッドとしては非推奨で、次のメジャー版で削除予定です。新しく書くなら createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }) }) を使います。
ツールを渡したのに応答テキストが空になるのはなぜ?
generateText と streamText の stopWhen の既定値が isStepCount(1) だからです。最初の応答がツール呼び出しだけだった場合、ツールを実行した時点でループが終わり、結果を踏まえて文章を書くステップまで進みません。stopWhen: isStepCount(5) のように上限を指定します。
LangChainAdapterが見つからないのはなぜ?
本体の ai パッケージから削除されたためです。現在は @ai-sdk/langchain の toUIMessageStream と、ai の createUIMessageStreamResponse を組み合わせます。
関連記事
- Vercel AI SDK v6とは?新機能(ToolLoopAgent・needsApproval)とv7との違い・移行【2026】
- LangChainとLangGraphの違いと使い分け|v1.0以降の関係をコードで確認【2026年10月】
- LangChain.jsとは?npm導入からcreateAgent・構造化出力・会話メモリまで【v1.5対応】
- Smooth Text Streamingとは?AI SDK smoothStreamの使い方と日本語の分割設定
- LangGraphのTool Calling実装|ToolNode・create_agent・エラー処理とループ上限【2026年10月】