レッスン6: ハンズオン: 2エージェントレビューパイプラインの構築
学習目標:
- Claude API を使用して本当に実行可能なプロデューサー・レビュアーの2エージェントパイプラインを書く
- レビュアーに包括的な「良さそうだ」ではなく、構造化されたチェック可能なレビュー結果を返させる
- プロデューサーとレビュアーが永遠に磨き合わないように、ループに安全弁を設置する
前提: レッスン1〜5を完了し、基本的な JavaScript/Node.js を読むことができ、動作する Claude API キーを持っていること | 前: レッスン5 <<
まず、結果: 1回の完全な実行
これはレッスンの終わりまでに実行できるようになるものです。ターミナルにタスクを渡すと、2つのエージェントがレビューが通るかラウンド制限に達するまで交代します。
最初のバージョンはレビュアーによって跳ね返され、各具体的な基準に紐付いた理由が示されます。プロデューサーは2番目のバージョンに修正し、レビュアーは再度確認し、今回は通ります。これはレッスン4のプロデューサー・レビュアーパターンをコードに変えたものです。"one LLM call generates a response while another provides evaluation and feedback in a loop."1
全体的な形: 実行ループと同じ骨格
このシリーズでAgent Tool Calling: Getting Agents to Actually Do Thingsコースを受けた場合、このパイプラインの骨格は馴染みがあるでしょう。ループ、ラウンドごとの1つの判定、続けるかどうかを決定する結果、さらに無限ループに対する安全弁です。唯一の違いは判定が何を判定するかです。そのコースのツール実行ループは「モデルはまだツールを呼び出したいか」を判定します(ループのセマンティクスはそのコースのレッスンThe Full Round-Trip of a Tool Callとその公式ソースにあります)。ここでは「レビュアーは合格と言ったか」を判定します。同じ骨格、ループ本体の内容が異なります。
パイプライン全体は3つの関数をつなぎ合わせたものです。runProducerはテキストを生成または修正し、runReviewerは基準に対してスコアを付けて具体的なメモを提供し、runPipelineは2つをループに結び付け、安全弁としてラウンドの上限を設定します。
ステップ1: プロデューサー — タスクを受け取り、テキストを生成する
最初の実行では、プロデューサーはタスクそのものしか持っていません。却下後の2回目の実行では、完全な前のバージョンとレビューメモも運びます。そのため、プロデューサーはゼロから自由にスタイリングするのではなく、メモに従って最後のバージョンの上に修正します。
プロデューサーのプロンプトは独立しています。レッスン3でカバーしたように、サブエージェントはオーケストレーター側で何が起こったかを見ることができず、前回どうレビューされたかも見ることができません2。そのため、すべての呼び出しは「タスクが何か」「前のバージョンが何と言ったか」「(もしあれば)前のラウンドの問題が何だったか」をこの呼び出しのプロンプトに逐語的に書き込みます。プロデューサー自身の以前の下書きでさえ明示的に戻す必要があることに注意してください。これは独立性の原則の中で最も見逃しやすい半分です。Messages API はステートレスで、すべてのリクエストは必要な完全な履歴を運ばなければならず、サーバーはリクエスト間で何も保持しません3。「前のバージョンを修正する」は、前のバージョンが実際にこのプロンプトに書き込まれたときにのみ意味があります。
ステップ2: レビュアー — 具体的な基準に対してスコアを付け、曖昧な評決をしない
レビュアーはモデルに「これは良いか」と尋ねるだけではありません。レッスン5でカバーしたように、検証は印象ベースのスコアではなく、具体的でチェック可能な基準に着地しなければなりません4。ここでレビュアーは明示的なチェックリストを取得し、固定された JSON フォーマットで返信することを要求されます。
approvedとissuesフィールドが一緒になって構造化されたレビュー結果を構成します。単一の「大丈夫だ」ではなく、「合格か不合格か」プラス「失敗した各基準の背後にある具体的な問題」です。プロデューサーがissuesを持っていると、曖昧な評決からどこに行くかを推測するのではなく、それらの具体的な問題を修正します。
ステップ3: レビュー結果を盲目的に信頼しない — パース失敗を却下として扱う
runReviewerは文字列を返し、実際の JSON オブジェクトではないので、まだパースする必要があります。レビュアーが「厳密に JSON で返信する」ように言われていても、構造化出力の制約がなければ、モデルは依然として構文的に無効な JSON を生成したり、フィールドをドロップしたり、JSON をコードブロックで囲み、その周りにいくつかの説明行を追加したりする可能性があります5。ここでの罠は、パースが失敗したときに何が起こるかです。怠惰なルートを取る(パース失敗時にデフォルトで通す)と、「レビュアーが仕事をしなかった」失敗を静かに「レビュー合格」に変えます。それはまさにレッスン5が指摘したポイントです。「見える」完了した出力は実際に正しい出力と同じではなく、検証できないものは出荷すべきではありません6。ここでは逆を行います。パース失敗は常に却下としてカウントし、決して合格としてはカウントしません。
typeof parsed.approved !== "boolean"と!Array.isArray(parsed.issues)の行は同じアイデアを拡張します。JSON.parseが成功しても、パースされたフィールドが正しい形を持っていることを確認し、間違ったフィールドタイプも却下としてカウントします。「少なくとも有効な JSON だ」からといって警戒を解かないでください。
一つの余談: レスポンスがスキーマに厳密に一致することをサンプリングレベルで保証する公式の構造化出力機能があります5。このレッスンは意図的に「素の呼び出しプラス自分の防御的なパース」スタイルを使用して、モデルの出力を盲目的に信頼できないことを直接感じるようにします。本番では構造化出力を使用してこの落とし穴を完全に取り除くことができます。
ステップ4: ループに配線し、安全弁を追加する
runProducer、runReviewer、parseReviewを手に入れたら、runPipelineは3つを一緒に配線し、MAX_ROUNDSはここでの唯一の安全弁です。プロデューサーとレビュアーは理論的には永遠に磨くことができるので、上限が必要です。
まだ合格せずにMAX_ROUNDSに達したとき、runPipelineは「合格」の評決を強制しません。最後の下書きとまだ解決されていない問題を人間のレビューのために正直に引き渡します。これもレッスン5のポイントをクロージングステップに適用したものです。結果統合段階が判断できないものに遭遇したとき、コードで自分で決定することでそれを覆い隠すべきではありません。
まとめ
- プロデューサー・レビュアーパイプラインの骨格は実行ループと同じものです。ループ、ラウンドごとの1つの判定、続けるかどうかを決定する結果、さらに無限ループに対する安全弁です。このパターンの公式定義はまさに "one LLM call generates a response while another provides evaluation and feedback in a loop"1 です。ここで判定は「ツールを呼び出すべきか」から「レビュアーは合格と言ったか」に切り替わります。
- プロデューサーのプロンプトは独立しています。すべての呼び出しはタスク、完全な前のバージョン、(もしあれば)前のラウンドの具体的な問題をプロンプトに逐語的に書き込みます。Messages API はステートレスで、すべてのリクエストは完全な履歴を運ばなければならず、リクエスト間で何も保持されません3。そのため、モデルが前のラウンドで何が起こったかを自分で覚えていることを頼りにすることはできません2。
- レビュアーは具体的でチェック可能な基準に対して項目ごとにスコアを付け、包括的な評決ではなく構造化された
{approved, issues}を返します4。
- レビュアーが返すものも盲目的に信頼できません。パース失敗または間違ったフィールド形状は静かな合格ではなく却下としてカウントすべきです6。この原則は「サブエージェントが言うことを信頼する」だけでなく、「サブエージェントが返すデータフォーマットを信頼する」にも適用されます。
- まだ合格せずに最大ラウンド数に達したとき、パイプラインはコードで自分で合格を決定するのではなく、最後の下書きと未解決の問題を人間のレビューのために正直に引き渡すべきです。
このコースの6つのレッスンはすべてここまでです。「なぜ複数エージェントか」から始まり、オーケストレーターとサブエージェントがどう作業を分割するか、委任プロンプトをどう書くか、どのコラボレーションパターンがどのシナリオに適合するか、失敗をどう処理するかを経て、手作業で動作するプロデューサー・レビュアーパイプラインを構築することで終わります。次に最も価値のあることは説明を読み直すことではありません。手元にある小さな実際のタスクを選び、このパイプラインの骨格にドロップし、レビュー基準を調整し、実行して下書きが跳ね返されるかどうか、何回跳ね返されるかを確認することです。自分でレビュー基準を1回調整することは、理論を10回読み直すことに勝ります。