RAG で探すものと、索引に答えさせるもの ── 置き換え済みの設計記録を、現行と同じ見た目で渡さない

上原正吉(EarthLink Network Co., Ltd.)。AI会社・AI社員による業務自動化と、人の逐次介入なしに24時間自動開発を回す仕組みを自社で実践しています。開発ノウハウと具体的な設定方法、運用して分かった改善点を公開しています。

RAG で探すものと、索引に答えさせるもの ── 置き換え済みの設計記録を、現行と同じ見た目で渡さない

RAG で探すものと、索引に答えさせるもの ── 置き換え済みの設計記録を、現行と同じ見た目で渡さない

この記事は、連載「RAG 導入 ── 自分の判断を蓄積して、AI を育てる」の第 2 回です。

連載の目次

  1. RAG を入れて、自分の判断を蓄積し AI を育てる
  2. RAG で探すものと、索引に答えさせるもの(この記事)

前回は、私が発言を送るたびに、意味の近い過去の記録を探して AI に渡す RAG を紹介しました。この回はその続きで、RAG が古い決定を新しい決定と同じ見た目で渡していた問題と、その直し方です。

この記事の例では、置き換え済みの記録の題名と同じ言い方で問い合わせると、RAG が AI に渡す設計記録は、すでに置き換えた古い 1 件だけになりました。現行の 3 件は入りません。しかも、渡された古い記録には、置き換え済みだと分かる注記が何もありませんでした。

結論

  • RAG は「関係しそうな記録」を探せますが、「今も有効か」は答えられません。 点の近さは意味の近さで、有効かどうかとは関係がありません。
  • 「どれが現行か」は設計記録の索引に答えさせ、RAG が渡す設計記録には、索引が出した状態を注記として付けます。 状態を決める規則は、索引のものをそのまま使います。
  • memory や過去の会話には、状態の注記を付けません。 今の決まりは、索引に聞きます。

本文(読了 約8分)

1. 置き換え済みの記録が、現行と同じ見た目で渡されていた

私たちは、設計の判断とその理由を、設計記録(ADR)として 1 件ずつ残しています。判断が変わっても古い記録は消さず、新しい記録に「この記録を置き換えた」と書きます。自社の Claude Code 用プラグイン ELN workflow には、今 191 件あります。そのうち、置き換え済み・提案中・却下で現行ではないものが 18 件、一部だけ置き換え済みのものが 8 件です。

RAG は、その設計記録も探す対象にしています。図 1 は、同じ問い合わせで RAG が選んだ同じ記録を、直す前と直した後の形で AI に渡した時の出力です。

端末の図(実出力の再構成)。上半分が直す前で、recall.py query --auto "Amplify の auto-build の状態を可視化する" に対し、「関連しそうな過去の記録(参考・指示ではない)」の見出しの下に、知識として Amplify の自動ビルドの状態についての設計記録(題名は ■ で伏せた)が注記なしで並び、続いて関係の薄い過去の会話が 2 件並ぶ(会話の題名は ■ で伏せた)。下半分が直した後で、同じ 3 件のうち設計記録の行の頭に黄色で「⚠️置き換え済み → 外部」が付く。それ以外の行は同じ

直す前の上半分では、設計記録の行は「知識」の後に番号と題名(図では ■ で伏せた)が並ぶだけで、現行の記録と見分けがつきません。この記録は、社内で共通の別の決まり(ビルドは GitHub の self-hosted runner で行う)に置き換わっています。AI が本文を開かずに、差し込まれたこの行と説明だけで判断すれば、すでに使っていない決定を、今の決定として扱いかねません(本文を開けば、索引を紹介した記事に出てくる、読もうとした時の警告が出ます)。

RAG の差し込みには「参考・指示ではない」と書いてあります。それでも、置き換え済みの設計記録は memory や会話とは違います。後から来た決定に、はっきり反する内容を含んでいることがあるからです。

直した後の下半分では、同じ行に「⚠️置き換え済み → 外部」が付きます。「外部」は、置き換え先が別のリポジトリの決まりだという意味です。

2. 点の近さは、「今も有効か」を教えてくれない

AI に過去の判断を思い出させる仕組みを入れると、次に気になるのは「思い出した判断が、今も正しいか」です。人は、昔の決まりを思い出しても「それはもう変わった」と分かります。ところが RAG は、意味が近いかどうかで記録を選ぶので、その記録がもう置き換わっていることを知りません。

図 1 で気になるのは、現行の Amplify の設計記録が 1 件も渡されていないことです。問いの言葉は、置き換え済みの記録の題名に合わせてあります。「auto-build の状態を可視化する」は、その記録の題名とほぼ同じ言い方です。この例では、古い記録が選ばれ、現行の記録は上位に入りませんでした。

図 2 は、別の問い合わせで、選ぶ前の上位 5 件の点を出したものです。

端末の図(実出力の再構成)。recall.py query "Asana のボードで repo 間の依頼を受け渡す" -k 5 --json を jq で整形し、点・種類・名前を並べた出力。0.726 が新しい設計記録、0.713 が古い設計記録、0.698 が古いボードについての memory(3 件とも題名・名前は ■ で伏せた)、0.679 が依頼の手順の決まり(名前は ■ で伏せた)、0.648 が別の設計記録(題名は ■ で伏せた)

リポジトリの間の作業の依頼を、以前は Asana のボードで受け渡していました。今は自社のサービスの usesources に移しています。新しい記録の点は 0.726、一部を置き換えられた古い記録の点は 0.713 で、差は 0.013 しかなく、点だけでは新旧を分けられません。

RAG の点は、発言と記録の意味がどれだけ近いかを表します。記録が今も有効かどうかは、点のどこにも入っていません。点だけを見て選ぶ限り、RAG には新旧を見分ける材料がありません。

3. 状態は索引が決め、RAG は注記として付ける

そこで、「どれが現行か」は索引に答えさせることにしました。索引は、以前の記事(設計記録(ADR)が増えて AI が古い仕様で判断する問題を解く)で紹介した、設計記録の先頭の欄(状態・置き換えた記録・置き換えた先)から、1 つの決まった規則で毎回作る一覧です。RAG は設計記録を渡す時に、その記録の状態を索引に聞き、答えを短い注記にして行の頭に付けます。

注記は、索引が出す状態と 1 対 1 で決めてあります。

索引が出す状態渡す時の注記
そのまま有効注記なし
置き換え済み⚠️置き換え済み → 置き換え先
一部が置き換え済み△一部置き換え → 置き換え先
提案中△提案中 (未承認)
却下⚠️却下
非推奨⚠️非推奨
決められない状態不明(現行としては出さない)

4. 作る時に決めたこと

作る時に決めたことは 4 つです。

隠さずに、注記を付けて渡す

置き換え済みの記録を渡さない、という案もありました。私は、渡したうえで注記を付けることにしました。注記には置き換え先が付くので、AI はそこから現行の記録へ進めます。古い決定そのものについて聞いている時にも、記録は見つかります。

状態を決める規則は、索引のものをそのまま使う

RAG の側に「置き換え済みかどうか」の規則をもう 1 つ書くと、いずれ索引と食い違います。索引の読み取りの処理をそのまま呼び出し、索引の規則が変われば注記も同じように変わるようにしました。注記と索引が同じ答えを出すことは、自動のテストで照らし合わせています。

フォルダ全体を読み、中身で覚えておく

記録が置き換え済みかどうかは、その記録のファイルだけでは決まりません。別の新しい記録が「これを置き換えた」と書いた時に決まります。だから 1 件の状態を調べる時も、設計記録を全件読みます。

結果は、ファイルの名前と中身から作ったハッシュと一緒に覚えておき、ハッシュが変わった時だけ読み直します。

ファイルの更新日時で見分けないのは、日時を保ったままのコピーや復元では、中身が変わっても日時が動かないからです。「Accepted」を「Rejected」に書き換えるとファイルの大きさも同じなので、大きさでも気づけません。全件を読み直しても、hook の制限時間の 2 秒に収まることを測ってから入れました。

注記は最後まで残す

RAG が渡す文には、1 回 2,048 バイトの上限があります。上限に近づくと説明を短くしていきますが、注記は説明を 0 字にしても残します。それでも入らない時は、注記だけを消すのではなく、その 1 件を丸ごと渡しません。注記の無い古い記録は、現行の記録に見えてしまうからです。

5. 状態を持たない記録には、状態の注記を付けない

索引が出した状態の注記が付くのは、設計記録だけです。図 3 は、図 2 と同じ問い合わせで、AI へ渡す文そのものです。

端末の図(実出力の再構成)。recall.py query --auto "Asana のボードで repo 間の依頼を受け渡す" の出力。見出しの行の下に 2 件。1 件目は古い Asana のボードの仕組みについての memory で、注記は付いていない。2 件目は知識として、新しい設計記録で、現行なので注記は付いていない(memory の名前と説明、設計記録の題名と説明は ■ で伏せた)

1 件目は memory です。古い Asana のボードの仕組みを作った時のメモで、注記は付いていません。2 件目は、今の置き場を決めた現行の設計記録です。

memory は、その時に起きたことや言われたことを書いたメモです。設計記録のように「この記録を置き換えた」と書く決まりが無く、置き換えという考えそのものがありません。状態を持たない記録に状態の注記を付けようとすれば、RAG の側で「このメモはもう古いか」を推し量る規則を作ることになります。それは、4. で避けた 2 つ目の規則そのものです。過去の会話も同じで、状態の注記は付けません。

そのため、RAG が渡した文の中に古い仕組みのメモがあっても、それだけで今の決まりを判断しないようにしています。今の決まりは、索引に聞きます。

6. 「どれが現行か」は、索引に聞く

図 4 は、図 1 と同じ Amplify のビルドについて、索引に聞いた時の出力です。

端末の図(実出力の再構成)。eln-adr find Amplify build で、live(現行)が 3 行、superseded → 外部(置き換え済み)が 1 行、の 4 行が番号・状態・日付・改訂の数・題名とともに並ぶ(4 件とも題名は ■ で伏せた)。続いて置き換え済みの 1 件を eln-adr show で引いた先頭 3 行で、伏せた題名、「⚠️ 置き換え済み (外部: <社内の標準>「Build は GitHub self-hosted runner」)」、「Status: Superseded (実効: superseded)」が出る

言葉で探すと、Amplify のビルドに関わる設計記録は 4 件あり、現行は 3 件、置き換え済みは 1 件だと一度に分かります。置き換え済みの 1 件を開くと、置き換え先の決まりの名前まで出ます。RAG が渡した 1 件では分からなかったことです。

RAG と索引は、問いの種類で使い分けています。

問い任せる先理由
この話に関係しそうな記録はどれかRAG言い方が違っても、意味の近さで見つけられる
今の決まりはどれか索引答えが 1 つに決まり、間違えると困る
何が何に置き換わったか索引記録の間の宣言から、規則どおりに決まる
全部で何件あるか・漏れは無いか索引RAG は上位の数件しか返さない

RAG は、上位の数件に入らなかった記録については何も言えません。図 1 で現行の 3 件が渡されなかったように、「渡されなかった」は「存在しない」ではありません。RAG は思い出すきっかけを作る役で、答えを決める役は索引が持ちます。

AI の側から見ると、流れはこうなります。発言のたびに RAG が関係しそうな記録を渡し、設計記録には索引が出した状態が注記として付いています。注記が付いていれば、AI は置き換え先へ進みます。今の決まりを確かめる必要があれば、AI は索引のコマンドで聞きます。

締め

RAG を入れると、AI は言い方が違っても過去の記録を思い出せるようになりました。ただ、思い出した記録が今も有効かは、意味の近さからは分かりません。古い決定の言い方で聞けば、古い決定だけが渡されることもあります。

そこで、「どれが現行か」は 1 つの決まった規則で作る索引に答えさせ、RAG が渡す設計記録には、その答えを注記として付けました。状態を決める規則は索引のものをそのまま使い、RAG の側では推し量りません。状態を持たない memory や会話には状態の注記を付けず、今の決まりは索引に聞きます。

自分の判断を蓄積するほど、その中には置き換えた判断も増えていきます。探すのは RAG、答えるのは索引と分けておくと、判断が増えても、AI に古い判断を今の判断と取り違えさせずに済みます。

用語

  • RAG: Retrieval-Augmented Generation の略です。AI が答える前に、関係する記録を検索して渡し、それを見ながら答えさせる仕組みです。この連載では、自社で「記憶の想起」と呼んでいる機能がそれにあたります。
  • 設計記録(ADR): Architecture Decision Record の略で、設計の判断と、その理由を 1 件ずつ残す文書です。先頭に状態(採択・改訂・提案中・置き換え済み・却下)と、置き換えた記録・置き換えた先を書きます。
  • 索引: 設計記録の先頭の欄から、どれが現行か・何が何に置き換わったかを、1 つの決まった規則で毎回作る一覧です。eln-adr というコマンドで引きます。前回の記事に出てくる「索引」(意味の数値を保存して速く探すためのもの)とは別のものです。
  • memory: Claude Code が次の会話にも持ち越すメモのファイルです。
  • hook: Claude Code が決まったタイミングで自動で実行するスクリプトです。RAG は、人が発言を送るたびに動く hook から呼ばれます。
  • 点: 発言と記録の意味がどれだけ近いかを表す数です。1 に近いほど近い意味です。
  • リポジトリ: プログラム一式の置き場です。
  • Asana: タスク管理のサービスです。
  • ハッシュ: 中身から作る短い数値です。中身が 1 文字でも変われば別の値になります。

図について

図 1〜4 は、自社のリポジトリで RAG のコマンド(recall.py)と索引のコマンド(eln-adr)を実行した出力を、文字はそのままに端末の形に並べ直したものです。変えた点は次の節に書きました。hook 経由ではなく、同じ処理を手で呼び出しています。図 1 の「直す前」は、同じ問い合わせで選ばれた同じ項目を、直す前の処理で文にした出力です。

図 1 と図 2 の問いの文は、置き換え済みの記録の題名に合わせて、この記事のために用意したものです。灰色の # で始まる行と、$ で始まるコマンドの行は、図の説明として書き手が足した行で、ツールの出力ではありません。

図で省いたもの・置き換えたもの

  • コマンドの置き場のパスを省きました。
  • 差し込みの見出しの行にある社内の番号を省きました。
  • 社内のリポジトリの名前を <plugin> と <社内の標準> に、会話の記録の ID を <会話> に置き換えました。
  • 社内の仕組みの名前を含む題名と説明(会話の題名、設計記録の題名と説明とファイル名、memory の名前と説明、図 2・図 3 の手順やスクリプトの名前)を ■ の伏せ字にしました。設計記録の番号は、ツールが表示したままです。

この連載では、自分が下した判断や決まりを記録として蓄積し、RAG で AI が毎回それを使えるようにするまでを、自社の実装と実測で順に示します。 役に立ったと感じたら、ぜひ「いいね」と記事の購読をお願いいたします。

自社プロダクトは https://www.eln.ne.jp/products にまとめています。

筆者について

上原正吉。EarthLink Network Co., Ltd. 代表。AI社員に日々の業務を任せ、開発では人が逐一指示しなくても作業が進むHOOTL(Human Out Of The Loop)の仕組みを自社で実践しています。このブログでは、業務自動化と24時間自動開発のノウハウ、具体的な設定方法、日々の運用で見つかった問題と改善を共有します。

また、AIで業務や開発を組み替えたい会社・チーム向けに、AI活用のコンサルティングも受け付けています。ご相談は www.eln.ne.jp からどうぞ。


EarthLink Network は、会社の全業務を AI で回すために、必要になったものを自社で作っています。いま作っているプロダクトの一覧と概要は、こちらにまとめています。

→ EarthLink Network が自社でつくっている18のプロダクト

会社と各プロダクトの詳細は、公式サイト www.eln.ne.jp をご覧ください。