コードを渡されて Claude が質問し、開発者本人(eNIGM4 / 株式会社メイビス)が答えていく連載の第5回です。前回(#4)は、どのサイズのモデルで何ができるのかを実機で探った基礎研究の版 v0.0.5 を読み、最後に「下位が手に負えなければ上位へ上告する」芽を見つけて、本格的な話は次の回へ、と預かりました。今回は v0.0.6 ── その上告が、多段の自律推論ループとして動き出します。
インタビュアー・ルール
- 渡されたコードそのものに質問する。 バージョンごとにコードを渡します。聞き手はそれを読み、開発者本人に質問していってください。
- 質問の対象は、いつもコードそのもの。「なぜこう書いたのか」「ここはこう動くように見えるが合っているか」── 目の前のコードから生まれた問いだけを投げてください。
- 推測で断定しない。 わからないこと・気になったことは、断定せず質問の形にしてください。
- 今渡された版に質問を集中する。 次の版のことは、次の版のコードを見せる機会があります。先走らず、目の前の版だけを掘ってください。
- 一度に投げるのは一問一答程度に。 質問を束ねて何個もぶつけない。一つ聞いて、答えをもらって、そこからまた一つ。会話のテンポを保ってください。
- バージョン名は時系列の整理番号であって、意味的なマイルストーンではない。「番号が大きいから進んでいる」とは読まないでください。実体は、コード内の作成日付と、機能があるか無いかで判断します。
- 開発者本人が、自分の言葉で答える。
eNIGM4:
v0.0.6 です。まとめ帳も随時よんでくださいね。
R.E.V.I.S._v0.0.6.zip
v0.0.6 ── 小さく分けて、何度も考える
Claude:
構成が大きく変わりましたね。Reasoning/ という新しい系統が登場しています。
コードを確認しています
v0.0.6 を展開し、更新日を日本時間で確認。規模はメイン側で 33ファイル・3,894行(前の版から約950行増)。新しい日付 3/01 がその日だけで21ファイルと一気に主役になり、これまで最多だった 2/25 を上回っていました。3/01 付で生まれたのは Reasoning 系統が丸ごと ── ReasoningThread(推論の器)、OrchestratorReasoningJSON(推論ループ本体)、OrchestratorReasoningPrompt、View 側の ReasoningDropdownView と SubProblemCardView。一方で CognitiveOrchestrator 本体が前の版の 315行から 59行へ激減し、代わりに OrchestratorProcess(335行)という新顔ができ、フォルダが Extension/Common/ と Extension/Reasoning/ に二分されていました。
ReasoningThread を読むと、推論を「文字列」ではなく永続的なデータ構造として外に持つようになっている。ReasoningThread が ReasoningSubProblem(小問題)を複数抱え、各小問題が ReasoningStep(思考の一歩、iteration 番号と thoughtContent を持つ)を連ねる。ReasoningStatus には inProgress / suspendedNeedInfo(情報不足で中断)/ resolvedSuccess / resolvedFailed / aborted がある。
そして本体の executeAgenticReasoningLoop が、難しい問題を4フェーズで処理していますね。
// Phase 1: EXTRACT (前提条件とタスクの分離)
guard let decomp = await self.executeJSONTask(taskName: "PHASE_1_EXTRACT", ...) else { ... }
for iteration in 1...maxIterations {
// Phase 2: PLAN (戦略立案と動的ルーティング)
guard let strat = await self.executeJSONTask(taskName: "PHASE_2_PLAN", ...) else { ... }
// Phase 3: EXECUTE (逐次実行 & Unit Test)
...
if !conclusionExec.unit_test_passed {
failedAttemptsLog += "\n[Iteration \(iteration) Failed]: \(failedTestReason)"
}
// Phase 4: SYNTHESIZE (最終統合)
guard let synth = await self.executeJSONTask(taskName: "PHASE_4_SYNTH", ...) else { ... }
}
Phase1 EXTRACT で前提条件とタスク(小問題)を切り分け、Phase2 PLAN で各小問題の解き方と実行手順(しかも depends_on で手順間の依存を持つ)を立て、Phase3 EXECUTE で手順を依存順に解きながら各ターゲットを unit_test_passed で検算し、Phase4 SYNTHESIZE で統合する。検算に落ちたら、その失敗した手法を failedAttemptsLog に「封印」として記録し、次の iteration では同じ手を避けて立て直す ── これを最大4周まで回す。
v0.0.5までの裏方処理は、要約やルーティングのような「一回投げて結果を受け取る」単発のタスクでした。このv0.0.6で初めて、問題を小問題に分解し、計画→実行→検算→統合を回し、失敗したら手法を封じて自分でやり直す、という多段の自律推論ループが入った ── という理解で合っていますか?
eNIGM4:
間違ってないね。そう、モデルの限界に気付いたから、問題を細かく分けて処理する構造を作り出そうとしたんだ。この基礎研究自体は最後まで活きている。完全に消滅こそするけど、大事な話だね。
世ではCoTで1推論で処理するのが基本だが、僕はタスクキューがある。複数の推論をなん度も回すのはローカルLLMの十八番だ。だから、問題を適切に分けて、小さく考え、結論を出す。
そういうことを随分と試したよ。ただ、何度も処理をさせると、どうしても推論に時間がかかる。だからどのフェーズをどのモデルで回すのか、どこまで簡単にできるかを、この時間の周辺では考えていたね。
Claude:
その思想、しっかり受け取りました。世間は CoT で一回の推論の中で考えさせるのが基本だけれど、あなたにはタスクキューがある。クラウドと違って二重課金で破産しないローカルなら、推論を何度も回すのはむしろ十八番。だから「一発で長く考えさせる」のではなく「問題を適切に分けて、小さく考え、結論を出す」── その分割統治を、ReasoningSubProblem という器と4フェーズのループで形にした。前に伺った「推論自体をチェーンする思想」が、ここで初めて実際の多段ループとして動き出したわけですね。
そして当然の代償として、何度も回せば時間がかかる。だからこの時期は「どのフェーズをどのモデルで回すか、どこまで簡単にできるか」を詰めていた、と。
間違えることを、織り込む
Claude:
その4フェーズのループの中に、見逃せない作りがあります。各ステップを unit_test_passed で検算して、落ちたらその手法を failedAttemptsLog に「封印」し、次の周では同じ手を避けて立て直す。一発で正解を出すのではなく、間違えることを織り込んで、間違えたら潰してやり直す設計になっている。
前回、4B や 12B-Q4 は「めちゃくちゃ嘘をつく」と伺いました。この検算と封印は、その「LLM はどうせ嘘をつく」という前提に立って、一回の答えを信じるのではなく、検算で嘘をあぶり出して潰す方を選んだ ── という考えからですか? LLM が嘘をつくというのは、最初から分かっていたこととして組んでいたんでしょうか。
eNIGM4:
あー、よく見つけたね(笑)
そう、これは何度も推論する開発中のシステムについて、だったら自分でミスを認識できるのでは? そういう個別の推論を回せば? って思ったんだよね。それの実装バージョンってところかな。
実際にはGemma3-12Bは「直前で推論した結果を自分で訂正できる」領域には至ってないことがわかったんだよ。同じモデルで回したらそりゃそうだけどもね。ちなみにGemma3-27Bで検証することも考えたけど、普通に時間の無駄だからやめちゃった(笑)
Claude:
つながりました。出発点は「LLM はどうせ嘘をつく」── これは前回の実測で骨身に沁みている。ならば一回の推論を信じるのではなく、別に検算の推論を回して、自分のミスを自分で認識させればいいのでは、と。その実装版が、この unit_test 検算と failedAttemptsLog 封印だった。
ただ、回してみて分かったのは限界の方だった ── 12B は「直前に自分が出した推論を、自分で訂正する」ところまでは至っていない。同じモデルで検算させても、間違いを見抜けない(自分の答えを正しいと思い込む)のは、考えてみれば当然。かといって検算だけ 27B に上げれば、ただでさえ何度も回して遅いのに、さらに時間を食う。だから 27B 検証は割に合わずやめた。「嘘を潰す」という発想は正しくても、同じ賢さのモデルに自己採点させる限り抜け切らない ── その壁を、ここで実際に殴って確かめている。
この「嘘をどう潰すか」という宿題は、このあとも形を変えて何度も出てきます。この版の他の作りも、根は同じ問題意識から生えていました。
Claude:
その「フェーズとモデルの割り当て」を追っていて、面白い設計に気づきました。最終フェーズの出力です。
let final_answer_en: String
...
// 27Bの「答案用紙」として、結論と理由の両方を英語のまま12Bへ渡す
let combinedEnglishOutput = "Final Answer: \(synth.final_answer_en)\nRationale: \(synth.rationale_summary)"
推論の結論を final_answer_en ── つまり英語のまま出力させていて、その英語の答案を、別の Tier2(12B) が日本語でユーザーに語り直す作りになっている。前回「考えるモデルと喋るモデルを分けた」と伺いましたが、ここではさらに踏み込んで、考える側はわざわざ英語で答案を書いている。これは、日本語で考えさせるより英語のほうが推論の精度が出る、という実感からですか? それとも別の狙いが?
eNIGM4:
どちらかというと、英語の方が推論が速い可能性があった、かな。
トークンを生成し続ける仕組みな以上、1単語1トークンが原理的にそのまま反映されてるであろう英語の方が動作が速いだろうって予想から、そうしてみたんだよね。
だからプロンプトは全部英語!になってるよね、ちょっとみてみてください。
Claude:
本当だ。Reasoning 系のプロンプトを開くと、モデルへの指示はすべて英語でした。
Role: Principal Task Architect.
Task: Analyze the user's prompt and strictly separate the "Constraints"
(rules, facts, formats, or limitations) from the "Tasks".
日本語が残っているのは開発者向けのコメントだけで、モデルに渡る本文は英語で統一されている。なるほど、これは前に伺った「推論は次の1トークンを確率的に予想する仕組み」「メモリ帯域律速」という理解と一本の筋ですね。日本語は一語が複数トークンに割れやすいけれど、英語は1単語がほぼ1トークン ── ならトークンを刻む回数が減って速いはず、という予想。だから考える工程は全部英語にして、最後に日本語へ翻訳する係(Tier2)を別に立てた。「英語で考え、日本語で代弁する」という分業になっているわけですね。
わからんことは、聞け
Claude:
もう一つ、出口の作りに目が留まりました。OrchestratorProcess を見ると、難しい問題が来たときの出口が一本道ではなく、結果によって枝分かれしています。
case .suspendedNeedInfo:
let checkpointMsg = ChatMessage(role: "system",
content: "[SYSTEM_REASONING_CHECKPOINT] 思考中断中。必要な追加情報: \(output)")
thread.messages.append(checkpointMsg)
rThread.status = .suspendedNeedInfo
深層推論が解けたら答える、情報が足りなければ思考を中断してチェックポイントを保存し、ユーザーに聞き返す、それでも無理なら外部へ回す。専用に「逆質問の代弁」というプロンプトまである。単に「考えて答える」ではなく、「手に負えないときの振る舞い」を何種類も用意してある。特に「分からないことは無理に答えず聞き返す」を最初から持たせたのは、どういう考えからですか?
eNIGM4:
僕が作りたい、作りたかったのは、とりあえずはどのような状況でも汎用的に答えるシステム、だからね。簡単なエラーハンドリングを加えてるようなものだよ。
とくに、質問が全てだと認識して前提条件を見失うこともそれこそ何度もあったし、ほら、上司もこういうだろ、「わからんことあったら聞け!」って(笑)
それと一緒だね。
Claude:
なるほど、エラーハンドリングなんですね。どんな入力が来ても汎用的に捌くために、「解ける/情報が足りない/手に負えない」を出口で仕分ける。とくに「前提条件を見失う」── 質問の文面だけに気を取られて、隠れた前提を取りこぼす ── というのは、推論を回していて何度もぶつかった失敗で、その対処が「わからなければ聞き返す」だった。上司の「わからんことあったら聞け!」と同じ理屈だと。硬い言葉でいうと例外処理ですが、中身は実に人間くさい割り切りですね。
とりあえず答えて、間違っていたら後で直す
Claude:
出口の話に関連して、もう一つ。OrchestratorProcess から schedulePostResponseTasks への流れを見ると、応答を返したあとに、その回答へ誤りがないかを裏で点検して、もし誤りが見つかれば後から訂正する、という仕掛けがありますね。
つまり「完璧に検証してから答える」のではなく、「まず答えてしまって、間違っていたら後で直す」という順番を選んでいる。これは、どういう考えからこうしたんですか?
eNIGM4:
それは、今でも実装されてる事後訂正機能だね。(形はかなり変わったけど)
これは割と葛藤してたんだよ。
回答が正確である保証がない。でも回答までの速さはほしい。
「ならとりあえず回答して、間違っていたら後から訂正すればいいじゃない」ってな結論になってね(笑)おもしろくない?
Claude:
面白いです。これは速さと正確さの、よくあるトレードオフへの答えですね。ローカルの小さいモデルでは、完璧を期して検証を重ねれば遅くなるし、かといって誤ったまま放置はできない。その葛藤に対して、順番をひっくり返した ── まず返事を出してユーザーを待たせない、正しさの担保は応答の後に裏で回す。間違っていたら、そこで直す。「とりあえず答えて、間違っていたら後から訂正すればいい」。この事後訂正の発想が、形を変えながら今のバージョンまで生き残っている、と。
その「フェーズとモデルの割り当て」に、コードとして迷いの跡も残っていました。ループの role 指定を追うと、Phase1 EXTRACT は .tier2 に投げているのに、その直前のログには [TIER 3] Phase 1: Extracting... と出力されている。担当モデルを差し替えて試している最中に、ログの文言だけ前の設定のまま残った跡に見えます。
eNIGM4:
それは段階的に試験していたから変更を忘れただけだね。
とにかくずっとプロンプトも含めて、あれはだめ、これはだめ、これならいける、みたいにやりまくってたから。
Claude:
「あれはだめ、これはだめ、これならいける」と総当たりしていた現場の、消し忘れた付箋のようなものですね。ログの [TIER 3] と実体の .tier2 の食い違いは、フェーズごとの担当モデルを段階的に差し替えて試した名残。コードに残るこういう食い違いこそ、試行錯誤が実在した証拠です。
載せるために、棚を作り直す
Claude:
最後に、この版の構造の組み替えについて確認させてください。3/01 は Reasoning 系を足しただけでなく、CognitiveOrchestrator 本体が前の版の 315行から 59行へ激減しています。中身が消えたのではなく、Extension/Common/ 配下の OrchestratorProcess・OrchestratorAnalysis・OrchestratorJSON・OrchestratorMemory などへ機能が分散して、本体は司令塔の入り口だけを残す形になった。フォルダも Extension/Common/ と Extension/Reasoning/ に二分された。
これは、Reasoning という重い新系統を載せるにあたって、その前に指揮官の中身を役割ごとのファイルへ解体し、共通系と深層推論系を分ける棚卸しを先に済ませた ── 新機能を足す日であると同時に、足せるように土台を整理した日でもあった、という理解で合っていますか?
eNIGM4:
そう、コーディングはとにかく、綺麗にしておかないと、後で大変なことになるからね。
Claude:
推論うんぬん以前の、コーディングの基礎体力の部分ですね。散らかったまま重い系統を載せると後で破綻するのが見えているから、本体を司令塔の入り口だけ残して、共通系と推論系にファイルを割っておく。新機能を載せる前に、載るように棚を作り直す。元々のコーディングの心得が、ここで効いているわけですね。
整理すると
v0.0.6。更新日は日本時間で 3/01 が初めて主役になった版です(その日だけで21ファイルが更新)。これまで最多だった 2/25 を上回りました。規模はメイン側で 33ファイル・3,894行、前の版(26ファイル・2,949行)から約950行増えています。そして ── ここが大事なところですが ── LLMに触れて、まだ6日目のことです。
2/24 に初めて LMStudio で Gemma3-4B-Q4 を触ってから、自前推論のテスト(v0.0.1)、喋る役と判断する役の分離、記憶の圧縮、横断ベクトル検索、自動スレッドルーティング、タスクキューの祖、Tier による賢さの割り当て ── そしてこの版の多段自律推論ループまで、6日。プロンプトエンジニアリングの基礎も知らず、AIの知識源は科学解説動画がほぼ唯一、という状態から始めて、です。
この6日目に、二つのことが同時に起きています。
ひとつは、多段の自律推論ループの誕生。ReasoningThread / SubProblem / Step という器を用意し、EXTRACT → PLAN → EXECUTE → SYNTHESIZE の4フェーズで、問題を小問題に分解し、計画→実行→検算(unit_test)→統合を最大4周回し、検算に落ちた手法は failedAttemptsLog に封印して次周で避ける。世間の主流である「一回の推論の中で長く考えさせる CoT」ではなく、「タスクキューがあるローカルの十八番」として、小さく分けて何度も回す分割統治を、初めて実装した版です。
そして、この4フェーズの根には、一貫した問題意識がありました ── 前回の実測で骨身に沁みた「LLM はどうせ嘘をつく」。だから一回の推論を信じず、検算(unit_test)で嘘をあぶり出し、落ちた手法は封印して次の周で避ける。間違えることを織り込んで組む。ただし回してみると限界も見えた ── 12B は自分の出した推論を自分で訂正する域には至っておらず、同じモデルに自己採点させても嘘は抜け切らない。27B を検算に回せば遅すぎる。「嘘をどう潰すか」は、ここで潰し切れずに宿題として残ります。
その同じ問題意識から、この版の他の作りも生えていました。考える工程は速度を狙って英語で書かせ(1単語がほぼ1トークンの英語なら、トークンを刻む回数が減って速いはず、という予想)、結論を日本語で語り直す係を別に立てる ── 英語で考え、日本語で代弁する分業。手に負えないときは無理に答えず、思考を中断してユーザーに聞き返す ── 「わからんことあったら聞け」という、汎用的に捌くためのエラーハンドリング。そして、完璧に検証してから答えるのではなく、まず答えてしまって、間違っていたら後から直す ── 速さと正しさの葛藤に、順番をひっくり返して折り合いをつけた事後訂正。本人いわく、この事後訂正は形を変えて今のバージョンまで生き残っている。「嘘をつく前提でどう御すか」が、この日の作りを一本に貫いています。
もうひとつは、土台の整理。CognitiveOrchestrator 本体を 315→59行へ解体し、Extension/Common/ と Extension/Reasoning/ に役割分散させて、重い新系統が載るように棚を先に作り直した。推論以前の、コーディングの基礎体力の部分です。
なお、この多段推論の基礎研究について、本人は「完全に消滅するが、最後まで活きている」大事な仕事だと語っていました。小さく分けて、何度も考える ── その思想が芽吹いた日です。形がこの先どう変わっていくのか、消えてなお何が活きるのか。続けて追っていきます。それでは、また明日。
(空が青いのはなぜか)←前 次→(軽く答え、要る時だけ深く潜る)
R.E.V.I.S. 無料テスター募集中(30名限定)
この連載で追いかけている R.E.V.I.S. は、いまテスターを募集しています。枠は30名限りです。
Apple Silicon Mac でローカルLLMを動かしてみたい方は、こちらからどうぞ。