Shinjo Syntax

アイコン
📚

ナレッジベースを作りたい! その2

公開日 2025/12/14
更新日 2025/12/14
シリーズ記事の続きです

前回はテキストファイルの中身をChromaDBに落とし込み、そこから検索するという事をしました。

今回は、検索結果がどのファイルに書かれていた情報であるかを取得できるように改良しました。

これによって、ユーザー自身が指定されたファイルを確認でき、 場合によっては修正やファイルを全体的に確認するなどとても便利になるはず!

現状のプロジェクトディレクトリの中身はざっくりと以下の通りです。

この中でも今回重要となるのは下記になります。

  • documents: ナレッジベースの基となるテキストファイルが配置されているディレクトリ
  • test_chromadb.py: データベースに落とし込む / 抽出するPythonファイル
1.\documents
2.\project_env
3.gitignore
4.python-version
5.\README.md
6.\run.bat
7.\test_chromadb.py
8.\chroma_db\076d002e-dfb8-4b08-8658-547a426a7234
9.\chroma_db\chroma.sqlite3
10.\documents\profile.txt
11.\documents\sample.txt

ポイントは、データベースに登録する際にメタ情報を持たせる事

メタ情報にファイル名や作成日付などを格納する事で、データの抽出の際に取得されるというわけです。

今回使用しているpythonのコードは以下になります。 ソースファイルや作成日だけでなく、位置やカテゴリも格納してより詳細な情報を取得できるようにしました。

1# 一般的なメタデータを設定
2metadata = {
3    "source": os.path.basename(file_path),
4    "chunk_index": i,
5    "created_at": today,
6    "category": "standard"
7}
8
9collection.add(
10    documents=[paragraph],
11    metadatas=[metadata],
12    ids=[doc_id]
13)

test_chromadb.py

1import chromadb
2from chromadb.utils import embedding_functions
3
4# 日本語対応の埋め込みモデルを使用
5sentence_transformer_ef = embedding_functions.SentenceTransformerEmbeddingFunction(
6    model_name="intfloat/multilingual-e5-small"
7)
8
9# ChromaDBクライアントの初期化
10client = chromadb.PersistentClient(path="./chroma_db")
11
12# 既存のコレクションを削除して新しく作る
13try:
14    client.delete_collection(name="test_collection")
15except:
16    pass
17
18# ★重要:embedding_functionを指定してコレクション作成
19collection = client.get_or_create_collection(
20    name="test_collection",
21    embedding_function=sentence_transformer_ef  # ←ここが重要!
22)
23
24import os
25import glob
26from datetime import datetime
27
28# documentsディレクトリ内のテキストファイルを読み込む
29documents_dir = "documents"
30files = glob.glob(os.path.join(documents_dir, "*.txt"))
31
32if not files:
33    print(f"Warning: {documents_dir} ディレクトリに .txt ファイルが見つかりません。")
34    exit()
35
36print(f"✓ {len(files)}個のファイルを検出しました")
37
38for file_path in files:
39    with open(file_path, "r", encoding="utf-8") as f:
40        content = f.read()
41    
42    # 空行で分割
43    paragraphs = [p.strip() for p in content.split("\n\n") if p.strip()]
44    
45    # 今日の日付を取得
46    today = datetime.now().strftime("%Y-%m-%d")
47
48    # 各段落を追加
49    for i, paragraph in enumerate(paragraphs):
50        # IDを一意にするためにファイル名を含める
51        doc_id = f"{os.path.basename(file_path)}_{i}"
52        
53        # 一般的なメタデータを設定
54        metadata = {
55            "source": os.path.basename(file_path),
56            "chunk_index": i,
57            "created_at": today,
58            "category": "standard"
59        }
60
61        collection.add(
62            documents=[paragraph],
63            metadatas=[metadata],
64            ids=[doc_id]
65        )
66    
67    print(f"  - {os.path.basename(file_path)}: {len(paragraphs)}個の段落を追加しました")
68
69print("\n✓ すべてのデータの登録が完了しました")
70
71# 検索してみる(1件だけ)
72results = collection.query(
73    query_texts=["名前を教えてください。"],
74    n_results=1
75)
76
77print("\n検索結果(最も関連性の高い段落):")
78metadata = results['metadatas'][0][0]
79print(results)
80print(f"ファイル名: {metadata['source']}")
81print(f"作成日: {metadata['created_at']}")
82print(f"カテゴリ: {metadata['category']}")
83print(f"段落番号: {metadata['chunk_index']}")
84print("-" * 20)
85print(results["documents"][0][0])

documents/profile.txt

1名前は田中太郎。
2
3年齢は20歳。
4
5出身はアメリカのカリフォルニア州。
6
7趣味はサーフィンとネットサーフィン。

実行結果は以下になります。

名前を尋ねる質問に対して、正しいファイルから名前だけではなく ファイル名や作成日なども取得できました。

12個のファイルを検出しました
2  - profile.txt: 4個の段落を追加しました
3  - sample.txt: 4個の段落を追加しました
4
5✓ すべてのデータの登録が完了しました
6
7検索結果(最も関連性の高い段落):
8{'ids': [['profile.txt_0']], 'embeddings': None, 'documents': [['名前は田中太郎。']], 'uris': None, 'included': ['metadatas', 'documents', 'distances'], 'data': None, 'metadatas': [[{'chunk_index': 0, 'category': 'standard', 'source': 'profile.txt', 'created_at': '2025-12-14'}]], 'distances': [[0.11765271425247192]]}
9ファイル名: profile.txt
10作成日: 2025-12-14
11カテゴリ: standard
12段落番号: 0
13--------------------
14名前は田中太郎。

メタ情報を加えればデータの管理もしやすくなるので今後も活用します!

次はテキストファイルではなくPDFファイルの読み取りなどにも挑戦したいです!