every Tech Blog

株式会社エブリーのTech Blogです。

Databricks Asset Bundlesを活用したAIプロダクトのCI/CDパイプライン

こんにちは。
開発本部のデータ&AIチームでデータサイエンティストをしている古濵です。

最近はAIプロダクト開発をメインで担当しています。 今回は、Databricks Asset Bundlesを活用して、AIプロダクト開発向けにCI/CDパイプラインを整備した内容をまとめます。

Databricks Asset Bundlesとは

Databricks Asset Bundlesは、データやAIプロジェクトでソフトウェア開発におけるソース管理、コードレビュー、テスト、CI/CDなどを導入しやすくするツールです。 簡単に言えば、Databricksの各種リソースをInfrastructure-as-Code(IaC)として管理できます。 ノートブックをはじめとするソースコードやDatabricks上で動かすJobなどのリソースを、ymlファイルで定義できます。

docs.databricks.com

図1: Asset Bundlesを使用した開発およびCI/CDパイプライン

動機

社内の多くのDatabricks用途は、メダリオンアーキテクチャを踏襲したデータ基盤です。 例えば、ダッシュボードで参照するデータのETL、A/Bテスト結果の集計、バッチ推論ベースのMLモデルの学習・推論・デプロイなどがあります。

一方でAIプロダクト文脈では、モデルの学習はバッチ処理ですが、推論はリアルタイム処理、モデルのデプロイ(Model Servingの機能を利用)はソースコードを更新したタイミングなことが多いのではないでしょうか。 特にLLMの場合は、OpenAIなどのAPIを利用することが多いため、モデルの学習自体不要です。 つまり、推論とデプロイに重点を置いた開発がメインになります。 この開発・運用上のギャップを埋めたい思いがありました。

これらのギャップを、As-Is(現状)とTo-Be(理想)としてまとめると以下のようになりました。

No. 項目 As-Is To-Be 理由
1 ソースコード管理 pythonノートブック pythonファイル コードの再現性・テストの導入のしやすさを目指し、モジュール単位でコード管理したいため
2 ワークスペース Default workspace(ap-northeast-1)のみ Default workspace(ap-northeast-1)とTest workspace(us-east-1) 推論にLLMを利用することが多く、LLMに対する評価のフィードバックを得ながら開発・運用したいため(us-east-1の方がアップデートが早く、LLMを評価する機能が利用可能)
3 処理タイミング スケジューリングされたバッチ処理 githubのpushをトリガーとした処理 CI/CDパイプラインを用いて、テストの実行、モデルの保存と評価、Model Servingへのデプロイを自動化したいため

全体像

図1を参考に、今回のsample-project用のCI/CDパイプラインを作成しました(図2)。 右上のDatabricks workspacesを一部変更しています。

ワークスペースは2つのregion(ap-northeast-1、us-east-1)を利用します。

  • Default workspace(ap-northeast-1)
  • Test workspace(us-east-1)

図2: sample-project用のAsset Bundlesを使用した開発およびCI/CDパイプライン

コードは以下のような構成で進めます。

sample-project
    ├── .github
    │   └── workflows
    │       ├── _deploy_databricks.yml
    │       └── sample_project_cd.yml
    ├── bundles
    │   ├── resources
    │   │   └── job_deploy_sample_project_model.yml
    │   └── targets
    │       ├── default_databricks.yml
    │       └── test_databricks.yml
    ├── src
    │   ├── sample_project
    │   │   ├── __init__.py
    │   │   ├── pipeline.py
    │   │   ├── rag.py
    │   │   └── model.py
    │   └── deploy_model.py
    ├── tests
    └── databricks.yml

実装してみる

1. pythonファイルによるソースコード管理

モジュール

メイン機能となるモジュール群はsrc/sample_project/*に置きます。

rag.py

RAGの機能を有したモジュールを定義します。 今回は、シンプルなRAGクラスを定義し、OpenAIのAPIを利用して回答を生成するコードを記述しています。

from openai import OpenAI

class RAG:
    def __init__(self):
        self.llm = OpenAI()

    def generate(self, query, contexts):
        context_str = "\n".join([f"- {context['content']}" for context in contexts])
        messages = [
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": f"Context:\n{context_str}\n\nQuery: {query}"},
        ]
        completion = self.llm.chat.completions.create(
            model="gpt-4o-mini",
            messages=messages,
        )
        answer = completion.choices[0].message.content
        return answer
    
    def retrieve(self, query):
        query_embedding = self.llm.embeddings.create(
            input=query,
            model="text-embedding-3-large",
        )
        contexts = self.retieve_databricks_vector_store(query_embedding)
        return contexts

    def retieve_databricks_vector_store(self, query_embedding):
        # query_embeddingをもとにベクトル検索したコンテキスト情報を返す
        # ここではダミーのコンテキスト情報を返す
        contexts = [
            {"doc_uri": "doc1.txt", "content": "In 2013, Spark, a data analytics framework, was open sourced by UC Berkeley's AMPLab."},
            {"doc_uri": "doc2.txt", "content": "To convert a Spark DataFrame to Pandas, you can use toPandas()"},
        ]
        return contexts
pipeline.py

ユーザのクエリを受け取り、LLMの回答を返すパイプラインを定義します。

from sample_project.rag import RAG

def generate_answer(query):
    rag = RAG()
    contexts = rag.retrieve(query)
    answer = rag.generate(query, contexts)
    return answer
model.py

pipelineを利用するmlflow.pyfunc.PythonModelを継承したクラスを定義します。 このクラスは、モデルの保存時に指定することで、python modelとしてMLflowのモデルとして保存され、Model Servingで利用できるようになります。

import mlflow
from sample_project.pipeline import generate_answer

class SampleAI(mlflow.pyfunc.PythonModel):
    def predict(self, context, model_input):
        query = model_input['query'][0]
        return generate_answer(query)

テスト

テストはtests/*に置きます。 LLMによる生成が絡む処理のテストは難しいですが、前処理や後処理などのテスト可能なコードに対しては単体テスト書く想定です。 今回のサンプルコードでは前処理や後処理はないですが、要件が複雑化していくと必要になってくるかと思います。

今までの開発では、ノートブックかつDatabricksのコンソール上での開発だったため、モジュール単位の実装やテストコードが書きづらい問題がありました。 しかし、pythonファイルによるモジュール化ができたことで、モジュール単位に実装が容易になりました。 これにより、コーディングや単体テストはローカルで行い、Spark、Mlflow、Unity CatalogなどDatabricksのメイン機能を利用する開発はコンソール上で行う、といった開発フローができるようになりました。

モデルの保存と評価

モデルの保存と評価はdeploy_model.pyに記述します。 このコードは例外的にノートブックで管理します。 理由としては

  • 将来的に自作のパッケージをinstallするとき、動的にpip installできることが便利なため
  • MLflow Tracingを利用したため

などがあります。

色々記述していますが、重要なのはmlflow.start_run()で実行される、モデルの保存と評価コードです。

保存

mlflow.pyfunc.log_modelで、モデルの保存をしています。 このとき引数にcode_pathsを指定することで、モデルと一緒に該当のソースコードを保存することができます。 注意点として、MLflowのドキュメントにある通り、code_pathsは親ディレクトリを見ることができない仕様になっているようです。 そのため、以下のようなディレクトリ構成にし、code_paths=["sample_project"]を指定しています。

├── sample_project
└── deploy_model.py

この問題に関しては、MLflowのIssueでも言及されており、今後仕様が変わる可能性があります。

評価

mlflow.evaluateで、モデルの評価をしています。
このとき、model_type="databricks-agent"を指定することで、Mosaic AI Agent Evaluationに組み込まれているAI審査員機能(一般的にはLLM as a Judgeと呼ばれる審査員用のLLMがプロダクトのLLMを評価する機能)を利用することができます。 今回は以下のAI審査員を指定しています。

  • correctness: エージェントの実際の応答がground truth(expected_response)と比較して誤っていないことを保証
  • relevance_to_query: エージェントの応答が無関係なトピックに逸脱することなくユーザーの入力に直接対処することを保証
  • safety: エージェントの応答に有害、攻撃的、または有毒な内容が含まれていないことを保証


%load_ext autoreload
%autoreload 2

# COMMAND ----------

%pip install --upgrade mlflow cloudpickle databricks-vectorsearch databricks-agents openai tiktoken
%restart_python

# COMMAND ----------

dbutils.widgets.text("model_env", "dev")
dbutils.widgets.text("workspace_url", "https://{sub-domain}.cloud.databricks.com")
model_env = dbutils.widgets.get("model_env")
workspace_url = dbutils.widgets.get("workspace_url")

# COMMAND ----------

import os

os.environ["OPENAI_API_KEY"] = dbutils.secrets.get(...)
os.environ["DATABRICKS_VECTOR_SEARCH_HOST"] = "https://{sub-domain}.cloud.databricks.com"
os.environ["DATABRICKS_VECTOR_SEARCH_TOKEN"] = dbutils.secrets.get(...)

# COMMAND ----------

import mlflow

from sample_project.model import SampleAI

# DatabricksのUnity Catalogを利用するための設定
mlflow.set_registry_uri("databricks-uc")

# COMMAND ----------

# Unity Catalogで登録するモデル名
model_name = (
    "{catalog}.{schema}.sample_project_model"
    if model_env == "prd"
    else "{catalog_dev}.{schema}.sample_project_model"
)

# 実験管理先を設定
mlflow_experiment_name = '/Shared/experiments/sample_project'
mlflow.set_experiment(mlflow_experiment_name)

# COMMAND ----------

from mlflow.models import ModelSignature
from mlflow.types.schema import Schema, ColSpec

input_schema = Schema([ColSpec("string", "query")])
output_schema = Schema([ColSpec("string", "answer")])

signature = ModelSignature(inputs=input_schema, outputs=output_schema)

# COMMAND ----------

# https://docs.databricks.com/ja/generative-ai/agent-evaluation/index.html
# 今回は評価データは直に書く
import pandas as pd

eval_df =  pd.DataFrame({
    "request": [
        {"query": "What is Spark?"},
        {"query": "How do I convert a Spark DataFrame to Pandas?"}
    ],
    "expected_response": [
        "Spark is a data analytics framework.",
        "To convert a Spark DataFrame to Pandas, you can use the toPandas() method.",
    ]
})
display(eval_df)

# COMMAND ----------

# モデルの保存・評価
with mlflow.start_run():
    model_info = mlflow.pyfunc.log_model(
        artifact_path="model",
        python_model=SampleAI(),
        signature=signature,
        registered_model_name=model_name,
        code_paths=["sample_project"],
    )

    # test-databricks環境でのみ評価
    if "test" in workspace_url:
      mlflow.evaluate(
          model=generate_answer,
          data=eval_df,
          model_type="databricks-agent",
          evaluator_config={
              "databricks-agent": {
                  "metrics": [
                      "correctness",
                      "relevance_to_query",
                      "safety",
                  ]
              }
          }
      )

2. 複数のregionに分けてワークスペースを運用

図1をはじめ、Databricksのドキュメントでは、Development、Staging、Productionごとにワークスペースを分けた運用例が多いです。 しかし、弊社では、1つのワークスペースでdev/prdをwidgetsで切り替えて運用しているケースがほとんどです。
例えば、

dbutils.widgets.text("model_env", "dev")

と書くと、model_envという名前のwidgetsが作成され、

model_env = dbutils.widgets.get("model_env")

と書くと、model_envの値を取得できます。 以降、model_envの値によって参照するデータソースを変えるなど、処理の分岐をさせることができます。
Databricks Asset Bundlesは、上記のようなケースでも柔軟に対応することができました。

ここでは、冒頭の全体像で述べたようにDefault workspaceとTest workspaceの2つのワークスペースを利用します。

  • Default workspace: 主要なワークスペースで、regionはap-northeast-1を使用。 Model ServingやVector Storeなどを運用する。

  • Test workspace: テスト用のワークスペースで、regionはus-east-1を使用。 モデルの保存時に、AI審査員によるLLMの評価を行う。

ここではDevelopment、Staging、Productionを、それぞれdevstageprdとし、以下のような意味を持つとします。 図2と合わせて参照ください。

  • dev: 個人開発用。 ワークスペースのName(自分の名前)ディレクトリに反映される。 ローカルから、コードやJobの設定などのリソースをデプロイするときに使用する。
  • stage: 開発用。 ワークスペースのStagingディレクトリに反映される。 作業ブランチ→developブランチにpushした時に、CI/CDでコードやJobの設定などのリソースをデプロイするときに使用する。
  • prd: 本番用。 ワークスペースのProductionディレクトリに反映される。 developブランチ→masterブランチにpushした時に、CI/CDでコードやJobの設定などのリソースをデプロイするときに使用する。

.databrickscfg

~/.databrickscfgにワークスペースの設定を記述します。 この例では、パーソナルアクセストークン(PAT)の認証方法を利用しています。 databricksの認証に関しての詳細はこちらを参照してください。

[DEFAULT]
host = https://{default-databricks-subdomain}.cloud.databricks.com
token = dapi11111111111111111111111111111111

[TEST]
host = https://{test-databricks-subdomain}.cloud.databricks.com
token = dapi22222222222222222222222222222222

databricks.yml

Databricks Asset Bundlesの各種設定をdatabricks.ymlに記述します。 ここでは、ymlファイルの見通しをよくするために、resourcesとtargetsを別のファイルに分離しています。 devstageprdのpathは、それぞれは使いまわしやすいようにvariablesに定義しています。

bundle:
  name: sample-project

variables:
  dev_file_path:
    description: "The path to the development"
    default: /Repos/${workspace.current_user.userName}/${bundle.name}
  stage_file_path:
    description: "The path to the staging"
    default: /Repos/Staging/${bundle.name}
  prd_file_path:
    description: "The path to the production"
    default: /Repos/Production/${bundle.name}

run_as:
  user_name: ${workspace.current_user.userName}

include:
  - "bundle/resources/*.yml"
  - "bundle/targets/*.yml"

resources

JobなどのDatabricksのリソースを定義するファイルをresources/*.ymlに記述します。 他にも設定できるリソースに関してはこちらを参照してください。

resourcesの設定は全てのtargetsで共通のため、defaultではdevの設定で記述します。 これらの設定は、targetsの設定で上書きすることができます。

ここでは、モデルの保存と評価を実行するdeploy_sample_project_modelというJobを定義しています。 このJobを実行することで、モデルの保存と評価をCI/CDのパイプラインに組み込むことができます。

resources:
  jobs:
    deploy_sample_project_model:
      name: deploy_sample_project_model
      tasks:
        - task_key: deploy_model
          notebook_task:
            notebook_path: ${var.dev_file_path}/src/deploy_model
            source: WORKSPACE
            base_parameters:
              model_env: dev
              workspace_url: "{{workspace.url}}"
          job_cluster_key: deploy_model_cluster
      job_clusters:
        - job_cluster_key: deploy_model_cluster
          new_cluster:
            spark_version: 15.4.x-cpu-ml-scala2.12
            aws_attributes:
              first_on_demand: 0
              availability: SPOT
              zone_id: auto
              instance_profile_arn: arn:aws:iam::123456789101:instance-profile/databricks_shared-instance-profile
              spot_bid_price_percent: 100
              ebs_volume_count: 0
            node_type_id: r7gd.large
            enable_elastic_disk: false
            data_security_mode: SINGLE_USER
            runtime_engine: STANDARD
            autoscale:
              min_workers: 1
              max_workers: 2
      permissions:
        - group_name: dai-engineer
          level: CAN_MANAGE
      queue:
        enabled: false

targets

各環境ごとの設定をtargets/*.ymlに記述します。 ここでは、Default workspace(default_databricks)と Test workspace(test_databricks)の設定を記述しています。

default_databricks.yml

devstageprdの3つの環境を定義しています。 devをdefaultに設定し、stageprdの設定は必要な箇所を上書きしています。

run_as

  • CI/CD用のサービスプリンシパルを指定
  • 指定するサービスプリンシパルはstageprdで同様

resources

  • jobの設定を上書き
  • stageでは、notebook_pathを開発用に上書き
  • prdでは、notebook_pathとbase_parametersを本番用に上書き


targets:
  dev:
    mode: development
    default: true
    workspace:
      host: https://{default-databricks-subdomain}.cloud.databricks.com
      file_path: ${var.dev_file_path}

  stage:
    mode: production
    workspace:
      host: https://{default-databricks-subdomain}.cloud.databricks.com
      file_path: ${var.stage_file_path}
    run_as:
      service_principal_name: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    resources:
      jobs:
        deploy_sample_project_model:
          name: "[${bundle.target}] deploy_sample_project_model"
          # notebook_pathを開発用に上書き
          tasks:
            - task_key: deploy_model
              notebook_task:
                notebook_path: ${var.stage_file_path}/src/deploy_model
                source: WORKSPACE

  prd:
    mode: production
    workspace:
      host: https://{default-databricks-subdomain}.cloud.databricks.com
      file_path: ${var.prd_file_path}
    run_as:
      service_principal_name: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    resources:
      jobs:
        deploy_sample_project_model:
          name: deploy_sample_project_model
          # notebook_pathとbase_parametersを本番用に上書き
          tasks:
            - task_key: deploy_model
              notebook_task:
                notebook_path: ${var.prd_file_path}/src/deploy_model
                source: WORKSPACE
                base_parameters:
                  model_env: prd
                  workspace_url: "{{workspace.url}}"
test_databricks.yml

default_databricks.ymlとほとんど同じ設定です。 Test workspace固有の設定をする場合、こちらに記述します。

targets:
  test-dev:
    mode: development
    workspace:
      host: https://{test-databricks-subdomain}.cloud.databricks.com
      file_path: ${var.dev_file_path}

  test-stage:
    mode: production
    workspace:
      host: https://{test-databricks-subdomain}.cloud.databricks.com
      file_path: ${var.stage_file_path}
    run_as:
      service_principal_name: "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
    resources:
      jobs:
        deploy_sample_project_model:
          # notebook_pathを開発用に上書き
          name: "[${bundle.target}] deploy_sample_project_model"
          tasks:
            - task_key: deploy_model
              notebook_task:
                notebook_path: ${var.stage_file_path}/src/deploy_model
                source: WORKSPACE

  test-prd:
    mode: production
    workspace:
      host: https://{test-databricks-subdomain}.cloud.databricks.com
      file_path: ${var.prd_file_path}
    run_as:
      service_principal_name: "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
    resources:
      jobs:
        deploy_sample_project_model:
          # notebook_pathとbase_parametersを本番用に上書き
          name: deploy_sample_project_model
          tasks:
            - task_key: deploy_model
              notebook_task:
                notebook_path: ${var.prd_file_path}/src/deploy_model
                source: WORKSPACE
                base_parameters:
                  model_env: prd
                  workspace_url: "{{workspace.url}}"

3. pushのタイミングで処理を実行

sample_project_cd.yml

CI/CDのパイプラインをGithub Actionsでsample_project_cd.ymlに記述します(テストなどはsample_projet_ci.ymlを追加想定)。 このパイプラインは、developブランチとmasterブランチに、それぞれpushされたタイミングで処理します。 devは個人開発用のため、CI/CDのパイプラインではstageprdのみ記述します。

name: CD sample-project

on:
  push:
    branches:
      - master
      - develop

jobs:
  deploy_test_stage:
    if: ${{ github.ref_name == 'develop' }}
    uses: ./.github/workflows/_deploy_databricks.yml
    with:
      targets: test-stage
      deploy_model_name: deploy_sample_project_model
    secrets:
      DATABRICKS_CLIENT_ID: ${{ secrets.TEST_DATABRICKS_CLIENT_ID }}
      DATABRICKS_SECRET: ${{ secrets.TEST_DATABRICKS_SECRET }}

  deploy_stage:
    if: ${{ github.ref_name == 'develop' }}
    uses: ./.github/workflows/_deploy_databricks.yml
    with:
      targets: stage
      deploy_model_name: deploy_sample_project_model
    secrets:
      DATABRICKS_CLIENT_ID: ${{ secrets.DEFAULT_DATABRICKS_CLIENT_ID }}
      DATABRICKS_SECRET: ${{ secrets.DEFAULT_DATABRICKS_SECRET }}
  
  deploy_test_prd:
    if: ${{ github.ref_name == 'master' }}
    uses: ./.github/workflows/_deploy_databricks.yml
    with:
      targets: test-prd
      deploy_model_name: deploy_sample_project_model
    secrets:
      DATABRICKS_CLIENT_ID: ${{ secrets.TEST_DATABRICKS_CLIENT_ID }}
      DATABRICKS_SECRET: ${{ secrets.TEST_DATABRICKS_SECRET }}
  
  deploy_prd:
    if: ${{ github.ref_name == 'master' }}
    uses: ./.github/workflows/_deploy_databricks.yml
    with:
      targets: prd
      deploy_model_name: deploy_sample_project_model
    secrets:
      DATABRICKS_CLIENT_ID: ${{ secrets.DEFAULT_DATABRICKS_CLIENT_ID }}
      DATABRICKS_SECRET: ${{ secrets.DEFAULT_DATABRICKS_SECRET }}

_deploy_databricks.yml

sample_project_cd.ymlで利用するデプロイのパイプラインを_deploy_databricks.ymlに記述します。 このパイプラインは、databricks/setup-cliを利用し、Databricks Asset Bundleを使ってリソースをデプロイします。

databricks bundle deployでリソースをデプロイし、databricks bundle runでデプロイしたコードをJobとして実行します。

name: deploy databricks

on:
  workflow_call:
    inputs:
      targets:
        required: true
        type: string
      deploy_model_name:
        required: true
        type: string
    secrets:
      DATABRICKS_CLIENT_ID:
        required: true # service_principal_nameと同じ
      DATABRICKS_SECRET:
        required: true

jobs:
  deploy_resources:
    name: "Deploy resources"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: databricks/setup-cli@main
      - run: databricks bundle deploy -t ${{ inputs.targets }}
        working-directory: .
        env:
          DATABRICKS_CLIENT_ID: ${{ secrets.DATABRICKS_CLIENT_ID }}
          DATABRICKS_CLIENT_SECRET: ${{ secrets.DATABRICKS_SECRET }}

  deploy_model:
    name: "Deploy model"
    runs-on: ubuntu-latest
    needs:
      - deploy_code
    steps:
      - uses: actions/checkout@v4
      - uses: databricks/setup-cli@main
      - run: databricks bundle run ${{ inputs.deploy_model_name }} -t ${{ inputs.targets }}
        working-directory: .
        env:
          DATABRICKS_CLIENT_ID: ${{ secrets.DATABRICKS_CLIENT_ID }}
          DATABRICKS_CLIENT_SECRET: ${{ secrets.DATABRICKS_SECRET }}

結果

Jobの実行結果

test_databricks.ymlの設定を利用する場合は、targetsにはtest-devtest-stagetest-prdなどを指定します。 test-devはローカルから、test-stagetest-prdはGithub Actionsから実行します。 そのため、Created byを見ると、test-devのみユーザ名であり、test-stagetest-prdはCI/CDのサービスプリンシパル名であることが確認できます(図3)。

図3: 作成されたJob一覧

Jobの画面に進むと実行結果も確認できます(図4)。 resourcesに記述した各種設定内容もここで確認できます。 正直、Jobの設定をコード管理できるという時点で感無量です。

図4: deploy_sample_project_modelのJob設定画面

モデルの評価の結果

モデルの評価結果は、mlflow.evaluateで指定したAI審査員によって判定されます。 今回評価データとして設定した、「What is Spark?」と「How do I convert a Spark DataFrame to Pandas?」は、contextに含まれるため、AI審査員をPassすることができました(図5)。

図5: AI審査員評価

AI審査員の評価結果の詳細は以下のとおりです(図6)。 Responseに書かれているModel outputとExpected outputの文章は一致していなくても、回答の文脈として合っているかで評価されていることがわかります。

Correct

期待される回答は「Sparkはデータ分析フレームワークである」と述べている。
回答はSparkを「暗黙のデータ並列性とフォールトトレランスを備えたクラスタ全体をプログラミングするためのインタフェースを提供するオープンソースのデータ処理フレームワーク」と説明している。
回答では「データ分析フレームワーク」という用語は明確に使われていないが、SQLクエリ、機械学習、グラフ処理、ストリーム処理などのタスクを含むデータ処理のために設計されたフレームワークとしてSparkを説明している。
これらのタスクは一般的にデータ分析に関連している。したがって、期待される回答がサポートされている。

Relevant

質問は「Sparkとは何か?回答は、Sparkの目的、起源、特徴、サポートされているプログラミング言語など、Sparkとは何かについて詳しく説明している。
解答のすべての部分が、Sparkとは何かを理解するのに関連している。

Safe

回答に有害なコンテンツが検出されない

図6: AI審査員評価詳細

ここで、contextになく、gpt-4o-miniの学習データに含まれない、最新の情報を問う質問を評価データに加えてみることにします。 今回は、巷で話題の2025/02/02に発表されたOpenAIのDeep researchに関して質問してみます。 ground truthであるexpected_responseの文章は、発表時の文章をgpt-4oで要約させて作成しました。

eval_df =  pd.DataFrame({
    "request": [
        {"query": "What is Spark?"},
        {"query": "How do I convert a Spark DataFrame to Pandas?"},
        {"query": "What is Deep research"},
    ],
    "expected_response": [
        "Spark is a data analytics framework.",
        "To convert a Spark DataFrame to Pandas, you can use the toPandas() method.",
        "Deep research is a ChatGPT feature that autonomously conducts multi-step web research, generating detailed, cited reports in minutes. It’s ideal for in-depth inquiries, leveraging OpenAI’s o3 model for advanced analysis and synthesis.",
    ]
})

図7からわかるとおり「What Is Deep research」というクエリで、CorrectnessがFailとなりました。 「Deep research」を「深掘った調査」という意味合いとして回答してしまっています。

図7: Deep researchに関して質問した場合のAI審査員評価

Deep researchに関して質問した場合のAI審査員の評価結果の詳細は以下のとおりです(図8)。 なぜFailとなったかを説明してくれています。

Incorrent

Deep researchはChatGPTの機能で、多段階のweb調査を自律的に行い、詳細な引用レポートを数分で作成する。
OpenAIのo3モデルを活用し、高度な分析と合成を行うため、詳細な調査に最適である。
この回答では、「Deep research」とは、広範なデータ収集と分析、批判的思考、情報の統合を含む、特定の分野における徹底的かつ集中的な調査または研究であると説明している。
これには、包括的な文献調査、方法論の厳密さ、革新的な問題解決、学際的アプローチ、縦断的研究、知識への貢献などが含まれる。
この回答には、ChatGPT、自律的なweb調査、数分でレポートを作成すること、OpenAIのo3モデルの活用については何も言及されていない。したがって、期待される回答は回答によってサポートされていない。

Relevant

設問は「Deep research」について尋ねており、回答は、包括的な文献レビュー、方法論の厳密さ、革新的な問題解決、学際的アプローチ、縦断的研究、知識への貢献といった側面を含む、深い研究とは何かを詳細に説明している。
これらの点はすべて、深い研究とは何かを理解するのに関連している。

Safe

回答に有害なコンテンツは検出されない

図8: Deep researchに関して質問した場合のAI審査員評価詳細

なお、RelevanceやSafetyをPassしているのは、expected_responseと比較して評価していないためです。 「Deep research」を「深掘った調査」と回答したとしても、質問の回答としては関連性がある答えのため、RelevanceをPassしているのだと考えられます。

おわりに

Databricks Asset Bundlesを使って、モデルの保存と評価をCI/CDパイプラインとして組み込むまでの一連の流れを紹介しました。 今回はモデルのデプロイの自動化に関しては取り組めませんでした。 ここも、Model Servingへのデプロイ処理をdeploy_model_serving.pyのように作成し、Jobに設定を追加するようにすれば、同様のパイプラインを組み込むことができると思います。

モデルの評価では、AI審査員について紹介しました。 個人的に、このLLMの評価を実運用に乗せるには、各AI審査員がどのような評価しているかの特性に対して理解が必要だと感じました。 他にもretrieval観点で評価するAI審査員もおり、LLMの評価には様々な観点を考える必要がありそうです。 LLMの評価に関しては、また別の機会にまとめたいと思います。

また、今回は日本語で評価しませんでしたが、日本語でも概ね精度は変わらない印象です。 ただ、AI審査員の回答は英語のため、Pass/Failとなった説明に日本語と英語が入り混じることになり、文章として読みづらい感は否めません。 ap-northeast-1のパブリックプレビューと同時に、日本語で回答するAI審査員の登場に期待です。