RAGデータの作成
GoogleDrive に
学校関連はSchool
特に使うのがshizensaigai.pdfに書いてある
休校や自然災害の時の対処方法
保険フォルダにある
自動車保険、県民共済の約款
取扱説明書フォルダにある
各種家電の取説
これらをダウンロードしてRAGにする
まず以前wikipediaから一部記事をダウンロードしRAGにした
/aw10s/ollama/save_index_test.py
のコード
import ollama
from llama_index.core import Settings
from llama_index.llms.ollama import Ollama
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.readers.web import SimpleWebPageReader
from llama_index.core.node_parser import TokenTextSplitter
from llama_index.core import VectorStoreIndex
# --- 1. LLMと埋め込みモデルの設定 ---
# OllamaのLLMを設定
# 'elyza8b'モデルが既にOllamaサーバーに存在していることを前提とします。
Settings.llm = Ollama(model="elyza8b", request_timeout=360.0)
# 日本語性能の高い埋め込みモデルを設定
Settings.embed_model = HuggingFaceEmbedding(model_name="intfloat/multilingual-e5-large")
print("LLMと埋め込みモデルの設定が完了しました。")
# --- 2. データのロード ---
# Wikipediaの指定されたページからデータをロード
print("Wikipediaページからデータをロード中...")
urls = [
"https://ja.wikipedia.org/wiki/%E7%81%AB%E8%BB%8A_(%E5%B0%8F%E8%AA%AC)",
"https://ja.wikipedia.org/wiki/%E7%90%86%E7%94%B1_(%E5%B0%8F%E8%AA%AC)",
"https://ja.wikipedia.org/wiki/%E5%90%8D%E3%82%82%E3%81%AA%E3%81%8D%E6%AF%92",
"https://ja.wikipedia.org/wiki/%E3%82%BD%E3%83%AD%E3%83%A2%E3%83%B3%E3%81%AE%E5%81%BD%E8%A8%BC"
]
documents = SimpleWebPageReader(html_to_text=True).load_data(urls)
print(f"合計 {len(documents)} ページのドキュメントをロードしました。")
# --- 3. ドキュメントのチャンク化とノード化 ---
# 読み込んだドキュメントをチャンクに分割し、ノードとして処理
print("ドキュメントをノードに分割中...")
splitter = TokenTextSplitter(
chunk_size=1000,
chunk_overlap=100,
separator=" ",
)
nodes = splitter.get_nodes_from_documents(documents)
print(f"合計 {len(nodes)} 個のノードが生成されました。")
# 確認のため、一部のノードを表示(オプション)
# print("\n一部のノードの例:")
# for i, node in enumerate(nodes[10:13]):
# print(f"Node {i+10}: {node.text[:100]}...") # 最初の100文字を表示
# print("---")
# --- 4. VectorStoreIndexの作成 ---
# ノードからVectorStoreIndexを作成
print("VectorStoreIndexを作成中...")
index = VectorStoreIndex(nodes, show_progress=True)
print("VectorStoreIndexの作成が完了しました。")
# --- 5. インデックスの保存 ---
# インデックスにIDを設定し、指定したディレクトリに保存
# 保存先ディレクトリが存在しない場合は自動で作成されます。
index_id = "miyabe_miyuki"
persist_dir = "./storage/simple_vs"
print(f"インデックスを '{persist_dir}' に ID '{index_id}' で保存中...")
index.set_index_id(index_id)
index.storage_context.persist(persist_dir)
print("インデックスの保存が完了しました。")
print("これで、次回からはこのインデックスを再利用できます。")
これを改造して
wikipedia のページからではなく
data/
school/
shizensaigai.pdf
他の学校関連PDF
insurance/
自動車保険.pdf
県民共済.pdf
他の保険PDF
manuals/
洗濯機.pdf
冷蔵庫.pdf
他の家電PDF
のRAGインデックスを作成するようにする
なお
llama-index-ocr
も考えたけど
https://x.com/alexweberk/status/1858316517668249755
によれば
2024年9月時点だと日本語はダメっぽい
とりあえず実験する
GPTによれば
pip install llama-index llama-index-pdf
となっているけど
そんなパッケージは存在しない…
pip install llama-index llama-index-pdf Collecting llama-index Downloading llama_index-0.12.38-py3-none-any.whl.metadata (12 kB) ERROR: Could not find a version that satisfies the requirement llama-index-pdf (from versions: none) [notice] A new release of pip is available: 24.3.1 -> 25.1.1 [notice] To update, run: pip install --upgrade pip ERROR: No matching distribution found for llama-index-pdf
となる
pip install llama-index-readers-file
が正解
これは geminiの回答
次にいくつか疑問点を解決していく
│ ├── shizensaigai.pdf │ └── other_school_document.pdf ├── insurance/ │ ├── 自動車保険.pdf │ └── 県民共済.pdf └── manuals/ ├── 洗濯機.pdf └── 冷蔵庫.pdf
のPDFファイル名はインデックスデータの精度に関係するか?
例:家電のマニュアルが製品の型番、例えば001.pdf だった場合など
これに関しては
ファイル名自体がインデックスデータの精度に直接的な影響を与えることは、ほとんどありません。
LlamaIndexのようなRAGシステムでは、インデックスを作成する際にPDFファイルの中身(テキストコンテンツ)を読み込み、それをチャンクに分割し、埋め込みモデルがそのチャンクのテキストからベクトル表現を生成します。このベクトル表現が、検索時の関連度計算の基礎となります。
ファイル名が精度に間接的に影響する可能性
しかし、ファイル名が間接的に、そして限定的ながら影響を与える可能性はあります。
1. メタデータとしての利用: もし、あなたがRAGシステムを高度にカスタマイズし、PDFファイル名からメタデータを抽出してノードに追加するような処理を実装した場合、そのメタデータは検索の精度に影響を与える可能性があります。 例えば、「洗濯機.pdf」というファイル名から「製品カテゴリ:家電、製品名:洗濯機」といったメタデータを自動生成し、ノードに付与するとします。ユーザーが「家電の修理方法」と質問した際に、このメタデータを使って関連ドキュメントを絞り込むことができます。 ただし、これはLlamaIndexのデフォルトの動作ではなく、カスタムで実装が必要です。
2. 可読性と管理: これは直接的な精度とは異なりますが、ファイル名が「001.pdf」のような意味のないものだと、システムを管理する側がどのファイルが何を指しているのか把握しにくくなります。デバッグやドキュメントの追加・削除の際に、非効率的になる可能性があります。
結論
基本的には、shizensaigai.pdf や 001.pdf のようなファイル名の違いは、インデックス作成後のRAGの検索精度や回答精度に直接的な影響を与えません。 RAGの精度は、主に以下の要素によって決まります。
* PDFファイルの内容: 情報の質、網羅性、テキストのクリーンさ。
* 埋め込みモデルの性能: テキストをどれだけ適切にベクトル化できるか。
* チャンク化の戦略: チャンクサイズ、オーバーラップ、セパレーターの選び方。
* LLMの性能: 検索されたコンテキストを理解し、クエリに適切に回答する能力。
ですので、製品の型番や具体的な内容がわかるようなファイル名(例: 洗濯機_ABC123.pdf)にしておくことは、人間がファイルを管理する上での利便性には繋がりますが、RAGシステムの内部的な精度向上に劇的に貢献するわけではありません。
とのこと
後々、参照元のPDFを回答の根拠として表示する場合には影響するか?
については
その場合はファイル名が非常に重要になります。
LlamaIndexのようなRAGシステムでは、最終的な回答を生成する際に、どのドキュメント(チャンク)から情報が取得されたかを示す「参照元(ソース)」情報を提供できます。この参照元情報には、通常、以下のものが含まれます。
* ファイル名: どのPDFファイルから情報が得られたか。
* ページ番号: PDFの何ページ目から情報が得られたか(PDFReaderが対応している場合)。
* チャンクのテキスト: 実際に参照されたテキストスニペット。
ファイル名が「参照元のPDFを回答の根拠として表示する」際に影響する理由
1. ユーザーへの視認性・理解度:
* 分かりやすい例: ユーザーが「洗濯機の操作方法について」質問し、回答とともに参照元として「洗濯機_ABC123.pdf」と表示されれば、ユーザーはすぐに「これは私が持っている洗濯機のマニュアルだ」と理解し、その根拠を信頼できます。
* 分かりにくい例: もし参照元が「001.pdf」と表示された場合、ユーザーはそれがどのドキュメントなのか全く分かりません。自分でファイルを探し出して内容を確認することが困難になります。
2. 根拠の信頼性:
* ユーザーが回答の正確性を検証したい場合、ファイル名が具体的であれば、そのファイルを直接開いて確認できます。抽象的なファイル名では、確認作業が非常に煩雑になります。
3. デバッグとシステム管理:
* RAGシステムを開発・運用する側にとっても、どの回答がどのソースドキュメントに由来しているのか、ファイル名を見るだけで一目瞭然であることは非常に重要です。問題が発生した場合の原因特定や、特定のドキュメントの更新・削除などの管理が容易になります。
対策と推奨されるファイル名
参照元の表示を考慮すると、PDFファイル名は以下の点を満たすようにすることをお勧めします。
* 内容を簡潔に表す: そのファイルに何が書かれているのかが、ファイル名からある程度推測できること。
* 具体的な情報を含める: 製品名、型番、バージョン、日付など、ドキュメントを一意に特定できる情報を含めること。
* 日本語名でもOK: LlamaIndexは日本語のファイル名を問題なく扱えます。
良い例:
* shizensaigai_防災ガイド_2023年版.pdf
* 自動車保険_ABC社_約款_2024.pdf
* Panasonic_洗濯機_NA-VX9000_取扱説明書.pdf
* 冷蔵庫_三菱_MR-B46F_マニュアル.pdf
悪い例:
* doc1.pdf
* file.pdf
* image.pdf
* 123.pdf
コード上の考慮点
LlamaIndexのSimpleDirectoryReaderやPDFReaderは、読み込んだドキュメントにファイルパス(したがってファイル名)をメタデータとして自動的に付与します。このメタデータは、後でクエリエンジンからソースノードを取得する際に利用可能です。
例えば、クエリ結果からソース情報を取得する際には、以下のようにアクセスできます。
# Assuming 'response' is the object returned by query_engine.query(query_text)
for source_node in response.source_nodes:
file_name = source_node.metadata.get('file_name')
page_label = source_node.metadata.get('page_label') # ページ番号
text = source_node.text # チャンクのテキスト
print(f"参照元ファイル: {file_name} (ページ: {page_label or 'N/A'})")
print(f"関連テキスト: {text[:200]}...") # 関連するテキストの一部
したがって、参照元の表示を考えている場合は、PDFファイル名に意味を持たせるように命名規則を設けることが非常に重要
つまりファイル名を家電や約款の場合ダウンロードしてからわかりやすい名前に変更する必要がある
mkdir rag_data
ここにファイルをまとめる
cd rag_data mkdir school insurance manuals
Mkdirコマンドでスペースで区切れば複数フォルダを一度に作成可能
cp ~/Downloads/取扱説明書/* manuals cp ~/Downloads/保険/* insurance cp ~/Downloads/School/shizensaigai.pdf school mv shizensaigai.pdf 自然災害発生時、警報発表・避難情報発表時等に伴う学校の対処.pdf
でファイル名を変更しておく
また manualsの中の家電ごとのファイル名も変更しておく
cd ../manuals mv axha20_mn.pdf ウォーターオーブンAX-HA20.pdf mv np_tr8.pdf 食洗機NP-TR8.pdf mv MCK70ZKS-T_取扱説明書_3P698812-3E.pdf 空気清浄機MCK70ZKS-W.pdf
とりあえずこれくらいにして実験を開始
なお
elyza8bで作成したRAGデータを他のモデルで使用するとどうなるか?
に対しては
elyza8b (LLM) で作成したRAGデータを他のLLMで使用することは、一般的に問題なく可能です。RAGのデータ保存とLLMの利用は、それぞれ異なる役割を持つ
とのこと
RAGシステムにおけるLLMの役割
RAG (Retrieval Augmented Generation) システムは、大きく以下の2つの主要なコンポーネントに分けられます。
1. Retrieval (検索) フェーズ:
* 埋め込みモデル (Embedding Model): ドキュメントのテキストやクエリのテキストを数値ベクトル(埋め込みベクトル)に変換します。このベクトルが類似度計算に使用され、関連性の高いドキュメントを検索します。
* ベクトルデータベース/インデックス: 生成された埋め込みベクトルを保存し、クエリベクトルとの類似度に基づいて関連ドキュメントを効率的に検索します。
2. Generation (生成) フェーズ:
* LLM (Large Language Model): 検索フェーズで取得された関連ドキュメントのコンテキスト情報とユーザーのクエリを受け取り、それに基づいて最終的な回答を生成します。
elyza8bで作成したRAGデータとは?
ご質問の「elyza8bで作成したRAGデータ」という文脈では、おそらく以下の部分を指していると考えられます。
* VectorStoreIndex (ベクトルストアインデックス): これは、ドキュメントのテキストを埋め込みモデル(この場合は intfloat/multilingual-e5-large)がベクトル化したものを保存したものです。elyza8b (LLM) は、このインデックスの作成には直接関与していません。LLMは、RAGの「生成」フェーズで使われるものです。
つまり、elyza8bはRAGの「生成」フェーズで使われるLLMであり、RAGデータの「作成」には関与していません。RAGデータ(VectorStoreIndex)は埋め込みモデルによって作成されます。
埋め込みモデルとLLMの分離
* 埋め込みモデル: intfloat/multilingual-e5-large このモデルがドキュメントのテキストをベクトルに変換し、そのベクトルがVectorStoreIndexに保存されます。この埋め込みモデルを変更しない限り、RAGデータ(VectorStoreIndex)の構造や内容は変わりません。
* LLM: elyza8b このモデルは、ユーザーのクエリと検索されたコンテキストを受け取って最終的な回答を生成します。
elyza8bで作成したRAGデータを他のLLMで使用するとどうなるか?
「elyza8bで作成したRAGデータ」が、実際にはintfloat/multilingual-e5-largeという埋め込みモデルによって生成されたVectorStoreIndexを指していると仮定します。
このVectorStoreIndexは、特定のLLM(elyza8b)とは独立したデータです。したがって、このRAGデータ(VectorStoreIndex)を、異なるLLM(例: gemma:2b、gpt-3.5-turbo、mixtralなど)と組み合わせて使用することは全く問題ありません。
LLMを変更することで、RAGシステムの回答生成の質が変わります。
1. 回答のスタイルと質:
* 自然さ、流暢さ: 新しいLLMの言語生成能力に依存します。例えば、gemma:2bは高速ですが、elyza8bと比較して日本語の自然さや文脈理解に差が出る可能性があります(特に日本語に特化していない場合)。
* 正確性: LLMが提供されたコンテキスト情報をどれだけ正確に理解し、それに基づいて回答を生成できるかに影響します。推論能力の高いLLMであれば、より適切な回答が期待できます。
* 情報統合能力: 検索された複数の関連チャンクをどれだけうまく統合し、矛盾のない回答を生成できるか。
* 冗長性: 回答が長すぎたり短すぎたりするか。
* トーン: 回答のトーン(フォーマル、カジュアルなど)が変わる可能性があります。
2. 推論速度:
* LLMのモデルサイズやアーキテクチャによって、回答生成にかかる時間が大きく変わります。gemma:2bのような小型モデルに変更すれば、推論速度が向上する可能性が高いです。
3. リソース消費:
* LLMを変更すると、VRAM使用量やCPU使用率が変わります。小型のLLMに変更すれば、必要なリソースが減り、より効率的に動作する可能性があります。
4. ハルシネーション(幻覚)の傾向:
* LLMによって、提供されたコンテキストから逸脱した情報を生成する「ハルシネーション」の傾向が変わります。一般的に、より高性能なLLMほどハルシネーションが少ないとされますが、これはモデルの特性やチューニングにも依存します。
まとめ
* elyza8bで「作成」されたRAGデータ(VectorStoreIndex)は、実際には埋め込みモデルによって作成されており、LLMとは独立しています。
* このRAGデータは、他の任意のLLMと組み合わせて使用できます。
* LLMを変更すると、RAGシステムの回答生成の質、スタイル、推論速度、リソース消費が変わります。
* 異なるLLMを試すことで、ご自身の用途や環境に最適なRAGシステムを構築できる可能性があります。
あと
ファイル名自体がインデックスデータの精度に直接的な影響を与えることは、ほとんどありません
しかし、ファイル名が間接的に、そして限定的ながら影響を与える可能性はあります。
1. メタデータとしての利用: もし、あなたがRAGシステムを高度にカスタマイズし、PDFファイル名からメタデータを抽出してノードに追加するような処理を実装した場合、そのメタデータは検索の精度に影響を与える可能性があります。 例えば、「洗濯機.pdf」というファイル名から「製品カテゴリ:家電、製品名:洗濯機」といったメタデータを自動生成し、ノードに付与するとします。ユーザーが「家電の修理方法」と質問した際に、このメタデータを使って関連ドキュメントを絞り込むことができます。 ただし、これはLlamaIndexのデフォルトの動作ではなく、カスタムで実装が必要です。
2. 可読性と管理: これは直接的な精度とは異なりますが、ファイル名が「001.pdf」のような意味のないものだと、システムを管理する側がどのファイルが何を指しているのか把握しにくくなります。デバッグやドキュメントの追加・削除の際に、非効率的になる可能性があります。
とりあえず問題はなさそうなので
実際に実験を開始する
touch create_rag_data.py
でファイルを作成
import ollama
from llama_index.core import Settings
from llama_index.llms.ollama import Ollama
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.core import SimpleDirectoryReader
from llama_index.core.node_parser import TokenTextSplitter
from llama_index.core import VectorStoreIndex
import os
# --- 1. LLMと埋め込みモデルの設定 ---
# OllamaのLLMを設定
# 'elyza8b'モデルが既にOllamaサーバーに存在していることを前提とします。
Settings.llm = Ollama(model="elyza8b", request_timeout=360.0)
# 日本語性能の高い埋め込みモデルを設定
Settings.embed_model = HuggingFaceEmbedding(model_name="intfloat/multilingual-e5-large")
print("LLMと埋め込みモデルの設定が完了しました。")
# --- 2. データのロード ---
# 指定されたディレクトリからPDFファイルをロード
print("指定されたディレクトリからPDFファイルをロード中...")
base_data_dir = "./data"
sub_dirs = ["school", "insurance", "manuals"]
all_documents = []
for sub_dir in sub_dirs:
dir_path = os.path.join(base_data_dir, sub_dir)
if os.path.exists(dir_path) and os.path.isdir(dir_path):
print(f"ディレクトリ '{dir_path}' からPDFを読み込み中...")
# PDFReaderはllama-index-readers-fileに含まれています
reader = SimpleDirectoryReader(input_dir=dir_path, required_exts=[".pdf"])
documents = reader.load_data()
all_documents.extend(documents)
print(f"'{dir_path}' から {len(documents)} ページのドキュメントをロードしました。")
else:
print(f"警告: ディレクトリ '{dir_path}' が見つからないか、ディレクトリではありません。スキップします。")
print(f"合計 {len(all_documents)} ページのドキュメントをロードしました。")
# ロードされたドキュメントがない場合は処理を終了
if not all_documents:
print("ロードされたドキュメントがありません。インデックスの作成をスキップします。")
else:
# --- 3. ドキュメントのチャンク化とノード化 ---
# 読み込んだドキュメントをチャンクに分割し、ノードとして処理
print("ドキュメントをノードに分割中...")
splitter = TokenTextSplitter(
chunk_size=1000,
chunk_overlap=100,
separator=" ",
)
nodes = splitter.get_nodes_from_documents(all_documents)
print(f"合計 {len(nodes)} 個のノードが生成されました。")
# --- 4. VectorStoreIndexの作成 ---
# ノードからVectorStoreIndexを作成
print("VectorStoreIndexを作成中...")
index = VectorStoreIndex(nodes, show_progress=True)
print("VectorStoreIndexの作成が完了しました。")
# --- 5. インデックスの保存 ---
# インデックスにIDを設定し、指定したディレクトリに保存
# 保存先ディレクトリが存在しない場合は自動で作成されます。
index_id = "my_local_pdfs" # インデックスIDを分かりやすい名前に変更
persist_dir = "./storage/my_local_pdfs" # 保存先ディレクトリを分かりやすい名前に変更
print(f"インデックスを '{persist_dir}' に ID '{index_id}' で保存中...")
index.set_index_id(index_id)
index.storage_context.persist(persist_dir)
print("インデックスの保存が完了しました。")
print("これで、次回からはこのインデックスを再利用できます。")
として保存
しかし実行時にエラー
フォルダ名が
Dataではなく rag_dataだったので修正
import ollama
from llama_index.core import Settings
from llama_index.llms.ollama import Ollama
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.core import SimpleDirectoryReader
from llama_index.core.node_parser import TokenTextSplitter
from llama_index.core import VectorStoreIndex
import os
# --- 1. LLMと埋め込みモデルの設定 ---
# OllamaのLLMを設定
# 'elyza8b'モデルが既にOllamaサーバーに存在していることを前提とします。
Settings.llm = Ollama(model="elyza8b", request_timeout=360.0)
# 日本語性能の高い埋め込みモデルを設定
Settings.embed_model = HuggingFaceEmbedding(model_name="intfloat/multilingual-e5-large")
print("LLMと埋め込みモデルの設定が完了しました。")
# --- 2. データのロード ---
# 指定されたディレクトリからPDFファイルをロード
print("指定されたディレクトリからPDFファイルをロード中...")
base_data_dir = "./rag_data"
sub_dirs = ["school", "insurance", "manuals"]
all_documents = []
for sub_dir in sub_dirs:
dir_path = os.path.join(base_data_dir, sub_dir)
if os.path.exists(dir_path) and os.path.isdir(dir_path):
print(f"ディレクトリ '{dir_path}' からPDFを読み込み中...")
# PDFReaderはllama-index-readers-fileに含まれています
reader = SimpleDirectoryReader(input_dir=dir_path, required_exts=[".pdf"])
documents = reader.load_data()
all_documents.extend(documents)
print(f"'{dir_path}' から {len(documents)} ページのドキュメントをロードしました。")
else:
print(f"警告: ディレクトリ '{dir_path}' が見つからないか、ディレクトリではありません。スキップします。")
print(f"合計 {len(all_documents)} ページのドキュメントをロードしました。")
# ロードされたドキュメントがない場合は処理を終了
if not all_documents:
print("ロードされたドキュメントがありません。インデックスの作成をスキップします。")
else:
# --- 3. ドキュメントのチャンク化とノード化 ---
# 読み込んだドキュメントをチャンクに分割し、ノードとして処理
print("ドキュメントをノードに分割中...")
splitter = TokenTextSplitter(
chunk_size=1000,
chunk_overlap=100,
separator=" ",
)
nodes = splitter.get_nodes_from_documents(all_documents)
print(f"合計 {len(nodes)} 個のノードが生成されました。")
# --- 4. VectorStoreIndexの作成 ---
# ノードからVectorStoreIndexを作成
print("VectorStoreIndexを作成中...")
index = VectorStoreIndex(nodes, show_progress=True)
print("VectorStoreIndexの作成が完了しました。")
# --- 5. インデックスの保存 ---
# インデックスにIDを設定し、指定したディレクトリに保存
# 保存先ディレクトリが存在しない場合は自動で作成されます。
index_id = "my_local_pdfs" # インデックスIDを分かりやすい名前に変更
persist_dir = "./storage/my_local_pdfs" # 保存先ディレクトリを分かりやすい名前に変更
print(f"インデックスを '{persist_dir}' に ID '{index_id}' で保存中...")
index.set_index_id(index_id)
index.storage_context.persist(persist_dir)
print("インデックスの保存が完了しました。")
print("これで、次回からはこのインデックスを再利用できます。")
これで再度実行
5:12 からスタート
python create_rag_data.py LLMと埋め込みモデルの設定が完了しました。 指定されたディレクトリからPDFファイルをロード中... ディレクトリ './rag_data/school' からPDFを読み込み中... './rag_data/school' から 4 ページのドキュメントをロードしました。 ディレクトリ './rag_data/insurance' からPDFを読み込み中... Impossible to decode XFormObject /Fm0: Invalid Elementary Object starting with b'\x80' @0: b'\x80' Impossible to decode XFormObject /Fm1: Invalid Elementary Object starting with b'\x81' @0: b'\x81' Impossible to decode XFormObject /Fm0: Invalid Elementary Object starting with b'\x9a' @0: b'\x9a' Impossible to decode XFormObject /Fm1: Invalid Elementary Object starting with b'\x97' @0: b'\x97' Impossible to decode XFormObject /Fm0: Invalid Elementary Object starting with b'\x80' @0: b'\x80' Impossible to decode XFormObject /Fm1: Invalid Elementary Object starting with b'\xc4' @0: b'\xc4' Impossible to decode XFormObject /Fm0: Invalid Elementary Object starting with b'\xc7' @0: b'\xc7' Impossible to decode XFormObject /Fm1: Invalid Elementary Object starting with b'\xb1' @0: b'\xb1' Impossible to decode XFormObject /Fm0: Invalid Elementary Object starting with b'\xd7' @0: b'\xd7' Impossible to decode XFormObject /Fm1: Invalid Elementary Object starting with b'\x8e' @0: b'\x8e' Impossible to decode XFormObject /Fm0: Invalid Elementary Object starting with b'\xc8' @0: b'\xc8' Impossible to decode XFormObject /Fm1: Invalid Elementary Object starting with b'\x99' @0: b'\x99' './rag_data/insurance' から 322 ページのドキュメントをロードしました。 ディレクトリ './rag_data/manuals' からPDFを読み込み中... Failed to load file /Users/snowpool/aw10s/ollama/rag_data/manuals/ウォーターオーブンAX-HA20.pdf with error: RetryError[<Future at 0x367318710 state=finished raised DependencyError>]. Skipping... Failed to load file /Users/snowpool/aw10s/ollama/rag_data/manuals/食洗機NP-TR8.pdf with error: RetryError[<Future at 0x368388250 state=finished raised DependencyError>]. Skipping... './rag_data/manuals' から 48 ページのドキュメントをロードしました。 合計 374 ページのドキュメントをロードしました。 ドキュメントをノードに分割中... 合計 1180 個のノードが生成されました。 VectorStoreIndexを作成中... Generating embeddings: 100%|████████████████| 1180/1180 [05:33<00:00, 3.54it/s] VectorStoreIndexの作成が完了しました。 インデックスを './storage/my_local_pdfs' に ID 'my_local_pdfs' で保存中... インデックスの保存が完了しました。 これで、次回からはこのインデックスを再利用できます。
実行終了まで約7分
次に回答が出るかテストする
touch chat_with_rag.py
中身を
import sys
from llama_index.core import StorageContext, load_index_from_storage
from llama_index.core import Settings
from llama_index.llms.ollama import Ollama
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
# --- 1. LLMと埋め込みモデルの設定 ---
# インデックス作成時と同じモデルを設定する必要があります。
# これにより、クエリのベクトル化と回答生成が正しく行われます。
print("LLMと埋め込みモデルの設定を開始します...")
try:
Settings.llm = Ollama(model="elyza:jp8b", request_timeout=360.0)
# 日本語性能の高い埋め込みモデルを設定
Settings.embed_model = HuggingFaceEmbedding(model_name="intfloat/multilingual-e5-large")
print("LLMと埋め込みモデルの設定が完了しました。")
except Exception as e:
print(f"LLMまたは埋め込みモデルの設定中にエラーが発生しました: {e}")
print("Ollamaサーバーが起動しており、'elyza:jp8b'モデルがダウンロードされているか確認してください。")
sys.exit(1)
# --- 2. インデックスのロード ---
print("インデックスをロード中...")
try:
# 保存先のディレクトリとインデックスIDを指定
# 以前のインデックス保存時のパスとIDに合わせる必要があります。
persist_dir = "./storage/my_local_pdfs" # あなたのインデックス保存パスに修正
index_id = "my_local_pdfs" # あなたのインデックスIDに修正
# StorageContext を設定し、保存先のディレクトリを指定
storage_context = StorageContext.from_defaults(persist_dir=persist_dir)
# 指定されたストレージコンテキストとインデックスIDでインデックスをロード
loaded_index = load_index_from_storage(storage_context, index_id=index_id)
print("インデックスのロードが完了しました。")
# ロードしたインデックスからクエリエンジンを作成
# similarity_top_k は、回答生成のために検索するノードの数を指定します。
# この値を調整することで、回答の精度や網羅性が変わる可能性があります。
query_engine = loaded_index.as_query_engine(similarity_top_k=3)
print("クエリエンジンの準備ができました。これで質問を開始できます。")
# --- 3. ユーザーからの質問を受け付けるループ ---
print("\n--------------------------------------------------")
print("質問を入力してください(終了するには 'exit' と入力)。")
print("--------------------------------------------------")
while True:
query = input("あなたの質問: ")
if query.lower() == 'exit':
print("プログラムを終了します。ご利用ありがとうございました!")
break
if not query.strip(): # 空の入力の場合
print("質問を入力してください。")
continue
print("回答を生成中...")
try:
# クエリエンジンで質問を処理し、回答を取得
response = query_engine.query(query)
print("\n--- 回答 ---")
print(response.response) # .response をつけて回答テキストのみを出力
print("------------\n")
except Exception as e:
print(f"質問処理中にエラーが発生しました: {e}")
print("もう一度お試しください。")
except FileNotFoundError:
print(f"\nエラー: インデックスデータが見つかりません。")
print(f"'{persist_dir}' ディレクトリが存在し、以前にインデックスがID '{index_id}' で保存されているか確認してください。")
print("インデックスを保存するには、インデックス作成コードを先に実行する必要があります。")
sys.exit(1) # プログラムを終了
except Exception as e:
print(f"\n予期せぬエラーが発生しました: {e}")
sys.exit(1) # プログラムを終了
これを実行すると
LLMと埋め込みモデルの設定を開始します... LLMと埋め込みモデルの設定が完了しました。 インデックスをロード中... インデックスのロードが完了しました。 クエリエンジンの準備ができました。これで質問を開始できます。 -------------------------------------------------- 質問を入力してください(終了するには 'exit' と入力)。 -------------------------------------------------- あなたの質問: 台風の時の対処 回答を生成中... --- 回答 --- 暴風警報や特別警報が発表された場合、下校後は放課後児童クラブに登所し、状況に応じて速やかな迎えを依頼し、安全を確認した後保護者への引き渡しが行われます。 ------------ あなたの質問: 大雨警報の時は 回答を生成中... --- 回答 --- 原則として登校します。 ------------ あなたの質問: 保険金の支払いはいつ? 回答を生成中... --- 回答 --- 請求完了日からその日を含めて30日以内に、自転車賠償保険金が支払われます。 ------------ あなたの質問: エアコンから異音がする 回答を生成中... --- 回答 --- 運転中、「シュー」や「ヒュー」という音がする場合、ストリーマの運転中はストリーマ放電の音であり、異常ではありません。また、ご使用環境により、音が小さくなったり、音質が変わることがありますが、異常ではありません。 ------------ あなたの質問: 食洗機の乾燥時間は? 回答を生成中... --- 回答 --- There is no mention of食洗機 or乾燥時間 in the provided context information. ------------ あなたの質問: 対物の金額 回答を生成中... --- 回答 --- 対物保険金額とは、同一事故につき既に当社が支払った対物賠償保険金または第8条(損害賠償請求権者の直接請求権)の損害賠償額がある場合、その全額を差し引いた額をいいます。 ------------ あなたの質問: 対物の補償金額は? 回答を生成中... --- 回答 --- 対物保険金額とは、同一事故につき既に当社が支払った対物賠償保険金または第8条(損害賠償請求権者の直接請求権)の損害賠償額がある場合、その全額を差し引いた額をいいます。 ------------ あなたの質問: 生命 次回の保険の更新日は? 回答を生成中... --- 回答 --- この保険の更新日を特定する情報が提供されていないため、次回の保険の更新日はわかりません。 ------------ あなたの質問: 保険期間は? 回答を生成中... --- 回答 --- 保険期間が1年未満の場合、保険期間の日数を365で除した割合を乗じて保険料を算出したものと記載されています。 ------------ あなたの質問: 自動車保険の保険期間は? 回答を生成中... --- 回答 --- 「条」の中に保険期間に関する規定があるため、自動車保険の保険期間は始期と終期があります。始期は記名被保険者の管理下に入った時もしくは保険契約の始期、終期は保険契約の終期です。 ------------ あなたの質問: 登録番号 回答を生成中... --- 回答 --- 登録番号とは、自動車や交通乗用具が持つ一意に識別される番号を指します。 ------------ あなたの質問: 自動車保険の内容で仕様について 回答を生成中... --- 回答 --- 自家用8車種は、以下のいずれかに該当する自動車をいう。 ① 自家用普通乗用車 ② 自家用小型乗用車 ③ 自家用軽四輪乗用車 ④ 自家用普通貨物車 (最大積載量0.5 トン超2 トン以下) ⑤ 自家用普通貨物車 (最大積載量0.5 トン以下) ⑥ 自家用小型貨物車 ⑦ 自家用軽四輪貨物車 ⑧ 特種用途自動車 ------------ あなたの質問: 自動車保険証の中で仕様を知りたい 回答を生成中... --- 回答 --- 自動車の用途や種類は、当社が定める区分表によるものとし、例えば登録番号標や車両番号標の分類番号や塗色に基づいて区分しています。 ------------
実験結果からPDFの単純なものはすぐに出る
例:学校のPDF
しかし保険証券のように少し複雑になると途端に精度が下がる