ローカル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を指定することで改行されて結合されていく