chromaDB

ローカルLLMを使ってRAGが導入された本格的なチャットボットを作ろう!ローカルLLM実行ツールであるOllamaとPythonを使ってローカル環境にいくら実行しても無料のAI環境を作ろう!

で word ファイルをRAGにしているので参考になるかも
chromaDBも取り扱っているし

https://ollama.com/library/nomic-embed-text
でollamaでベクトル化するモデルをインストールする

ollama pull nomic-embed-text

でインストール

次にchromaDBのインストール

pip install chromadb

なお
ChromaDBでは、テキストデータを直接ベクトルストアに保存することもできる
ChromaDBをローカルファイルで使う

も参考に

そしてドキュメントファイルをインストールして使えるようにする
python-docxと request もインストールする

pip install python-docx  

python-docx でWordファイルを操作する
も参考に

とりあえず
OpenAI のAPIなので
後でOllamaに書き換えて行う

import streamlit as st
from openai import OpenAI
import chromadb
from docx import Document
import requests


# ChromaDBの設定
DB_DIR = "./chroma_db"
chroma_client = chromadb.PersistentClient(path=DB_DIR)

if "collection" not in st.session_state:
    st.session_state.collection = chroma_client.get_or_create_collection(
        name="local_docs"
    )

# Ollamaからインストールしたモデルを使ったベクトル化関数
def ollama_embed(text):
    r = requests.post(
        "http://localhost:12000/api/embeddings",
        json={"model": "nomic-embed-text", "prompt": text}
    )
    data = r.json()
    return data["embedding"]

# Wordファイルを読み込む関数
def load_word_document(file):
    return "\n".join(para.text for para in Document(file).paragraphs)

# テキスト分割関数
def split_text(text):
    chunk_size = 200
    overlap = 50
    chunks = []
    start = 0
    while start < len(text):
        end = start + chunk_size
        chunks.append(text[start:end])
        start += chunk_size - overlap
    return chunks

## サイドバーの設定 ##
st.set_page_config(page_title="Local LLM Chat")

st.sidebar.title("設定")
model = st.sidebar.text_input("モデル名", value="llama3.1:8b")
temperature = st.sidebar.slider("temperature", 0.0, 2.0, 0.3, 0.1)
system_prompt = st.sidebar.text_area(
    "System Prompt",
    "あなたは有能なアシスタントです。日本語で回答してください",
)

# ワードファイルのアップロード
uploaded_files = st.sidebar.file_uploader(
    "Wordファイルをアップロード(.docx)",
    type=["docx"],
    accept_multiple_files=True
)

if st.sidebar.button("インデックス作成"):
    for file in uploaded_files:
        text = load_word_document(file)
        chunks = split_text(text)
        for i, chunk in enumerate(chunks):
            embed = ollama_embed(chunk)
            st.session_state.collection.add(
                documents=[chunk],
                embeddings=,
                ids=[f"{file.name}_{i}"]
            )
    st.sidebar.success("インデックス作成完了")

# タイトル
st.title("Local LLM Chat")

# 会話の履歴を保管
if "messages" not in st.session_state:
    st.session_state.messages = []

# 会話の履歴をリセットするボタン
if st.sidebar.button("会話をリセット"):
    st.session_state.messages = []

# 会話の履歴を表示
for m in st.session_state.messages:
    with st.chat_message(m["role"]):
        st.write(m["content"])


prompt = st.chat_input("メッセージを入力")

client = OpenAI(
    api_key="ollama",   
    base_url="http://localhost:12000/v1"
)

if prompt:

    # ユーザーのプロンプトを表示
    with st.chat_message("user"):
        st.write(prompt)

    # RAG検索
    query_embed = ollama_embed(prompt)
    results = st.session_state.collection.query(
        query_embeddings=[query_embed],
        n_results=2
    )

    # {
    #     "ids":
    #     "documents": [["doc1", "doc2"]]
    #     "distances": [[XXX, XXX]]
    # }

    if results["documents"]:
        context_text = "\n".join(results["documents"][0])
        rag_prompt = f"""
        以下は関連ドキュメントの抜粋です。
        {context_text}
        この情報を参考に以下の質問に答えてください。
        {prompt}
        """
        final_user_prompt = rag_prompt
    else:
        final_user_prompt = prompt

    st.session_state.messages.append({"role": "user", "content": final_user_prompt})

    if system_prompt.strip():
        messages = [{"role": "system", "content": system_prompt}] + st.session_state.messages
    else:
        messages = st.session_state.messages
    
    # LLMの返答を表示
    with st.chat_message("assistant"):
        placeholder = st.empty()
        stream_response = ""
        stream = client.chat.completions.create(
            model=model,
            messages=messages,
            temperature=temperature,
            stream=True
        )
        for chunk in stream:
            stream_response += chunk.choices[0].delta.content
            placeholder.write(stream_response)

    # 会話の履歴を保存
    
    st.session_state.messages.append({"role": "assistant", "content": stream_response})

がコードの内容

以下は解説
まず最初にchromaDBの設定をする

DB_DIR = "./chroma_db"


この指定フォルダにDBを保存する

chroma_client = chromadb.PersistentClient(path=DB_DIR)

でchromaDBのDBをセット

この2つで使う準備ができる

if "collection" not in st.session_state: st.session_state.collection = chroma_client.get_or_create_collection( name="local_docs" )

でDBにテーブルがなければ作成

ここから必要になる機能を作成する
作成する機能は
ベクトル化関数
Wordファイルを読み込む関数
テキスト分割関数
がRAG必要

# Ollamaからインストールしたモデルを使ったベクトル化関数
def ollama_embed(text):
    r = requests.post(
        "http://localhost:12000/api/embeddings",
        json={"model": "nomic-embed-text", "prompt": text}
    )
    data = r.json()
    return data["embedding"]

この解説

  r = requests.post(
        "http://localhost:12000/api/embeddings",

の部分は local の Ollamaへのリクエスト
これで処理をする

json={"model": "nomic-embed-text", "prompt": text}

の部分は
使用するモデルの指定、今回はnomic-embed-textをベクトル化するモデルとして指定
プロンプトでtext変数を指定

data = r.json()

で返ってきたレスポンスを辞書型へ変換している

return data["embedding"]

で辞書型の中に
Embedding ば埋め込みベクトルなので
これを取り出して返している

もしdataの中を知りたいのなら
Printなどで表示すれば中にembeddingがあるのがわかる

次にwordファイルを読み込む関数

# Wordファイルを読み込む関数
def load_word_document(file):
    return "\n".join(para.text for para in Document(file).paragraphs)

この解説

from docx import Document

で使えることになる doucment を使うことで簡単に処理ができる

Document(file).paragraphs)

によりファイルの段落情報がリストになる

これを for で取り出していく

for para in Document(file).paragraphs

とすれば para に格納していく

そしてその結果を受け取る時に

"\n".join(para.text

としておくことで
Joinで ¥nを指定することで改行されて結合されていく

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です