Python × ChromaDB × Ollamaで実現 ── 知識の継続的アップデートを可能にする『自分専用AI・RAGシステム』開発ガイド

AI

前回の記事では、ChromaDBとOllamaを連携させ、ローカルPC内でAIがデータベース検索して回答するための土台となるアプリを構築しました。

しかし、実際の運用を考えると、ソースコードを書き換えないと質問や知識の登録ができない状態では、まだ到底「便利なツール」とは言えません。

そこで今回は、前回作成したプログラムを「知識の登録」「AIの回答」2つの役割に分離し、さらに自在にアップデート可能な仕組みへとバージョンアップさせます。

「プログラムを一度動かして終わり」ではなく、日々増えていくあなたの知見やデータを、ノートを書き足すかのようにAIに授けていく。
そんな継続的な学習を可能にする、実戦的・実用的なRAGシステムの完成を目指しましょう。

第1章:プログラムの分割が、実用化への第一歩

前回の演習では、1つのプログラムの中に「知識の登録」と「AIへの質問」をまとめて記述しました。
動作確認としては正解ですが、実際に運用を始めると、すぐ次のような不便に直面するはずです。

  • 知識を追加するたびに、質問コードまで編集が必要になる(あるいはその逆)。
  • ソースコードを直接書き換えないと、覚えさせる内容を変えられない。
  • 将来的に「PDFから自動読み込み」などの機能を追加したくなったとき、コードが複雑になりすぎる。

これらの問題を解決するのが、プログラムの役割分担(モジュール化)です。

1.1. 「登録用」と「回答用」の分離

今回はシステムを以下の2本に切り分けます。

  1. 知識の登録 (ingest.py)
    • 新しい情報をデータベース(ChromaDB)に保存する「書き込み専用」プログラム。
  2. AIの回答 (ask.py)
    • 保存された知識を読み取り、AI(Ollama)に答えさせるプログラム。
      「ユーザーの問いかけに対して、登録した知識を使って回答を生成する」という、対話型エージェントの核心部分を担います。

※ ただし、設定用の共通モジュールとして config.py は別で作成します。

1.2. 「継続的なアップデート」の重要性

RAGの最大の武器は、「AI本体を再学習(ファインチューニング)させずに、後出しで知識を教えられること」です。

これを「継続的なアップデート」と呼ぶのには、実用上の切実な理由が3つあります。

  1. 情報の「鮮度」を保ち、嘘を抑制する:
    • LLM(大規模言語モデル)の知識は、学習が完了した時点で止まっています。
      昨日のニュースや、独自のアイデアをAIは知りません。
      RAGなら、データを1件追加するだけでAIの回答を最新に保ち、根拠に基づいた正確な回答(ハルシネーションの抑制)を促せます。
  2. 「プライベートな情報」を自在に管理する:
    • 一般的なAIは、あなたの個人的な趣味の記録や社内の独自ルールまでは知りません。
      これらを「外付けの記憶」として渡し、必要に応じて「情報の追加、修正や削除(新陳代謝)」を行えるようにすることで、AIは初めてあなたの私生活や実務に深く入り込めるようになります。
  3. 「運用のコスト」を最小化する:
    • AIを丸ごと再学習させるには膨大な計算資源と時間が必要ですが、ChromaDBへの知識追加は、「あなたが作成した文章情報を、作成したアプリで読み込ませるだけ」で完了します。
      ベクトル化の処理も数秒から数十秒程度です。
      この「必要な分だけを、低コストで書き換えられる」という手軽さがあるからこそ、日々の気づきをAIに教え込む作業が、負担ではなく楽しい習慣(ナレッジ・ルーティン)に変わるのです。

プログラムを分けて「登録用」を独立させることは、単なる整理整頓ではありません。
AIを「過去の知識で喋る機械」から、「あなたの最新の知見を常に反映し、嘘をつかないパートナー」へと進化させるための、極めて重要な設計なのです。

第2章:設定を一箇所にまとめる「config.py」の作成

プログラムを「登録用」と「回答用」の2つに分けたとき、初心者の方が陥りやすい落とし穴があります。
「登録したはずのデータが、回答用プログラムで見つからない」という現象です。

これは、プログラムを実行する場所(カレントディレクトリ)によって、データベースの保存先がバラバラになってしまうことが原因です。
このトラブルを防ぐために、今回はパス(場所)の指定を絶対パスで行い、システムの安定性を高めます。

また、将来的な拡張性の観点から、プログラムが2つ、3つと増えていった場合、それぞれのファイルに同じ設定(保存場所やモデル名)を書くのは非効率です。

そこで、システムの「住所」や「設定」を一括管理する専用のファイル config.py を作成します。

Pythonなどの開発環境の構築がお済みでない方は、まずは入門シリーズ 第1回記事を参照のうえ、環境構築の作業を完了させておいてください。

2.1. 共通設定ファイル(config.py)の作成

以下のコードを config.py という名前で、プロジェクトフォルダの直下(例: ai_test)に保存してください。

config.py
import os

# プログラムの親フォルダを取得(絶対パス)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))

# データベースの保存先フォルダ名
PERSIST_DIRECTORY = os.path.join(BASE_DIR, "chroma_db")

# 使用するAIモデルの名前
OLLAMA_MODEL = "llama3.1"

一見、ファイルが増えて面倒に思われるかもしれませんが、これには「継続的アップデート」を支える大きなメリットがあります。

  • 一元管理: データベースのフォルダ名や、使用するAIモデルを変更したくなったとき、config.py を一箇所書き換えるだけでシステム全体に反映されます。
  • ミスの防止: 「登録用と回答用で、フォルダ名のスペルを書き間違えた」といった、ケアレスミスによるデータの不一致を防げます。
  • プロの設計思想: 処理(ロジック)と設定(パラメータ)を分けるのは、本格的なアプリ開発でも使われる「保守性の高い」書き方です。

2.3. Anaconda環境の有効化

前回記事でご紹介したAnacondaプロンプトからの起動方法が確実です。
本記事にも手順を再掲します。

事前準備として、Antigravityを起動している場合はいったん閉じておいてください。

  1. Anaconda Prompt を起動します。
  2. 以下のコマンドを実行し、環境を有効にします。
    • conda activate ai_test
  3. 以下のコマンドで、プロジェクトの作業ディレクトリへ移動します。
    黄色アンダーラインの個所は、自身の環境に合わせて読み替えてください)
    • cd c:\Users\(ユーザー名)\ai\ai_test
  4. ここがポイント! そのままAnaconda PromptからAntigravityを起動します。
    • antigravity .
    • ※ 「.」(ドット)は「現在のフォルダを開く」という意味です。

あらためてAntigravityが起動できましたら、本格的にプログラムの実装を再開していきましょう。

2.4. 必要ライブラリのインストール

Antigravityのターミナルで、以下のコマンドを実行してください。
今回のアプリに必要な外部ライブラリがインストールされます。

※ 念のため、前回までにインストールしてあるライブラリも記述しています。

pip install langchain langchain-community langchain-ollama langchain-chroma chromadb

第3章:知識の登録プログラム(ingest.py)の作成

本章の主役は、ingest.py です。
「Ingest(摂取する・取り込む)」という意味通り、テキスト情報を読み込み、AIが検索しやすい「ベクトルデータ」に変換してChromaDBへ保存する役割を担います。

3.1. プログラムの仕様と使い方

この ingest.py は、AIの「外部記憶」への書き込み専用ツールです。
以下の仕様で実装します。

  • 機能:
    • Document 形式で記述されたテキストデータをベクトル化し、ChromaDB(config.py で指定したフォルダ)に保存する。
    • 今回は、ジャズアルバムの情報をデータベースに入れていますが、お好みに応じてテーマを変更してもOKです。
  • データの追加と重複についての注意:
    • 追加(追記): このプログラムを実行するたびに、文章情報はデータベースへ「追記」されます。まったく同じ文章情報のままプログラムを2回実行すると、データベース内には完全に同一のデータが2つ存在することになります。AIが同じ回答を繰り返す原因になるため、注意が必要です。
  • データの更新・削除方法:
    • 今回の簡易版では、情報を書き換えたい場合、chroma_db フォルダをフォルダごと手動で削除して、プログラムを再実行するのが最も確実でミスがありません。
    • 特定のデータだけを狙って「更新・削除」する高度な管理術(ID指定など)は、コードが複雑になるため、本連載の後半でメンテナンス専用回として解説する予定です。
  • 今後の拡張予定:
    • 今回は学習のためにコード内にテキストを直接記述(ハードコーディング)しますが、次回以降の記事で「特定のフォルダに置いたテキストファイルを自動で読み込む」機能へとアップデートします。

3.2. ingest.py の完成コード

以下のプログラムを ingest.py という名前で作成し、設定ファイルの config.py と同じフォルダ階層に保存してください。

ingest.py
from langchain_chroma import Chroma
from langchain_ollama import OllamaEmbeddings
from langchain_core.documents import Document
from config import PERSIST_DIRECTORY, OLLAMA_MODEL

def main():
    # 1. 覚えさせたい知識
    jazz_data = [
        Document(page_content="マイルス・デイヴィスのアルバム『Kind of Blue』は1959年に録音されたモダン・ジャズの金字塔です。"),
        Document(page_content="『Kind of Blue』には、ピアニストのビル・エヴァンスが参加しており、モード・ジャズという手法を確立しました。"),
        Document(page_content="マイルスの別の名盤『Bitches Brew』は1970年にリリースされ、エレクトリック・ジャズ(フュージョン)の先駆けとなりました。")
    ]

    print(f"langchain-ollama を使用して '{OLLAMA_MODEL}' でベクトル化中...")

    # 2. 推奨される新しいインポート元からの準備
    embeddings = OllamaEmbeddings(model=OLLAMA_MODEL)

    # 3. データの保存 (Chromaも専用パッケージを使うのが現在の推奨です)
    vectordb = Chroma.from_documents(
        documents=jazz_data,
        embedding=embeddings,
        persist_directory=PERSIST_DIRECTORY
    )

    print(f"登録完了! '{PERSIST_DIRECTORY}' フォルダに保存されました。")

if __name__ == "__main__":
    main()

このプログラムを実行すると、ChromaDBに文章データが保存され、以下のようなメッセージが表示されます。
(実際のPATHは伏字として xxxx で表示しています。ご自身の環境に読み替えてください。)

実行結果
langchain-ollama を使用して 'llama3.1' でベクトル化中...
登録完了! 'c:\Users\xxxx\ai\ai_test\chroma_db' フォルダに保存されました。

3.3. 技術的なポイント

コード内の jazz_dataDocument(page_content="...") という形式を使っています。
これは単なる文字列ではなく、LangChainというライブラリが推奨する「文書単位」のデータ形式です。

今は内容(page_content)だけですが、将来的に「どの本の何ページから引用したか」といったメタデータ(付随情報)を一緒に保存したくなったとき、この形式にしておくことでスムーズに拡張できます。

第4章:回答用プログラム(ask.py)の作成

本章のゴールは、AIがジャズの知識を、データベースから探し出して答えられるようにすることです。

4.1. プログラムの仕様

ask.py は、ユーザーがキーボードから打ち込んだ質問に対して、AIがデータベースを検索して回答する対話型アプリとして実装します。

  1. 対話ループ: 1回回答して終わりではなく、exit と入力するまで何度でも質問を繰り返せるようにします。
  2. 検索の準備: 第3章と同じ OllamaEmbeddings を使い、質問内容をベクトルに変換します。
  3. データベースの参照: 指定された保存先(chroma_db)から、質問に似た内容のテキストを探し出します。
  4. AIの回答: 見つかったテキストを「参考資料」としてAI(llama3.1)に渡し、それを元に回答を生成させます。

4.2. ask.py の完成コード

以下のプログラムを ask.py という名前で作成し、設定ファイルの config.py と同じフォルダ階層に保存してください。

ask.py
from langchain_chroma import Chroma
from langchain_ollama import OllamaEmbeddings, OllamaLLM
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
# 第2章の設定を読み込む
from config import PERSIST_DIRECTORY, OLLAMA_MODEL

def main():
    # 1. データベース(記憶)の読み出し
    embeddings = OllamaEmbeddings(model=OLLAMA_MODEL)
    vectordb = Chroma(
        persist_directory=PERSIST_DIRECTORY,
        embedding_function=embeddings
    )

    # 2. AI(考える脳)の準備
    llm = OllamaLLM(model=OLLAMA_MODEL)

    # 3. 検索の仕組みを定義(Retriever)
    retriever = vectordb.as_retriever()

    # 4. 回答のテンプレート(指示書)を作成
    template = """以下の「参考情報」を元に、質問に日本語で答えてください。
    
    【参考情報】
    {context}
    
    【質問】
    {question}
    """
    prompt = ChatPromptTemplate.from_template(template)

    # 5. 処理の流れ(チェーン)を構築
    # 検索 -> テンプレート適用 -> AI回答 -> 文字列として出力
    chain = (
        {"context": retriever, "question": RunnablePassthrough()}
        | prompt
        | llm
        | StrOutputParser()
    )

    print(f"\n--- ローカルAIチャット起動(モデル: {OLLAMA_MODEL}) ---")
    print("終了するには 'exit' と入力してください。\n")

    # 6. インタラクティブな対話ループ
    while True:
        query = input("質問をどうぞ: ")
        
        if query.lower() in ["exit", "quit", "終了"]:
            print("アプリを終了します。")
            break
        
        if not query.strip():
            continue

        print("AIが回答を生成中...")
        
        # 実行(invoke)
        response = chain.invoke(query)
        
        print("\n[AIの回答]")
        print(response)
        print("-" * 30 + "\n")

if __name__ == "__main__":
    main()

実行してみると、以下のようにAIと対話ができるようになっています。

実行結果(証拠画像)

ただし、現状ではたった3件のテキスト情報しかデータベースに与えていないため、データベースにない質問をしても、答えは返ってきません。

AIが知らない質問をした際の結果

第4章:まとめと次のステップ

4.1. まとめ

本記事では、前回作成したプログラムを元にして、ユーザーが質問を繰り返すことができ、AIが情報源から検索して回答するインタラクティブなアプリに進化させました。

  • config.py(司令塔): システム全体の設定を一箇所にまとめ、保守性を高めました。
  • ingest.py(記憶): 独自のテキストデータを「ベクトル」というAIが理解できる数値に変換し、データベース(ChromaDB)に保存しました。
  • ask.py(対話): ユーザーの質問に対して、データベースから関連情報を探し出し、AIが日本語で回答する仕組み(RAG:検索拡張生成)を構築しました。

上記のバージョンアップによって、現代のAIアプリケーション開発における最も標準的で強力なフレームワークの基礎が固まりました。

4.2. 次のステップ

これで基本の「型」はできましたが、今はまだコードの中に直接ジャズのメモを書き込んでいます。
これでは、何百ページもの資料を読ませるには限界があります。

次回記事では: 「特定のフォルダにテキストファイル(.txt)を放り込むだけで、AIが自動的にすべてを読み込み、あなたの最強の書記官になる」という、一括自動登録機能の実装に挑戦します。

コメント

タイトルとURLをコピーしました