chromaDB + streamlit + elyza:jp8b

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})

のコードを
OpenAI のAPIではなく
Ollama のelyza:jp8bを使用するようにコードを変更

openai の依存を削除し、/api/chat(ストリーミング)を requests で直接呼び出し。
モデル既定値を elyza:jp8b に変更。
Ollama のベースURL をサイドバーで指定可能(既定: http://localhost:11434)。埋め込みも同じURLを使用。
温度などは options 経由で送信。
ストリーミングは1行ごとの JSON を読み、message.content を随時描画。

import streamlit as st
import chromadb
from docx import Document
import requests
import json

# -----------------------------
# 共通設定
# -----------------------------
st.set_page_config(page_title="Local LLM Chat")

st.sidebar.title("設定")
ollama_base_url = st.sidebar.text_input("Ollama ベースURL", value="http://localhost:11434")
model = st.sidebar.text_input("モデル名", value="elyza:jp8b")  # 既定を elyza:jp8b に
temperature = st.sidebar.slider("temperature", 0.0, 2.0, 0.3, 0.1)
system_prompt = st.sidebar.text_area(
    "System Prompt",
    "あなたは有能なアシスタントです。日本語で回答してください",
)

# -----------------------------
# 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 API ユーティリティ
# -----------------------------
def ollama_embed(text: str):
    """Ollama の /api/embeddings を使ってベクトルを取得"""
    url = f"{ollama_base_url}/api/embeddings"
    r = requests.post(
        url,
        json={"model": "nomic-embed-text", "prompt": text},
        timeout=60,
    )
    r.raise_for_status()
    data = r.json()
    return data["embedding"]

def ollama_chat_stream(messages, model_name: str, temperature: float = 0.3):
    """
    Ollama の /api/chat ストリームを逐次 yield するジェネレータ。
    messages: [{"role":"system|user|assistant", "content":"..."}]
    """
    url = f"{ollama_base_url}/api/chat"
    payload = {
        "model": model_name,
        "messages": messages,
        "stream": True,
        "options": {
            "temperature": float(temperature)
        }
    }
    with requests.post(url, json=payload, stream=True) as r:
        r.raise_for_status()
        for line in r.iter_lines(decode_unicode=True):
            if not line:
                continue
            # 各行は JSON。末尾で {"done":true} が来る
            try:
                obj = json.loads(line)
            except json.JSONDecodeError:
                continue
            if obj.get("done"):
                break
            msg = obj.get("message", {})
            delta = msg.get("content", "")
            if delta:
                yield delta

# -----------------------------
# 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

# -----------------------------
# UI: アップロード → インデックス
# -----------------------------
uploaded_files = st.sidebar.file_uploader(
    "Wordファイルをアップロード(.docx)",
    type=["docx"],
    accept_multiple_files=True
)

if st.sidebar.button("インデックス作成"):
    try:
        for file in uploaded_files or []:
            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("インデックス作成完了")
    except Exception as e:
        st.sidebar.error(f"インデックス作成でエラー: {e}")

# -----------------------------
# 会話領域
# -----------------------------
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("メッセージを入力")

if prompt:
    # ユーザーの入力を表示
    with st.chat_message("user"):
        st.write(prompt)

    # --- RAG: 近傍検索 ---
    try:
        query_embed = ollama_embed(prompt)
        results = st.session_state.collection.query(
            query_embeddings=[query_embed],
            n_results=2
        )
    except Exception as e:
        results = {"documents": []}
        st.warning(f"RAG検索でエラーが発生しました: {e}")

    # ドキュメントの結合
    if results.get("documents"):
        context_text = "\n".join(results["documents"][0])
        rag_prompt = f"""
以下は関連ドキュメントの抜粋です。
{context_text}

この情報を参考に以下の質問に答えてください。
{prompt}
"""
        final_user_prompt = rag_prompt
    else:
        final_user_prompt = prompt

    # セッション履歴にユーザー発話(RAG統合後)を追加
    st.session_state.messages.append({"role": "user", "content": final_user_prompt})

    # Ollama へ投げる message 配列を組み立て
    chat_messages = []
    if system_prompt.strip():
        chat_messages.append({"role": "system", "content": system_prompt})
    # 既存履歴をそのまま渡す(role は "user"/"assistant")
    chat_messages.extend(st.session_state.messages)

    # --- ストリーミング応答 ---
    with st.chat_message("assistant"):
        placeholder = st.empty()
        stream_response = ""
        try:
            for delta in ollama_chat_stream(chat_messages, model, temperature):
                stream_response += delta
                placeholder.write(stream_response)
        except Exception as e:
            st.error(f"Ollama応答でエラーが発生しました: {e}")

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


コメントを残す

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