
技術解説
だれでもわかる Corpus2Skill 実装詳細
約17分
目次を開く
- この記事を読むために必要な前提知識は?
- 全体像:文書が答えになるまでの流れは?
- 第1部:コンパイル編 — 文書はどうやってツリーになるのか?
- Step 1 — テキスト抽出:まず「読める文字」にする
- Step 2 — チャンク化:意味のかたまりに切る
- Step 3 — 要約:各かたまりに見出しと要約をつける
- Step 4 — クラスタリング:似た話題をまとめて章にする
- Step 5 — ファイル生成:ツリーをファイルとして書き出す
- Step 6 — 切り替え:新バージョンを一気に有効化する
- なぜコンパイルは「非同期のジョブ」なのか?
- 第2部:ナビゲーション編 — 会話中にツリーをどう辿るのか?
- ナビゲータに渡す道具は、たった1つ
- 探索ループの中身:AIとプログラムの往復
- 一段降りるたびに、何を記録しているのか?
- 「わかりません」は、どう判定しているのか?
- 最初の一歩を先読みして、往復を1回減らす
- 会話の文脈は、どう保っているのか?
- 第3部:会話を止めないための工夫
- 探索の途中経過を、その場で流す
- 途中でやめるとき、何が記録に残るのか
- 実装して初めて分かった「教科書に載っていない」こと
- 結局、RAG と何が違うのか?
- まとめ
Corpus2Skill の実装は、大きく「コンパイル」と「ナビゲーション」の2つに分かれます。コンパイルは、文書を切り分け・要約し・グループにまとめて、階層構造のファイル群として書き出す処理です。ナビゲーションは、会話中にAIが「文書を1つ取ってくる」という道具を繰り返し使いながら、ツリーを一段ずつ降りて答えにたどり着く処理です。この記事では、この2つがどんなコードの動きとして実現されているのかを、専門用語をすべて定義しながら順番に解説します。
この記事はCorpus2Skill とは何か — 概要編の続編です。概要編を読んでいなくても読めるように用語は再定義しますが、「そもそも何のための技術か」を先に知りたい方は概要編からどうぞ。
この記事を読むために必要な前提知識は?
必要な前提知識はありません。プログラムを書いたことがなくても読めるように、コードはすべて「日本語の擬似コード」に書き直してあります。ただし、以下の言葉だけは繰り返し出てくるので、最初に約束しておきます。
| 用語 | この記事での意味 |
|---|---|
| コーパス | お客様からお預かりした文書の集まり。規約・マニュアル・FAQ・社内規程など。 |
| チャンク | コーパスを扱いやすい大きさに切り分けた、意味のかたまり。おおむね数段落ぶん。 |
| ノード | ツリーを構成する1つの点。枝分かれの途中の点と、いちばん下の点(葉)がある。 |
| 要約ノード | 「この枝には何が書いてあるか」だけを持つ、途中の点。ファイル名は SKILL.md。 |
| 葉ノード | 実際の文書本文を持つ、いちばん下の点。ファイル名は doc_ で始まる。 |
| docID | 葉ノードにつく文書の識別子。回答の根拠を指し示すために使う。 |
| コンパイル | コーパスをツリーへ変換する処理。会話中ではなく、事前に一度だけ実行する。 |
| ナビゲータ | 会話中にツリーを辿る役のAI。VoiceCore では Claude が担当する。 |
| 道具(ツール) | AIがプログラム側に依頼できる操作。ここでは「文書を1つ取ってくる」の1種類だけ。 |
| 足跡 | どのノードを、なぜ選び、何秒かかったかの記録。回答と一緒に残る。 |
全体像:文書が答えになるまでの流れは?
全体は2本の線でできています。1本目はお客様が文書を登録したときに一度だけ走る線(コンパイル)、2本目は質問を受けるたびに走る線(ナビゲーション)です。この2本が完全に分かれていることが、Corpus2Skill の設計上いちばん大事な点です。
- コンパイル(文書の登録時に一度):文書 → テキスト抽出 → チャンク化 → 要約 → グループ化 → ツリーのファイル群を書き出し → 新バージョンへ切り替え。
- ナビゲーション(質問のたび):質問 → ルートの要約を読む → 枝を選んで降りる → 葉ノードの本文に到達 → 回答を作る → 足跡を記録。
会話中にやっているのは2番だけです。文書を読み直したり、検索用の索引を作り直したりはしません。だから会話のたびに重い処理が挟まらず、応答時間を短く保てます。
第1部:コンパイル編 — 文書はどうやってツリーになるのか?
コンパイルは6つのステップに分かれています。1つずつ、何をしていて、なぜそうしているのかを見ていきます。
Step 1 — テキスト抽出:まず「読める文字」にする
最初にやるのは、アップロードされたファイルから文字を取り出すことです。Word や PDF はそのままでは文字の並びとして扱えないので、テキストに変換します。ここで2つだけ、先に弾く条件があります。文字コードが壊れていて読めない場合と、取り出した結果が空だった場合です。
この2つを最初に弾くのには理由があります。後続のステップはAIを呼び出すため、お客様のAPI利用料が発生します。読めない文書のためにAIを呼ぶのは、時間もお金も無駄です。壊れた入力は、お金がかかる処理に入る前に止める——これは実装上の原則としてかなり効きます。
Step 2 — チャンク化:意味のかたまりに切る
次に、長いテキストを扱いやすい大きさに切ります。これをチャンク化と呼びます。切り方はとても素朴で、空行で段落に分け、上限の文字数に達するまで段落をくっつけていくだけです。擬似コードにすると次のようになります。
function 文書をチャンクに切る(全文) {
const 段落たち = 全文を空行で分割する() // 空行 = 意味の切れ目とみなす
let かたまり = ""
const 結果 = []
for (const 段落 of 段落たち) {
const 候補 = かたまり + 段落
if (候補の文字数 > 1500 && かたまりの文字数 >= 40) {
結果に かたまり を追加 // 上限を超えたので、ここで区切る
かたまり = 段落 // 新しいかたまりを始める
} else {
かたまり = 候補 // まだ入るので、くっつける
}
}
if (かたまりが空でない) 結果に かたまり を追加
return 結果 // chunk_1, chunk_2, ... と番号をつけて返す
}上限を1500文字前後、下限を40文字前後に置いているのは、この後のステップでAIに1チャンクずつ要約させるからです。長すぎるとAIへの入力が膨らんで時間とコストがかかり、短すぎると「第12条」のような見出しだけの断片が独立したチャンクになってしまい、要約が意味をなしません。文の途中で切らず段落の境目で切るのも、同じ理由です。
なお、空行がまったくない短い文書のように段落分割で1件も得られないケースでは、全文をまとめて1チャンクとして扱います。素朴ですが、こういう「取りこぼし」の穴を最初に塞いでおかないと、後段で原因不明の空ツリーが生まれます。
Step 3 — 要約:各かたまりに見出しと要約をつける
切り分けたチャンクを1つずつAIに渡し、短い見出し(40字以内)と要約(160字以内)を作らせます。ここで初めてAIが登場します。やっていることは単純で、プロンプトを投げてJSONを受け取るだけです。ツールも使いませんし、往復もしません。
入力(chunk_7 の本文):
第12条(解約)
年額プランの解約は、契約管理画面の「解約申請」から行うものとし、
契約期間の末日までサービスを利用できる。日割りによる返金は行わない。
AIへの依頼:
次の文章に短い見出し(title, 40字以内)と要約(summary, 160字以内)を
付けてください。出力は必ず JSON のみで返してください。
出力(AIが返すJSON):
{
"title": "年額プランの解約条件",
"summary": "年額プランは契約管理画面の解約申請から手続きし、契約期間の末日まで利用できる。日割り返金は行わない。"
}実装上の小さな工夫として、AIの返答をそのままJSONとして読もうとはしません。モデルは説明文を添えたり、コードブロックで囲んだりすることがあるからです。返ってきた文字列から最初の波かっこのかたまりだけを取り出してからJSONとして読みます。地味ですが、これがないと運用中に数%の確率で失敗します。
この段階でできあがるのは、「チャンクID・見出し・要約・本文」の4点セットのリストです。本文を捨てずに持ち続けているのがポイントで、これがそのまま後で葉ノードの中身になります。
Step 4 — クラスタリング:似た話題をまとめて章にする
次が、ツリーの骨組みを作る工程です。要約のリストだけをAIに渡し、意味的に近いものをグループにまとめさせます。ここで本文を渡さないのは意図的です。本文まで渡すと入力が巨大になり、しかもAIが細かい記述に引きずられて分類がぶれます。見出しと要約だけを見せるほうが、章立てはきれいに出ます。
入力(要約だけのリスト):
chunk_5 月額プランの解約条件 月額は申請した月の末日で終了し...
chunk_7 年額プランの解約条件 年額は契約期間の末日まで利用でき...
chunk_9 従量課金の超過時の扱い 上限を超えた分は超過レートで加算...
chunk_12 固定料金の内訳 基本料金に含まれる範囲は...
出力(AIが返すグループ分け):
{
"groups": [
{
"slug": "cancellation",
"title": "解約手続き",
"summary": "プラン別の解約方法と、利用できる期間の扱い。",
"memberIds": ["chunk_5", "chunk_7"]
},
{
"slug": "pricing",
"title": "料金体系",
"summary": "固定料金と従量課金の内訳、上限を超えたときの扱い。",
"memberIds": ["chunk_9", "chunk_12"]
}
]
}slug(スラッグ)は、そのグループを表す英数字の短い名前です。これがそのままフォルダ名になります。この Step 4 が、Corpus2Skill の心臓部です。バラバラだったチャンクに親子関係が生まれ、「解約手続き」という枝の下に「月額」と「年額」が並ぶ——つまり、あとでAIが降りていくための分岐点がここで作られます。
現在の VoiceCore の実装では、この階層化を1段だけ行っています(ルート → カテゴリ → 文書)。論文の手法は何段にも重ねられますが、扱っているコーパスの規模では1段で実用上十分な木が得られるため、まず動くものを完成させることを優先しています。より深い階層は、同じクラスタリング処理を繰り返し呼ぶだけで拡張できる作りにしてあります。
Step 5 — ファイル生成:ツリーをファイルとして書き出す
グループ分けができたら、それをファイル群として書き出します。ここが実装として面白いところで、ツリーはデータベースの中の複雑な構造ではなく、ただのフォルダとテキストファイルとして表現されます。
- SKILL.md(ルートの要約・子ノード一覧)
- cancellation/SKILL.md(解約手続きの要約)
- cancellation/doc_chunk_5.md(月額の解約条件・本文)chunk_5
- cancellation/doc_chunk_7.md(年額の解約条件・本文)chunk_7
- pricing/SKILL.md(料金体系の要約)
- pricing/doc_chunk_9.md(従量課金の超過・本文)chunk_9
ルートの SKILL.md には、子ノードへのリンクと一言説明が並びます。中身は次のようなただのテキストです。
# 株式会社サンプル 利用規約ツリー
このスキルツリーは、アップロードされたコーパスから自動生成されました。
## 子ノード
- cancellation/SKILL.md — 解約手続き: プラン別の解約方法と、利用できる期間の扱い。
- pricing/SKILL.md — 料金体系: 固定料金と従量課金の内訳、上限を超えたときの扱い。葉ノード(doc_ で始まるファイル)は、見出し・要約・区切り線・本文という順に並びます。AIが取得したときに、まず要約で概要をつかみ、続けて本文の原文を読める形です。ファイル名の一部がそのまま docID になるので、回答に「根拠は doc_chunk_7 です」と添えられます。
ツリーをただのファイル群にしておく利点は3つあります。第一に、人が直接開いて中身を確認できること。第二に、専用のデータベースやベクトル検索基盤を運用しなくてよいこと。第三に、次の Step 6 の「切り替え」が単純になることです。
Step 6 — 切り替え:新バージョンを一気に有効化する
新しいツリーは、稼働中のツリーとは別のバージョン番号の場所に書き出します。書き出しが全部終わってから、「今使うのはこのバージョン」という指し先を1回の操作で切り替えます。
なぜこうするのか。もし稼働中のツリーを直接上書きしていたら、書き換えの途中で質問が来たときに、半分だけ新しい・半分は古いという壊れた状態のツリーを辿ることになります。規約の改定中に「解約手続き」の枝だけが消えている、といった事故です。別の場所に作ってから指し先を切り替えれば、利用者から見えるツリーは常に古い完成版か、新しい完成版のどちらかになります。
コンパイルの途中で失敗した場合も同じ理屈で安全です。新しいバージョンの書きかけが残るだけで、稼働中のツリーには指一本触れていません。お客様から見れば「更新に失敗した」だけで、サービスは止まりません。
なぜコンパイルは「非同期のジョブ」なのか?
コンパイルは、アップロードのリクエストの中で実行しません。別のジョブとして裏で走らせます。理由は3つです。
- 時間がかかる:チャンクの数だけAIを呼ぶので、大きな文書では分単位になります。ブラウザを開いたまま待たせる処理ではありません。
- 途中で失敗しうる:AIの呼び出しは、ネットワークや相手側の事情で失敗することがあります。ジョブにしておけば、失敗したステップから自動で再試行できます。
- 前提条件が後から整うことがある:VoiceCore は BYOK(お客様ご自身のAPIキーを使う方式)なので、キーの登録が済むまでコンパイルを始められません。ジョブなら「キーの登録が済んだら再開する」という待ち方ができます。
ジョブの各ステップは、失敗したときにそのステップだけをやり直せる単位で区切ってあります。要約を50個作り終えたあとにグループ化で失敗しても、要約からやり直しません。AIの呼び出しはお客様の実費なので、やり直しの単位を細かくすること自体がコスト設計になります。
第2部:ナビゲーション編 — 会話中にツリーをどう辿るのか?
ここからが、質問を受けるたびに走るほうの処理です。担当するのはナビゲータと呼ばれるAI(VoiceCore では Claude)で、やることは「文書を1つ取ってくる」という道具を繰り返し使いながら、ツリーを降りて答えにたどり着くことです。
ナビゲータに渡す道具は、たった1つ
現代のAIモデルには「道具(ツール)を使う」機能があります。AIが「この道具をこの引数で使ってほしい」と申告し、プログラム側が実行して結果を返す、という往復ができる仕組みです。Corpus2Skill のナビゲータに渡す道具は、次の1つだけです。
道具の名前 : get_document(文書を1つ取ってくる)
説明 : スキルツリーのノードを取得する。
要約ノード(SKILL.md)なら要約と子ノードの一覧を、
葉ノードなら文書の本文を返す。
入力 : path — ツリー内の位置を表す文字列
例) "cancellation/annual/SKILL.md"
ルートは "SKILL.md"道具を1つに絞っているのは、AIに与える選択肢を減らすためです。「検索する」「一覧を出す」「本文を読む」と道具を増やすほど、AIはどれを使うかで迷い、往復が増え、応答が遅くなります。できることを1つに絞れば、AIが考えるのは「次はどこへ降りるか」だけになります。これは探索の正確さにも効きます。
道具を使うと、次のような結果が返ります。
{
"path": "cancellation/SKILL.md",
"content": "# 解約手続き / プラン別の解約方法と、利用できる期間の扱い。...",
"node_type": "skill_summary", // 要約ノード = まだ途中
"children": ["annual", "monthly"], // 降りられる枝の一覧
"doc_id": null // 本文ではないので空
}AIはこの children を見て、次にどの枝へ降りるかを決めます。ここが「似ている断片を集める」方式との決定的な違いです。選択肢が明示的に2つ提示され、AIはどちらかを選ばなければならない。年額と月額が混ざる余地は、構造的にありません。
探索ループの中身:AIとプログラムの往復
探索の本体は、次のようなループです。実際のコードから、本質だけを抜き出して日本語に書き直しています。
会話履歴 = [同じセッションの過去のやりとり] + [今回の質問]
取得回数 = 0
足跡 = []
くりかえし {
返事 = AIに聞く(会話履歴, 使える道具 = [get_document])
もし 返事が「道具を使いたい」と言っている なら {
もし 取得回数 が上限(8回)に達している なら {
→ 打ち切り。「見つかりませんでした」として終了
}
取得回数 = 取得回数 + 1
ノード = ツリーから取ってくる(返事が指定した path)
足跡に追加 {
どのノードか : ノードの位置,
なぜ選んだか : 返事に書かれていた理由の文章,
かかった時間 : 前の段からの経過ミリ秒,
}
もし ノードが葉ノード なら 根拠のdocIDとして覚えておく
会話履歴に「道具の結果」を追加する
つづける // もう一周まわる
}
// 道具を使わず、文章だけを返してきた = 答えが出た
回答 = 返事の文章
ぬける
}
最後に必ず: 足跡と回答と所要時間を記録に残すこのループの美しいところは、「どこで止まるか」をAI自身が決めている点です。プログラム側が「3段降りたら終わり」と決めているのではありません。AIが「もう十分な情報が集まった」と判断したときに、道具を使わず文章を返してきます。それが終了の合図です。
同時に、AIに任せきりにもしていません。取得回数には上限(8回)を設けてあります。ツリーの深さは通常4段程度なので、8回あれば「1回間違った枝に降りて、戻ってやり直す」余裕まで含めて足ります。上限がないと、迷ったAIが延々とツリーを歩き回り、利用者を待たせ続けることになります。
一段降りるたびに、何を記録しているのか?
ループの中で毎回記録している「足跡」は、次の4項目です。これが監査性の実体です。
[
{ ノード: "root",
位置 : "(ルート)",
理由 : "探索の起点",
時間 : 42ms },
{ ノード: "cancellation",
位置 : "cancellation",
理由 : "解約に関する記述を確認するため、この枝に降ります",
時間 : 910ms },
{ ノード: "annual",
位置 : "cancellation/annual",
理由 : "質問は年額プランの解約についてなので、月額の枝は除外します",
時間 : 870ms },
{ ノード: "doc_chunk_7",
位置 : "cancellation/annual",
理由 : "年額の解約条件の本文を取得します",
時間 : 780ms }
]「理由」の欄に入るのは、AIが道具を使う直前に書いた文章です。多くのモデルは道具を呼ぶ前に「まず解約の枝を見ます」といった一言を書きます。それを取り出して保存しているだけで、AIに別途「理由を書け」と追加で頼んでいるわけではありません。追加のコストをかけずに、判断の過程が残るのがこの設計の効率のよいところです。
画面上では、この足跡は次のように表示されます。
- 規約・課金ツリー
- 解約手続き
- 年額プラン
- 解約条件(年額)doc-42
実際の画面での見え方はナレッジツリー探索の仕組みと体験会話ページで確認できます。
「わかりません」は、どう判定しているのか?
ここは、実装していて最も学びの多かった部分です。当初は「葉ノード(本文)に到達したかどうか」だけで成否を判定していました。docID を持って帰ってきたら成功、そうでなければ「見つかりませんでした」。一見すると妥当に思えます。
ところが、これには2つの穴がありました。1つは、ツリーを一度も引かずに答えられる発話——「ありがとう」「さっきの件だけど」のような会話——まで失敗扱いになったこと。もう1つは、途中の要約ノードだけで十分に答えられたケースまで失敗扱いになったことです。結果として、正しい回答が「見つかりませんでした」というメッセージに置き換わって利用者に届いていました。会話として最悪の挙動です。
そこで判定基準を変えました。「該当が無い」とAI自身が申告したときだけ、見つからなかったと扱う方式です。AIへの指示文の側で「情報が見つからない場合は『適合する情報が見つかりませんでした』と書く」と言い回しを固定し、返ってきた文章の冒頭にその申告があるかを見ます。到達したノードの種類ではなく、AIの結論そのものを見るように変えたわけです。
この変更が示しているのは、AIを使うシステムの正しさは、構造だけでは決まらないということです。「何をもって成功とするか」の定義がずれていると、内部的には完璧に動いていても、利用者には壊れて見えます。
最初の一歩を先読みして、往復を1回減らす
会話の1手目は、ほぼ確実に「まずルートの要約を見る」から始まります。分かりきっているのに、そのためだけにAIとの往復を1回使うのはもったいない——実測で2秒前後かかっていました。
そこで、会話の1手目に限り、プログラム側が先にルートの要約を取得し、「AIが道具を使って取得した体」で会話履歴に差し込みます。ツリーの読み取り自体は数十ミリ秒で終わるので、そのぶんまるごと待ち時間が減ります。AIから見れば、最初から手元にルートの要約がある状態で考え始められます。
2手目以降は先読みしません。「ありがとう」への返事のためにツリーを引くのは無駄ですし、そもそも調べるべきかの判断はAIに委ねたいからです。決まりきっているところだけ先回りし、判断が要るところは委ねる——高速化の勘所は、たいていこの線引きにあります。
会話の文脈は、どう保っているのか?
同じセッションでの過去のやりとりは、会話履歴の先頭に積みます。これがないと、「それ」「さっきの」が通じません。実際、この仕組みを入れる前のテキスト対話は毎回ゼロから始まる一問一答で、会話として成立していませんでした。
ここで意識的に落としているものがあります。過去のターンの探索過程(どの道具をどう使ったか)は再現しません。残すのは「何を聞かれて、何と答えたか」という発言だけです。探索の過程まで積み上げると、会話が続くほど履歴が膨らんで応答が遅くなりますし、古い探索結果が新しい質問の判断を引きずってしまいます。探索は毎ターンやり直し、文脈だけを引き継ぐ。これがちょうどよい線でした。
もう1つ、地味ですが重要な設計があります。会話履歴はサーバー側の記録から復元し、クライアントから受け取りません。もしブラウザから「過去のAIの発言」として任意の文章を送れてしまうと、AIに偽の前提を吹き込めることになります。履歴の出どころをサーバーに固定するのは、正確さの問題であると同時に、安全性の問題です。
第3部:会話を止めないための工夫
探索の途中経過を、その場で流す
探索には数秒から十数秒かかります。その間ずっと無反応だと、利用者は「固まった」と感じます。そこで、ループの中から3種類の合図を外へ流せるようにしています。
- ターン開始:AIが新しく考え始めた。表示側は、書きかけの文を捨ててよい合図。
- 文字の断片:AIが書き出した文章の一部。選択理由か最終回答のどちらかだが、この時点では区別がつかない。
- 一段降りた:どのノードに、どんな理由で降りたか。足跡ツリーが1段点灯する。
この通知は、失敗しても会話本体を落とさないようにしてあります。利用者がタブを閉じるなどして通知先がいなくなっても、探索そのものは最後まで走り切り、記録も残ります。表示は落ちてもよいが、記録は落としてはいけない——この優先順位は、監査性を売りにする以上ゆずれません。
途中でやめるとき、何が記録に残るのか
探索は途中で打ち切られることがあります。時間がかかりすぎた場合、利用者が会話を切った場合、ツリーが読めなかった場合などです。どの経路で終わっても、ここまでに辿った足跡と終了の種別が必ず記録に残ります。「途中で止まったから何も残っていない」という状態を作らないために、記録の書き出しはループの外側の後始末処理に一本化してあります。
| 終わり方 | 意味 | 利用者に見えること |
|---|---|---|
| 見つかった | 答えにたどり着いた | 回答と足跡が表示される |
| 見つからなかった | AI自身が「該当なし」と申告した | 正直に保留し、確認に回す案内 |
| 時間切れ | 制限時間を超えた | すぐに答えられない旨を伝える |
| 会話が切れた | 利用者側が離脱した | (表示なし・記録のみ) |
| ツリーが読めない | ナレッジの取得に失敗した | エラーとして案内 |
| キー側の事情 | お客様のAPIキーで呼び出せなかった | 設定の確認を促す案内 |
実装して初めて分かった「教科書に載っていない」こと
論文や設計書どおりに作れば動く、とはいきませんでした。実際にぶつかって直したものを、いくつか共有します。
- 空の思考ブロックで通信が拒否される:AIの応答をそのまま次のリクエストへ送り返すと、中身が空の思考ブロックが混ざっていてエラーになることがありました。送り返す前に、中身のないブロックを取り除く処理を挟んで解決しています。
- 知らない道具を要求されると会話が壊れる:AIがまれに、渡していない道具を使おうとします。その要求を黙って無視すると、返す内容が空になって通信自体が失敗しました。「その道具は使えません」と明示的に返して仕切り直させる、かつ同じことが続いたら打ち切る、という2段構えにしています。
- 回答が長いこと自体が遅さの原因:最終ターンの出力上限に張り付いて、その1ターンだけで15秒かかっていたことがありました。「2〜4文で答える」という指示と整合する長さまで上限を下げたところ、体感が大きく変わりました。速度は計算量だけでなく、生成する文字数でも決まります。
- 音声とテキストでは適切な長さが違う:読む文章と聞く文章では、ちょうどよい長さが異なります。同じ探索エンジンを使いつつ、チャネルごとに出力の上限と指示文を切り替えています。
どれも設計書には書かれていない、実際に動かして初めて出てきた問題です。AIを組み込んだシステムの品質は、通常系の設計よりも、こうした異常系の積み重ねで決まります。
結局、RAG と何が違うのか?
実装を追ったうえで両者を並べると、違いは「精度」という一語には収まりません。何を残すか、いつ処理するか、どこで止まるか——設計思想そのものが異なります。
| 観点 | RAG(検索拡張生成) | Corpus2Skill(ナビゲーション探索) |
|---|---|---|
| 事前処理 | 分割してベクトル化し、索引を作る | 要約・グループ化してツリーのファイル群を作る |
| 会話中の処理 | 類似度で断片を検索して渡す | 要約を読み、枝を選んで降りる(数往復) |
| AIの役割 | 渡された断片から回答を書く | どこへ降りるかを判断し、到達後に回答を書く |
| 条件分岐 | 似た条項が混ざりやすい | 分岐点でどちらか一方を選び切る |
| 残る記録 | 参照した断片の一覧 | 選んだノード・選択理由・所要時間の連なり |
| 答えがないとき | 何かしらの断片を返しがち | 該当なしと申告して保留できる |
| 弱点 | 文書の構造を失う | 往復のぶん時間がかかる/ツリー品質が文書に依存 |
最後の行は正直に書いておきます。ツリーを一段ずつ降りる方式は、断片を一発で引くRAGより原理的に時間がかかります。だからこそ、先読みや相槌、途中経過の表示といった「待たせ方」の工夫に手間をかけています。そして、元の文書の書かれ方が雑であれば、そこから作られるツリーもその曖昧さを引き継ぎます。技術で全部は解けません。
まとめ
- コンパイルは6ステップ(抽出 → チャンク化 → 要約 → グループ化 → ファイル生成 → 切り替え)。文書の登録時に一度だけ走ります。
- ツリーはただのフォルダとテキストファイルとして表現され、人が直接開いて確認できます。
- ナビゲーションは「文書を1つ取ってくる」という道具1つだけを繰り返すループ。上限は8回。
- 一段降りるたびにノード・選択理由・所要時間を記録し、これがそのまま監査記録になります。
- 終了の判定は「葉に到達したか」ではなく「AI自身が該当なしと申告したか」で行います。
- 新バージョンは別の場所に作ってから一気に切り替えるため、更新中も稼働中のツリーは壊れません。
VoiceCore は、この Corpus2Skill の実装を中核に据えたお問い合わせ対応AIです。お預かりした規約・マニュアル・FAQをナレッジツリーへコンパイルし、会話中にそのツリーを辿って根拠つきで答え、「どのノードを、なぜ選び、どの文書に至ったか」を足跡として残します。仕組みの全体像はナレッジツリー探索の仕組みで、自社の文書でどう動くかを確かめたい場合はデモのご相談からご案内しています。