💡

onnxruntime地獄からの脱出!LangChain × Ollama × ChromaでローカルRAG環境を構築するまでの戦い

に公開

はじめに

こんにちは。今回はローカル環境で LangChain + Ollama + Chroma を使って RAG(Retrieval-Augmented Generation)を構築しようとしたら、onnxruntime との終わりなき戦いに巻き込まれた話を記録します。

LangChain + Ollama の構成は非常に魅力的なのですが、内部で勝手に onnxruntime を呼び出す chromadb の仕様に悩まされました。


やりたかったこと

  • LangChain を使って Markdown ドキュメントをベクトル化
  • Ollama で埋め込み生成(nomic-embed-text モデル)
  • Chroma に保存して質問検索できるようにする
  • Dockerやクラウドを使わず、ローカル環境で完結

環境構成

  • OS: Windows 11
  • Python: 3.11
  • LangChain: langchain, langchain-community, langchain-ollama
  • Ollama: ローカルで動作
  • chromadb: 0.4.x
  • Embedding model: nomic-embed-text

最初のコードと初回エラー

from langchain.vectorstores import Chroma
from langchain.embeddings import OllamaEmbeddings
from langchain.document_loaders import DirectoryLoader, TextLoader
...

embedding = OllamaEmbeddings(model="nomic-embed-text")
vectordb = Chroma.from_documents(docs, embedding=embedding, persist_directory="chroma_db")

すると最初にぶち当たったのがこちら:

ImportError: DLL load failed while importing onnxruntime_pybind11_state

onnxruntime なんて使ってないのに…なぜ…


onnxruntimeとの戦い(第1ステージ)

chromadbDefaultEmbeddingFunction() を import 時点で勝手に呼ぶ仕様になっており、
たとえ使わなくても onnxruntime が必要になる地獄構成でした。


空ドキュメントでもonnxruntime呼ばれる(第2ステージ)

docs = []  # 空でも
Chroma.from_documents(docs, ...)  # ← ここで呼ばれる

→ 空のドキュメントでも内部で DefaultEmbeddingFunction() が呼ばれ、onnxruntime が爆発!


CHROMA_NO_DEFAULT_EMBEDDINGSが効かない罠(第3ステージ)

環境変数 CHROMA_NO_DEFAULT_EMBEDDINGS=True を設定しても効果なし!

なぜなら:

  • import chromadb のタイミングで環境変数が設定されていないと無効になる
  • os.environ で最上部に書く必要がある

真の解決策と動作確認

✅ 1. retriever.py の最上部で封じる

import os
os.environ["CHROMA_NO_DEFAULT_EMBEDDINGS"] = "True"

✅ 2. UTF-8 読み込み指定

from functools import partial
loader = DirectoryLoader(..., loader_cls=partial(TextLoader, encoding="utf-8"))

✅ 3. モデルをPull

ollama pull nomic-embed-text

✅ 4. 完全勝利のログ

✅ Vector store created in 3.90 seconds

まとめ:学びと教訓

学び 対策
chromadbimport だけで onnxruntime を呼ぶことがある CHROMA_NO_DEFAULT_EMBEDDINGS=Trueos.environ で最上部に
chunks=[] でも DefaultEmbeddingFunction() が実行される 早期 return で防御
ollama のモデルは明示的に pull が必要 ollama pull nomic-embed-text
TextLoader のエンコーディングに注意 encoding='utf-8' を明示すること

最終的な動作コード(コピペOK)

import os
os.environ["CHROMA_NO_DEFAULT_EMBEDDINGS"] = "True"

from langchain_community.vectorstores import Chroma
from langchain_ollama import OllamaEmbeddings
from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from functools import partial
import time

CHROMA_PATH = "chroma_db"
DOCUMENTS_PATH = "documents"
embedding = OllamaEmbeddings(model="nomic-embed-text")

def create_vector_store():
    loader = DirectoryLoader(DOCUMENTS_PATH, glob="**/*.md", loader_cls=partial(TextLoader, encoding="utf-8"))
    docs = loader.load()
    if not docs:
        return
    splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
    chunks = splitter.split_documents(docs)
    if not chunks:
        return
    vectordb = Chroma.from_documents(chunks, embedding=embedding, persist_directory=CHROMA_PATH)
    print("✅ Vector store created")

def get_relevant_docs(question: str, k=3):
    vectordb = Chroma(persist_directory=CHROMA_PATH, embedding_function=embedding)
    docs = vectordb.similarity_search(question, k=k)
    return [doc.page_content for doc in docs]

おわりに

LangChain は便利ですが、ちょっとした仕様の違いでハマりポイントも多いです。
この記事が同じように苦しむ人の助けになれば幸いです🙏


💬 コメント歓迎!

質問・補足・改善案などあればぜひコメント欄で教えてください!

Discussion