【実践編】Corpus2Skill 方式でつくる社内規程 AI チャットボット 〜SQLite × Claude API で「検索しない」知識ボットをゼロから実装〜

2026 年 7 月 7 日 | AI・機械学習

【技術相談】本件の内容に関して 30 分間の無料相談承ります →

【実践編】Corpus2Skill 方式でつくる社内規程 AI チャットボット 〜SQLite × Claude API で「検索しない」知識ボットをゼロから実装〜

この記事をシェア

今度は、自分の手で動かす番です

前編では「なぜ RAG が頭打ちになるのか」と Corpus2Skill の設計思想を、後編では公式ライブラリを使ったコンパイルとサーブの手順を解説しました。

今回の実践編は、その続きであり、ある意味での「種明かし」でもあります。ライブラリには頼りません。SQLite と Claude API だけを使って、コーパス・ナビゲーション方式のチャットボットをゼロから組み立てます

なぜわざわざ自作するのか。理由は 2 つあります。

  • 仕組みが腹落ちする:「地図を作る」「エージェントが歩く」という 2 段階を自分のコードで書くと、Corpus2Skill が何をやっているのかが具体的に分かります。ライブラリのオプションの意味も、以後は迷わなくなります。
  • 自社データにそのまま応用できる:業務データは、たいてい既に何らかのデータベースに入っています。「まとまった情報を DB にしまい、その上にナビゲーション層を被せる」という今回の構成は、既存システムへの後付けがしやすい形です。

題材には、どの会社にもある「社内規程集」(就業規則・経費精算規程・情報セキュリティ規程・リモートワーク規程)を使います。前編で書いたとおり、章立てと条件分岐がはっきりした文書群こそ、この方式が最も効く領域だからです。コードはすべて Python で、コピーして順に実行すれば手元で動くように書いています。


今回つくるものの全体像

作るものは、次の 2 つのフェーズに分かれます。前編で解説した Compile Time / Serve Time の考え方そのままです。

【コンパイル(事前・1回だけ)】
  規程データ ──→ SQLite documents テーブル(原文の一次ソース)
                     │
                     ▼  LLM が各階層の「案内文」を生成
              SQLite nodes テーブル(スキルツリー = 歩ける地図)

【サーブ(質問のたび)】
  ユーザーの質問
      │
      ▼
  Claude(エージェント)
      ├─ list_children ツール ──→ nodes テーブル(地図を辿る)
      ├─ read_document ツール ──→ documents テーブル(原文を確認)
      └─ 根拠付きの回答(歩いたログ=推論の透明性)

ポイントは 3 つです。

  • ベクトル検索は一切使いません。埋め込みモデルもベクトル DB も不要です。使うのは Python 標準ライブラリの sqlite3 と、Anthropic の公式 SDK だけです。
  • DB は「原文の一次ソース」と「地図」の 2 役を担います。documents テーブルが原文を、nodes テーブルがスキルツリーを保持します。
  • エージェントはツール経由でしか情報に触れません。地図を見る(list_children)、原文を読む(read_document)という 2 つのツールを行き来しながら、自分の判断で回答に到達します。

Step 0:環境準備

必要なのは Python 3.10 以降と、Anthropic の API キーだけです。

pip install anthropic
export ANTHROPIC_API_KEY="sk-ant-..."

SQLite は Python に標準搭載されているため、インストール不要です。「データベース」と聞くと身構えますが、ファイル 1 個で完結する SQLite は今回の用途に十分ですし、本番で PostgreSQL 等に載せ替えるときもテーブル設計はそのまま使えます。


Step 1:題材データを用意する — 架空の社内規程集

まず「まとまった情報」を用意します。実務では既存の規程 PDF や Wiki から起こすことになりますが、ここでは記事内で完結するよう、架空の規程集を Python の辞書で定義します。重要なのは、文書の階層構造(どの規程の・どの章の・どの条項か)をデータとして残しておくことです。前編で述べたとおり、この骨格こそがナビゲーションの命綱になります。

# corpus.py — 架空の社内規程データ(抜粋)
DOCUMENTS = [
    # ---- 就業規則 ----
    {
        "id": "reg-101",
        "regulation": "就業規則",
        "chapter": "第4章 退職",
        "title": "退職の手続き",
        "content": "従業員は、自己都合により退職しようとするときは、退職日の30日前までに"
                   "所属長を経由して退職届を提出しなければならない。退職日までに業務の引き継ぎを"
                   "完了し、会社から貸与された物品一切を総務部に返却すること。貸与物品の詳細な"
                   "返却手順は、情報セキュリティ規程 第3章の定めに従う。",
    },
    {
        "id": "reg-102",
        "regulation": "就業規則",
        "chapter": "第4章 退職",
        "title": "退職時の年次有給休暇",
        "content": "退職予定者が残存する年次有給休暇の取得を申し出た場合、会社は業務の引き継ぎに"
                   "支障のない範囲でこれを認める。ただし、退職日を超えての取得はできない。",
    },
    # ---- 情報セキュリティ規程 ----
    {
        "id": "reg-301",
        "regulation": "情報セキュリティ規程",
        "chapter": "第3章 貸与機器の管理",
        "title": "貸与PCの返却",
        "content": "退職または長期休職に際して、貸与PCは最終出社日までに情報システム部へ直接"
                   "返却しなければならない。総務部ではなく情報システム部である点に注意すること。"
                   "返却前に個人データの削除を行ってはならない。データ消去は情報システム部が"
                   "所定の手順で実施する。",
    },
    {
        "id": "reg-302",
        "regulation": "情報セキュリティ規程",
        "chapter": "第3章 貸与機器の管理",
        "title": "アカウントの停止",
        "content": "退職者のシステムアカウントは、最終出社日の翌営業日までに情報システム部が"
                   "停止する。ただし、退職者から引き継ぎ資料の共有依頼がある場合は、所属長の"
                   "承認を条件に最大5営業日の猶予を認める。",
    },
    # ---- 経費精算規程 ----
    {
        "id": "reg-201",
        "regulation": "経費精算規程",
        "chapter": "第2章 出張旅費",
        "title": "宿泊費の上限",
        "content": "国内出張の宿泊費は1泊あたり12,000円を上限として実費精算とする。ただし、"
                   "繁忙期の都市部など上限内での確保が困難な場合は、事前に所属長の承認を得る"
                   "ことで15,000円まで認める。",
    },
    # ---- リモートワーク規程 ----
    {
        "id": "reg-401",
        "regulation": "リモートワーク規程",
        "chapter": "第2章 勤務環境",
        "title": "在宅勤務手当",
        "content": "在宅勤務を月10日以上行った従業員に対し、月額3,000円の在宅勤務手当を支給する。"
                   "在宅勤務日数は勤怠システムの記録に基づき自動集計される。",
    },
    # 実際には各規程の全条項ぶんを同じ形式で用意します
]

あえて仕込みを 1 つ入れてあります。就業規則(reg-101)は「貸与物品は総務部に返却」と読める書き方をしつつ、情報セキュリティ規程(reg-301)が「貸与 PC だけは情報システム部へ」と例外を定めている、という構造です。複数文書にまたがる条件分岐——まさに RAG が取りこぼしやすいパターンを、後ほど動作確認でぶつけます。


Step 2:SQLite に格納する — 原文の一次ソース

用意したデータをデータベースにしまいます。documents テーブルは「原文の一次ソース」です。チャンク化はしません。条項という意味のまとまりを 1 行としてそのまま保存するのがこの方式の肝です。

# build_db.py
import sqlite3
from corpus import DOCUMENTS

conn = sqlite3.connect("kitei.db")
conn.execute("""
CREATE TABLE IF NOT EXISTS documents (
    id         TEXT PRIMARY KEY,   -- 例: reg-301
    regulation TEXT NOT NULL,      -- 規程名(例: 情報セキュリティ規程)
    chapter    TEXT NOT NULL,      -- 章(例: 第3章 貸与機器の管理)
    title      TEXT NOT NULL,      -- 条項タイトル
    content    TEXT NOT NULL       -- 原文
)
""")
conn.executemany(
    "INSERT OR REPLACE INTO documents VALUES (:id, :regulation, :chapter, :title, :content)",
    DOCUMENTS,
)
conn.commit()
print(f"{len(DOCUMENTS)} 件の文書を格納しました")

実務データを流し込む場合も、この 4 カラム(所属する大分類・中分類・タイトル・本文)に落とし込めれば、以降のコードはそのまま動きます。マニュアルなら「製品 → 機能 → 操作手順」、FAQ 集なら「カテゴリ → サブカテゴリ → Q&A」という具合です。


Step 3:地図をコンパイルする — スキルツリーの生成

ここが Corpus2Skill 方式の心臓部です。文書群を「規程 → 章 → 条項」の 3 階層ツリーとして nodes テーブルに構築し、各階層に「この下には何があるか」を伝える案内文(サマリー)を LLM に書かせます。本棚でいう書架の見出しと棚札にあたるものです。

階層そのものは regulation / chapter カラムから機械的に作れるので、LLM の仕事は案内文の生成だけです。ここが公式ライブラリとの割り切りの違いで、クラスタリングによる自動階層化は行わず、文書が元々持っている構造をそのまま地図にします。業務文書は既に人間が整理した階層を持っているので、実務ではこれで十分に機能します。

# compile_tree.py
import sqlite3
import anthropic

client = anthropic.Anthropic()
conn = sqlite3.connect("kitei.db")

conn.execute("""
CREATE TABLE IF NOT EXISTS nodes (
    id        INTEGER PRIMARY KEY AUTOINCREMENT,
    parent_id INTEGER,        -- NULL ならルート直下(=規程レベル)
    label     TEXT NOT NULL,  -- 例: 「情報セキュリティ規程」「第3章 貸与機器の管理」
    summary   TEXT,           -- LLM が生成する案内文
    doc_id    TEXT            -- リーフのみ: documents.id への参照
)
""")
conn.execute("DELETE FROM nodes")  # 再コンパイル時は作り直す

def write_summary(label: str, texts: list[str]) -> str:
    """階層の「案内文」を生成する。地図の棚札にあたる部分。"""
    joined = "\n---\n".join(texts)[:8000]
    res = client.messages.create(
        model="claude-opus-4-8",  # コンパイルはコスト重視で claude-haiku-4-5 に替えても良い
        max_tokens=300,
        system="あなたは社内文書の目次を作る司書です。",
        messages=[{
            "role": "user",
            "content": f"次の文書群「{label}」が扱う内容を、AI エージェントが"
                       f"「この階層に進むべきか」を判断するための案内文として"
                       f"2〜3文で要約してください。含まれる例外規定や条件分岐には"
                       f"必ず触れてください。\n\n{joined}",
        }],
    )
    return res.content[0].text

# 規程 → 章 → 条項 の3階層を構築する
regulations = [r[0] for r in conn.execute(
    "SELECT DISTINCT regulation FROM documents")]

for reg in regulations:
    rows = conn.execute(
        "SELECT chapter, title, content FROM documents WHERE regulation = ?",
        (reg,)).fetchall()

    # 第1階層: 規程ノード
    reg_summary = write_summary(reg, [f"{c}/{t}:{body}" for c, t, body in rows])
    reg_id = conn.execute(
        "INSERT INTO nodes (parent_id, label, summary) VALUES (NULL, ?, ?)",
        (reg, reg_summary)).lastrowid
    print(f"[規程] {reg}")

    # 第2階層: 章ノード
    chapters = sorted({c for c, _, _ in rows})
    for ch in chapters:
        ch_rows = [(t, body) for c, t, body in rows if c == ch]
        ch_summary = write_summary(f"{reg} {ch}",
                                   [f"{t}:{body}" for t, body in ch_rows])
        ch_id = conn.execute(
            "INSERT INTO nodes (parent_id, label, summary) VALUES (?, ?, ?)",
            (reg_id, ch, ch_summary)).lastrowid
        print(f"  [章] {ch}")

        # 第3階層: 条項ノード(リーフ)。原文への参照 doc_id を持つ
        for doc_id, title in conn.execute(
            "SELECT id, title FROM documents WHERE regulation = ? AND chapter = ?",
            (reg, ch)):
            conn.execute(
                "INSERT INTO nodes (parent_id, label, doc_id) VALUES (?, ?, ?)",
                (ch_id, title, doc_id))

conn.commit()
print("コンパイル完了")

要約プロンプトの中で 「例外規定や条件分岐には必ず触れてください」と指示している点に注目してください。「ただし〜の場合は除く」という一文が案内文に含まれているかどうかで、後段のエージェントが正しい枝を選べるかが大きく変わります。私が実案件でチューニングするときも、真っ先に手を入れるのはこの案内文の生成プロンプトです。

コンパイルは文書更新時にだけ走らせる事前処理なので、多少時間とコストがかかっても構いません。逆に言えば、ここで良い地図を作っておくほど、サーブ時の探索は速く・安く・正確になります


Step 4:地図を歩くエージェントを実装する

いよいよサーブ側です。Claude にツールを 2 つ渡し、自分の判断で地図を歩かせます。Claude API のツール使用(Tool Use)を使った、いわゆる「エージェントループ」の実装です。

4-1. ツールの定義

ツールは「地図を見る」と「原文を読む」の 2 つだけです。description には、ツールの機能説明だけでなく「いつ使うべきか」を書き込むのがコツです。モデルはこの説明文を読んで行動を決めます。

# bot.py(前半)
import json
import sqlite3
import anthropic

client = anthropic.Anthropic()
conn = sqlite3.connect("kitei.db")

TOOLS = [
    {
        "name": "list_children",
        "description": "スキルツリーのあるノード直下の子ノード一覧(ラベルと案内文)を返す。"
                       "node_id を省略するとルート直下(規程の一覧)を返す。"
                       "質問に回答する前に、必ずこのツールでルートから地図を辿ること。",
        "input_schema": {
            "type": "object",
            "properties": {
                "node_id": {
                    "type": "integer",
                    "description": "親ノードの ID。省略時はルート直下を返す",
                },
            },
        },
    },
    {
        "name": "read_document",
        "description": "リーフノードに紐づく規程原文の全文を返す。"
                       "回答の根拠は必ずこのツールで原文を確認してから引用すること。"
                       "記憶や推測で条文の内容を答えてはならない。",
        "input_schema": {
            "type": "object",
            "properties": {
                "doc_id": {
                    "type": "string",
                    "description": "文書 ID(例: reg-301)",
                },
            },
            "required": ["doc_id"],
        },
    },
]

def run_tool(name: str, args: dict) -> str:
    """ツール呼び出しを SQLite への問い合わせに変換する"""
    if name == "list_children":
        rows = conn.execute(
            "SELECT id, label, summary, doc_id FROM nodes WHERE parent_id IS ?",
            (args.get("node_id"),)).fetchall()
        return json.dumps(
            [{"node_id": r[0], "label": r[1], "summary": r[2], "doc_id": r[3]}
             for r in rows],
            ensure_ascii=False)

    if name == "read_document":
        row = conn.execute(
            "SELECT regulation, chapter, title, content FROM documents WHERE id = ?",
            (args["doc_id"],)).fetchone()
        if row is None:
            return "該当する文書がありません。list_children で別のルートを探索してください。"
        return f"【{row[0]}|{row[1]}|{row[2]}】\n{row[3]}"

    return f"不明なツール: {name}"

4-2. システムプロンプトとエージェントループ

システムプロンプトには、前編で解説した Corpus2Skill の行動原則——ナビゲーション・原文主義・バックトラック・根拠提示——をそのまま言語化して埋め込みます。

# bot.py(中盤)
SYSTEM = """あなたは社内規程の案内係 AI です。次のルールを厳守してください。

1. 回答の前に、必ず list_children でスキルツリーをルートから辿り、
   関係のありそうな階層を探索する
2. 回答の根拠は read_document で原文を確認してから引用する。
   記憶で答えてはならない
3. 辿った階層が的外れだったら、一つ上の階層に戻って別のルートを試す
   (バックトラック)
4. 質問が複数の規程にまたがる可能性を常に疑い、関係しそうなルートは
   すべて確認する
5. 回答には根拠となった文書 ID(例: reg-301)を必ず添える
6. どのルートを探しても見つからなければ、正直に「規程に記載がない」と答える
"""

def ask(messages: list) -> anthropic.types.Message:
    """ツール呼び出しがなくなるまで回すエージェントループ"""
    while True:
        response = client.messages.create(
            model="claude-opus-4-8",
            max_tokens=2000,
            system=SYSTEM,
            tools=TOOLS,
            messages=messages,
        )
        if response.stop_reason != "tool_use":
            return response  # 探索が終わり、最終回答が出た

        # アシスタントのツール呼び出しを履歴に積む
        messages.append({"role": "assistant", "content": response.content})

        # ツールを実行し、結果をまとめて1つの user メッセージで返す
        results = []
        for block in response.content:
            if block.type == "tool_use":
                print(f"  [歩行ログ] {block.name}"
                      f"({json.dumps(block.input, ensure_ascii=False)})")
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": run_tool(block.name, block.input),
                })
        messages.append({"role": "user", "content": results})

ループの構造は単純です。stop_reasontool_use である限り、ツールを実行して結果を返し続け、モデルがツールを呼ばなくなったら(=回答が確定したら)抜ける。それだけです。途中の print が出力する「歩行ログ」が、前編で強調した推論の透明性の実体になります。どの階層を通って原文に辿り着いたかが、そのまま監査可能な足跡として残ります。

1 点だけ実装上の注意を。複数のツール呼び出しが 1 回の応答に含まれる場合、その結果はすべて 1 つの user メッセージにまとめて返す必要があります。ばらして返すと、モデルが並列ツール呼び出しをやめてしまう副作用があります。


Step 5:チャットボットに仕立てる

最後に会話ループを被せれば、チャットボットの完成です。会話履歴 messages を持ち回ることで、「さっきの件だけど…」という文脈を踏まえた追い質問にも答えられます。

# bot.py(後半)
def chat():
    messages = []
    print("社内規程チャットボットです。exit で終了します。")
    while True:
        question = input("\nあなた> ").strip()
        if question.lower() in ("exit", "quit"):
            break
        messages.append({"role": "user", "content": question})
        response = ask(messages)
        answer = next(
            (b.text for b in response.content if b.type == "text"), "")
        messages.append({"role": "assistant", "content": response.content})
        print(f"\nボット> {answer}")

if __name__ == "__main__":
    chat()

実行は 3 ステップです。

python build_db.py      # Step 2: 原文を DB へ
python compile_tree.py  # Step 3: 地図のコンパイル(文書更新時のみ再実行)
python bot.py           # Step 5: チャット開始

動作確認 — RAG 泣かせの質問をぶつける

Step 1 で仕込んだ「複数規程にまたがる条件分岐」を突く質問をしてみます。実行すると、次のような流れの応答が得られます(出力例)。

あなた> 退職するとき、会社から借りているノートPCはどこに返せばいいですか?

  [歩行ログ] list_children({})
  [歩行ログ] list_children({"node_id": 1})        ← 就業規則へ
  [歩行ログ] list_children({"node_id": 2})        ← 第4章 退職へ
  [歩行ログ] read_document({"doc_id": "reg-101"})  ← 原文確認
  [歩行ログ] list_children({"node_id": 9})        ← 情報セキュリティ規程へ移動
  [歩行ログ] list_children({"node_id": 10})       ← 第3章 貸与機器の管理へ
  [歩行ログ] read_document({"doc_id": "reg-301"})  ← 例外規定の原文確認

ボット> 貸与ノートPCの返却先は「情報システム部」です(総務部ではありません)。

就業規則 第4章(reg-101)では貸与物品一般を総務部へ返却すると定めつつ、
貸与PCなどの機器については情報セキュリティ規程 第3章の定めに従うと
されています。その情報セキュリティ規程(reg-301)では、貸与PCは
最終出社日までに情報システム部へ直接返却することと定められています。

また、返却前にご自身でデータを削除してはいけない点にご注意ください。
データ消去は情報システム部が所定の手順で実施します。

根拠: reg-101(就業規則 第4章 退職の手続き)、
      reg-301(情報セキュリティ規程 第3章 貸与PCの返却)

注目すべきは歩行ログです。エージェントはまず就業規則の退職の章に降り、原文の「情報セキュリティ規程 第3章の定めに従う」という参照を読んで、自分の判断で別の規程ツリーに渡り直しています。類似度検索であれば「PC 返却」に一番"似ている"どちらか片方のチャンクを引いて終わるところを、ナビゲーション方式は文書間の参照関係を素直に追跡できる。前編で理屈として説明した強みが、そのままログに現れています。

もう 1 つ、規程に存在しないこと(例:「副業の申請方法は?」)を聞くと、ツリーを一通り探索した上で「規程に記載がありません」と答えます。ハズレのチャンクから無理やり回答をでっち上げるという RAG 特有の事故が、構造的に起きにくいのです。


コスト最適化 — プロンプトキャッシュを効かせる

この方式の弱点は、1 つの質問に対して API を複数回呼ぶことです。体感では 1 質問あたり 4〜8 ターン。素朴に回すと、毎ターン「システムプロンプト+ツール定義+会話履歴」を全部再送信することになります。

ここで効くのが Anthropic API のプロンプトキャッシュです。安定している先頭部分(ツール定義・システムプロンプト)にキャッシュポイントを打つと、2 ターン目以降はその部分が約 1/10 の料金で処理されます。さらに、コンパイル済みの「ルート階層の地図」をシステムプロンプトに焼き込んでおくと、毎回の最初の list_children 呼び出しも節約できます。

# ルート階層の案内文を起動時に1度だけ組み立てる
ROOT_MAP = "\n".join(
    f"- node_id={r[0]}: {r[1]} — {r[2]}"
    for r in conn.execute(
        "SELECT id, label, summary FROM nodes WHERE parent_id IS NULL"))

def ask(messages: list) -> anthropic.types.Message:
    while True:
        response = client.messages.create(
            model="claude-opus-4-8",
            max_tokens=2000,
            system=[{
                "type": "text",
                "text": SYSTEM + "\n\n# 規程マップ(ルート階層)\n" + ROOT_MAP,
                "cache_control": {"type": "ephemeral"},  # ← ここまでをキャッシュ
            }],
            tools=TOOLS,
            messages=messages,
        )
        # …ループ本体は Step 4 と同じ…

効果は response.usage で確認できます。cache_read_input_tokens が 2 ターン目以降に増えていれば、キャッシュが効いています。

print("キャッシュ書き込み:", response.usage.cache_creation_input_tokens)
print("キャッシュ読み取り:", response.usage.cache_read_input_tokens)

注意点を 2 つ。

  • キャッシュはプレフィックス一致です。キャッシュポイントより前(ツール定義やシステムプロンプト)に 1 バイトでも変化があると無効になります。現在時刻の埋め込みなどはご法度です。
  • 短すぎるプレフィックスはキャッシュされません。モデルごとに最低トークン数があり(Claude Opus 4.8 では 4,096 トークン)、それ未満だとエラーも出ずに素通りします。規程マップを焼き込む構成なら自然に超えますが、cache_read_input_tokens が常にゼロの場合はまずここを疑ってください。

モデルの使い分けも有効です。目安として、コンパイル(案内文の生成)は Claude Haiku 4.5 で十分なことが多く、サーブ側の「どの枝に進むか」という判断には上位モデルを残す、という配分がコスト対効果に優れます。探索の質はほぼサーブ側のモデルで決まります。


運用のポイント — 実案件で効いてくる 3 つのこと

1. 文書更新と再コンパイルをセットにする

規程が改定されたら、documents テーブルを更新して compile_tree.py を再実行します。地図と原文がずれると、案内文は古いのに原文は新しい(またはその逆)という嫌な不整合が生まれます。実運用では、文書管理システムの更新をトリガーに再コンパイルを自動実行する構成(例えば GitHub Actions や EventBridge)にしておくと事故が減ります。差分のある規程ノード配下だけ作り直す部分再コンパイルにすれば、コストも抑えられます。

2. 歩行ログを保存する

今回は print で流しただけですが、歩行ログはぜひ DB やログ基盤に保存してください。「なぜこの回答になったのか」を後から説明できることが、この方式をエンタープライズで採用する最大の理由だからです。加えて、ログを眺めるとエージェントが迷いやすい階層(何度もバックトラックが発生する箇所)が見えてきます。そこは案内文の書き直し対象です。

3. RAG と併用する

前編の繰り返しになりますが、この方式は万能ではありません。章立てのないチャットログや最新ニュースは、素直に RAG や全文検索に任せるべき領域です。現実的な本番構成は、入口のエージェントが質問の性質を判定し、「規程・手順はナビゲーション方式、それ以外は RAG」へルーティングする 2 段構えです。今回作った ask() は独立した関数なので、既存のチャットボットの 1 ルートとして組み込むのも難しくありません。


まとめ — 「検索」を書かずに検索システムができた

振り返ると、今回書いたのは次の 4 つだけです。

  1. 原文を意味のまとまりごとに SQLite へ格納する(Step 1〜2)
  2. 階層ごとの「案内文」を LLM に書かせて地図を作る(Step 3)
  3. 地図を見る/原文を読む、の 2 ツールを Claude に渡す(Step 4)
  4. 会話ループを被せる(Step 5)

埋め込みベクトルも類似度計算も登場していません。それでも、複数規程にまたがる条件分岐を正しく追い、根拠の文書 ID と歩行ログ付きで回答するチャットボットができました。検索アルゴリズムを書く代わりに、エージェントが歩ける地図を用意する——Corpus2Skill が提唱した発想の転換は、この 200 行足らずのコードにすべて詰まっています。

手元で動かしたら、次は自社のデータで試してみてください。マニュアル、FAQ、業務手順書——「構造のある文書群 × 正確さが求められる業務」に当てはまるものなら、DOCUMENTS の中身を差し替えるだけで動き始めます。理論編からもう一度おさらいしたい方は、前編・後編もあわせてどうぞ。

← 前編:設計思想と RAG の限界 後編:公式ライブラリの徹底実践ガイド →


目次

  1. 今度は、自分の手で動かす番です
  2. 今回つくるものの全体像
  3. Step 0:環境準備
  4. Step 1:題材データを用意する — 架空の社内規程集
  5. Step 2:SQLite に格納する — 原文の一次ソース
  6. Step 3:地図をコンパイルする — スキルツリーの生成
  7. Step 4:地図を歩くエージェントを実装する
  8. Step 5:チャットボットに仕立てる
  9. 動作確認 — RAG 泣かせの質問をぶつける
  10. コスト最適化 — プロンプトキャッシュを効かせる
  11. 運用のポイント — 実案件で効いてくる 3 つのこと
  12. まとめ — 「検索」を書かずに検索システムができた

参考文献


関連キーワード

  • Corpus2Skill
  • AI チャットボット自作
  • SQLite
  • Claude API
  • ツール使用(Tool Use)
  • コーパスナビゲーション
  • RAG 代替技術
  • 社内規程検索

この記事が役に立ったらシェアしてください

コーパス・ナビゲーション型チャットボットの実装ノウハウを、より多くのエンジニアと共有しましょう。

カテゴリ

AI・機械学習

公開日

2026 年 7 月 7 日

💬 無料技術相談のご案内

この記事でご紹介した技術について、導入や活用のご相談を30 分間無料承っております。

  • 「自社でも導入できる?」といった技術的な疑問
  • 既存システムとの連携・移行に関するご相談
  • コスト感や導入スケジュールの目安

30 年以上の IT 経験をもとに、率直にお答えします。強引なセールスや勧誘は一切ありません。

野口真一 野口真一

お気軽にご相談ください

記事に関するご質問や、AI・IT 技術導入のご相談など、お気軽にお問い合わせください。