この記事は、連載「言わない技術」の第 6 回です。うまくいかない症状と、その対処を一つずつ並べています。各回はファイルやスクリプトを一つ置けば完結します。その仕組みがなぜ要るのかは、置いたあとに解説を読めば分かります。連載の全体像と各回の一覧は序論にあります。

今回は、サブエージェントの話です。サブエージェントというのは、メインのセッション(AIとの、始まりから終わりまで一続きのやり取り)を引き継がずに、別に立ち上がって働くAIのことです。この連載で私が細かく指示を書く相手はサブエージェントだけで、細かく書く中身も、出力の形に絞っています。

この連載で、私は、AIに言うことを減らしてきました。

ただし、この回だけは、AIに言うことを増やします。

サブエージェントの出力だけは、細かく指示してください。

AIの出力を読まずに済ませる 1 作目「読まない技術」では、第 5 回で「サブエージェントの出力だけは読め」という例外を作りました。AIの提案を検討しない前作「AIの意見を聞かない技術」では、第 4 回で「サブエージェントの入力だけは、確かめろ」という例外を作っています。3 作とも、例外は同じ相手です。

まず一つだけ、思い出してみてください。

直近でサブエージェントを走らせたときのことです。

そのサブエージェントに渡した指示の書き方は、サブエージェントが動いたモデルで試したものでしたか。

CC BY 4.0

仕事の進め方を書いた手順が、1 本だけ残っていました

第 2 回で私は、指示ファイルとスキルと用途カタログ(サブエージェントに任せてよい仕事と、起動前に用意しておくファイルの一覧)の 3 つに、仕事の進め方を書いた手順は 0 件だと書きました。手元に残っている手順も 0 本だと、そのときの私は思っていたのです。私が数え直すと、手順が 1 本ありました。

第 2 回で私が数えたのは、その 3 つの本文までです。用途カタログが「起動前に用意しておけ」と指しているファイルの中身は、数えた範囲の外でした。残っていた 1 本は、そうしたファイルの一つで、サブエージェントに渡す手引きです。

同じ課題を、2 つのモデルに 2 通りずつ解かせました

第 1 回で、私は、検証を指示したAIと、指示しなかったAIを比べました。その比較では、2 体ともopusを使っています。ところが、サブエージェントが走るモデルは、opusとは限りません。

私の用途カタログでは、画面の文言を訳す用途に model: [sonnet, haiku] と書き、コメントに「opus を既定にしない」と添えています。連載記事を訳す用途は model: [sonnet, opus] で、どちらのモデルでも走ります。第 1 回の結果が、opus以外のモデルでも同じになるかは、確かめていませんでした。

そこで私は、第 1 回と同じ課題を、sonnetにも、検証を指示する場合と指示しない場合の 2 通りで解かせました。課題は、ある連載記事 10 本から、決まった形の文を 1 つずつ抜き出して表にすることです。

モデル検証の指示ツール呼び出し
Aopusあり8
Bopusなし4
Csonnetあり13
Dsonnetなし3

4 体の答えは、どれも、私が先にスクリプトで作った正解表と完全に一致しました。課題には罠を一つ仕込んであり、10 本目の記事にだけ、抜き出す形の文がありません。4 体とも、この罠を避けています。

私の仮説は、「下位モデルには細かい指示が要る」でした。この仮説は外れました。sonnetは、検証の指示が無くても 10/10 を出し、手数(ツール呼び出しの回数)もopusより少なく済んでいます(3 回 対 4 回)。

検証を指示しても、4 体の答えは変わりませんでした。変わったのは手数です。第 1 回のopusで起きた、検証を指示すると手数が増えることは、sonnetでも起きました。増え方は、モデルによって異なりました。検証を指示しなかったときと比べて、opusの手数は 2 倍(4 → 8)、sonnetの手数は 4.3 倍(3 → 13)になっています。

もう一つ、検証を指示しなかった 2 体は、報告の中身が分かれました。opusは、報告の冒頭に「目印の語をディレクトリ全体で grep した。9 ファイルにちょうど 1 回ずつ、10 本目には 0 回」と、検証した数字を書いています(第 1 回の B です。原文は英語)。sonnetが検証について報告に書いたのは、「他の9本すべてで該当フレーズと太字の一文を確認しました」という一文だけでした。

報告の違いは、確かめた範囲の違いではありませんでした。2 体の記録を開くと、sonnetも、opusと同じく 2 つの目印の語を 10 本すべてで grep し、10 本目は全文を開いて「一文」の語を探しています。違ったのは、確かめたことを報告に書いたかどうかでした。

この違いは、記録を開いて初めて分かりました。「読まない技術」の第 5 回で書いたとおり、メインエージェントの手元に返ってくるのは、サブエージェントの最後のメッセージ、つまり報告です。答えが正解表と一致したことから分かるのは、答えが合っていたことまでで、AIが答えを検証したかどうかは、一致からは分かりません。「読まない技術」の第 9 回で書いたとおり、報告はAIの自己申告です。sonnetの「確認しました」も、opusが書いた数字も、それぞれのモデルが報告に書いた申告です。

Anthropic のプロンプト指針にも、モデルについての但し書きがあります。特定のモデル名が付いた技術は、そのモデルで測ったものとして扱い、他のモデルへ当てる前に自分で確かめるように、という内容です。

あなたが、同じ形の仕事をサブエージェントに任せていたとします。opusで走ったときの報告には、確かめた件数が並んでいました。別のモデルで走ったときの報告は、「すべて確認しました」の一文だけでした。出力を見ると、どちらも合っていました。次に同じ仕事を任せるとき、あなたなら次のどちらにしますか。

  • 別のモデルで走るときのために、仕事の進め方を手順として細かく書き足す
  • 出力の形——何をもって完了とするか——を書き、出力がその形になっているかを、報告を読まずにプログラムで判定する

答えは、出力の形を書いてプログラムで判定するほうです。報告が一文だけでも、そのモデルが確かめなかったとは限りません。私の手元の記録では、一文だけを報告したsonnetは、opusと同じ範囲を grep で確かめていました。違ったのは、確かめたことを報告に書くかどうかで、報告は、どのモデルが書いても、そのモデルの自己申告です。手順を細かく書き足しても、返ってくるのは同じく報告で、確かめたかどうかは報告の中からは見えません。しかもsonnetは、手順も検証の指示も無いまま、opusより少ない手数で正解していました。出力の形を書き、その形かをプログラムで判定すれば、どのモデルで走っても、通るかどうかは、出力から決まります。

理解したことは一文にできます。

言わずに済ませる技術のうち、モデルが自分でやることに任せるものは、私が測ったモデルの上で確かめたものだ。サブエージェントは別のモデルで走ることがあり、私の測定では、確かめたことを報告に書くかどうかもモデルで違った。だから、出力の形——何をもって完了とするか——を書き、出力がその形になっているかは、報告を読まずにプログラムで判定する。

仕組み: 出力の形を手引き 1 枚に書き、判定はプログラムに任せる

私が置くのは、手引き 1 枚です。サブエージェントを起動して仕事を割り振り、出力を受け取るメイン側のAIを、この先「メインエージェント」と呼びます。一般の用語ではオーケストレーターです。

# 連載記事の翻訳の手引き(手元のコードを短くしたもの)

| | |
|---|---|
| 入力 | 原本 1 本と、その言語の「連載の入口」の訳(用語の正) |
| 出力 | `<series>/<lang>/<file>.md` **1 本だけ** |

⛔ 原本と記事の登録一覧は触らない。原本に手が要ると分かったら、止めずに、その旨を伝えて判断を仰ぐ
⛔ 受け入れテストの中身を先に読みに行かない。落ちたら、落ちたファイルと次の一手を名指しするので、それを読めば足りる

## 原本と 1 対 1
  段落・囲み・リンク・数値・装飾は、原本と同じ数だけ置く。訳者が足すものでも削るものでもない

## 囲み(```)は 3 種類ある —— 訳してよいかが違う
  code      = 読者が手元で走らせるもの。一字一句そのまま(行コメントだけ訳す)
  markdown  = 読者が自分の指示ファイルに書く文章。訳す
  mermaid   = 図。ラベルは訳す(矢印と構造はそのまま)

前作の第 4 回で私が書いたのは、用途カタログの契約です。契約は、サブエージェントの入力の側を決めるもので、どの用途で、何を用意できたら起動してよいかを書きます。手引きは、出力の側を決めるもので、サブエージェントの出力の形を書きます。

手引きに書いた形で出力を判定するのは、手引きとは別のファイルにある受け入れテスト(サブエージェントの出力をメインエージェントの側で判定するスクリプト)です。受け入れテストは、原本と訳で、リンク・見出し・箇条書き・段落・コード・数値・装飾がそろっているかを突き合わせます。

受け入れテストが細かく何をどう数えるか(1 桁の数は語で綴ってよい、など)は、手引きに載せていません。受け入れテストは、落ちたときに、ずれたものの名前と数と、次にすることを出します。手引きに書いてあるのは、出力の形と、訳す前に知っておかないと書けないもの(用語の正、囲みの訳し分け、触らないもの)です。

そして、依頼は短くなりました

手引きを書いたあとも、私は、1 回ごとの依頼に細かく書いていました。この連載の 12 本を英訳させたときの依頼は、1 本 63〜74 行です。私はそこに、読む順、訳を決めてある語、原本の段落や太字の数、報告の形を書いています。そのあと私は、受け入れテストが数えるものを、手引きから外しました。外したあと、原本の直しに訳を追随させる依頼は、3〜10 行になっています。

注意: 4 体とも、正解しました

一つ目は、この結果からは「下位モデルは間違える」と書くことができない点です。4 体とも 10/10 でした。私が測ったのは、1 つの課題を、各条件で 1 回ずつ解かせた結果だけで、統計ではありません。この測定で観測することができたのは、「易しい課題では差が出なかった」ということまででした。

二つ目は、私が手引きを書いているのが、同じ形の出力が何本も出る用途だけだということです。読み取りだけの調査の用途は、手引きなしで起動しています。

三つ目は、手引きに書いた条件が守られるのも、ほかの指示と同じく、サブエージェントが手引きを読んだときだけだということです。条件を欠いた出力が、手引きが読まれなくても落ちるのは、受け入れテストがあるからです。

検証手順: 手引きに書いた要素を 1 つ欠いた出力で、受け入れテストが落ちることを確かめる

前提

サブエージェントに渡す手引きを 1 枚書き、その中に出力の形(原本と同じ数だけ置くもの、など)を書いておきます。受け入れテストも、書き終えておきます。この検証手順で動かすのは、受け取る側の受け入れテストだけで、サブエージェントは起動しません。

所要時間: 10 分

手順

  1. あなたが、正しい出力を 1 つ用意し、受け入れテストに通して合格することを先に見ておきます(過去にサブエージェントが出したもので構いません)。ここを飛ばすと、手順 3 で落ちたときに「欠いたから落ちた」のか「元から落ちていた」のかが分かりません
  2. あなたが、手順 1 の出力のコピーを作ります。手引きに書いた出力の形から 1 つ選び、コピーからその要素をわざと欠きます(見出しを 1 つ消す、項目を 1 行減らす)。元のファイルは残し、コピーのほうを壊してください
  3. あなたが、コピーを受け入れテストに通します

合格条件(すべて満たすこと)

受け入れテストが失敗になり、失敗のメッセージに、何が欠けたかが出れば合格です(「見出しが 1 つ足りない」に当たる形)。

合格しなかったとき

  • 通ってしまった —— 手引きに書いた出力の形のその要素を、受け入れテストが判定していません
  • 「不合格」としか出ない —— 直す側(サブエージェントか、あなた)が、欠けた箇所を自分で探すことになります

後始末

壊したコピーを消します。

受け入れテストが見ているのは、構造の同一性、つまり原本と訳の構造が同じかどうかです。訳文の意味が正しいかは、人が見ます。プログラムで決めることのできるところまでは、私はプログラムに決めさせています。


CC BY 4.0 はここまで

次回は「AI を問い詰めない技術」。私は、AI が間違えても問い詰めません。 それでも、間違いは直ります。

連載「言わない技術」