Amazonのアソシエイトとして、当ブログは適格販売により収入を得ています。
この記事は、Geminiとの相談結果が多分に含まれています。
利用する場合は、自己責任でお願いします。
記事の内容は以下のシステムでテストされています。
Ubuntu24.04.4LTS
Ryzen7 8700G(Radeon 780M)
メインメモリ96GB(4800MT/s)
前回の記事でAIエージェントの作り方をやってみました。
今回はその続きです。
- Webからの情報の取得
- 情報の構造化
- 構造化されたスキルデータのベクトル化 → 保存
- 検索拡張生成(RAG: Retrieval-Augmented Generation)
- Pi Coding Agentに利用してもらう
前回の記事では、「Webからの情報の取得」と「情報の構造化」まででした。
今回の記事では残りの部分をやっていきます。
構造化されたスキルデータのベクトル化 → 保存
正直よく分かっていません。
取得したテキストベースの情報をLLMが理解できるベクトルに変換するのだそうです。
そのベクトル化する処理(Embeddingというらしい)に「unsloth/bge-m3」というモデルを使い、ベクトルデータベースに「chromadb」というものを使います。
(スクリプトをみたらこの処理はCPUになってました・・・。)
ここからAIエージェントに働いてもらうわけですが、基本的には
- AGENTS.mdでプロジェクトのゴールを明示
- TODO.mdで進捗を管理
- プロンプトで細かい指示を出す
という方針になります。
ただ、基本的ルールを記述する「AGENTS.md」は前回の記事までの内容にしか対応していません。
なので、今回のプロジェクトゴールを書き直します。
# Project Goal: Web-to-Skill Extractor
本プロジェクトの最終目標は、ウェブ上の記事からテキストを抽出し、AIエージェントが自律的に検索・活用できるRAG(検索拡張生成)パイプラインを構築することです。
以下の4つのフェーズを通じて、段階的に開発を進めます。
- **Phase 1: Webからの情報の取得**
- 指定URLからノイズ(広告等)を除外したテキストを抽出する。
- **Phase 2: 情報の構造化**
- 別ファイル `EXTRACTION_PROMPT.md` の定義をシステムプロンプトとして読み込み、LLMを用いて抽出テキストを「実践可能なスキル」として構造化・保存する。
- **Phase 3: スキルデータのベクトル化と保存**
- 構造化されたMarkdownデータをセクションごとにチャンク分割し、Embeddingモデルを用いてベクトルDB(ChromaDB等)に保存する。
- **Phase 4: 検索拡張生成(RAG)の構築**
- ユーザーのクエリに対してベクトルDBを検索し、関連するスキルデータを参照してLLMが回答を生成する仕組みを実装する。
※ あなたが実行すべき現在のタスクと範囲は、常に `TODO.md` によって厳密に管理・制御されます。目標の全体像を把握した上で、TODOの指示を超えた実装は勝手に行わないでください。Phase3とPhase4が追加されています。
そしてTODO.mdに以下を追記
## Phase 3: スキルデータのベクトル化と保存
- [ ] 9. ベクトルDBおよびEmbedding用パッケージの追加
- `uv add chromadb sentence-transformers` を実行する。
- [ ] 10. ベクトル化ロジック(`vectorizer.py`)の実装
- 構造化された `{title}_skill.md` を読み込み、Markdownのセクション(##)ごとにチャンク分割する。
- `unsloth/bge-m3` モデルを用いてCPU上でテキストをベクトル化し、ChromaDBに保存する。
- [ ] 11. 実行フローへの統合
- `main.py` を修正し、「Web抽出 → LLM解析・構造化保存 → ベクトルDBへの保存」の統合パイプラインを完成させる。
- [ ] 12. ベクトル検索のテスト実行
- ChromaDBに保存されたデータを検索し、正しく取得できるかを確認する(コンソール出力は200文字制限を厳守)。そして、実行プロンプトですが、
現在のディレクトリにある `AGENTS.md` と `TODO.md` を読み込んで、ルールと現在のタスクを理解してください。
TODO.md に新しいタスクとして「Phase 3: スキルデータのベクトル化と保存(ステップ9〜12)」を追記しました。
内容を確認し、自律的に実装を進めてください。
【実装にあたっての指示】
1. 最初に uv add chromadb sentence-transformers を実行して必要なパッケージを追加してください。
2. 「unsloth/bge-m3(変更不可厳守)」でチャンク分割とベクトル化を行い、ChromaDBへ保存する処理は vectorizer.py として新しく作成してください。
3. main.py を書き換えて、Web抽出からベクトル保存までのパイプラインが自動で繋がるように統合してください。
4. 最後に検索テストを実行してください。テスト時のコンソール出力も、AGENTS.mdのルール通り長文を避け、200文字程度にスライスするよう徹底してください。
【⚠️既存ファイル修正時の絶対ルール】
main.py などの既存ファイルを書き換える際、部分修正(edit)を使おうとするとエラーになりやすいため、ファイル全体の上書き(write)を行ってください。
その際、既存の import 文(llm_clientなど)や既存の関数を絶対に消去しないよう、細心の注意を払って完全なコードを出力してください。
準備ができたら、ステップ9から順に実行をお願いします。以上で実行に移ります。
これでテキストデータをベクトル化することができるようになりました。
検索拡張生成(RAG: Retrieval-Augmented Generation)
ここからベクトルデータベースに書き込むツールをエージェントに作ってもらいます。
AGENTS.mdはもう追記したり変更したりすることは無いです。
TODO.mdにはこれからやってもらうことのリストを追記します。
## Phase 4: 検索拡張生成(RAG)の構築
- [ ] 13. RAGロジックの実装(`rag_agent.py` の作成)
- ユーザーの質問(クエリ)を受け取り、`vectorizer.py` を使って ChromaDB から関連チャンクを検索・取得する。
- 取得したチャンクをコンテキスト(背景知識)として結合し、ユーザーの質問と共にLLM(`llm_client.py`)へ渡して回答を生成する関数を実装する。
- 必要に応じて `llm_client.py` にRAG問い合わせ用の新しいメソッドを追加する。
- [ ] 14. 実行フローへの統合
- `main.py` に新しく `rag` モードを追加し、`uv run src/main.py rag "<質問>"` の形式でRAGを実行できるようにする。
- [ ] 15. RAGの最終テスト実行
- 保存されたスキルデータに関する質問でRAGモードをテストし、DB内の知識を踏まえた回答が生成されるか確認する。そして実行プロンプトは以下のとおり。
現在のディレクトリにある `AGENTS.md` と `TODO.md` を読み込んで、ルールと現在のタスクを理解してください。
TODO.md にプロジェクトの最終段階となる「Phase 4: 検索拡張生成(RAG)の構築(ステップ13〜15)」を追記しました。
内容を確認し、自律的に実装を進めてください。
【実装にあたっての指示】
1. 新しく rag_agent.py を作成し、RAGのコアロジックを実装してください。ユーザーの質問を元に vectorizer.py でChromaDBを検索し、得られたテキストをコンテキストとしてLLMに渡して回答を生成する処理です。
2. LLMとの通信には既存の llm_client.py を活用してください。必要であれば llm_client.py にRAG用の新しいメソッド(システムプロンプトとコンテキストを組み合わせて渡す処理など)を追加・修正しても構いません。
3. main.py を更新し、uv run src/main.py rag "任意の質問" のように rag モードで実行できるように統合してください。
4. 最後にRAGモードのテストを実行してください。テスト時のコンソール出力について、LLMが生成した「最終的な回答文」は全文出力して構いませんが、検索で取得したソースドキュメントの中身など、途中の過程を print する場合は、AGENTS.mdのルール通り200文字程度にスライスしてください。
【⚠️既存ファイル修正時の絶対ルール】
main.py や llm_client.py などの既存ファイルを書き換える際、部分修正(edit)を使おうとするとエラーになりやすいため、必ずファイル全体の上書き(write)を行ってください。
その際、既存の import 文や既存の関数を絶対に消去しないよう、細心の注意を払って完全なコードを出力してください。
準備ができたら、ステップ13から順に実行をお願いします。それでは実行。




これで、取得した情報をデータベースに書き込むところまでできました。
失敗・・・?
Webから情報を取り出して、データベース化するために最低限必要なファイルは以下のとおり。

しかし、ここで異変に気づきました。


なんでだろうといろいろ考えていたんですが、ここで自分で取り入れた「book-to-skill」について思い出しました。
「book-to-skill」は取得した情報を要約し構造化する概念です。

大幅に容量が削減されてあるだけあって情報がスカスカになっていました。
# SKILL.md
## 1. 目的 (Purpose)
このドキュメントは、AIエージェントが『ドラゴンクエスト』(第1作目)に関する質問に対し、正確かつ詳細な情報を提供するための知識ベースおよび行動指針である。ユーザーがゲームの歴史、システム、ストーリー、操作方法について尋ねた際、提供されたテキストの範囲内で、事実に基づいた回答を行うことを目的とする。
## 2. 主要なテーマ (Main Themes)
AIは以下の4つの主要なカテゴリに基づいて情報を整理し、回答を構成すること。
### A. 作品の基本情報と歴史
* **基本属性:** 1986年5月27日にエニックス(現:スクウェア・エニックス)より発売されたファミリーコンピュータ用RPG。
* **重要人物:** プロデューサー(千田幸信)、ディレクター(中村光一)、シナリオ(堀井雄二)、音楽(すぎやまこういち)、美術(鳥山明 他)。
* **歴史的意義:** 家庭用ゲーム機における日本初のオリジナルタイトルRPG。
* **展開:** MSX、SFC、GB、スマートフォン、Switchなど多岐にわたる移植・リメイクが存在する。北米では『Dragon Warrior』として発売。
* **シリーズの位置付け:** 『ドラゴンクエストII』『III』と共に「ロトシリーズ」の一部。
### B. ストーリーと世界観
* **目的:** 伝説の勇者「ロト」の血を引く主人公が、竜王にさらわれた姫を救出し、竜王を倒すこと。
* **探索:** フィールドマップ(平地、森、砂漠、山、海、毒の沼)を移動し、町やダンジョンを探索する。
### C. ゲームシステムとメカニクス
* **キャラクター成長:** 経験値とゴールドを獲得してレベルアップ。ステータス(HP, MP, 力, 素早さ等)が上昇し、呪文を習得する。
* **戦闘システム:** 1対1のターン制コマンドバトル(ランダムエンカウント)。「たたかう」「じゅもん」「どうぐ」「にげる」を選択。
* **コマンド:** 「はなす」「つよさ」「かいだん」「とびら」「じゅもん」「どうぐ」「しらべる」「とる」の8種類。
* **特殊仕様:**
* ダンジョン内は「たいまつ」や「レミーラ」を使用しないと周囲が見えない(暗闇)。
* 装備品は購入すると自動的に装備され、古いものは買い取られる。
* **セーブ機能:** バッテリーバックアップ非搭載のため、「復活の呪文(パスワード)」による中断・再開を行う。
### D. アイテムと呪文
* **呪文:** 全10種類。回復、補助、攻撃などがある。
* **重要アイテム:** やくそう(HP回復)、たいまつ(暗闇対策)、せいすい(聖水)、かぎ(扉を開ける)、キメラのつばさ(ラダトーム城への帰還)など。
## 3. 回答のトーン&マナー (Tone & Manner)
AIは回答の際、以下のガイドラインを遵守すること。
* **客観性と正確性:** Wikipediaの記述に基づき、主観を排除した事実ベースの回答を行うこと。
* **専門性と分かりやすさ:** ゲーム用語(MP、エンカウント、ステータス等)は適切に使用しつつ、初心者にも理解しやすいよう、必要に応じて補足説明を加えること。
* **文脈の区別:** ユーザーが「ドラゴンクエスト」について尋ねた際、それが「第1作目」に関する質問であることを前提としつつ、リメイク版や続編(ロトシリーズ)との違いについて言及が必要な場合は、混同を避けるよう明示すること。
* **丁寧な解説:** システムの仕組み(例:なぜ「復活の呪文」が必要なのか、なぜダンジョンが暗いのか)について質問された場合は、その背景(技術的制約やゲームデザインの意図)を含めて丁寧に解説すること。
## 4. 禁止事項 (Constraints)
* 提供されたテキストに記載のない、個人的な感想や「どの作品が面白いか」といった主観的な評価は行わないこと。
* 第1作目の仕様(例:コマンドの仕様や暗闇の仕様)について、後続作品の仕様と混同して回答しないこと。確かにレベルや所持金上限といった情報は削除されています。
作り直し
上記のskill.mdをベクトル化しているのだから、そりゃ答えられませんよね。
と、いうわけで今回は「book-to-skill」での構造化自体を取りやめることにしました。
うまく指示を出せば省容量化できるしRAG検索の時も有利に働くと思います。
が、それをやっていると記事がいつまでたってもまとまりません。
全体的にスクリプトを書き直さなければなりません。
なのでTODO.mdとプロンプトを書き直しです。
Phese5を追加しました。
## Phase 5: 汎用RAGへの軌道修正(ファクト完全記憶型への変更)
- [ ] 16. ベクトル化対象の変更(`main.py`の改修)
- LLMによるスキル抽出・構造化処理(book-to-skill)をパイプラインから削除し、処理を軽量化する。
- `extractor.py` で抽出した生データ(`_raw.md`)を直接 `vectorizer.py` に渡し、DBに保存するように変更する。
- ChromaDBのコレクション名を `skills_collection` から `raw_knowledge` に変更する(`rag_agent.py` と `main.py` の両方)。
- [ ] 17. チャンク分割ロジックの汎用化(`vectorizer.py`の改修)
- 生のテキストデータに対応するため、`chunk_markdown` メソッドを改修し、見出し(`##`)が存在しなくても機能するように「段落(空行や `\n\n`)」などでテキストをチャンク分割するように変更する。
- [ ] 18. 汎用RAGの最終テスト
- 再度URLを読み込ませて生データをDBに記憶させ、「主人公の最高レベルは?」のような具体的なファクトに対して正しく回答できるかテストする。プロンプトは以下のとおり。
現在のディレクトリにある `AGENTS.md` と `TODO.md` を読み込んで、ルールと現在のタスクを理解してください。
TODO.md に新しいタスク「Phase 5: 汎用RAGへの軌道修正(ステップ16〜18)」を追記しました。
私たちが本当に求めていたのは、情報を要約して捨てるのではなく、Webのファクト(事実データ)を余すことなく記憶し、どんな細かい質問にも答えられるシステムでした。内容を確認し、自律的に実装を進めてください。
【実装にあたっての指示】
1. main.py を改修し、LLMによる要約(スキル抽出)プロセスを削除してください。Webから抽出した生テキスト(_raw.md)をそのまま vectorizer.py でベクトル化するようにパイプラインをシンプルにします。
2. これに伴い、main.py, rag_agent.py, vectorizer.py で使用しているコレクション名を `raw_knowledge` に変更してください。
3. vectorizer.py のチャンク分割ロジックを改修し、見出し(##)が存在しない生テキストでも適切に分割できるよう、段落(空行 \n\n など)で分割するように書き換えてください。
4. 一度 main.py を実行してWikipediaのドラクエの記事( https://ja.wikipedia.org/wiki/%E3%83%89%E3%83%A9%E3%82%B4%E3%83%B3%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88 )を読み込ませ直したあと、ragモードで「主人公の最高レベルは?」と質問し、今度こそ正しいファクトが回答されるかテストしてください。
【⚠️既存ファイル修正時の絶対ルール(再度徹底)】
main.py や vectorizer.py 等の既存ファイルを書き換える際は、部分修正(edit)を使わず、必ずファイル全体の上書き(write)を行ってください。既存の必要な import 文が消えないように注意してください。
準備ができたら、ステップ16から順に実行をお願いします。
スクリプトを修正
上のとおり修正作業をエージェントにやってもらいました。
スクリプトを作り直してもらって、「〜raw.md」そのものをデータベース化することには成功。ただ、それでもうまく情報を引き出せない(質問に答えられない)時がある。
なんでかなぁと思っていたら「rag_agent.py」の検索候補数が足りないとGeminiからのアドバイス。
results = self.vectorizer.search(query, self.collection_name, top_k=3)上の行の「k」の値を20にすることで問題はとりあえず解決しました。
動作テスト
例によって動画が長いので、興味のあるひとだけどうぞ。
前回の記事でllama.cppに直接「raw.md」を渡した時と比べると、応答がかなり遅いです。
これなら、前回の記事のような運用がいいじゃんって思いました。
ただ、Geminiが言うにはデータベースが肥大化していくにしたがって、「raw.md」だとコンテキストに収まらなくなってくるのだそうです。
RAG用のデータベースにすることで、必要な情報を必要な時に引き出せるようになるので、メモリの節約にもなるし、推論速度も維持できるみたいです。
とはいえ、Ryzen7 8700Gでは推論そのものに時間がかかりすぎなので、もうちょっと協力なGPUが欲しいなぁって思いました。
今回は以上です。





コメント