2026年9月4日 金曜日
AI時短ラボ
検証· 約14

プロンプトではなく「プログラムの構造」自体を最適化する──DSPyの実験的機能`dspy.Flex`

LLMアプリケーション構築フレームワークDSPyは、v3.3.0(2026年8月3日)で実験的な`dspy.Flex`を追加した。最適化アルゴリズムGEPAが、プロンプトの文言だけでなく、Predictorの構成・制御フロー・PythonとLM呼び出しの配分といったプログラムの実装そのものを書き換えて探索できるようにする機能で、公式リリースノートには実際のコード例と実装の仕組みまで示されている。

プロンプトではなく「プログラムの構造」自体を最適化する──DSPyの実験的機能`dspy.Flex`
執筆・編集:
目次

LLMを使ったプログラムを「書く」のではなく「コンパイル(最適化)する」という発想で知られるフレームワークDSPy(スタンフォード大学発)が、v3.3.0(2026年8月3日公開)で新しい実験的モジュールdspy.Flexを追加した。これまでの最適化は主にプロンプトの文言を対象にしていたが、Flexはプログラムの構造そのものを最適化の対象に含める。

3行まとめ

  • dspy.Flexは、最適化アルゴリズムGEPAがプロンプトの文言だけでなく、predictorの構成・制御フロー・Python/LM呼び出しの配分を含むプログラム実装そのものを書き換えられるようにする実験的モジュール
  • 公式ドキュメントを読むと、GEPAはFlexを「コードコンポーネント」・他のpredictorを「指示コンポーネント」に分けて別々のproposer(提案器)で最適化しており、Flex内部のpredictorの指示文は個別チューニングされない(flex.named_predictors()は空を返す)
  • 生成コードが1回のforwardで呼べるpredictor数の上限(max_predictor_calls)は既定で100。実装PR #10047はGitHub API確認で33ファイル変更・追加4,487行という大規模な変更だった

「構造は固定、指示文だけ最適化」という従来の限界

公式リリースノートは、既存のDSPyモジュールが抱える制約をこう説明している。

"Most DSPy modules fix the shape of a program up front: Predict makes one prediction, ReAct runs a tool loop, and RLM runs a code interpreter in a loop. Optimizers can improve the instructions around that structure, but the structure itself stays fixed. The new experimental dspy.Flex moves the implementation into the search space so GEPA can discover the decomposition instead."

(既存のDSPyモジュールのほとんどは、プログラムの形を事前に固定する。Predictは1回の予測を行い、ReActはツールループを回し、RLMはコードインタープリタをループさせる。最適化器はその構造の周りにある指示文を改善できるが、構造自体は固定されたままだ。新しい実験的なdspy.Flexは、実装そのものを探索空間に組み込むことで、最適化アルゴリズムGEPAが(タスクをどう分解するかという)分解の仕方自体を発見できるようにする)

つまり、これまでのDSPyは「1回で答えを出す」「ツールを使ってループする」といったプログラムの骨格をユーザーが決め打ちし、最適化器(GEPAなど)はその骨格の中の指示文だけを改善していた。Flexは、その骨格自体を最適化の対象にする、という一段抽象度の高い試みだ。

使い方はPredictと同じシグネチャから始まる

リリースノートに示されているコード例はこうだ。

program = dspy.Flex("question -> answer")
optimized = dspy.GEPA(metric=metric, reflection_lm=reflection_lm).compile(
    program,
    trainset=trainset,
    valset=valset,
)

print(optimized.module_src)

Flexに渡すシグネチャ("question -> answer")自体は、Predictに渡すものと同じだ。だが挙動は異なる。

"Give Flex the same signature you would give Predict and it starts with the simplest working baseline: one dspy.Predict, or one dspy.RLM when tools are supplied. During compilation, GEPA can rewrite the complete module implementation—changing the predictors, control flow, DSPy primitives, and balance between Python and LM calls—against your metric."

Predictに渡すのと同じシグネチャをFlexに与えると、最もシンプルに動くベースライン——ツールが与えられていなければ1つのdspy.Predict、ツールが与えられていれば1つのdspy.RLM——から開始する。コンパイル中、GEPAはユーザーが指定したメトリクスに対して、predictorの構成・制御フロー・DSPyのプリミティブ・PythonとLM呼び出しのバランスを含む、モジュール実装全体を書き換えることができる)

最適化の出発点はシンプルなベースラインであり、そこからGEPAがタスクに合わせてプログラムの実装コードそのものを書き換えていく、という流れになる。

生成されたコードはサンドボックスで実行され、壊れたら失敗として扱われる

最適化中に生成されるプログラムコードの実行には、安全策が組み込まれている。

"Optimizer-authored source always runs in a CodeInterpreter sandbox, using dspy.PythonInterpreter by default. Predictor construction and LM calls bridge back to the host, broken candidates score as failures instead of crashing the search, and max_predictor_calls guards against runaway generated programs. Metrics can also accept a program_trace to score how a result was produced—for example, penalizing programs that make too many LM calls."

(最適化器が生成したソースコードは、常にCodeInterpreterサンドボックス内で実行される(既定ではdspy.PythonInterpreterを使用)。Predictorの構築とLM呼び出しはホスト側に橋渡しされ、壊れた候補は探索をクラッシュさせるのではなく単に「失敗」としてスコアリングされる。max_predictor_callsは、暴走した生成プログラムに対する歯止めとして機能する。メトリクスはprogram_traceを受け取ることもでき、結果がどう生み出されたかを評価できる——例えば、LM呼び出しが多すぎるプログラムにペナルティを与える、といった使い方ができる)

「壊れたコードが生成されても探索プロセス全体をクラッシュさせない」「呼び出し回数に上限を設ける」という2つの安全策は、LLMに実装コードそのものを書かせる以上、生成物が想定外に暴走するケースを前提にした設計だと分かる。

公式の設計判断ドキュメント(diving-deeper/flex.md)を読むと、リリースノート本文には出てこない、より踏み込んだ仕様が並んでいる。

項目 仕様
max_predictor_callsの既定値 100(forward1回あたり)
GEPAの最適化対象の分け方 Flexサブモジュールは「コードコンポーネント」、それ以外のpredictorは「指示コンポーネント」に分類され、専用のproposer(提案器)がそれぞれを担当
Flex内部のpredictorの扱い flex.named_predictors()は常に空を返す。内部のpredictorは現在のmodule_srcが構築するものであり、次の候補コードで丸ごと置き換わるため、個別の指示文チューニング対象にはならない
サンドボックスの生成 interpreter_factoryは引数無しのファクトリ関数である必要があり、既存のインスタンスやNoneは拒否される。1回のforwardごとに新しいインタープリタが生成される
壊れたコードの扱い(パース不能) GEPAがプログラムを構築する際に例外が発生 → そのバッチ全体を失敗スコアとして記録し、探索は継続
壊れたコードの扱い(実行時エラー) 例ごとにforward実行時に失敗 → 該当する例だけが失敗スコアになり、他の例のスコアとの対応関係は保たれる

(出典:dspy.Flex公式ドキュメント「Flex: Optimizable module code」)

最適化結果はプログラムに保存される

Flexで最適化した結果は、モデルの重みのように保存・再利用できる。

"The optimized module_src is part of the program's serialized state, so saving and loading preserves the implementation GEPA discovered. Flex is experimental, and ordinary GEPA behavior is unchanged when a program does not contain a Flex module."

(最適化されたmodule_srcはプログラムのシリアライズされた状態の一部であり、保存・読み込みによってGEPAが発見した実装が保持される。Flexは実験的機能であり、プログラムにFlexモジュールが含まれない場合、通常のGEPAの挙動は変わらない)

開発チーム自身が「フィードバックが欲しい」と明言

リリースノート冒頭には、この機能に対する開発チームの姿勢も明記されている。

"We would especially appreciate feedback on Flex, ReActV2, and the typed LM path. These APIs expand what DSPy can optimize and how it can connect to model providers, and real-world usage will help shape their next iterations."

(Flex・ReActV2・型付きLMパスについては、特にフィードバックを歓迎する。これらのAPIはDSPyが最適化できる範囲と、モデルプロバイダーへの接続方法を拡張するものであり、実際の利用が今後のイテレーションの形を決める助けになる)

実験的機能として明示されている通り、APIが今後変わる可能性がある前提で公開されている。

設計判断をアルゴリズムに委ねる、という一段違う話

筆者は普段プロンプトエンジニアリングを手作業でチューニングすることが多いが、Flexが扱っているのは一段違う問題——「そもそもタスクをどう分解するか」という設計判断自体を、人間ではなくGEPAという最適化アルゴリズムに委ねる、という発想だ。Predict(1回の予測)で済ませるか、RLM(コードインタープリタのループ)を使うかという判断は、これまでDSPyを使う開発者が最初に決めなければならないことだった。それをメトリクス次第で自動的に切り替えられる余地がある、という点は、プロンプトチューニングの延長線上ではなく、プログラム設計そのものの自動化に踏み込んでいる。

生成コードの可読性やベンチマーク数値は、公式ドキュメントを読んでも見当たらなかった

公式APIリファレンスと設計判断ドキュメントまで確認したことで、max_predictor_callsの既定値やGEPAの内部での役割分担は判明した。一方、Flexが実際にどの程度、人手で設計したプログラム構造を上回る(あるいは同等の)成果を出すのかという定量的なベンチマーク結果は、リリースノート・APIリファレンス・設計判断ドキュメントのいずれにも記載が見当たらなかった。生成されたコードの可読性やデバッグのしやすさについても、同様に公式情報源の範囲では言及を確認できていない。本記事はこれら3つの公式ドキュメントの記述にもとづいており、dspy.Flexを実際に自分の手元でGEPAコンパイルにかけて、生成されたコードを目視で確認する検証はしていない。

関連記事

シェア: ポスト はてブ

出典・参照資料

AIニュースの解説を動画でも

YouTubeでは注目ニュースの背景を解説し、Xでは新着記事をお知らせしています。

コメント

まだコメントはありません。最初のコメントを書いてみませんか?

AIについて聞きたいことはありますか?

質問箱で無料で受け付けています。回答は公開され、他の方の参考にもなります。

質問箱を見る →

新しい記事をメールで受け取る

AIの新しい発表を、出典付きで整理して届けます。

関連記事