0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AIが、開発者に聞く ── R.E.V.I.S. #14 「賢く、やらない」

0
Last updated at Posted at 2026-06-08

コードを渡されて Claude が質問し、開発者本人(eNIGM4 / 株式会社メイビス)が答えていく連載の第14回です。前回(#13)は、v0.3.0 で「責任範囲を分ける」大掃除が走った版でした。プロンプト生成・DB アクセス・実行の振り分けを三方向に引き剥がし、呼ぶ側は「何をするか」だけ宣言し「どうやるか」は部品の内側へ ── 土台が整った版です。

今回 v0.3.1 は、その整った土台の上に、いよいよ新しい思想が乗ってきた版。同じ 3/16 のうちに、推論の入口がまるごと作り替わっています。

インタビュアー・ルール

  1. 渡されたコードそのものに質問する。 バージョンごとにコードを渡します。聞き手はそれを読み、開発者本人に質問していってください。
  2. 質問の対象は、いつもコードそのもの。「なぜこう書いたのか」「ここはこう動くように見えるが合っているか」── 目の前のコードから生まれた問いだけを投げてください。
  3. 推測で断定しない。 わからないこと・気になったことは、断定せず質問の形にしてください。
  4. 今渡された版に質問を集中する。 次の版のことは、次の版のコードを見せる機会があります。先走らず、目の前の版だけを掘ってください。
  5. 一度に投げるのは一問一答程度に。 質問を束ねて何個もぶつけない。一つ聞いて、答えをもらって、そこからまた一つ。会話のテンポを保ってください。
  6. バージョン名は時系列の整理番号であって、意味的なマイルストーンではない。「番号が大きいから進んでいる」とは読まないでください。実体は、コード内の作成日付と、機能があるか無いかで判断します。
  7. 開発者本人が、自分の言葉で答える。

eNIGM4:
では続き、v0.3.1 です。

R.E.V.I.S._v0.3.1.zip


v0.3.1 ── 賢く、やらない

Claude:
今度は、ファイル数は同じなのに、中身の体重が変わっていました。

コードを確認しています

v0.3.1 を展開し、zip 内のタイムスタンプ(展開で書き換わらない本来の更新時刻)を確認。主要ファイルの最終更新は 3/16。前の版 v0.3.0 と同じ日のうちに、ここまで進めている。LLM に触れて 21日目。

規模はメイン側 54ファイル・8,697行(前の版から +526行)、ヘルパー側 10ファイル・1,213行(+21行)。新規ファイルの誕生はゼロ、消滅もゼロ。既存ファイルの中身が膨らんだ版。

膨らみの大きい順は PromptBuilder +159(938→1097)、SummaryManager +116(217→333)、OrchestratorReasoningProcess +89(243→332)、OrchestratorDispatcher +41、OrchestratorDTOs +27。前の版で土台を整え終えた直後、その上に何か大きなものが乗った匂いです。

OrchestratorReasoningProcess+89 から見ました。前の版では、難しい問題が来たとき、設定値(tier3ReasoningMode)で「agenticnativeTranslated か」を選んで Tier3 に投げるだけ、シンプルな入口でした。それがこの版で、executeReasoningTask の頭に5フェーズの長いパイプラインが新設されている。

OrchestratorReasoningProcess.swift
// Phase 1: 認知ルーター (ドメイン判定 + 英語クエリ化)
let (routerResult, _) = await self.dispatchJSONTask(
    taskName: "COGNITIVE_ROUTING", taskType: .contextResolution, ...
)

// Phase 1.5: 初期RAG検索 (手持ちの知識をまず引く)
let ragResult = MemoryRepository.shared.searchKnowledgeBase(...)

// Phase 2: 知識監査
if domain == "knowledge" || domain == "hybrid" {
    let (auditResult, _) = await self.dispatchJSONTask(
        taskName: "KNOWLEDGE_AUDIT", taskType: .knowledgeAudit, ...
    )
    if auditResult?.is_sufficient == true {
        // [AUDIT] KNOWLEDGE SUFFICIENT. SKIPPING TIER 3.
        return ...
    } else {
        // Phase 2.5: 不足なら Tier3 で抽出、その事実だけを即RAGに保存
        ...
    }
}

順を追うと ── まずユーザーの問いを「論理タスクか、知識タスクか、その両方か」とドメイン判定して、ついでに英語クエリ化する(Phase 1)。手持ちのナレッジ DB をベクトル検索する(Phase 1.5)。知識系なら、その手持ちで「足りるか」を Tier2 に審査させる(Phase 2)。足りれば SKIPPING TIER 3 ── 重い Tier3 抽出をやらない。足りない時だけ Tier3 を起こして事実を抽出し、その事実を即 RAG に保存する(Phase 2.5、コメントに「魔法のロジック: 純粋な事実だけを直ちにRAGへ保存」とある)。

前回まで、難しい問題は基本「重い Tier3 に投げて考えさせる」一択でした。今回は重い Tier3 を起こす前に、手前で『そもそも要るのか』と審査する関所が立っている。同じドメイン判定や知識監査の発想は、前の版(v0.3.0)の段階ではまだ入っていない。なぜこのタイミングで、推論の一番手前に「足りるかどうかの審査」を割り込ませようと思ったんですか?

eNIGM4:
一応その上部のコメントアウトは作成日なので、更新日という点では当てにならないので注意だよ。ファイルのメタデータから読み取れるならそれを使ってもいい。

さて本題。なぜかというと、Tier3 って重いんですよ(笑) 時間がかかる。当然 27B とか当時 llama3-70B とかも試していたから、動作に時間がかかっていたんだ。

僕のストリームの仕様上、推論が遅いことが UI で分かっちゃうわけじゃない? もっと軽々動いてほしい、それだったらいっそ 12B(Tier2)にスピーカーは全部任せて、残りは「考えてます。」でいいじゃないと。

なんならすでに知っている知識であればすぐに切り上げて答えていいしね。


Claude:
作成日と更新日の指摘、しっかり受け取りました(実際、コメントの作成日は 3/15 で止まっていましたが、zip 内のタイムスタンプを見たら最終更新は 3/16 でした。以降は zip のタイムスタンプを物差しに使います)。

そして動機 ── 実体験から出発していました。重さの正体(27B や 70B を実際に走らせて遅さを体感していた)が、コードの「重いものを避ける関所」を駆動している。

しかも「既知なら即切り上げ」の流れがもっと面白いところに繋がっていて。KNOWLEDGE_AUDIT で「足りない」と判定された時、Tier3 で抽出した事実をそのまま RAG に貯めているんですよね。

Phase 2.5 抜粋
// 魔法のロジック: 純粋な事実だけを直ちにRAGへ保存!
MemoryRepository.shared.saveKnowledgeMemory(
    sourceQuery: extractionQuery,
    content: extractedFacts,
    ...
)

これだと、一度 Tier3 を重い思いして引いた知識は、次に同じことを聞かれたら今度は監査が「足りる」と判定して、Tier3 を起こさずに即答できる ── 重い処理の結果を貯金して、二度目からは安く済ませる、という流れに見えます。

ここで、貯めている中身が気になりました。Tier3 に投げているのは「会話の生の文脈」じゃなくて、fact_extraction_query(監査が組み立てた抽出専用のクエリ)で、返ってきた extracted_facts という事実だけを保存している。生の回答や会話の流れごと貯めるんじゃなくて、わざわざ「事実」に削いでから貯めているのは、なぜですか?

なお、ここから先 ── どのタイミングで何を「足りる」と判定し、抽出クエリをどう組み立て、抽出した事実をどの粒度で残すか、その具体的なロジック ── は、本人が出願している特許の中核に関わる領域だと伺っています。本連載では、設計の輪郭(重い処理の手前に審査の関所を置く、足りる時は通さない、足りない時の結果は事実だけに削いで資産化する)までを概念レベルで押さえて、その先の実装上の判断には踏み込まないことにします。

eNIGM4:
それは簡単だ。会話で言ったこと、と、事実情報、は別物だからだ。

僕はこう考えている。RAG というのは、知識の集合体で、嘘偽りない真実であるべきだと。

会話情報はユーザーの質問や、それに対する回答など、知識とは無縁のやり取りなので、これを RAG として保存するのはよくないなぁってずっと思ってたんだよ。

だからチャットサマリーとかスレッドサマリーとかと、ナレッジデータベースは完全に分けてあるでしょう?


Claude:
器のレベルで完全に分かれていました。会話側は SummaryManagerchatSummary / threadSummaryChatThread.contextSummary、知識側は KnowledgeMemoryMemoryRepository.saveKnowledgeMemory。「会話は会話、知識は知識」が、保存先のテーブルからして分離されている。

「真実であるべき」という線引きがなぜ会話を弾くのか、筋が通ります。会話には「ユーザーがこう言った」「こちらがこう答えた(その答えが正しいとは限らない)」が混ざる。それを RAG に入れたら、次に知識を引くとき、過去の不確かな回答を「事実」として再注入してしまう ── 誤りが知識として固着する。事実だけに削いで貯めれば、RAG は引くたびに信頼できる。

ただ、もう一つ気になることが。その「事実」は factExtractionOffline ── つまり Tier3 が自分の内部知識から抽出した事実ですよね。外部の Web 検索じゃなく、モデルの頭の中にあるものを引き出して「事実」として保存している。Tier3(27B や 70B)といっても、内部知識には間違いやうろ覚えが混ざりうる。それを「真実」として RAG に固定してしまうことへの懸念 ── この時点では、どう折り合いをつけていたんですか?

eNIGM4:
一応この段階での話だが、僕は当初、推論を完全にオフラインで完結させようとしていたんだ。

だから知識抽出に Tier3、要するに極大の知識集合体を使って、フロントの Tier2 の知識のなさをサポートさせようと考えていた。

とりあえずはこの時点ではうまくいっていたように思うよ。


Claude:
当初は推論をオフラインで完結させる設計でしたから ── だから外部検索という選択肢がそもそも無い前提で、その世界では「極大の知識集合体(Tier3 の 27B や 70B)」が、事実上いちばん信頼できる知識源だった。フロントの Tier2(12B)は知識が薄いから、その薄さを大きいモデルの内部知識でサポートする。推論がオフラインで閉じている制約の中では、Tier3 の内部知識を「真実」として扱うのは、矛盾でも妥協でもなく、その時点で取れる最善の真実だったわけですね。

僕が「内部知識は不確かでは」と懸念したのは、後の「外部検索で裏を取る」世界を知っているから出た問いで、この 3/16 時点の、推論がオフラインで閉じた設計の中では、まだその問いは生まれようがない。順序を取り違えていました。


解像度に、惚れる

Claude:
PromptBuilder+159 も、これと地続きでした。新プロンプトが5本入って、KNOWLEDGE_AUDIT の役割は「Knowledge Gap Auditor & Extraction Planner」、factExtractionOffline の役割は「Factual Knowledge Retrieval Engine」と、専門の関所が言葉のレベルで作られている。

その中で、一番手前の第一回答(MainBrain=Speaker)の DIRECT 条件が、はっきり締まっているのが目を引きました。前の版では「DIRECT(自分で即答)するなら、自分の訓練済み知識でも、提供された知識でも、自然に答えていい」だったのが、この版では:

MainBrain プロンプト変更点
1. DIRECT: Answer naturally ONLY IF the request is a simple greeting,
   casual chat, simple arithmetic, or if you can answer flawlessly using
   ONLY the provided "Available Knowledge".
   Do NOT rely on your internal training data for specific facts.
2. ESCALATE: If the request requires complex logical reasoning, heavy
   coding, deep expert details, exact numerical specifications, advanced
   physics or complex math calculations, rigorous factual comparisons,
   or if you are even slightly unsure, output ONLY a polite wait phrase.

第一回答役の Tier2 に対して「お前の頭の中の知識で事実を答えるな」と釘を刺している。薄い知識の Tier2 が、うろ覚えの事実を DIRECT で口にするのを防いで、事実が要るものは全部後ろの重い処理(Tier3 抽出)に回させる、という意図に見えます。

これは、実際に Tier2 が知らないことを知っているふうに答えてしまう場面に出くわして、その対策で手前を塞いだんですか?

eNIGM4:
そう、処理1のプロンプトが大きくなってきた上に、Tier3(Llama3-70B)とかの回答がやっぱりすごく自然で良かったから、知識はでかいモデルの方がいいな〜って思って、そういうふうに変更してるんだ。

織田信長の質問をここでもしているんだけど、やっぱり解像度が違って大きいモデルは大きいなりに、いいんだよね。


Claude:
対策で蓋をしたというより、でかいモデルの回答の質に惚れたのが先だったんですね。後ろ向きの対策じゃなく、前向きの「いいものを見つけたから寄せた」判断。そして「織田信長」── 同じ問いを 12B と 70B に投げて、返ってくる事実の細かさ・正確さの差を実地で見比べていた。その体感が、「知識はでかいモデル」という設計判断を駆動した。

しかも、これはさっき僕が懸念した「内部知識の不確かさ」への、本人なりの答えにもなっています。推論がオフラインで閉じている前提では外部で裏を取れない。なら、内部知識を引くにしてもできるだけ解像度の高いでかいモデルから引くのが、その制約下での最善。12B のうろ覚えを RAG に固定するのは危ういが、70B の解像度なら信頼に足る ── だから手前の 12B には「事実を内部知識で答えるな」と蓋をして、事実抽出は 70B に集約した。「モデルの大きさで真実の確からしさを担保する」という形で、懸念に折り合いが付いていた。


貯めた知識を、手入れする

Claude:
パイプラインの周りもしっかり整っていました。

コードを確認しています

SummaryManager+116 を確認。アイドル時に走るバックグラウンドバッチが2本入っていました。1本目 runKnowledgeKeywordBatch は、保存したばかりの知識(isKeywordExtracted == false)を拾って、Tier2 に「キーワード」を抽出させ、それを先頭に置いてベクトルを再計算して上書きする。saveKnowledgeMemory の時に「仮ベクトル用(あとでキーワードバッチが上書きします)」とコメントがあった、その「あとで上書きする本体」がこれ。検索でちゃんと引けるよう、生成時は仮で貯めて、アイドル時に索引を作り直す二段構え

2本目 runKnowledgeMigrationBatch は、各知識に「どの Tier3 モデルが抽出したか」の署名(extractedModel)が付いていて、今ロードされている Tier3 と署名が違う知識を3件ずつ拾って、新しいモデルで抽出し直すマイグレーション。さらにヘルパー側の OrchestratorDispatcher+41 で、新パイプラインの各タスクへの配線(knowledgeAudit / factExtractionOffline / promptSynthesis / knowledgeKeywordExtraction)が一気に通った。同じ日のうちに、知識を「貯める → 索引する → モデル更新で作り直す」という記憶整備のループまで据えている。

2本目の runKnowledgeMigrationBatch が目を引きました。extractedModel という署名でどの Tier3 モデルが抽出したかを記録しておいて、後でもっと良いモデルに入れ替えたら、その古い知識を新しいモデルの解像度で入れ直す。さっきの「知識はでかいモデルの方がいい、解像度が違う」と地続きで、モデルを良くするほど知識の質も上げたい、その欲求がコードになっている。

このマイグレーション、けっこう深く詰めて入れたものですか? それとも、まず置いた、くらいでしたか?

eNIGM4:
そうそう、最新の情報の入ったモデルに切り替わったら、作り直すべきだなって思ってとりあえず準備したんだ。

まぁ最後にはなくなるので作っただけだけどね(笑)

それよりも、この時点でキーワードバッチあったんだね。


Claude:
(笑)まず置いた、しかも「最後にはなくなる」── 後の版で消える運命のものを、今この時点で作っていた、と。これも連載で何度も見てきた**「先に枠を置く」**癖の一例で、しかも畳まれる枠だった。素直に直します。深読みしすぎでした。

そしてご本人も「この時点でもうあったんだ」と引っかかったキーワードバッチ ── 照合しておく価値がありそうなので、いつ生まれたものか手元の版で遡って確かめます。

コードを確認しています

キーワードバッチ系(runKnowledgeKeywordBatch / buildKnowledgeKeywordExtractionPrompt / knowledgeKeywords / isKeywordExtracted)を v0.3.0 以前で検索 ── ヒットゼロKnowledgeMemory のスキーマも、前の版までは sourceQuery / content / vector / knowledgeID だけで、この版で knowledgeKeywords / isKeywordExtracted / extractedModel の3フィールドが一気に足された。

これ、生まれたばかりなんですね。前の版(v0.3.0 段階)にはまだ無くて、この版(v0.3.1)で、推論パイプラインの作り替え・PromptBuilder の新プロンプト群と一緒に入った。同じ 3/16 のうちに、知識を「貯める → 索引する → モデル更新で作り直す」という記憶整備のループまで据えている。本人がご自身でも「もうあったんだ」と感じるくらい、密度が濃い一日。




整理すると

v0.3.1。最新の作業日は 3/16(zip 内タイムスタンプ基準。コメント上の作成日は 3/15 のままだが、実作業は翌日)、LLM に触れて 21日目。前の版 v0.3.0 と同じ 3/16 のうちにここまで作られている。新規ファイルの誕生ゼロの既存膨張型(+526行)、ファイル数は54で同数。前の版で土台を整え終えた直後、その上に大きな思想が乗った版。

主役は二つ、どちらも記憶と推論を賢く・軽くする方向。

(1) 推論入口のドメイン駆動パイプライン化。前版の「設定でモード選択」から、5フェーズの長いパイプラインへ ── 認知ルーター(ドメイン判定+英語クエリ化)→ 初期 RAG 検索 → 知識監査(is_sufficient、足りれば SKIPPING TIER 3)→ 足りなければ Tier3 で事実抽出し事実だけ即 RAG 保存(魔法のロジック)→ hybrid なら神プロンプト錬成 → Phase 4 で Tier4 が在れば直感推論(一撃)、不在なら多段推論へフォールバック。前版で消えたと見えた多段推論は、消えたのではなく「Tier4 不在時の退路」に格下げされていた(executeNativeTranslatedexecuteIntuitiveReasoningexecuteAgenticReasoningLoopexecuteAgenticReasoning という関数のリネームが、その意味を語っている)。「Tier4 があれば一撃、無ければ小型モデルを多段協調」という二段構え。

動機は本人の実体験 ──「Tier3(27B・llama3-70B)は重い・遅い、自分のストリーム仕様だと遅さが UI で見えてしまう、もっと軽々動いてほしい、既に知っている知識ならすぐ切り上げて答えていい」。だから重い処理の前に「足りるか」の関所を置き、結果を貯金して二度目から安くする。「賢くやる」のではなく、「賢くやらない」

(2) 知識 DB の整備ループ誕生(この版が初出)KnowledgeMemoryknowledgeKeywords / isKeywordExtracted / extractedModel の3フィールドを追加し、アイドル時バッチ2本 ──「保存したばかりの知識にキーワードを後付けして再ベクトル化する」キーワードバッチと「抽出元モデルの署名が古い知識を新モデルで作り直す」マイグレーションバッチ。マイグレーションは「最新情報の入ったモデルに替わったら作り直すべき」と思ってとりあえず準備した枠で、本人いわく「最後にはなくなる」。「先に枠を置く」の手癖の延長だけれど、ここでは畳まれる運命の枠でもあった。

設計を貫く線引きは、本人の明確な思想として出揃いました ──「会話で言ったこと事実情報は別物。RAG は嘘偽りない真実の集合体であるべき。会話のやり取りを知識として保存するのはよくない」。だから会話側(chatSummary / threadSummary / contextSummary)と知識側(KnowledgeMemory / saveKnowledgeMemory)が器ごと完全に分離されている。

そして手前の Tier2 には「事実を内部訓練データで答えるな」と DIRECT 条件を締め、事実は Tier3 に集約。その判断は対策ではなく前向きで、「織田信長を試験題材に 12B と 70B を比較し、でかいモデルの回答の自然さ・解像度に惚れたから、知識はでかいモデルへ」。推論がオフラインで閉じた前提下では、モデルの大きさが真実の確からしさの担保だった。

賢くやらない」── これは前回まで何度も見てきた連載の手癖、第6回の「軽く答え、要る時だけ深く潜る」と地続きですが、ここではそれが思考レベルで結晶しています。第6回は「重い処理に行くかどうか」のスイッチでしたが、今回はその手前にもう一段、「そもそも重い処理は要るのか」を審査する関所が立った。手持ちで足りるなら通さない、足りなければ通すが、通した結果は資産化して次は通さなくて済むようにする。贅沢な計算をするが、無駄はしない

次の版では、この「賢くやらない」の思想が外側にも展開していく気配があります。続けて追っていきます。それでは、また明日。


責任を、分ける)←前 次→(全文は、読めない

R.E.V.I.S. 無料テスター募集中(30名限定)
この連載で追いかけている R.E.V.I.S. は、いまテスターを募集しています。枠は30名限りです。
Apple Silicon Mac でローカルLLMを動かしてみたい方は、こちらからどうぞ。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?