【マルチエージェントAI – デバッグ】障害がエージェント間の「隙間」で発生するとき

Article by: Sergiy Dybskiy 私は最近、マルチエージェント型のリサーチシステムを構築していました。アイデア自体はシンプルです。「PythonバックエンドをRustへ書き換えるべきか?」のような議論の分かれる技術トピックを与えると、3つのエージェントがそれぞれ役割を担います。Advocate は賛成側を主張し、Skeptic は反対側を主張し、Synthesizer は両者のブリーフを先入観なしに読み込んで、バランスの取れた分析を生成します。各エージェントはそれぞれ異なるモデル、異なるツール、異なるシステムプロンプトを持っています。 テストではうまく動いていました。しかし、そのうち Synthesizer が片側に強く寄った分析を繰り返し生成していることに気づきました。間違っているわけではないのですが、明らかに偏っていたのです。たしかに Sentry のモノレポをRustへ書き換えるのは悪いアイデアかもしれませんが、本来なら賛成になるべきだと明確に分かっているケースでも反対寄りの結論になっていました。 最終的に原因をたどると、Skeptic 側の web_search ツールに行き着きました。Advocate はクエリごとに3〜4件のしっかりしたデータポイントを返していました。一方 Skeptic は、データとうまく一致しない別の検索語を使っており、結果として汎用的な検索結果を1件返しているだけでした。そのため、Advocate のブリーフには引用付きの十分な根拠がありましたが、Skeptic のブリーフは……雰囲気だけになっていました。 Synthesizer は、合理的な読み手なら当然するであろう判断をしただけです。より根拠が揃った側の主張を、より重く扱ったのです。 問題は、ある1つのエージェント内のツール呼び出しにありました。そしてその問題が、2段階後にまったく別のエージェントへ渡される入力品質を静かに劣化させていたのです。私がそれを発見できたのは、トレースをクリックしながら各ステップのツール出力を順番に読み進めたからでした。 マルチエージェントのオブザーバビリティとは? マルチエージェントのオブザーバビリティとは、複数のAIエージェントがどのように協調し、作業を引き継ぎ合い、互いの意思決定へ影響を与えているかを可視化することです。 おそらく、単一エージェントのオブザーバビリティについてはすでにご存じでしょう。1本の推論チェーンがあり、いくつかのツール呼び出しがあり、最終的なレスポンスが返る、というものです。マルチエージェント版では、1つのエージェントの出力が別のエージェントの入力になる、相互接続された推論チェーンのグラフ全体を追跡します。このグラフのどこか1か所で失敗が起きると、その後段すべてを静かに壊してしまう可能性があります。 もし、いくつかのツールを持つ単一エージェントを動かしているだけなら、通常のエージェントオブザーバビリティで十分です。しかし、エージェント同士が他のエージェントを呼び出したり、サブタスクを委譲したり、並列実行した結果を後から統合したりし始めた瞬間、必要になる可視性のレベルは別物になります。 なぜ単一エージェント向け監視では不十分なのか 既存のエージェント監視では、「Skeptic が3.1秒で実行され、2,400トークン消費した」ということは分かります。しかし、それだけでは、Skeptic の web_search が弱い検索結果しか返していなかったこと、その結果生成されたブリーフが Advocate に比べて薄かったこと、そして Synthesizer が片方の入力品質の低さによって偏った分析を生成したことまでは分かりません。 これが破綻する理由は、主に3つあります。 まず、責任の所在が分散していることです。最終出力が間違っていたとしても、単一のエージェントだけを責めることはできません。Advocate はツールから得た情報を元に合理的な主張を組み立てていましたし、Synthesizer も受け取った情報を合理的に統合していました。問題は両者の相互作用の中にあり、単一エージェントのログだけを見ても発見できません。 次に、最悪の失敗ほど一見正常に見えることです。従来のソフトウェアでは、問題が起きればエラーが投げられます。しかしマルチエージェントAIでは、あるエージェントが「もっともらしいが薄い結果」を返し、次のエージェントがそれを疑わず取り込み、最終出力が返る頃には、弱いデータが何段階もの推論を経て自信満々に要約されています。生の入力同士を比較しない限り、その問題には気づけません。 さらに、すべての経路をテストできないという問題があります。5つのツールを持つ単一エージェントであれば、各ステップで取り得る行動は5通りです。しかし、5つのツールを持つ3つのエージェントが並列実行され、後で結果を統合する場合、可能な実行経路の数は膨大になります。すべての組み合わせを事前テストすることはできないため、本番環境で実際に何が起きているかを観測する必要があります。 多くの「マルチエージェント」は実際には単一エージェント 先へ進む前に、正直に言っておきたいことがあります。私は最初、この実験環境でマルチエージェント型のスタートアップアイデア検証システムを作りました。しかし途中で気づきました。これは偽物のマルチエージェントだったのです。 「Market […]
すべてをサンプリングせず、AIトレースを100%取得する

Article by: Sergiy Dybskiy 少し前、エージェントたちが「あなたは完全に正しいです!」と言っていた頃、私はwebvitals.comを作っていました。URLを入力すると、Next.jsのAPIルートへのAPIリクエストが開始され、いくつかのツールを持つエージェントが呼び出されてそれをスキャンし、あなたの……そう、想像どおり……Web Vitalsを改善するためのAI生成の提案を提供します。今もこれを気にする必要はあるのでしょうか? 開発環境ではtraceSampleRateを100%に設定していましたが、本番環境ではそれを10%まで下げていました。なぜなら……まあ、それが私たちのインストルメンテーションで推奨されているからですが。 Kyleは「【Sentry サンプリング戦略】すべてを見ようとすると結局なにも見えなくなる」と説明する優れたブログ記事を書いています。しかし、AIは非決定的です。そしてツールコールのエラーをデバッグしていたとき、そのサンプリング戦略のせいで、Vercel AI SDKから出力される非常に重要なスパンを見逃していることに気づきました。 7回のツールコールを伴うエージェントの実行は、部分的にサンプリングされることはありません。スパンツリー全体を取得するか、完全に失うかのどちらかです。これがヘッドベースサンプリングの仕組みです。 私は幻を追いかけていたわけです。 エージェントの実行はスパンツリーであり、サンプリングは全取得かゼロかのどちらか 一般的なエージェントの実行は、Sentryのトレースビューでは次のように表示されます。 1回の実行で11個のスパンがあり、サンプリングの判断はルートで一度だけ行われます。それは POST /api/chat のHTTPトランザクションです。すべての子スパンはその判断を引き継ぎ、ルートが破棄されれば、9個すべてのスパンが消えます。 これはHTTPリクエストのサンプリングとは本質的に異なります。GET /api/users を1つ捨てたとしても、次のリクエストはほぼ同じなので大きな問題にはなりません。 エージェントの実行は同一ではありません。それぞれが異なる判断を行い、異なるツールを呼び出し、異なるデータを処理します。67回目の実行でハルシネーションを起こしたエージェントが、420回目では完全に正常に動作するかもしれません。もしサンプルレートによって67回目が捨てられていたら、何が問題だったのかを知ることはできません。 ヘッドベースサンプリングが実際にどのように動作するのか(そしてここでなぜ重要なのか) SentryのJavaScriptおよびPython SDKはいずれもヘッドベースサンプリングを使用しています。判断はトレースの開始時、まだ子スパンが存在しない段階で行われます。 JavaScript SDKでは、SentrySampler.shouldSample() がこの点を明確に示しています。 ルートでないスパンには決定権はありません。ルートスパンが破棄された場合、gen_ai.request や gen_ai.execute_tool を含むすべての子スパンについて tracesSampler が呼び出されることはありません。子スパンは親の運命を引き継ぎます。 Pythonでも同じロジックは Transaction._set_initial_sampling_decision() にあります。traces_sampler コールバックには sampling_context の辞書が渡され、その中には transaction_context(op と name を含む)と parent_sampled が含まれます。これはルートトランザクションに対してのみ実行されます。 つまり、ヘッドベースサンプリングでは、親トランザクションとは別に […]