Vector DBを外したら、RAGではなくAgent Runtimeが残った
はじめに
最初は、普通にRAGっぽいものを作っていました。
扱いたかったのは、fashion や styling のような、正解が曖昧で、ノイズも多く、しかし人間は確かにそこから何かを読み取っている領域です。
最初は Vector DB も入っていました。
retrieve して、context を増やして、LLM に生成させる。
典型的な RAG の構造です。
でも、実装を進めるほど、欲しかったものが検索基盤ではなかったことに気づきました。
これはなに。
より正確には、自分が作りたかったのは、検索結果を増やす仕組みではなく、判断の境界が見える Agent でした。
何を根拠にしているのか。
どこからが解釈なのか。
何が分かっていないのか。
どこで判断を止めるべきなのか。
このあたりが見えないまま、LLM がそれっぽい文章を返すのが、どうにも気持ち悪かった。
最後に残ったのは、RAG というより reasoning runtime でした。
RAGを作っていたつもりだったが、 最後に残ったのはAgent Runtimeだった。
この記事は、その runtime を作る過程で、自分の中で
RAR (Retrieval Augmented Reasoning)という言葉がどういう意味を持つようになったかを書いた実験レポです。以後は、読みやすさのために
reasoning structureは基本的に「推論構造」と書きます。
普通のRAGでは足りなかった
最初に違和感を持ったのは、retrieve された context が、LLM の中で平均化されてしまうことでした。
例えば trend analysis をやると、evidence には普通に矛盾が含まれます。
特に fashion のようなトレンド性のある domain では、Vector DB に入れた context もすぐ古くなります。
昨日まで signal だったものが、今日はもう noise になる。
RAG なのに lag する。
いや、ラグだけに。
- 長期的には canonical な signal
- 一時的な hype
- domain 内の対立
- 説明できない drift
- evidence の欠損
しかし普通の
retrieve -> generateは、それらを滑らかな prose に潰してしまう。
いやーきついっす。
結果として:
- conflict が消える
- gaps が消える
- recommendation drift が起きる
- evidence と interpretation の境界が曖昧になる
- モデル依存が強くなる
これらは別々の問題に見えますが、根は同じでした。
推論構造が LLM の内部に閉じ込められたまま、外から観測も制御もできない。
つまり、retrieve はされているが、推論構造は見えない。
ここで必要だったのは、より多くの context ではなく、推論の構造そのものを扱う runtime でした。
まぁ、問題構造としてはかなり素直です。
LLM の中に閉じているものが見えないなら、runtime 側に出すしかない。
RARという設計
README には、RAR をこう書いています。
RAR means Retrieval Augmented Reasoning: retrieval is treated as part of the reasoning workflow rather than passive context lookup.
でも、v1.0 まで実装したあと、自分の中ではもう少し違う意味になりました。
RARは、モデルにより多くのcontextを与える設計ではない。 LLMの内部に閉じがちな推論構造を、 runtime側へ外出しする設計である。
RAG と RAR の違いを雑に書くとこうです。
RAG: retrieve more context to generate better answers RAR: externalize reasoning structure so the runtime can make reasoning visible, controllable, recoverable, and inspectable 雑に言えば、推論をLLMの中だけに置かず、外から見える構造として扱う。
ここで重要なのは、augment の意味です。
RAG における augment は、context の量を増やす方向に働く。
RAR における augment は、推論に構造を与える方向に働く。
つまり:
RAG: context-first RAR: structure-first
です。
とはいえ、最初からこの言葉がきれいに見えていたわけではありません。
むしろ、設計思想に照らしていろいろ考えた末に、最後は FISI でいく、という感じでした。
FISI は、自分の中では
F*** it, ship itの略です。
雑に投げるというより、「考えるべきことは考えたので、まず小さく出して現実に当てる」くらいの意味で使っています。
まずは poor E2E を作る。
その happy path を動かしてみる。
通してから、何が見えないと困るかを見る。
その結果、RAR という言葉の意味がだんだん変わっていった、という方が近いです。
happy path が通ると、最初は「動いた」と思えます。
でも、少し触るとすぐに、何が見えていないかが気になる。
conflict はどこに行ったのか。
gap はどこに残っているのか。
どこまでが evidence で、どこからが interpretation なのか。
poor E2E は雑な最初の実装ですが、問題構造を見つけるにはかなり強いです。
happy path のデモが通るだけでは、本番で何が弱いのかはまだ見えません。だから、きれいに見せるより先に、どこで壊れるかを早めに見にいく必要がありました。
reasoningをどう外出ししたか
retrieval lane
まず、retrieval 自体を推論構造の一部として扱うようになりました。
具体的には:
canonical_query emerging_query
を分けています。
- canonical: 長期的 / baseline / cycle をまたぐ signal
- emerging: 現在の noise / drift / pressure
ここで重要なのは、retrieval が推論に従属していないことです。
retrieve してから考えるのではなく、先に「何を見るために retrieve するのか」を構造化している。
separate(reasoning intent) -> retrieve -> compare -> structure
という順番です。
つまり retrieval が reasoning の方向を決めている。
といっても、やっていることはそんなに派手ではありません。
ただ、query を分けただけです。
でも、ここを分けると、runtime が「何を見ようとしているか」を持てるようになる。
これは地味ですが、構造的に合理的な解ではあるかなと。
typed artifact
次に、中間表現を全部 typed artifact にしました。
例えば:
WebSource Claim StructuredDraft FinalAnswerRubric
などです。
claim には:
observation interpretation signal norm
のような claim_type を持たせています。
これは、LLM に自由に考えさせるためではなく、推論構造を schema として runtime 側に置くためです。
つまり:
LLM: autonomous reasoner
ではなく:
LLM: controlled transformation component
として扱う。
LLM は自由に考える存在ではなく、schema を埋める transformation task を実行する component になる。
夢はないですが、設計としてはこの方が強い。
少なくとも、どこで何が壊れたかは見えるようになります。
conflicts / gaps
これはかなり重要でした。
RAR では conflicts と gaps を消しません。
むしろ:
conflicts gaps
を structured draft の必須 field にしています。
理由は単純で、epistemic honesty を runtime に持ち込みたかったからです。
分からない部分を、LLM の prose に吸収させたくなかった。
結果として:
- conflict が visible になる
- missing evidence が visible になる
- interpretation の限界が visible になる
つまり、推論の境界が見えるようになる。
ここまでの retrieval lane、typed artifact、conflicts / gaps は、独立した工夫ではありません。
どれも、推論を LLM の内部で起きる自律的な現象ではなく、runtime が schema として保持する観察対象に変えるための設計判断でした。
問題構造に対して、だいぶ素直な分解です。
RuntimeとしてのRAR
ここで面白かったのは、推論構造を runtime 側へ移した結果、実行状態も runtime 側で扱えるようになったことでした。
最終的な構造は:
Streamlit UI -> FastAPI boundary -> LangGraph state machine -> SQLite checkpoint -> typed artifacts -> SSE workflow status
になりました。
うおw runtime じゃん、となった。
ここで state machine は、単なる step executor ではありません。
A state machine is not just a way to run steps in order. It is a way to make interruption, failure, recovery, and visibility part of the system's vocabulary.
つまり:
- resume
- checkpoint
- workflow state
- persisted artifact
- SSE status
などが、runtime vocabulary として扱えるようになる。
結果として、UI も:
Thinking...
ではなく:
Retrieving evidence... Extracting claims... Structuring conflicts... Checking output boundary...
のような workflow state を表示するようになりました。
これは大きいです。
Thinking...は便利ですが、何も説明していない。
軽量モデルで成立したこと
この runtime を動かしていたのは、最終的には gemini 3.1 flash lite 系の軽量モデルでした。
これはかなり重要でした。
リッチなモデルを毎回使うつもりはありませんでした。普通にお金がかかるので。
コスパ重視で軽量モデルを使い、その前提で成立する設計にする。これも重要な制約でした。
もし設計が:
強いモデルに自由に考えさせる
方向なら、軽量モデルでは崩れていたはずです。
でも実際には:
- recommendation drift を避ける
- conflicts / gaps を維持する
- Interpreted Rules の form を維持する
- output boundary を reflection で gate する
などが、軽量モデルでも保たれた。
これは:
モデルが賢かった
というより、
runtime が reasoning structure を保持していた
と読む方が正しいと思っています。
つまり:
The goal was not to make the model smarter. The goal was to make the reasoning process harder to derail. 要するに、モデルを強くするより、推論のプロセスを脱線しにくくしたかった。
でした。
まぁ、強いモデルで殴るのも手ではあります。
ただ、問題構造が「reasoning が見えないこと」なら、強いモデルに寄せるだけでは根本的には解けない。
ここは、runtime 側に構造を持たせる方が筋がいいです。
強いモデルで happy path だけを通すと、問題が見えにくくなることもあります。軽量モデルでも壊れにくい形にする方が、構造的に合理的な解ではあるかなと。
なぜAgentに判断させないのか
この設計の背後にある問いは、ずっと一つでした。
whose agent are they?
Agent が誰かの代理人であるなら、Agent が勝手に verdict を出すことは、必ずしもユーザーのためではない。
ここは少し principal-agent 問題っぽい話でもあります。
誰が principal で、誰のために Agent が動いているのか。
そこが曖昧なまま Agent に判断を渡すと、便利ではあるけれど、責務境界がかなり怪しくなる。
少なくとも自分が作りたい Agent は、判断を代行するものではなく、判断の前提・境界・未確認事項を見えるようにするものです。
この project の thesis は、最初から:
frames, not verdicts
でした。
つまり、Agent は recommendation や prescription を返さない。
返すのは:
- interpreted rule
- reference frame
- traceable evidence
- conflict / gap
- structured uncertainty
です。
これは単に safety のためではありません。
自分は、disclaimer を増やすより、authority boundary を設計したかった。
Disclaimers are often symptoms of unclear judgment boundaries.
だと思っているからです。
いや、もちろん disclaimer は必要です。
ただ、それだけで責務境界を作ったことにはならない。
どこまでが evidence なのか。
どこからが interpretation なのか。
どこで Agent は止まるべきなのか。
どこから先は人間や専門家の判断なのか。
そこを runtime の構造として持つ。
その結果、最終的に残ったのは:
Agent design is not inside the model. It is the structure around the model.
という感覚でした。
おわりに
この記事は、RAG を作っていたつもりだったのに、最後に reasoning runtime が残った、という話でした。
自分の中では、RAR という言葉は、もう単に retrieval を増やす設計を指していません。
RAR = reasoning structure externalized into the runtime
です。
retrieval、schema、reflection、checkpoint、workflow state。
これらを通じて、LLM の内部に閉じがちな推論を runtime 側へ移し、見える・制御できる・復旧できるものにする。
そして、モデルを system の中心ではなく、replaceable transformation component として扱う。
trend-to-rule は、その考えを試すための実験レポとして作った repository でした。
同じ thesis は、契約レビューのような別 domain でも試しています。
contract-question-agent では、agent は verdict ではなく、専門家レビュー前の verification questions を返す。
領域は違っても、やっていることは近いです。
Agent に判断させるのではなく、判断の境界を見えるようにする。
とはいえ、最初から全部こう考えていたわけではありません。
設計思想に照らして考える。
でも、考えただけでは問題構造は見えきらない。
最後は FISI で小さく通す。
poor E2E を作る。
happy path を動かす。
通して、見えないものに困る。
困ったところから、runtime vocabulary を増やす。
その繰り返しでした。
まだ未完成な部分も多いですが、少なくとも、自分の中では:
RAGを作っていたつもりだったが、 最後に残ったのはAgent Runtimeだった。
というのが、一番正直な感想です。