レッスン2: つなぐ、振り分ける: チェイニングとルーティング
学習目標:
- 4つの仕事を1つに詰め込んだプロンプトをチェーンに分解し、何を差し出して何を得ているかを言語化する
- チェーンの各ステージのあいだにプログラム的なゲートを取り付け、基準を満たさない中間結果をそこで止める
- チェイニングが要るのか、ルーティングが要るのか、両方か、どちらも要らないかを判断し、ルーティングの呼び出しを出力を絞った安価な1回として書く
前提: レッスン1を読了し、stop_reason駆動のハーネスループを手書きできること(このシリーズのコース7)、決定的な検証器を理解していること(このシリーズのコース10) | 前: << レッスン1 | 次: レッスン3 >>
1つのプロンプトが4つの仕事をすると、1つ落ちる
新機能のヘルプドキュメントを書く必要があります: プロダクト要件を読む → アウトラインを起草する → アウトラインに沿って本文を書く → ドキュメントに非推奨の用語が混ざっていないか点検する。
最初のバージョンは、おそらく4ステップ全部を1つのプロンプトに詰め込んで、ハーネスループへ渡すでしょう。1回目の実行は良さそうに見えます。問題が現れるのは2回目、そして3回目です: 今回はアウトラインは良いのに本文で1節が抜けている。次はテキストは揃っているのに、前バージョンで非推奨になった「ユーザーグループ」が混ざっている。その次は途中でアウトラインを書き直してしまい、本文と噛み合わないアウトラインを納品してくる。
これらの失敗は表面上は別々に見えますが、根は同じです: 1回の呼び出しの中で、モデルは要件の理解、構造の設計、テキストの生成、一貫性の点検を同時にやりくりしなければなりません。どれが押し出されるかをあなたはコントロールできず、それが起きるところを見ることもできません。さらに悪いことに、介入する場所がありません——出力が手に入った時点で4つとも終わっており、「アウトラインが仕様を満たしていない」は最終稿の内側に埋まっています。
レッスン1では「誰がプランを持つか」という軸を扱いました。この一発勝負のプロンプトは、プランをまるごとモデルへ渡しています。このレッスンが最初にやるのは、それを取り返すことです。
チェイニング: レイテンシと引き換えに正確性を買う
このパターンの公式の定義はたった2文で、どの語も荷重を負っています。
プロンプトチェイニングは、タスクを一連のステップへ分解し、各LLM呼び出しが前の呼び出しの出力を処理します。プロセスがまだ軌道上にあることを確かめるために、任意の中間ステップにプログラム的なチェック(下図の"gate"を参照)を足せます1。このワークフローを使うとき: このワークフローは、タスクを簡単かつきれいに固定のサブタスクへ分解できる状況に理想的です。主な目的は、各LLM呼び出しをより易しいタスクにすることで、レイテンシと引き換えにより高い正確性を得ることです1。
「各LLM呼び出しをより易しいタスクにする」——この半文が診断と処方の両方を与えています。4つの仕事をする1回の呼び出しは難しいタスクです。アウトラインを起草するだけで本文は一切書かない1回の呼び出しは易しいタスクです。チェイニングは仕事の量を減らしません——1回の呼び出しでモデルが完了しなければならない仕事を、単純にするのです。
コストは値札にそのまま印字されています: レイテンシです。ステージを1つ足すごとに、完全な往復が1回増えます。Anthropicは同じ記事の冒頭でこの会計を並べています——エージェント的システムは、より良いタスク性能と引き換えに、しばしばレイテンシとコストを差し出すので、このトレードオフが意味を持つのはどんなときかを検討すべきだ、と1。「遅くて高い」はチェイニングの偶発的な副作用ではありません。
各区間の入力は、前の区間の出力です。1つのリンクが曲がれば、下流はすべてそれに従います。
各ステージは完全なハーネスループ
チェーンの1ステージは「1回のAPI呼び出し」ではありません——完全なハーネスループです。このシリーズのコース7で手書きしたあのwhileループと同じもの: messagesを送り、stop_reasonを確認し、tool_useならツールを実行して結果を送り返し、そうでなければテキストを返す。
この使い方には出典があります。Anthropicがエージェントの評価の仕方を説明したとき、推奨されたセットアップはまさにこの形でした: LLM APIを直接呼ぶこと、単純なエージェントループ(LLM APIの呼び出しとツールの呼び出しが交互に並ぶのを包むwhileループ)、評価タスク1つにつきループ1つ、各評価エージェントには単一のタスクプロンプトとあなたのツールを与えること2。あの記事は評価の話でしたが、ビルディングブロックそのものは汎用です: 1つのタスク、1つのループ、コード駆動。チェイニングとは、このブロックを並べてつなぎ、順序をコードに決めさせることです。
以降のレッスンでは同じ記法を使います:
チェーン全体は、ひと目で読める逐次的なコードです:
この数行に入っていないものに注目してください: 「次に何をするかをモデルが決める」余地がありません。順序はハードコードされており、中間結果のoutlineとdocはただのスクリプト変数です。モデルは各ステージの内側では相変わらず自律的です(好きなだけツールを呼べます)が、ステージ間の制御はコードの手にあります。副次的な利点として、各ステージのプロンプトはたった1つのことについて厳しくできます。アウトラインのプロンプトは見出しだけを要求し、本文を書くことを禁じます。執筆のプロンプトは文体と禁止用語に集中します——この2組の要求は、1つのプロンプトに詰め込めば衝突します。
この形はプロダクトにも現れます。Claude Codeの公式ドキュメントは、複数ステップのワークフローについてこう勧めています: Claudeにサブエージェントを順番に使わせる。それぞれが自分のタスクを完了してClaudeへ結果を返し、Claudeが関連するコンテキストを次のサブエージェントへ渡す3。違いはレッスン1の軸に着地します——プロダクトの形では「何を渡すか」をClaudeが決めます。スクリプトを書くなら、それはあなたのコードです。
ゲート: コース10の検証器をステージ間へ移す
定義の最後の半文こそが、「1つの大きなプロンプト」を超えてチェイニングが実際に足しているものです: プロセスがまだ軌道上にあることを確かめるために、任意の中間ステップにプログラム的なチェックを足せる1。原文はこのチェックを"gate"と呼んでおり、しかも引用符付きです: (see "gate" in the diagram below)——片方がストレート、片方がカーリーで、出典どおりです。ここでの誤植ではありません。
「プログラム的」が鍵です: 別のモデル呼び出しではなくコードであり、if文が数個あるだけです。
このシリーズのコース10は決定的な検証器を教えました: コードで正誤を判定できるものに、お金を払ってモデルへ尋ねない。あのコースは検証器を終端状態に取り付けました——全部走ったあとで、出力が受け入れ可能かを確かめる。チェイニングは、同じチェックに新しい設置場所を与えます: ステージのあいだです。
7行、モデル呼び出しはゼロ、同じ入力なら常に同じ判定です。まさにあの「本文に非推奨の用語が混ざる」失敗を止めます。アウトラインの章見出しがいくつあるかを数えるゲートも、同じくらい単純です。
ゲートが落ちたときに何をするかは設計上の判断です: 止めてエラーを報告する(このチェーンをまだ調整中のときに最善ですが、失敗メッセージはどのステージで落ちたかを言わなければなりません。さもないと「うまくいかなかった」ことだけが分かり、どのステージのプロンプトを直せばよいか分かりません)、失敗の理由を同じステージのプロンプトへ戻して再試行する(リトライ上限つきで)、記録して代替値で続行する(このステージが荷重を負っていないときだけ)。レベル2の演習は1つ目のやり方を使います。
これはこのシリーズのコース6の会計も片づけます: 委任する仕事には自己完結したプロンプトが要る——目的、出力フォーマット、使えるツール、境界の4つを全部書き出すこと。各ステージのタスクは、まさにその形をした委任プロンプトです。この4要素には正確な一次出典があり、レッスン4がオーケストレーターの委任の仕方を扱うときに1項目ずつ解きほぐします。
ルーティング: まず分類し、それから振り分ける
チェイニングが扱うのは「1つのタスクをステップへ分けること」です。もう1つのクラスのタスクは、まったく違う形をしています: 入ってくるのは1種類のものではなく、いくつかの種類のもので、それぞれに固有の扱い方があります。
公式の定義はこうです: ルーティングは入力を分類し、専門化された後続タスクへ差し向けます。このワークフローは関心の分離と、より専門化されたプロンプトの構築を可能にします。このワークフローがなければ、ある種類の入力に向けた最適化が、他の入力での性能を損ないかねません1。
最後の一文が、ルーティングが存在する理由です。カスタマーサポートのメールが返金、障害、請求の3カテゴリに分かれるとしましょう。あなたは1つのプロンプトで全部を扱っています。返金をうまく扱うために「まず注文番号と決済チャネルを確認すること」という行を足す。このルールは障害のメールにとっては純粋なノイズで、モデルはそれを使って、白画面を報告してきた人に決済チャネルを尋ねます。あなたは「障害なら注文番号を聞かないこと」という行をもう1つ足し、プロンプトはパッチの上にパッチを生やしはじめます。
このワークフローを使うとき: ルーティングは、別々に扱ったほうが良い明確なカテゴリがあり、かつ分類そのものをLLMまたはより伝統的な分類モデルやアルゴリズムで正確に扱える複雑なタスクでうまく働きます1。この最後の前提条件はケーキの飾りではありません: 分類が間違うと、それは目立たない形で間違います——障害のフローへ振り分けられた返金メールは、良心的なトラブルシューティングの返信を受け取ります。
コードの上では、ルーティングはチェイニングより単純です:
注目に値することが3つあります。
分類呼び出しの出力を1語へ絞ること——このシリーズのコース10も、LLMジャッジを論じるときに同じ手を使いました: 許される値を列挙し、説明なしと言う。出力を絞ることで、パースのステップが決定的になります。表に無ければフォールバックへ——あのLABELS.includes(label) ? label : 'other'という行は、防御的な小うるささではありません。モデルはときどき「incidentだと思いますが、別の何かかもしれません」と返してきて、そうするとHANDLERS[その文字列まるごと]はundefinedになり、次の行がクラッシュします。フォールバックの分岐を1つ残しておけば、分類の不確かさはこの1行の中に封じ込められます。
分類器はモデルである必要がありません——定義は、伝統的な分類モデルやアルゴリズムも数に入ると明示しています1。メールが固定フォーマットの注文番号を持っていたり、専用のフォームの入口から来ていたりするなら、正規表現1本で十分ですし、そのほうがずっと速いです。
振り分けたあとは、各ハンドラは何であっても構いません: ハーネスループでも、チェーンでも、モデルをまったく使わないコードの一区間でも。
現行のAPIの語彙に見るルーティングの2つの変種
上のルーティングの定義は2024年末のパターンの記事から来ており、その記事にはツールのエコシステムに関する記述が古くなっているというバナーが載っています。だから確認しておく価値があります: このパターンは現行のファーストパーティの語彙の中でまだ生きているのか。生きていますし、名指しで呼ばれています。Claudeプラットフォームのマルチエージェントオーケストレーションのドキュメントには、ルーティングである項目が2つあります:
- 専門化(Specialization): 単一のエージェントにあらゆる能力を積み込むのではなく、セキュリティのエージェントやドキュメントのエージェントのように、ドメインに絞ったシステムプロンプトとツールを持つエージェントへルーティングする4。これが
HANDLERSテーブルの公式の言い回しです。
- エスカレーション(Escalation): 複雑なサブタスクの一部について、より能力の高いエージェントやモデルに相談する4。
2つ目は別に触れておく値打ちがあります: これはトピックではなく難易度で振り分けます。分類器が判定するのは「これは返金か障害か」ではなく「このメールは自分の安いティアで扱えるか」です。これはトピックの分類より正確に判定するのが難しいので、エスカレーションの経路にはもっと安定した書き方があります: まず安いティアを走らせ、出力がゲートを通らなければエスカレートする——判定の難しい分類問題を、チェック可能な検証問題へ取り替えるのです。
分けないときを知る
チェーンのリンク1つ1つがレイテンシを足します。 これは実装が最適化されていないからではなく、公式の定義が設定した価格です: レイテンシと引き換えにより高い正確性を得る1。ユーザーは全区間の合計を待ちます。ユーザーがUIの前で同期的に結果を待っているなら、チェーンにもう1本リンクを足す前に、その人がまだそこにいるかを考えてください。
カテゴリが1つしかないなら、ルーティングは純粋なオーバーヘッドです。 ルーティングの利益は関心の分離から来ます1。入力が本当に1種類しかないなら、分類呼び出しのコストとレイテンシを払って、見返りはゼロ、おまけに誤分類の機会が1つ増えます。
タスクをきれいに分けられないなら、無理に分けないこと。「簡単かつきれいに固定のサブタスクへ分解できる」という条件には歯があります1。全体を何度も行き来して見ないとうまく直せない草稿を、「まず構造を直す、それから文言を直す」に分けたら、2つ目のステージは1つ目のステージが書き残さなかった理由にアクセスできず、字面だけを見て直すことになります。この場合は1つのループ、1つのコンテキストのほうが実際に良い——これはまさにレッスン1の逆向き条件の使いどころです。
迷ったら、まず測ること。 Anthropicはこれを2度言いました: 複雑さを足すのは、それが結果を明白に改善するときだけにすべきだ1。このシリーズのコース10の評価のトラックは、まさにこのために作られています: 単一プロンプト版を走らせてスコアを取り、分割版を走らせてもう1つスコアを取り、差がどれだけあるか、その追加の数秒のレイテンシに見合うかを見る——それが、同僚との議論に持っていける証拠です。
最後に、境界を引いておきます。チェイニングとルーティングはどちらも形が固定されたオーケストレーションです: チェーンが何ステージ持つか、ルーターがどのカテゴリを持つかは、すべてコードを書く時点で決まっています。複数のステージを同時に走らせてから集約するのはレッスン3の並列化であり、入力を見るまでサブタスクがいくつあるかすら分からないのはレッスン4のオーケストレーター・ワーカーです。
💻 演習
まとめ
- チェイニングはタスクを一連のステップへ分解し、各呼び出しが前の出力を処理することで、1回の呼び出しをより易しいタスクにします1。その価格は明示されています: 主な目的は、レイテンシと引き換えにより高い正確性を得ることです1。
- チェーンの1ステージは1回のAPI呼び出しではなく、完全なハーネスループです——Anthropicが評価のために推奨したセットアップが、まさにこのビルディングブロックです: 1つのタスク、1つのwhileループ、コードから直接APIを呼ぶ2。
- 分割したあとに増える位置こそが鍵です: プロセスがまだ軌道上にあることを確かめるために、任意の中間ステップにプログラム的なチェックを足せます1。これはコース10の決定的な検証器を、別の設置場所へ移したものです——終端状態から、ステージのあいだへ。プロダクトの形では、サブエージェントが順番に実行され、それぞれが完了したら上のレイヤーが関連するコンテキストを次へ渡す、という見え方になります3。
- ルーティングは入力を分類して専門化された後続タスクへ振り分け、関心の分離とより専門化されたプロンプトを得ます。それが無ければ、ある種類の入力に向けた最適化が、他の入力での性能を損ないかねません1。前提条件: 明確なカテゴリがあり、分類そのものをLLMまたは伝統的な分類アルゴリズムで正確に扱えること1。
- 分類呼び出しの出力は1つのラベルへ絞り、表に当てはまらない答えを受け止めるフォールバックの分岐を1つ残すこと。
- このパターンは現行のファーストパーティの語彙の中で生きています: 専用のプロンプトとツールを持つエージェントへドメインで振り分けることを専門化と呼び、複雑なサブタスクの一部についてより能力の高いエージェントやモデルに相談することをエスカレーションと呼びます4——後者は難易度で振り分けるルーティングです。
- 無理に分けないこと: カテゴリが1つしかないならルーティングは純粋なオーバーヘッドであり、タスクをきれいに分けられないのに無理に分ければステージのあいだで情報を落とします1。迷ったらまず測ること——複雑さは「結果を明白に改善する」という敷居を越えなければなりません1。
>> レッスン3: 並列化: セクショニングと投票