エージェントが動画トランスコードのエンドポイントを呼び出します。エンドポイントは202 AcceptedとジョブIDを返します。エージェントは、システムにおける202の意味を理解していないため、トランスコードが完了したと報告し、まだ存在しないファイルを読み込もうとする次のステップに進みます。
時間のかかる操作は、特定の方法でエージェントを破綻させます。同期的な呼び出しには明確な契約があります。つまり、送信し、待ち、回答を得るというものです。非同期的な呼び出しは、それを開始と終了に分割し、その間のギャップでエージェントは混乱します。彼らは早期に成功を宣言したり、密なループで何千回もポーリングしたり、会話のターンを開いたまま6分間ブロックされたりします。
このガイドでは、エージェントが従えるように非同期契約を設計する方法、いつポーリングし、いつ引き渡すか、モデルが適切に動作するようにツールを記述する方法、そして遅いケースや失敗したケースを含む全体のパスをテストする方法について説明します。エージェントのエラー回復に関する当社の投稿ではAPI呼び出しの失敗側を扱っており、この投稿ではゆっくりと成功するケースを扱います。
Apidogは、エージェントが4分かかって失敗するジョブを処理できることを証明する必要がある場合に役立ちます。これは本番環境で発見したいことではありません。
なぜエージェントは非同期処理を誤るのか
主な問題は3つの習慣によって引き起こされます。
モデルは2xxを完了とみなします。202はリクエストが処理のために受け入れられたことを示し、HTTPセマンティクス仕様では処理が完了していない可能性があると明示されています。通常の要求/応答トラフィックでトレーニングされたモデルは、応答が明示的にそうでないと述べていない限り、任意の2xxを完了と読み取る傾向があります。
ループはコストがかかります。エージェントが推論ループ内でポーリングする場合、各チェックはモデルのターンと以前の会話のトークンを消費します。4分間のジョブに対して2秒ごとにポーリングすると120ターンになり、実行はコンテキストを使い果たすか、予算を超過します。コンテキストウィンドウからツール応答を除外することに関する当社の投稿では、それが人々が予想するよりも早く蓄積される理由を説明しています。
エージェントはジョブを見失います。作業を開始してジョブIDを返すツールは、エージェントが引き継がなければならない状態を作成します。IDが長い会話の途中に現れると、圧縮されてしまい、エージェントは進行中のジョブがあることを忘れてしまいます。
モデルが誤読できないようにレスポンスを設計する
最も効果的な解決策は、アーキテクチャではなく言葉遣いです。ステータスコードが何であれ、ボディに何が起こり、次に何をすべきかを明確に記載してください。
{
"status": "processing",
"job_id": "job_7f21c",
"message": "The transcode has STARTED and is NOT complete. Do not report success. Check status with getJobStatus(job_id) after at least 30 seconds.",
"poll_after_seconds": 30,
"estimated_duration_seconds": 240,
"status_url": "/v1/jobs/job_7f21c"
}
これは人間が利用するAPIの消費者にとっては過剰に感じるかもしれません。しかし、これはモデルを対象としており、モデルはステータスコードから意味を推測するよりも、レスポンスボディ内の明示的な指示にはるかに信頼性高く従います。3つの詳細が機能します:「未完了」という言葉、指定された次のツール、そして最小待機時間です。
Googleの長時間実行操作に関するAIP-151は、done、error、responseフィールドを持つ単一のOperationオブジェクトを使用して、これに対するクリーンなリソース形状を記述しています。この構造をコピーすることで、すべての遅いエンドポイントで一貫したインターフェースを提供できます。これは、1つのポーリングパターンを学習したエージェントがすべてのパターンを処理できるようになるため重要です。
ステータス応答も同様に直接的にしてください:
{
"job_id": "job_7f21c",
"status": "processing",
"done": false,
"progress_percent": 45,
"elapsed_seconds": 108,
"poll_after_seconds": 45,
"message": "Still processing. Do not proceed to the next step."
}
そして完了時には、結果が小さい場合はインラインで返すことで、エージェントが3回目の呼び出しをする必要がなくなります:
{
"job_id": "job_7f21c",
"status": "succeeded",
"done": true,
"result": { "output_url": "https://cdn.example.com/out/7f21c.mp4", "duration_seconds": 372 }
}
モデルの外部でポーリングする(内部ではない)
最も重要な実装上の選択は、待ち時間をエージェントの推論ループではなく、ツールのラッパー内に置くことです。
import time
def start_and_await_transcode(client, source_url, max_wait=600):
job = client.post("/v1/transcode", json={"source_url": source_url}).json()
job_id = job["job_id"]
delay = job.get("poll_after_seconds", 5)
waited = 0
while waited < max_wait:
time.sleep(delay)
waited += delay
status = client.get(f"/v1/jobs/{job_id}").json()
if status.get("done"):
if status["status"] == "succeeded":
return {"status": "succeeded", "result": status["result"]}
return {"status": "failed", "error": status.get("error")}
delay = min(int(delay * 1.5), 60)
return {
"status": "timed_out",
"job_id": job_id,
"message": f"Still running after {max_wait}s. Job {job_id} continues in the background.",
}
モデル側から見ると、これは時間がかかり、最終的な答えを返す1回のツール呼び出しです。コンテキスト内のポーリングループも、忘れられたジョブIDも、120ターンもありません。バックオフによりリクエスト数を適切に保ち、上限により停止したジョブが実行を永久にハングアップするのを防ぎます。タイムアウト、リトライ、ジッター付きバックオフに関するAmazonの記述は、これらの数値を調整する前に読む価値のあるリファレンスです。
この方法を安全にするには2つのルールがあります。常に待機時間に上限を設定し、タイムアウト時には必ずジョブIDを返すことで、エージェントまたは人間が後で確認できるようにします。曖昧な結果を返さないでください。succeeded、failed、timed_outは3つの異なる結果であり、モデルは3つの異なる言葉を認識すべきです。
分単位ではなく時間単位で測定されるジョブの場合、ラッパー内ポーリングは意味をなさなくなります。その場合、適切な形は、開始用とチェック用の2つのツールと、会話の外に進行中のジョブの永続的な記録を用意することです。これにより、圧縮によって何も失われることがありません。job_id、それが属するタスク、および開始時刻を保存し、エージェントが各実行の開始時にそのリストを読み取るようにします。
Webhookがより良い解決策となる場合
ポーリングはシンプルでどこでも機能します。コールバックはより効率的ですが、実行にはより多くの作業が必要です。トレードオフについては、Webhook対ポーリングの比較で詳しく説明されており、エージェントに特化したバージョンはより限定的です。
ジョブが数秒から数分で完了する場合、エージェントが結果を待って続行する場合、またはパブリックエンドポイントをホストできない場合にポーリングを使用します。ほとんどのエージェントのワークロードはここに該当します。
ジョブが数時間かかる場合、エージェントが作業を開始して次に進む場合、または多数のジョブが同時に実行され、各ジョブをポーリングするのが無駄な場合にWebhookを使用します。コストは実際に発生します。パブリックなレシーバー、署名検証、リトライ処理、そしてコールバックが到着したときにエージェントを起動する方法が必要です。信頼性の高いWebhookの設計方法とWebhook署名検証に関する当社のガイドが、その基礎をカバーしています。
中間的な選択肢も知っておく価値があります。サーバー送信イベントを介してジョブの進行状況をストリーミングすると、クライアントが接続を保持するため、パブリックエンドポイントなしでプッシュセマンティクスが得られます。これは人間が監視しているインタラクティブなエージェントに適しており、SSEでAPIレスポンスをストリーミングする方法に関する当社のガイドで実装について説明しています。
どちらを選択しても、完了パスは冪等でなければなりません。Webhookはリトライし、ポーリングは競合します。「succeeded」を2回見たエージェントは、後続のステップを2回開始すべきではありません。AIエージェントの冪等性キーに関する当社の投稿では、それを安全にするためのキーについて説明しています。
高速なパスだけでなく、低速なパスもテストする
非同期のバグは、テスト環境が高速であるために隠れてしまいます。本番環境で4分かかるジョブがローカルスタブに対して200ミリ秒で完了するため、エージェントは実際に遭遇する状態を経験することはありません。
意図的に構築すべき4つのシナリオがあります。
実際に遅いジョブ。 ステータスエンドポイントをモックし、最初の数回の呼び出しではprocessingを返し、その後succeededを返すようにします。これにより、ラッパーがポーリングし、バックオフし、最終的に返されることを証明します。Apidogでは、リクエスト数または制御パラメーターによって変化するモックを使用してこれを実行できるため、同じテストが常に同じ方法で実行されます。

遅れて失敗するジョブ。 processingを3回返し、その後エラーボディとともにfailedを返します。エージェントは、完了したポーリングを完了したジョブとして扱うのではなく、失敗を報告する必要があります。これは、誤っている場合にサイレントなデータ損失を引き起こすケースです。
タイムアウト。 モックがラッパーの上限を超えてprocessingを返し続け、ツールが例外や偽の成功ではなく、ジョブIDを保持したままtimed_outを返すことをアサートします。
重複する完了。 Webhookのリトライまたは競合するポーリングによって成功を2回渡し、ダウンストリームのステップが1回だけ実行されることをアサートします。
これら4つのシナリオすべてを保存し、CIで実行されるようにします。再実行にはコストがかからず、誰かがタイムアウトを短縮したりエラーを飲み込んだりする回帰を検出します。より広範なアプローチは、API契約テストのガイドに記載されています。
問題を露呈させる3つのジョブ
レポート生成。 財務エージェントが四半期ごとのエクスポートをリクエストします。これには90秒かかります。素朴なツールの場合、エージェントはジョブIDを受け取り、レポートが準備できたとアナウンスし、壊れたダウンロードリンクをユーザーに渡します。ブロッキングラッパーを使用すると、90秒間待機し、実際のURLを返します。同じAPIでも結果は正反対であり、唯一の違いは待機が発生する場所です。
一括インポート。 運用エージェントが20,000件のレコードをアップロードします。インポートは8分間実行され、14,000行目で部分的に失敗します。これは、素朴な成功チェックが罰せられるケースです。ジョブは終了したので、doneステータスは真ですが、結果には拒否された行のリストが含まれます。部分的な結果を、カウントとともに明示的に返し、エージェントが次に進む前にそれらを読み取るようにします。
モデルとビルドパイプライン。 エージェントが40分かかるトレーニング実行またはCIビルドをトリガーします。この場合、ラッパー内ポーリングは不適切な形です。実行はターンを長期間開きすぎてしまいます。ジョブを開始し、IDを永続ストレージに記録し、ターンを終了させ、スケジュールされたチェックまたはコールバックが後続の処理を起動するようにします。マルチエージェントの引き継ぎとコンテキスト受け渡しに関する当社の投稿では、その状態を実行間で失うことなく移動する方法を説明しています。
部分的な結果に形を与える
長時間ジョブは成功と失敗の間のどこかで終了することが多く、2つの状態のモデルではそれについて偽りを述べることになります。3番目の状態を明示的にしてください:
{
"job_id": "job_a11f",
"status": "completed_with_errors",
"done": true,
"summary": { "processed": 20000, "succeeded": 19860, "failed": 140 },
"errors_url": "/v1/jobs/job_a11f/errors?limit=50",
"message": "Import finished. 140 rows failed and were not written. Review errors before reporting success."
}
そのペイロードで重要な点が2つあります。カウントがインラインに含まれているため、エージェントは別の呼び出しなしで判断できます。失敗した行は制限付きのURLの背後にあるため、140個のエラーオブジェクトが不必要にコンテキストに送られることはありません。
滞ったジョブは誰かが見る必要がある
タイムアウトパスは、ジョブIDと作業がまだ実行中であることを示すメッセージで終わります。これは正しい戻り値ですが、それが人に届いて初めて役立ちます。
エージェントが自社サービスである場合、チームが既に監視しているキューにルーティングします。エージェントが割り当てられたタスクを処理するコーディングランタイムである場合、それを実行するプラットフォームには通常、この情報が送られる場所があります。Sharklyでは、ブロックされて終了した実行は、その実行状態と結果とともにタスクに残り、受信トレイは人間の返信やレビューが必要な項目を通常の更新から分離します。重要なのは特定のツールではなく、「まだ実行中、後で確認してください」という状態には所有者が必要であり、そうでなければ「誰も確認しなかった」になってしまうということです。

簡単なチェックリスト
- すべての遅いエンドポイントは、ジョブID、ステータスURL、および作業が完了していないことを示す平易なメッセージを返します。
- ステータス応答には、モデルが解釈する必要のある文字列だけでなく、ブール値の
doneフィールドが含まれます。 - ポーリングは、指数関数的バックオフと厳密な上限を持つツールラッパー内に存在します。
- タイムアウトはジョブIDを返すため、作業が失われることなく再開できます。
- 成功、失敗、タイムアウトは3つの異なる戻り値です。
- 数分より長い実行中のジョブは、会話の外部に記録されます。
- 完了処理は、シグナルがポーリングによって到着してもコールバックによって到着しても、冪等です。
- 遅い、遅れて失敗する、タイムアウトする、重複する完了はすべて、保存されたテストを持っています。
応答の言葉遣いとラッパーを適切に設定すれば、長時間実行される操作はエージェントにとって特別なケースではなくなります。ツールを呼び出し、待ち、回答を得る、これがエージェントが最も得意とする契約です。テストと並行してスロージョブのモックを構築するには、Apidogをダウンロードしてください。
よくある質問
非同期開始の場合、APIは202または200を返す必要がありますか? 202 Acceptedは正直なコードであり、標準的なクライアントに処理が完了していないことを伝えます。ただし、モデルが最も確実に読み取るのはボディであるため、エージェントに対してはこれだけに頼らないでください。両方を使用します。
ツールラッパーはどれくらいの時間待機してから諦めるべきですか? エンドポイントの現実的な最悪ケースよりわずかに高く上限を設定します。一般的には2〜10分です。それを超えると、ラッパーは会話のターンを長すぎる間ブロックすることになり、「後で確認する」ツールの方が良い形です。
どのポーリング間隔を使用すべきですか? サーバーがpoll_after_secondsヒントを提供している場合は、そこから開始し、約1.5倍の係数でバックオフし、上限を約60秒に設定します。固定の1秒ポーリングはリクエストを無駄にし、レート制限に引っかかる可能性があります。これについては、レート制限超過ガイドで説明しています。
エージェントは待機中に何か役立つことができますか? オーケストレーターが並行ツール呼び出しをサポートしている場合にのみ可能です。サポートしている場合は、ジョブを開始し、独立した作業を行い、その後ステータスを確認します。サポートしていない場合は、手動で作成したスケジューラよりもブロッキングラッパーの方がシンプルでエラーが発生しにくいです。
エージェントが早期に成功を主張するのをどうすれば止められますか? レスポンスボディで言葉で伝え、ブール値のdoneフィールドを公開し、完了ツールを結果が表示される唯一の場所にします。開始応答に結果が含まれていない場合、モデルが結果として報告するものは何もありません。
Webhookはラップトップで実行されているエージェントで機能しますか? パブリックエンドポイントがないため、直接は機能しません。WebhookサービスでローカルホストAPIをテストするガイドにあるように、開発にはトンネルを使用するか、エージェントがアドレス指定可能な場所で実行されるようになるまでポーリングに固執してください。
