DSPyのReActV2──「1本の巨大な文字列」だったツール履歴を、構造化されたメッセージに分解する
LLMフレームワークDSPyはv3.3.0(2026年8月3日)で、ネイティブなツール呼び出しに対応した新しいReAct実装`dspy.ReActV2`を追加した。公式リリースノートによれば、社内テストでコストが最大50%減った事例もあるという。ツールの実行履歴を1つの長い文字列ではなく、user/assistant/toolに分かれた構造化メッセージとして扱うことで、プロンプトキャッシュの再利用効率を上げる設計だ。

目次
LLMにツールを繰り返し呼ばせながらタスクを進める「ReAct」パターンは、多くのエージェントフレームワークが実装している基本設計だ。スタンフォード大学発のフレームワークDSPyは、v3.3.0(2026年8月3日公開)で、このReActの実装を刷新したdspy.ReActV2を追加した。まだ実験的機能という位置づけだが、公式リリースノートには設計の根拠となる実測データも記載されている。
3行まとめ
dspy.ReActV2は、ツール実行履歴を1つの巨大な文字列でなくuser/assistant/toolの構造化メッセージ(dspy.History)として扱う新しいReAct実装。社内テストでは一部タスクでコストが最大50%減少したという- 公式ドキュメント「ReAct and ReActV2」を読むと、この移行は暫定措置ではない:ReActV2はDSPy 3.5で正式に
dspy.ReActという名前そのものになる予定で、「ReActV2」という名前自体は3.5系では非推奨のエイリアスとして残り、3.6で削除される- 同ドキュメントは「50%のコスト削減」という数字に対して「実際の削減幅はプロバイダーのキャッシュポリシー・モデル・リクエストの形・ツール結果のサイズに依存し、すべてのワークロードで保証されるものではない」と公式に釘を刺している
何が変わったのか:会話履歴の持ち方
これまでのReAct実装との最大の違いは、ツールの実行履歴をどう表現するかにある。リリースノートはこう説明している。
"The signature now uses
dspy.History,dspy.Tool, anddspy.ToolCalls(which can now optionally storedspy.ToolCallResults), rather than the custom next_tool_args and custom trajectory syntax. Usingdspy.Historyalso means that messages are now broken up into user/assistant/tool groups rather than one long user message with the trajectory."
(シグネチャは、独自のnext_tool_argsや独自のtrajectory構文の代わりに、dspy.History・dspy.Tool・dspy.ToolCalls(dspy.ToolCallResultsをオプションで格納できるようになった)を使うようになった。dspy.Historyを使うということは、メッセージが、trajectoryを含んだ1つの長いユーザーメッセージではなく、user/assistant/toolのグループに分割される、ということも意味する)
従来のReAct実装は、ツール呼び出しの履歴(trajectory)を1つの巨大なユーザーメッセージのテキストとして蓄積していく方式だったとみられる。ReActV2は、これをuser・assistant・toolという役割別の構造化メッセージに分割する。
具体的に変わる3つの挙動
リリースノートは、この変更が実際にもたらす挙動の違いを3点挙げている。
"
parallel_tool_callssupport: DSPy preserves each call/result pair by ID. You can do this in native mode or in non-native mode" "Multi-turn native tool callsupport: Prior tool calls and results can be replayed as assistant and tool messages instead of being flattened into prompt text." "Each turn lives indspy.Historyas structured messages rather than one ever-growingtrajectorystring, so providers with prompt caching can reuse stable prefixes more effectively. We have seen up to 50% decreases in cost for some tasks when testing this internally."
(parallel_tool_calls対応:DSPyは各呼び出し・結果のペアをIDで保持する。これはネイティブモードでも非ネイティブモードでも可能。「マルチターン・ネイティブツール呼び出し」対応:過去のツール呼び出しと結果は、プロンプトテキストに平坦化されるのではなく、assistant・toolメッセージとして再生できる。各ターンは、際限なく伸び続けるtrajectory文字列ではなく、dspy.History内の構造化メッセージとして存在する。これにより、プロンプトキャッシュを持つプロバイダーは、安定したprefixをより効果的に再利用できる。社内テストでは、一部のタスクでコストが最大50%減少した例を確認している)
「最大50%のコスト削減」という具体的な数字が、開発チーム自身の社内テストの結果として明記されている点は目を引く。プロンプトキャッシュは、リクエストの先頭部分(prefix)が前回と一致していれば有効に働く仕組みだ。従来のように会話履歴を1つの長い文字列としてどんどん書き換えていく方式では、キャッシュのprefixが毎ターン壊れやすい。構造化メッセージとして固定化することで、この問題を避けられる、という理屈だ。
ReActV2が内部で行っていること
リリースノートには、実装の詳細も簡潔にまとめられている。
"
ReActV2converts callables todspy.Tool, adds an internalsubmittool for final outputs, handles unknown tools and tool exceptions, accepts serialized history input, and can force final submission when the model does not callsubmit."
(ReActV2は、呼び出し可能なオブジェクトをdspy.Toolに変換し、最終出力用の内部ツールsubmitを追加し、未知のツールやツール例外を処理し、シリアライズされた履歴の入力を受け付け、モデルがsubmitを呼ばない場合には最終的な提出を強制できる)
「モデルが自発的に終了しない場合、強制的に提出させる」という挙動は、Gooseの/goalコマンドが抱えていた「エージェントが自分から終わらせようとしない・あるいは終わったつもりで止まらない」問題とは逆の——「モデルがループから抜けられない」場合への対処だと考えられる。エージェント実装全般で、こうした「終了条件をどう強制するか」という課題が繰り返し扱われていることがうかがえる。
まだ実験的機能である――が、行き先はすでに決まっている
リリースノートの見出し自体にも明記されている通り、dspy.ReActV2は現時点では実験的な位置づけだ。
"
dspy.ReActV2is a new version of ReAct built around native tool calling. It is currently marked as experimental."
(dspy.ReActV2は、ネイティブなツール呼び出しを中心に構築された新しいバージョンのReActだ。現時点では実験的とマークされている)
リリースノートだけを読むと「既存のdspy.ReActと並行する新しい選択肢」という位置づけに見えるが、公式ドキュメント「ReAct and ReActV2」を確認すると、この2つの実装の関係はもう少しはっきりしている。
dspy.ReAct(現行) |
dspy.ReActV2(新) |
|
|---|---|---|
| ステータス | DSPy 3.4まで現行実装。3.5でReActV2の実装を採用 | 実験的な名前。3.5系では非推奨エイリアスとなり、3.6で削除予定 |
| 履歴の保持形式 | フラットなtrajectory辞書 |
構造化されたdspy.Historyイベント |
| モデルに見せる履歴 | trajectory全体を毎回1つの入力に整形 | 過去のuser/assistant/toolメッセージをそのまま個別ターンとして再生 |
| ツール選択 | next_tool_name+next_tool_args |
dspy.ToolCalls |
| 1モデルターンあたりの呼び出し数 | 1回 | 1回以上(並列可) |
| 完了の扱い | finishツール選択後、別途抽出用のLM呼び出しが発生 |
submitが最終的な型付き出力をそのまま運ぶ |
| プロンプトキャッシュ | 変化し続けるtrajectoryを毎回1フィールドとして再送信 | 安定した過去メッセージが再利用可能なプレフィックスとして残る |
(出典:DSPy公式ドキュメント「ReAct and ReActV2」)
つまりdspy.ReActV2という名前自体は一時的なものであり、その中身(構造化履歴・ネイティブツール呼び出し)が、DSPy 3.5でdspy.ReActという正式名称を引き継ぐことがすでに決まっている。現行のdspy.ReActは、アップグレードまでは現在の実装のまま使い続けられるが、公式ドキュメントは「履歴と出力の変更に備えておくべき」と注意を促している。
Gooseのキャッシュ対策と同じ問題意識に見える
筆者はプロンプトキャッシュの効きを意識してツールの会話設計をすることが多いが、「会話履歴をどう構造化するか」という一見地味な設計判断が、コストに直接跳ね返ってくるという点は、GooseのCache-safe request assembly(本サイトの別記事で取り上げた、append-only設計への変更)とまったく同じ問題意識だ。エージェントフレームワークやツールが異なっても、「会話履歴の組み立て方がプロンプトキャッシュの効率を左右する」という課題は、業界全体で同時多発的に対処されつつある領域だと感じる。
「社内テスト」の具体的な条件・サンプル数までは公式情報源にも見当たらなかった
公式ドキュメントを確認したことで、ReActV2の卒業時期(DSPy 3.5でdspy.ReAct化、3.6で旧名削除)は判明した。一方、「最大50%のコスト削減」という数値がどのタスク・どのモデル・どのプロバイダーで測定されたものかという具体的な条件、「社内テスト」のサンプル数や再現性についても、リリースノート・APIリファレンス・移行ガイドのいずれにも記載が見当たらなかった。公式ドキュメントは、この数字について「プロバイダーのキャッシュポリシー・モデル・リクエストの形・ツール結果のサイズに依存し、すべてのワークロードで保証されるものではない」と自ら注意書きを添えており、本記事もこの注意書きの範囲を超えて「50%減る」と一般化はしない。Anthropicモデル向けのプロンプトキャッシュ設定例(cache_control_injection_points)が公式ドキュメントに載っていることは確認したが、実際に自分の手元でこの設定を使ってコスト削減幅を測定する検証はしていない。
関連記事
出典・参照資料
AIニュースの解説を動画でも
YouTubeでは注目ニュースの背景を解説し、Xでは新着記事をお知らせしています。
コメント
まだコメントはありません。最初のコメントを書いてみませんか?
AIについて聞きたいことはありますか?
質問箱で無料で受け付けています。回答は公開され、他の方の参考にもなります。
質問箱を見る →新しい記事をメールで受け取る
AIの新しい発表を、出典付きで整理して届けます。