はじめに
Vercel AI SDK(ai パッケージ)は npm 週間ダウンロード数 1,514万件(2026-06-29〜07-05・npm公式レジストリAPI実測)のTypeScript向けAIツールキットです1。メジャーバージョン6でエージェント抽象化 ToolLoopAgent が追加された一方、地味だが実害の大きい破壊的変更が convertToModelMessages() に入っていました。
同期関数だったものが非同期関数になり、しかも呼び出し側が型チェックをしていないと、エラーを一切出さずに壊れたまま動き続けます。 実際に手元でv6をインストールして検証したので、その結果を共有します。
この記事で分かること
-
convertToModelMessages()がv6で非同期化した事実(型定義・実行結果で確認済み) - 型なし(JS・
any)のコードがどう壊れるか、実際のログで確認 - TypeScriptなら型エラーで守られる、その具体的なエラーメッセージ
-
ToolLoopAgentのデフォルトstopWhenも地味に変わっている点
前提環境
- Node.js v22.22.2
-
aiパッケージ v6.0.221(2026年7月時点。npmのlatestは既に7系だが、6系はまだ多くのプロダクションコードで稼働中)
TL;DR
-
convertToModelMessages()はv5では同期的に配列を返していたが、v6ではPromise<ModelMessage[]>を返す非同期関数になった -
awaitを付け忘れても 実行時エラーにならない。Promiseオブジェクトがそのまま次の処理に渡り、LLM APIへのリクエストが壊れた形で送られる - TypeScriptの型チェックを通していれば
error TS2740で検出できるが、any型や素のJavaScriptでは気づけない -
ToolLoopAgentのデフォルトstopWhenもstepCountIs(20)に変更されており、旧Experimental_Agentの1ステップ止まりから挙動が変わっている
背景: なぜ非同期化されたか
AI SDK 6のマイグレーションガイドによると、convertToModelMessages() の非同期化は Tool.toModelOutput() の非同期対応が理由です2。ツールの出力をモデル向けメッセージに変換する処理が非同期になったことで、それを呼び出す convertToModelMessages() 自体も連鎖的に非同期化されました。
設計としては筋が通っていますが、問題は 呼び出し側のコード です。v5時代に書かれた次のようなコードは、v6にアップグレードしても文法上は何のエラーも出さずに動き続けます。
// v5時代の書き方(同期呼び出しのつもり)
const messages = convertToModelMessages(uiMessages);
console.log(messages.length); // ← ここで初めて壊れ方に気づく
実機検証1: 同期呼び出しの結果を見る
手元に ai@6.0.221 をインストールし、実際に同期呼び出しを試しました。
npm install ai@6 zod@3
import { convertToModelMessages } from 'ai';
const uiMessages = [
{ role: 'user', parts: [{ type: 'text', text: 'こんにちは' }] },
];
const result = convertToModelMessages(uiMessages);
console.log('typeof result:', typeof result);
console.log('is Promise:', result instanceof Promise);
console.log('result:', result);
実行結果は次の通りです。
typeof result: object
is Promise: true
result: Promise { [ { role: 'user', content: [Array] } ] }
例外は一切発生しません。 result はオブジェクト(Promise)として扱えてしまうため、result.map(...) のような配列メソッド呼び出しをして初めて TypeError: result.map is not a function に気づく、という順序になります。ツール呼び出しを含む複雑なエージェントコードでは、この配列アクセスがネストした場所にあることも多く、発見が遅れがちです。
実機検証2: TypeScriptなら型エラーで守られる
同じコードをTypeScriptの型付きで書くとどうなるか確認しました。
import { convertToModelMessages, type ModelMessage } from 'ai';
const uiMessages = [
{ role: 'user' as const, parts: [{ type: 'text' as const, text: 'こんにちは' }] },
];
const messages: ModelMessage[] = convertToModelMessages(uiMessages);
console.log(messages);
npx tsc --noEmit を実行すると、次のエラーで即座に検出できます。
error TS2740: Type 'Promise<ModelMessage[]>' is missing the following properties
from type 'ModelMessage[]': length, pop, push, concat, and 29 more.
つまり 型チェックさえ通していれば絶対に見逃さない変更 です。逆に言えば、any 型で受けていたり、// @ts-ignore で握りつぶしていたり、そもそもJavaScriptで書いていたりすると、この保護が働きません。CIに tsc --noEmit を組み込んでいないプロジェクトは、今回のアップグレードで最も刺さりやすい部類です。
実機検証3: ToolLoopAgentのデフォルトstopWhenも変わっている
ついでにv6の新しいエージェント抽象化 ToolLoopAgent のソースも確認しました。node_modules/ai/dist/index.mjs を直接grepすると、デフォルトの停止条件が次のように実装されています。
stopWhen: (_a22 = this.settings.stopWhen) != null ? _a22 : stepCountIs(20),
旧 Experimental_Agent(v5の実験的エージェントAPI)はデフォルトが1ステップ(isStepCount(1))でしたが、ToolLoopAgent では デフォルト20ステップ に変わっています2。stopWhen を明示的に指定していないエージェントは、アップグレード後にツール呼び出しを以前よりずっと長く自律的に繰り返すようになる可能性があります。コスト・レイテンシに直結するため、移行時は必ず stopWhen: stepCountIs(N) を明示することをおすすめします。
なぜ「壊れたまま動く」のか
今回の一連の変更に共通しているのは、JavaScriptの動的型付けが「意味的に間違ったオブジェクト」を黙って受け入れてしまう という性質です。Promiseは .then() を持つオブジェクトとして扱われるため、「配列を渡したつもり」のコードは実行はできてしまいます。エラーになるのは、Promiseに配列メソッド(.map() や .length)を呼んだ瞬間か、あるいはそのままLLM APIにシリアライズされて送信され、プロバイダー側から「不正なメッセージフォーマット」としてエラーが返ってきた瞬間です。後者の場合、原因が自分のコードにあると気づくまでに時間がかかります。
対策
-
公式のcodemodを使う:
npx @ai-sdk/codemod v6で機械的な移行の大部分をカバーできます2。 -
convertToModelMessagesの呼び出し箇所を全てgrep:awaitの有無を目視で確認する。 -
TypeScriptの
strictモードを有効化し、CIでtsc --noEmitを必ず回す: 今回のような「実行時に気づきにくい」変更を型エラーで先回りできる唯一の防御線です。 -
ToolLoopAgent/Agentを使う箇所はstopWhenを明示指定する: デフォルト値の変更に依存しない。
著者視点の発見ポイント
今回手を動かして一番驚いたのは、Promiseを配列として扱おうとしても 即座にはエラーにならない という点でした。console.log(result) の出力を見て初めて Promise { [...] } という表示に気づく形で、ぱっと見では気づきにくい罠です。「型のないコードほど、非同期化のような変更に弱い」という当たり前の事実を、実際に手を動かして再確認できました。TypeScriptのメジャーアップグレードでは、変更ログを読むだけでなく tsc --noEmit を一度実行してから本番投入することを徹底したいと思います。
まとめ
- Vercel AI SDK 6で
convertToModelMessages()が非同期化。呼び出し側でawaitを忘れても実行時エラーにならず、Promiseオブジェクトがそのまま流れてしまう - TypeScriptの型チェック(
tsc --noEmit)を通していればerror TS2740で確実に検出できる -
ToolLoopAgentのデフォルトstopWhenもstepCountIs(20)に変更されており、意図しない自律ループの長期化に注意 - メジャーバージョンアップ時は公式codemodの実行+型チェックCIの整備がセットで必須
参考リンク
- AI SDK 6 - Vercel Blog — ToolLoopAgent・Agent抽象化の紹介
- Migration Guide: AI SDK 5.x to 6.0 — 非同期化・codemod手順の公式ガイド
- vercel/ai on GitHub — OSSリポジトリ本体
-
npm registry API(
aiパッケージ、2026-06-29〜2026-07-05の週間ダウンロード数を実測) ↩ -
Migration Guide: AI SDK 5.x to 6.0(2026年7月確認) ↩ ↩2 ↩3