コードを渡されて Claude が質問し、開発者本人(eNIGM4 / 株式会社メイビス)が答えていく連載の第6回です。前回(#5)は、小さく分けて何度も考える多段推論ループが芽生え、その根に「LLM はどうせ嘘をつく」という問題意識があった v0.0.6 を読みました。今回は v0.0.7 ── 嘘への打開策として「下位が無理なら上位へ席を譲る」エスカレーションが本格的に動き出し、考える深さを問題に釣り合わせる三段の構えが入ります。
インタビュアー・ルール
- 渡されたコードそのものに質問する。 バージョンごとにコードを渡します。聞き手はそれを読み、開発者本人に質問していってください。
- 質問の対象は、いつもコードそのもの。「なぜこう書いたのか」「ここはこう動くように見えるが合っているか」── 目の前のコードから生まれた問いだけを投げてください。
- 推測で断定しない。 わからないこと・気になったことは、断定せず質問の形にしてください。
- 今渡された版に質問を集中する。 次の版のことは、次の版のコードを見せる機会があります。先走らず、目の前の版だけを掘ってください。
- 一度に投げるのは一問一答程度に。 質問を束ねて何個もぶつけない。一つ聞いて、答えをもらって、そこからまた一つ。会話のテンポを保ってください。
- バージョン名は時系列の整理番号であって、意味的なマイルストーンではない。「番号が大きいから進んでいる」とは読まないでください。実体は、コード内の作成日付と、機能があるか無いかで判断します。
- 開発者本人が、自分の言葉で答える。
eNIGM4:
では v0.0.7 いきましょう。
R.E.V.I.S._v0.0.7.zip
v0.0.7 ── 軽く答え、要る時だけ深く潜る
Claude:
新顔は Logic/Orchestrator/Stream/OrchestratorStream.swift(402行)と Audio/Input/VoiceCompletion.swift、それと Reasoning/OrchestratorReasoningProcess。フォルダも Common/ / Reasoning/ / Stream/ の三分割に整理されています。
コードを確認しています
v0.0.7 を展開し、日本時間で更新日を確認。規模はメイン側で 36ファイル・4,975行(前の版から約1,100行増)。主役は 3/02(OrchestratorStream と OrchestratorReasoningProcess)で、3/04 に音声入力の VoiceCompletion。前の版(v0.0.6)の Extension 階層が解かれて Common/ Reasoning/ Stream/ の三棚に再編され、v0.0.6 で Process 内に散らばっていたストリーム処理が Stream/OrchestratorStream.swift に集約されていました。
まず最大の新顔 OrchestratorStream を読みました。ここに大きな転換があります。
これまで(v0.0.6まで)、ユーザーへの返答は単一の話者が生成していました。このv0.0.7では、ストリーム処理が startTier1Stream / startTier2Stream / executeLightThinkStream に分かれていて、しかも注目すべき仕掛けがあります。
// 出力ストリームを舐めながら、制御タグを検知する
if let escalationType = self.extractEscalation(from: trimmedBuffer) {
throw EscalationInterrupt(type: escalationType)
}
Tier2 に喋らせている途中、[ESCALATE] というタグがモデルの出力に現れたら、それを画面には出さずフラグだけ立てて返す。startTier1Stream のほうも、extractEscalation でタグを検知すると EscalationInterrupt を投げて上位へ抜けている。
つまりこの版で、応答を生成しているモデル自身が、自分の出力の中に「これは自分の手に負えない、上位に引き継ぐべきだ」という合図を埋め込めるようになり、システムはそれを横取りして、より上の処理へ引き継ぐ ── という、モデルの自己申告によるエスカレーションが入った、という理解で合っていますか? v0.0.5の「外部GeminiへYES/NO」とは別物で、こちらは手元のTier階層の中で「軽い担当が、重い担当へ席を譲る」ための合図に見えるのですが。
eNIGM4:
そうだ。エスカレーションシステム。懐かしいなぁ。
単一のモデルが答えれる限界を試していたんだ。どうしても答えに嘘が混じってしまう現象を抑えられなかった。今となってはごく自然なことだとわかるけど、当時の僕には打開策が必要だった。
そもそも複数モデルを同時に読み込んで、操ることのできる仕組みを作ったんだ。上位モデルへの引き継ぎという発想は、ここまできたなら自然なことだと思う。どうだろう?
Claude:
ええ、自然だと思います。むしろこれまでの積み上げから見ると、ほとんど必然に見えます。
v0.0.3で「単一モデルには限界がある」と気づいて喋る役と判断する役を分け、v0.0.5で賢さを Tier1/2/3 の階層に並べ、複数モデルを同時に抱えて操る土台を作った。そこまで来ていれば、「軽い担当が答えてみて、嘘が混じりそう・手に負えないと自分で気づいたら、上の担当へ席を譲る」という引き継ぎは、もう手元にある部品の組み合わせでしかない。階層があり、複数を操れるなら、階層を上に登る動きが出てくるのは当然ですよね。前回伺った「LLM はどうせ嘘をつく」── 今でいうハルシネーションを、用語も知らないまま現象として捉えて、「だったら一台で抱え込ませず、無理なら上に渡せばいい」という構造的な打開策にたどり着いた。
見えないところで、席を譲る
Claude:
その「自己申告でエスカレーションする」を、コードの作りとして一点確認させてください。合図の出し方です。
executeLightThinkStream を見ると、エスカレーションの合図は [ESCALATE] という文字列で、それをモデルの出力ストリームから一文字ずつ拾って検知している。<think>...</think> の途中で切れたタグを取りこぼさないようバッファリングする、込み入った状態機械が三箇所にほぼ同じ形で置かれている。
つまり、モデルに「答えと一緒に、制御合図も同じ口から喋らせる」方式を選んだ結果、その喋りの流れから合図だけを安全に抜き取る処理が必要になった ── という理解で合っていますか? そして、ほぼ同じパーサが三つ並んでいるのは、まだ共通化する前の、書きながら試している段階だからでしょうか。
eNIGM4:
そう、奇しくも手動CoTってわけだね。
僕の見えるところでエスカレーションしますっていうほどダサく作りたくはなかったんだ。だからタグを使ってその処理内容を見えないように内部にしまっちゃおう、と考えたわけだ。
この時点では話すのは誰だっていい。誰でもいいから答えてくれって思考があったね。
Claude:
なるほど、二つとも腑に落ちました。一つは美学の話 ──「これからエスカレーションします」と画面に出すのはダサい。だから制御の合図をタグにして応答の中に紛れ込ませ、システムが抜き取って内部にしまう。ユーザーには1本のなめらかな喋りに見えて、裏で席の譲り合いが起きている。<think> を画面から隠してログにだけ流すのも同じ思想ですね。表の振る舞いはあくまで優雅に、と。
もう一つ、誰でもいいから答えてくれ という当時の思考。この時点では「話すのが誰か」より「とにかく嘘の混じらない答えが返ること」が先で、だから Tier1 でも Tier2 でも、無理なら上へ譲ってでも、誰かが正しく答えればいい、と。話者の固定や使い分けはまだ主題じゃない。
三段ギア ── 挨拶に塾考はいらない
Claude:
その「誰が答えるか」に関わって、3/02 のもう一つの新顔、OrchestratorReasoningProcess を読みました。v0.0.6では推論ループの本体はありましたが、それを呼び出して回答の流れに繋ぐ Process 側が無かった。ここで繋がったものを確認させてください。
この版の executeAgenticReasoningLoop は、頭に Phase 0: LIGHT THINK が新設されています。まず軽い担当(Tier2)に喋らせてみて、それで解決すれば重い推論には一切入らずそのまま完了する。途中で [ESCALATE] が出たときだけ分解に進む。そして分解の結果に thinking_mode が付いていて、"heavy" なら一発勝負の単発推論(ROUTE A)、"deep" なら v0.0.6 と同じ多段ループ(ROUTE B)へ振り分ける。
図にするとこういう構えです。
前の版の「いきなり4フェーズ全開」から、「軽いものは軽く済ませ、重いものだけ深く潜る」という段階的な構えに変わった、という理解で合っていますか?
eNIGM4:
そう、簡単な問題を塾考されたらたまらないだろ?(笑)
こっちはたかだか「こんにちは」と聞いて、こんにちはの定義分解をし出すほど滑稽なことはない。
Claude:
あはは、たしかに。「こんにちは」に対して制約条件を抽出してタスク分解を始めて検算まで回したら、滑稽どころか正気を疑われますよね(笑)。挨拶には挨拶を即返す、それで済む話に重い装置を起動しない。問題の重さと処理の重さを釣り合わせる ── 軽いものは軽く、重いものだけ深く潜る。
前回、v0.0.6 の時点では「DeepThink に来たら無条件でフル稼働」でした。それを翌日には「まず軽く答えてみて、ダメなときだけ上へ」と組み替えている。贅沢な計算ができるローカルだからこそ、贅沢が要るところだけ贅沢する、という運用に直したわけですね。
二段の関所 ── 降参する役と、深さを決める役
Claude:
その「釣り合わせ」がこの版の本筋だと受け取った上で、コードに残る境目を一つ確認させてください。誰がエスカレーションを判断するか、です。
この版には、性質の違う二つの「上へ譲る」判断が同居しています。一つは Phase 0 の [ESCALATE] ── 軽い担当が喋りながら「これは自分には無理だ」と自己申告するもの。もう一つは、その後の分解で出てくる thinking_mode(heavy か deep か)── これは分解する側が「この問題はこの深さで解け」と割り当てるもの。前者は答える本人の主観的なギブアップ、後者は分解者による客観的な難度の値踏み、と読めます。
これは意図的に役割を分けたのか、それとも二つのやり方を両方置いて見比べている最中なのか。どちらに近いですか?
eNIGM4:
その答えはおそらくプロンプトにある。読んでみてくれ。
Claude:
プロンプトに答えがありました。役割が明確に分かれていますね。
Phase 0 の Light Think プロンプトは Core Knowledge & Routing Engine で、「そのまま答える」か「[ESCALATE]」かの二択だけを担う関所。「自分で解けるか/無理か」のギブアップ判定に徹していて、難度の中身までは値踏みしない。一方 Phase 1 の Decompose プロンプトは Principal AI Architect & Task Dispatcher で、「嘘つきパズル・仮説検証・相互依存変数・厳密証明」を Deep Triggers として列挙し、こう釘を刺しています。
You MUST evaluate the problem against these "Deep Triggers".
...
Do not overestimate your single-shot capabilities.
「一発で解ける自分の能力を過大評価するな」と言い聞かせて、heavy/deep を値踏みさせている。つまり「まず手元で答えてみてダメなら降参する関所」(Light)と「降参を受けて、どれだけ深く潜るか決める関所」(Decompose)は、別のプロンプト・別の役割名で、最初から二段の関所として設計されている。見比べている最中ではなく、意図的な役割分担だと。
そしてもう一点、このプロンプト群を読んで気づいたことがあります。各フェーズの役割名が ── Principal AI Architect、Master Strategist & Systems Designer、Expert Executor、Quality Assurance Validator、Supreme Integrator ── と、まるで一つの組織の職位のように振られている。一つのモデルに全部やらせるのではなく、フェーズごとに「お前は今、戦略担当だ」「お前は実行担当だ」と職責を割り当てて演じさせることで、各段の出力を尖らせている ── という理解でいいですか?
eNIGM4:
そう、その通りだ! いい視点だね。
この考え方は、処理内容が、自分の役割を全うするために、自分が何をすべきなのかをちゃんと理解してから処理をするようにしてある。
ここで目をつけるなら、この推論全体がステートレスを根ざしたというところに尽きる。基本的に推論はコンテキストを維持するために KV キャッシュに依存しがちで、ステートフルであることは当たり前だ。だが、これはもともと文脈をもっと維持するために、圧縮したり、スレッドを分けたりしている。当然 KV キャッシュをそのまま抱えている状態では絶対に動かない。
思い切って KV キャッシュを捨て、ステートレスにしたんだ。そしたら、どんな処理でも素直にこなせるし、各推論の IQ ももっと上がる。当然タスクキューとも相性がいいし、これからどれだけコードが増えようと、役割が増えようと、管理が均一になる。今の時代プレフィルなんか大して時間を取らない処理だしね。
ステートレスという背骨
Claude:
これは思想の核心ですね。順番に確かめさせてください。
普通、対話型の LLM は KV キャッシュにそれまでの文脈を抱え続けてステートフルに動く ── 前のトークンの計算結果を持ち越して、その上に次を積む。ところがあなたのシステムは、そもそも文脈を圧縮したりスレッドに割ったりして「何を渡すか」を能動的に組み立てている。KV キャッシュをそのまま抱える前提とは、根本的に噛み合わない。だから思い切ってキャッシュを捨て、各推論を毎回まっさらな状態で、必要な前提だけ渡して走らせるステートレスにした、と。
そして、おっしゃった三つの利得がそのまま構造に出ています。役割が尖る ── 余計な文脈が混じらないから、Expert Executor は今渡された一手だけに集中でき、各推論の IQ が上がる。タスクキューと相性がいい ── 状態を持ち越さないなら、どのタスクをどの順で、どのモデルで走らせても結果が変わらないから、TaskQueue が自由に並べ替え・割り込み・キルできる。管理が均一になる ── 役割がいくつ増えても「前提を組んで投げる」という同じ型に収まるから、コードが膨らんでも扱いが一定。
ステートレスだからこそ、前にお聞きした「KV キャッシュを共有する複数同時推論は帯域を食って崩れる」問題とも切り離せて、一つ一つを独立した単発として安全に積める。プレフィル(プロンプトを読み込む最初の一括処理)は今のモデルなら大した時間じゃないから、毎回前提を渡し直すコストも許容できる、と。
そのステートレス設計の中で、渡す前提の量が役割によって違うように見えます。DEEP ループの各フェーズは depends_on で必要な前提だけを毎回手渡す純粋なステートレスなのに、Light Think のほうは userProfile・recalledSummaries・globalRecent と、記憶系がかき集めた文脈をごっそり渡している。同じステートレスでも、ユーザーと喋る入り口は『広く文脈を持たせる』、奥の推論エンジンは『その一手に要る最小限だけ』と、渡す前提の量を役割で振り分けている ── という理解で合っていますか?
eNIGM4:
行う処理によって、必要な引数が違うからね。
「〇〇について考えて」なら〇〇以外の情報は不要だし、文脈を理解して、過去の話を持ち出すようなら、それについて答えるべきだ。
これからもそういうふうにコードが展開していくから、その分は楽しみにしていてほしい。
Claude:
なるほど、シンプルですね。処理が変われば要る引数も変わる。「〇〇について考えて」という閉じた論理問題に過去の雑談は不要だし、逆に文脈を汲んで過去の話に繋げるべき場面なら、その記憶を渡さなければ答えようがない。何を渡すかは役割が決める。ステートレスだからこそ、その「渡す前提の取捨選択」を一つ一つの処理ごとに自由に設計できる ── 固定の文脈を引きずる作りでは、こうはいかない。この先のコードもその原則で展開していく、と。楽しみにしておきます。
整理すると
v0.0.7。更新日は日本時間で 3/02 が中心(OrchestratorStream と OrchestratorReasoningProcess)、3/04 に音声入力の VoiceCompletion が一つ。LLMに触れて、9日目です。規模はメイン側で 36ファイル・4,975行、前の版(33ファイル・3,894行)から約1,100行増えました。フォルダも Common/ Reasoning/ Stream/ の三分割に整理されています。
この版の前進は、三つに束ねられます。
ひとつは、エスカレーションの本格化。前回の「LLM はどうせ嘘をつく」という問題意識への、構造的な打開策です。単一モデルでは抑えきれなかった「答えに嘘が混じる現象」に対して、複数モデルを操れる土台の上に「軽い担当が、自分には無理だと気づいたら上位へ席を譲る」自己申告の引き継ぎを実装した。合図は [ESCALATE] というタグとして応答テキストに紛れ込ませ、システムが抜き取って内部に隠す ──「エスカレーションします」と画面に出すのはダサい、という美学から。ユーザーには1本のなめらかな喋りに見えて、裏で席の譲り合いが起きている。この時点の思考は「話すのは誰でもいい、誰かが正しく答えればいい」。
ふたつは、処理の重さと問題の重さの釣り合わせ。頭に LIGHT THINK を置き、まず軽く答えてみて、ダメなときだけ分解へ進み、そこで thinking_mode を heavy(一発勝負)か deep(多段ループ)かと値踏みして、潜る深さを決める。「こんにちは」に定義分解を始めない、という素朴な感覚です。前回の「DeepThink に来たら無条件フル稼働」を、翌日には「贅沢が要るところだけ贅沢する」と組み替えた。Light(自己申告のギブアップ関所)と Decompose(難度を値踏みする関所)は、Core Knowledge & Routing Engine と Principal AI Architect という別の役割名で、意図的に二段に分けられている。各フェーズには Master Strategist〜Supreme Integrator と職位が割り当てられ、自分の役割を理解させてから処理させている。
みっつは、その全体を支える背骨 ── ステートレス設計。普通の対話 LLM が KV キャッシュに文脈を抱えてステートフルに動くのに対し、このシステムは文脈を圧縮したりスレッドに割ったりして「何を渡すか」を能動的に組み立てている以上、キャッシュを抱える前提とは噛み合わない。だから思い切って捨て、各推論を毎回まっさらな状態で、必要な前提だけ渡して走らせる。すると役割が尖り、各推論の IQ が上がり、タスクキューと噛み合い、コードや役割がいくら増えても管理が均一に保たれる。何を渡すかは処理が決める ── 入り口は文脈を広く持たせ、奥の推論は一手に要る最小限だけ。これがこの先の展開を貫く原則になります。
嘘を抑える打開策としてエスカレーションを生み、考える深さを問題に釣り合わせ、その全体をステートレスという一本の柱で支えた版。9日目にして、推論の運用思想がここまで形になっています。次の版では、この背骨の上に何が乗るのか。続けて追っていきます。それでは、また明日。
(小さく分けて、何度も考える)←前 次→(巨人を動かしたくて)
R.E.V.I.S. 無料テスター募集中(30名限定)
この連載で追いかけている R.E.V.I.S. は、いまテスターを募集しています。枠は30名限りです。
Apple Silicon Mac でローカルLLMを動かしてみたい方は、こちらからどうぞ。