ChromaDBベクターデータベースの構築とホスト¶
以下のノートブックでは、DataRobotのPythonクライアントを使用してベクターデータベースを構築、検証、およびDataRobotプラットフォームに登録する方法の例を示します。メタデータのフィルター機能を備えたChromaDBのインメモリーベクターストアを、カスタムモデル内にロードしてホストする方法を説明します。このノートブックは、DataRobot Notebooksで使用するように設計されているため、ダウンロードし、プラットフォームにアップロードして使用することを推奨します。
ChromaDBでホストされたドキュメントをカスタムモデルで使用する場合、1ファイルあたりの最大ファイルサイズは1GBです。
セットアップ¶
以下の手順では、ベクターデータベースをDataRobotプラットフォームと連携させるために必要な設定について説明します。
このワークフローは以下の機能フラグを使用します。これらの機能を有効にするには、DataRobotの担当者または管理者にお問い合わせください。
- すべてのカスタムモデルでパブリックネットワークへのアクセスを有効にする(プレミアム)
- 生成モデルの監視サポートを有効にする
- Enable Custom Inference Models
- Enable GenAI Experimentation
Use a codespace, not a DataRobot Notebook, to ensure this notebook has access to a filesystem. Use Python 3.12 to match the GenAI execution environment used for deployment.
ノートブックのセッションタイムアウトを180分に設定します。
少なくとも"Medium"(16GB RAM)インスタンスを使用して、ノートブックコンテナを再起動します。
ドキュメントアーカイブをノートブックファイルシステムにアップロードします(オプション)。
ライブラリのインストール¶
次のライブラリをインストールします。
# Upgrade pip to fix langchain installation issues
!pip install --upgrade pip "setuptools<82"
If you are running this notebook on MacOS or Windows substitute pysqlite3 for pysqlite3-binary.
!pip install "langchain" \
"langchain-community" \
"langchain-chroma" \
"langchain-text-splitters" \
"sentence-transformers==3.0.0" \
"datarobot" \
"datarobot-predict" \
"unstructured" \
"pysqlite3-binary"
# replace sqlite3 with pysqlite3 to fix chroma issues
__import__('pysqlite3')
import sys
sys.modules['sqlite3'] = sys.modules.pop('pysqlite3')
import datarobot as dr
from datarobot.models.genai.vector_database import CustomModelVectorDatabaseValidation
from datarobot.models.genai.vector_database import VectorDatabase
DataRobotに接続する¶
PythonクライアントからDataRobotに接続するためのオプションの詳細をご確認ください。
Download sample data¶
この例では、DataRobotの英語ドキュメントから作成されたサンプルデータセットを参照しています。 独自のデータを試すには、独自のローカルデータセットを参照して、このセクションおよび「テキストの読み込みと分割」セクションを変更します。
注:セルフマネージドAIプラットフォームでは、app.datarobot.comを参照するコードサンプルは、インスタンスに適したURLに変更する必要があります。
import requests, zipfile, io
SOURCE_DOCUMENTS_ZIP_URL = "https://s3.amazonaws.com/datarobot_public_datasets/ai_accelerators/datarobot_english_documentation_5th_December.zip"
UNZIPPED_DOCS_DIR = "datarobot_english_documentation"
STORAGE_DIR = "storage"
r = requests.get(SOURCE_DOCUMENTS_ZIP_URL)
z = zipfile.ZipFile(io.BytesIO(r.content))
z.extractall(f"{STORAGE_DIR}/")
テキストの読み込みと分割¶
次に、DataRobotのドキュメントデータセットをロードし、チャンクに分割します。このレシピを別のユースケースに適用する場合は、以下の点に注意してください。
- 追加または代替のドキュメントローダーを使用します。
- 余分で不要なドキュメントを除外します。
- 適切な
chunk_sizeとoverlapを選択しします。これらはトークンではなく、文字数でカウントされます。
import re
from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
SOURCE_DOCUMENTS_DIR = f"{STORAGE_DIR}/{UNZIPPED_DOCS_DIR}/"
SOURCE_DOCUMENTS_FILTER = "**/*.txt"
loader = DirectoryLoader(
f"{SOURCE_DOCUMENTS_DIR}",
glob=SOURCE_DOCUMENTS_FILTER,
loader_cls=TextLoader,
)
splitter = RecursiveCharacterTextSplitter(
chunk_size=128,
chunk_overlap=0,
)
print(f"Loading {SOURCE_DOCUMENTS_DIR} directory")
data = loader.load()
print(f"Splitting {len(data)} documents")
docs = splitter.split_documents(data)
for doc in docs:
doc.metadata['source'] = re.sub(
rf'{STORAGE_DIR}/{UNZIPPED_DOCS_DIR}/datarobot_docs/en/(.+)\.md',
r'https://docs.datarobot.com/en/docs/\1.html',
doc.metadata['source']
)
doc.metadata["category"] = doc.metadata["source"].split("|")[-1].replace(".txt", "")
print(f"Created {len(docs)} documents")
ドキュメントからベクターデータベースを作成する¶
以下のセルを使って、DataRobotのドキュメントデータセットからベクターデータベースを構築します。このノートブックはChromaDBを使用しています。これは、DataRobot Notebooksと互換性のある、メタデータのフィルター機能を備えたオープンソースのインメモリーベクターストアです。さらに、このノートブックでは、HuggingFaceのjina-embedding-t-en-v1 embeddings model(オープンソース)を使用しています。
from datetime import datetime
from langchain_chroma import Chroma
from langchain_community.embeddings.sentence_transformer import (SentenceTransformerEmbeddings)
CHROMADB_DATA_PATH = f"{STORAGE_DIR}/chromadb"
CHROMADB_EMBEDDING_CACHE_FOLDER = STORAGE_DIR + '/sentencetransformers'
CHROMADB_EMBEDDING_FUNCTION = SentenceTransformerEmbeddings(model_name="jinaai/jina-embedding-t-en-v1", cache_folder=CHROMADB_EMBEDDING_CACHE_FOLDER)
def create_chromadb_from_documents(docs, embedding_function, persist_directory):
start_time = datetime.now()
print(f'>>> BEGIN ({start_time.strftime("%H:%M:%S")}): Creating ChromaDB from documents')
print(f'Embedding function: {embedding_function}')
print(f'ChromaDB data directory: {persist_directory}')
print(' ')
print(f'Documents for loading: {len(docs)}')
db = Chroma.from_documents(docs, embedding_function, persist_directory=persist_directory)
end_time = datetime.now()
print(' ')
print(f'>>> END ({end_time.strftime("%H:%M:%S")}): Creating ChromaDB from documents')
print(f'Loaded {len(docs)} documents.')
print(f"Chroma VectorDB now has {db._collection.count()} documents")
total_elapsed_min = (end_time - start_time).total_seconds() / 60
document_average_sec = (end_time - start_time).total_seconds() / len(docs)
print("Total Elapsed", "%.2f" % total_elapsed_min, "minutes")
print("Document Average", "%.2f" % document_average_sec, "seconds")
return db
print(f"Created {len(docs)} documents")
db = create_chromadb_from_documents(docs, CHROMADB_EMBEDDING_FUNCTION, CHROMADB_DATA_PATH)
print(db._collection.count())
ベクターデータベースのテスト¶
以下のセルを使って、モデルにメタデータのフィルターによる類似性検索を実行させ、ベクターデータベースをテストします。提供されたクエリーに一致する上位5つのドキュメントが返されます。
question = "What is MLOps?"
top_k = 5
metadata_filter = {"category": {"$eq": "index"}}
results_with_scores = db.similarity_search_with_score(
question,
k=top_k,
filter=metadata_filter,
)
print(len(results_with_scores))
for doc, score in results_with_scores:
print("********************************************************************************")
print(" ")
print("----------")
print(f"METADATA: {doc.metadata}, Score: {score}")
print(" ")
print("----------")
print(f"CONTENT: {doc.page_content}")
print(" ")
非構造化カスタムモデルをデプロイするためのフックを定義する¶
以下のセルは、非構造化カスタムモデルのデプロイに使用されるメソッドを定義します。これらには、カスタムモデルのロードと、スコアリングのためのモデルの使用が含まれます。
def load_model(input_dir):
"""Custom model hook for loading our knowledge base."""
import os
print("Loading model")
chromadb_data_path = os.path.join(input_dir, "chromadb")
chromadb_embedding_cache_folder = os.path.join(input_dir, "sentencetransformers")
chromadb_embedding_model_name = "jinaai/jina-embedding-t-en-v1"
# https://docs.trychroma.com/troubleshooting#sqlite
__import__('pysqlite3')
import sys
sys.modules['sqlite3'] = sys.modules.pop('pysqlite3')
from langchain_chroma import Chroma
from langchain_community.embeddings.sentence_transformer import SentenceTransformerEmbeddings
print(f'CHROMADB_DATA_PATH = {chromadb_data_path}')
print(f'CHROMADB_EMBEDDING_CACHE_FOLDER = {chromadb_embedding_cache_folder}')
print(f'CHROMADB_EMBEDDING_MODEL_NAME = {chromadb_embedding_model_name}')
chromadb_embedding_function = SentenceTransformerEmbeddings(
model_name=chromadb_embedding_model_name,
cache_folder=chromadb_embedding_cache_folder,
)
db = Chroma(
persist_directory=chromadb_data_path,
embedding_function=chromadb_embedding_function,
)
print(f'Loaded ChromaDB with {db._collection.count()} chunks')
return db
def score_unstructured(model, data, **kwargs) -> str:
"""Custom model hook for retrieving relevant docs with our knowledge base.
When requesting predictions from the deployment, pass a dictionary
with the following keys:
- 'question' the question to be passed to the vector store retriever
- 'metadata' the metadata filter to be passed to the vector store retriever
- 'top_k' the number of results to return
datarobot-user-models (DRUM) handles loading the model and calling
this function with the appropriate parameters.
Returns:
--------
rv : str
Json dictionary with keys:
- 'question' user's original question
- 'relevant' the generated answer to the question
- 'metadata' - metadata for each document
- 'error' - error message if exception in handling request
"""
import json
try:
data_dict = json.loads(data)
question = data_dict['question']
top_k = data_dict.get("k", 10)
metadata_filter = data_dict.get("filter", None)
results_with_scores = model.similarity_search_with_score(
question,
k=top_k,
filter=metadata_filter,
)
print(f'Returned {len(results_with_scores)} results')
relevant, metadata = [], []
for doc, score in results_with_scores:
relevant.append(doc.page_content)
doc.metadata["similarity_score"] = score
metadata.append(doc.metadata)
rv = {
"question": question,
"relevant": relevant,
"metadata": metadata,
}
except Exception as e:
rv = {'error': f"{e.__class__.__name__}: {str(e)}"}
return json.dumps(rv), {"mimetype": "application/json", "charset": "utf8"}
ローカルでフックをテストする¶
デプロイに進む前に、以下のセルを使用して、カスタムモデルフックが正しく機能することを確認します。
import json
## Test the hooks locally
score_unstructured(
load_model(f"{STORAGE_DIR}"),
json.dumps(
{
"question": "How do I replace a custom model on an existing custom environment?",
"filter": {"category": {"$eq": "index"}},
"k": 1,
}
),
)
ナレッジベースのデプロイ¶
The cells below use the DataRobot Python client to:
- Package the custom model hooks and vector database artifacts.
- Create an unstructured custom model in the workshop.
- Upload a new custom model version to the GenAI Python 3.12 execution environment.
- Deploy the model and return a
dr.Deploymentobject for predictions.
import inspect
from pathlib import Path
CUSTOM_MODEL_NAME = "External DR Knowledge Base using ChromaDB"
DEPLOY_DIR = Path("storage/deploy_model")
DEPLOY_DIR.mkdir(parents=True, exist_ok=True)
(DEPLOY_DIR / "custom.py").write_text(
inspect.getsource(load_model) + "\n\n" + inspect.getsource(score_unstructured)
)
(DEPLOY_DIR / "requirements.txt").write_text(
"langchain_chroma\n"
"pysqlite3-binary\n"
"langchain-community\n"
"sentence-transformers==3.0.0\n"
)
model_files = [
(str(DEPLOY_DIR / "custom.py"), "custom.py"),
(str(DEPLOY_DIR / "requirements.txt"), "requirements.txt"),
]
for artifact_dir in ("chromadb", "sentencetransformers"):
source_root = Path("storage") / artifact_dir
for path in source_root.rglob("*"):
if path.is_file():
model_files.append(
(str(path), f"{artifact_dir}/{path.relative_to(source_root).as_posix()}")
)
genai_environment = dr.ExecutionEnvironment.list(
search_for="[GenAI] Python 3.12 with Moderations"
)[0]
existing_models = [m for m in dr.CustomInferenceModel.list() if m.name == CUSTOM_MODEL_NAME]
if existing_models:
custom_model = existing_models[0]
else:
custom_model = dr.CustomInferenceModel.create(
name=CUSTOM_MODEL_NAME,
target_type=dr.TARGET_TYPE.UNSTRUCTURED,
language="python",
)
print("Uploading custom model version")
model_version = dr.CustomModelVersion.create_clean(
custom_model_id=custom_model.id,
base_environment_id=genai_environment.id,
files=model_files,
maximum_memory=8 * 1024 * 1024 * 1024,
max_wait=60 * 30,
)
print("Building custom model dependency image")
try:
build_info = dr.CustomModelVersionDependencyBuild.start_build(
custom_model_id=custom_model.id,
custom_model_version_id=model_version.id,
max_wait=60 * 60,
)
except dr.errors.ClientError as e:
if "already has a dependency image" in str(e):
print("Dependency build already exists, skipping build step")
build_info = dr.CustomModelVersionDependencyBuild.get_build_info(
custom_model.id,
model_version.id,
)
else:
raise
if build_info is not None and build_info.build_status != "success":
raise RuntimeError(
f"Dependency build failed with status '{build_info.build_status}'.\n"
f"{build_info.get_log()}"
)
print("Creating deployment")
pred_server = dr.PredictionServer.list()[0]
deployment = dr.Deployment.create_from_custom_model_version(
model_version.id,
label=CUSTOM_MODEL_NAME,
default_prediction_server_id=pred_server.id,
max_wait=60 * 30,
)
print(f"Deployment ID: {deployment.id}")
デプロイのテスト¶
Test that the deployment can successfully provide responses to questions using the datarobot-predict library.
import json
from datarobot_predict.deployment import predict_unstructured
# deployment = dr.Deployment.get("ADD_VALUE_HERE")
content, response_headers = predict_unstructured(
deployment=deployment,
data={
"question": "How do I replace a custom model on an existing custom environment?",
"filter": {"category": {"$eq": "index"}},
"k": 1,
},
)
json.loads(content)
ベクターデータベースを検証して作成¶
These methods execute, validate, and integrate the vector database. This example associates a Use Case with the validation and creates the vector database within that Use Case.
use_case_idを設定して既存のユースケースを指定するか、その名前で新しいユースケースを作成します。
use_case_id = "ADD_VALUE_HERE"
use_case = dr.UseCase.get(use_case_id)
# UNCOMMENT if you want to create a new Use Case
# use_case = dr.UseCase.create()
Validate the vector database¶
CustomModelVectorDatabaseValidation.createは、ベクターデータベースの検証を実行します。デプロイIDを必ず入力します。
external_vdb_validation = CustomModelVectorDatabaseValidation.create(
prompt_column_name="question",
target_column_name="relevant",
deployment_id=deployment.id,
use_case=use_case,
wait_for_completion=True
)
assert external_vdb_validation.validation_status == "PASSED"
ベクターデータベースの作成¶
検証が完了したら、VectorDatabase.create_from_custom_model()を使用してベクターデータベースを連携します。ユースケース名(またはユースケースID)、外部ベクターデータベースの名前、および前のセルから返される検証IDを指定する必要があります。
vdb = VectorDatabase.create_from_custom_model(
name="DR Vector Database",
use_case=use_case,
validation_id=external_vdb_validation.id
)
assert vdb.execution_status == "COMPLETED"
print(f"Vector Database ID: {vdb.id}")
これで、このベクターデータベースIDをGenAI E2E基本ステップで使用して、ベクターデータベースでLLMブループリントを作成できるようになりました。