プログラミング
グラフエンジニアリングにはコンパイラが必要
Graph Engineering Needs a Compiler (fluxtion-playground.dev)
要約
AIによるコード生成が急速に進む中、個々のコード片は容易に作成できるものの、それらが組み合わさった際の実行全体を理解することが困難になっています。グラフエンジニアリングは、アプリケーションの構造を可視化し、その構造を決定論的なオーケストレーターに変換するコンパイラによって、この問題を解決しようとしています。LangChainのようなフレームワークは、エージェントシステムをグラフとして構築することで、AIが生成するコードの複雑な実行フローを管理しやすくしていますが、グラフの実行方法を定義するコンパイラ的なアプローチが重要であると論じています。
全文翻訳
AIは、人間がその複合実行を理解するよりも速くコンポーネントを生成できます。グラフはアプリケーション構造を可視化します。コンパイラは、その構造を決定論的なオーケストレーターに変換できます。
AIコーディングは奇妙な逆転現象を生み出しました。コードを書くことは安価になり、そのコード全体が何をするかを理解することは高価になっています。LLMは、数分でハンドラーを追加したり、APIを接続したり、キューを導入したり、リトライを実装したり、状態を更新したり、別のサービスを呼び出したりできます。各変更は、単独で読めば完全に合理的であるように見えるかもしれません。問題は、それらの合理的な部分が相互作用するときに現れます。
アプリケーションの実際の動作は、通常1つのメソッドに含まれていません。それは、コールバック、リスナー、タイマー、キュー、リトライ、ライフサイクルフック、および状態変更が組み合わされる順序から生じます。LLMは現在、人間が作成している実行モデルを再構築するよりも速く、このオーケストレーションを生成できます。これが、グラフエンジニアリングが注目を集めている理由の1つです。
LangChainは最近、エージェントシステムを、決定論的なコード、モデル呼び出し、ツール、および完全なエージェントを含むグラフとして構築することを説明するためにこの用語を使用しました。グラフは、すべての決定をLLMに任せるのではなく、システムがたどることができるパスを制約します。それは重要な改善ですが、グラフを可視化することは解決策の半分にすぎません。もう半分は、グラフが正確にどのように実行されるかを決定することです。
AIはローカルに書きます。システムはグローバルに実行します。LLMは、ローカルな動作を実装するのが非常に得意な場合が多いです。アプリケーションが取引を受け取り、以下を実行する必要があると仮定します。
位置を更新↓
リスクを再計算↓
結果を公開
LLMは次のように生成するかもしれません。
updatePosition(trade);
publishPosition();
recalculateRisk();
各メソッド呼び出しは有効であり、コードは読みやすく、実装はコンパイルして多くのテストに合格する可能性があります。しかし、グローバルな順序が間違っています。これらのメソッドシグネチャのどこにも、リスクは位置が公開される前に再計算されなければならないということはコンパイラに伝えられません。その要件は、コードのどこか外部に存在します。アーキテクチャドキュメント、テスト、コメント、または経験豊富な開発者の心の中にあります。
危険なAI生成コードは、通常、明白なナンセンスではありません。それは、グローバルな不変条件を微妙に違反する、ローカルに妥当なコードです。
問題は時間とともに増大します。1つのプロンプトがリトライを導入します。別のプロンプトがエグゼキューターに作業を移動します。3番目のプロンプトがメトリクスを追加します。4番目のプロンプトが別のイベントタイプをサポートします。各変更は、単独では合理的である可能性がありますが、以下を変更します。
実行順序;
状態の可視性;
再入可能性;
障害処理;
完了セマンティクス;
再生動作。
アプリケーションは、人間も単一のプロンプトも明示的に設計したことのない実行モデルを徐々に開発していきます。
ループは悪者ではない
小さなループは完全に決定論的である可能性があります。
while (running) {
Event event = queue.take();
updateState(event);
calculateRisk();
publishResult();
}
同じ初期状態と順序付けられた入力があれば、このループは毎回同じ結果を生成できます。問題は、ループが本質的に予測不可能であるということではありません。問題は、実際のアプリケーションが単一のループのままであることはめったにないということです。
時間とともに、updateStateが別のイベントを発行します。リスナーがそれを受け取ります。タイマーが参照データを更新します。リトライがさらに作業をスケジュールします。フレームワークがライフサイクルメソッドを呼び出します。コールバックが、別のコールバックが状態の更新を終える前に、一部の状態を観察します。元のループは消えていません。それはアプリケーション全体に分散しています。
イベントループ
├── リスナー
│ └── コールバック
│ └── キュー
├── スケジュールされたタスク
├── リトライハンドラー
└── 非同期パブリッシャー
誰かが完全なシーケンスを理解する必要があります。従来のプロジェクトでは、その人物はしばしば、数年かけてシステムに関する非公式なモデルを蓄積してきたシニア開発者です。AI生成プロジェクトでは、LLMにそのグローバルコーディネーションエンジニアになるように依頼するリスクがあります。それは責任の分担としては不適切です。
グラフエンジニアリングはアプリケーションモデルを可視化します
グラフは、暗黙的な制御フローを明示的な関係に置き換えます。
取引↓
位置↓
リスク↓
ポリシー↓
ルート
グラフは、リスクが更新された位置に依存し、ポリシーがリスクに依存し、ルーティングがポリシー決定に依存することを示しています。これは、キュー、コールバック、リスナーに分散された同等の動作よりも検査が容易です。また、通常のコードと確率的コンポーネントを組み合わせるシステムにも自然なモデルです。
リクエスト↓
分類エージェント↓
ポリシー検証↓
人間の承認↓
承認されたアクション
分類エージェントは確率的である可能性があります。周囲のグラフは、モデルが操作できる場所と、外部アクションが許可される前に何が起こらなければならないかを制約します。
現在のエージェントグラフフレームワークは、一般的にこのグラフを明示的にします。ノードは作業を実行し、エッジまたはルーティング定義が次に何が起こるかを決定します。たとえば、MicrosoftのAgent Frameworkは、ワークフローをエグゼキューターとエッジの有向グラフとして記述します。LangGraphも同様に、ノード、状態、および遷移を使用してエージェントワークフローを定義します。これは、ワークフローを大きなエージェントループ内に隠すよりもはるかに優れていますが、重要な問題が残ります。誰かがオーケストレーションを作成しなければならないということです。
グラフはオーケストレーターではない
単純なダイヤモンドを考えてみましょう。
A
/ \
B C
\ /
D
トポロジーは、BとCがAに依存し、DがBとCに依存することを示しています。図だけでは、必ずしも以下の質問には答えられません。
BはCの前に実行されますか?
BとCは並行して実行できますか?
それらの状態変更はいつ可視になりますか?
Bが変更を生成しなかった場合、Dは実行されますか?
Cが別のイベントを発行した場合、どうなりますか?
そのイベントはすぐに処理されますか、それともキューに入れられますか?
Bが失敗した場合、どうなりますか?
サイクル終了時のクリーンアップはいつ行われますか?
Dは部分的に完了した更新を観察できますか?
ワークフローランタイムは、それらの実行セマンティクスと作成者が提供するグラフ定義を通じてこれらの質問に答えます。ルートが動的なままでなければならない場合は、それは完全に適切です。しかし、それはグローバルな調整計画が依然として作成されたソフトウェアであることを意味します。
コンポーネントがすでに構造的依存関係を表現しているアプリケーションでは、明示的なワークフローは、プログラムの他の場所に存在する関係を重複させることもあります。文字通りの重複がない場合でも、誰かがグローバルルーティングとライフサイクル計画を構築および維持する必要があります。
Fluxtionはより狭い質問をします。コンポーネントグラフが閉じられ、ローカルイベントセマンティクスがわかっている場合、コンパイラはグローバルコーディネーターのどれだけを導き出すことができますか?
推論されたオーケストレーション
これは私がFluxtionを通じて探求してきたアプローチです。私はエージェントフレームワークからこの問題にたどり着いたわけではありません。Fluxtionは、イベント順序、レイテンシ、再生、および決定を再構築する能力が本番要件であった電子取引システムから成長しました。最近のAI生成ソフトウェアの台頭により、同じ調整問題がはるかに一般的になりました。
Fluxtionはオーケストレーションをコンパイラの問題として扱います。材料のどれも前例のないものではありません。Jane StreetのIncrementalは依存関係グラフを維持し、入力が変更されたときに影響を受ける部分を再計算します。そのグラフは実行時に変更されることもあります。Daggerはコンパイル時のグラフ分析を使用して、依存関係を構築および配線するJavaを生成し、Dagger Producersはそのアプローチを依存する非同期計算に拡張します。
Fluxtionは関連するアイデアを異なるレイヤーに適用します。状態を持つビジネスコンポーネントの閉じたグラフ全体での繰り返しイベント調整、イベント固有のディスパッチ、変更およびトリガー伝播、ライフサイクル、再入可能性、監査、再生を含み、スタンドアロンJavaプロセッサに特化しています。開発者は、ローカル状態と動作を含む通常のJavaコンポーネントを作成します。それらのコンポーネント間の参照はオブジェクトグラフを形成します。アノテーションとインターフェースは、イベント処理とライフサイクルセマンティクスを宣言します。作成スタイルに応じて、トポロジーはJavaオブジェクト参照、フローDSL、またはSpring配線を通じて表現される場合があります。重要なのは、すべてのグラフが同じように開始されることではなく、作成者がランタイムコーディネーターを別々に実装するのではなく、構造を一度宣言することです。完全なグラフが作成されると...