
はじめに
GDAI事業部 Lakehouse部の阿部です。
近年、LLM(大規模言語モデル)の活用が急速に広がり、企業のAI活用現場でも本番運用が進んでいます。しかし、LLMの回答品質を安定的に維持・向上させるためには、プロンプトのバージョン管理や継続的なチューニングが不可欠です。MLflow 3.0では、プロンプト管理や最適化を支援する新機能が追加され、Databricksとの統合も進んでいます。
本記事では、Databricks環境でMLflowのPrompt Registry機能を活用し、プロンプトの登録・管理・評価・最適化までの一連の流れを、Azure OpenAIモデルを用いた実装例とともに解説します。LLM運用の現場で役立つ実践的なノウハウをまとめましたので、ぜひ参考にしてください。
*2025年8月時点では本日扱うPrompt RegistryとOptimizeはベータ版となっております。
前準備
本記事ではこちらのドキュメントにあるnotebookをウォークスルーしながら解説します。
以下のnotebookをWorkspaceにインポートしておきます。
https://docs.databricks.com/aws/ja/notebooks/source/mlflow/prompt-optimization.html
必要なライブラリをインストールします。
%pip install --upgrade mlflow>=3.1.0 langchain-community langchain-openai langchain-azure-ai beautifulsoup4 langgraph dspy databricks-agents dbutils.library.restartPython()
今回使うLLMはAzure OpenAIのgpt-5-nanoを利用するため、api keyとendpointを事前にsecretに登録しておき、環境変数として保存します。
Secretの登録と管理の手順については、以下ドキュメントを参照ください。
https://learn.microsoft.com/ja-jp/azure/databricks/security/secrets/
Promptは後ほどUnity Catalogに登録するため、登録先のカタログ、スキーマを設定しておきます。
import os # OpenAIの設定 os.environ["AZURE_OPENAI_API_KEY"] = dbutils.secrets.get("demo-token-ka.abe", "openai_api_key") os.environ["AZURE_OPENAI_ENDPOINT"] = dbutils.secrets.get("demo-token-ka.abe", "openai_endpoint") CATALOG = "ka_abe" SCHEMA = "demo"
次にMLflowのレジストリURI(保存先)をDatabricks Unity Catalogに設定したあと、LLMのインプットデータをロードします。
WebBaseLoaderは指定したURL(今回はエージェントに関する技術ブログ)からテキストデータを取得するためのクラスです。今回はGitHubにポストされた技術ブログの内容を取得します。
import mlflow from mlflow.entities import Prompt mlflow.set_registry_uri("databricks-uc") from langchain_community.document_loaders import WebBaseLoader from langchain_text_splitters import CharacterTextSplitter text_splitter = CharacterTextSplitter.from_tiktoken_encoder( chunk_size=1000, chunk_overlap=0 ) loader = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-agent/") docs = loader.load() split_docs = text_splitter.split_documents(docs) print(f"Generated {len(split_docs)} documents.")
LLMのインスタンスを定義します。
今回利用するAzure OpenAIのモデルはgpt-5-nanoです。
from langchain.chat_models import init_chat_model llm = init_chat_model( model="gpt-5-nano", model_provider="azure_openai", azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"), api_key=os.getenv("AZURE_OPENAI_API_KEY"), deployment_name="gpt-5-nano", api_version="2024-06-01" )
MLflow prompt Registry
MLflow Prompt Registryは、LLMで利用するプロンプトを体系的に管理・バージョン管理できる機能です。従来のMLflowモデル管理と同様に、プロンプトの登録・更新・履歴管理が可能となり、実験や本番運用での品質向上や再現性の確保に役立ちます。
特にDatabricksと統合されていることで、Unity Catalog上でプロンプトの保存・管理・アクセス制御が一元化され、チームでの共同開発や運用が容易になります。MLflow UIからプロンプトのバージョン管理や最適化も行えるため、LLM活用の現場で非常に有用な仕組みです。
以下のコードを実行し、先ほど指定したカタログ/スキーマに要約を行うプロンプトを保存します。
# First prompt for summarization. summary_prompt = mlflow.genai.register_prompt(name=f"{CATALOG}.{SCHEMA}.summary_prompt", template="Write a concise summary of the following:{{content}}")
ExperimentsのMLFLow UIから作成されたexperimetを選択し、Promptタブをクリックすると、先ほど登録した要約を行うプロンプトが確認できます。 この画面から新しいバージョンのプロンプトを作成することや、notebook上でバージョン更新もできます。

※2025年8月時点では、Unity CatalogのUI上でPromptの確認や編集ができず、Experimentから参照できます。
要約を行うプロンプトとLLMからchainを定義し、LLMの入出力結果をトレースできるように関数にデコレーター@mlflow.trace()をつけます。
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser summary_chain = llm | StrOutputParser() @mlflow.trace() def call_summary_chain(content): return summary_chain.invoke([HumanMessage(summary_prompt.format(content=content))])
次に、ドキュメントの要約結果をもとにメイントピックのワードをいくつか抽出するプロンプトをMLFLowに登録します。
# Second prompt for topic extraction. topic_prompt = mlflow.genai.register_prompt(name=f"{CATALOG}.{SCHEMA}.topic_prompt", template=""" The following is the summary: {{summary}} Extract the main topic in a few words. Return the response in JSON format: {"topic": "..."} """) topic_chain = llm | JsonOutputParser() @mlflow.trace() def call_topic_chain(summary): return topic_chain.invoke([HumanMessage(topic_prompt.format(summary=summary))])
LLMによる要約とトピック抽出を一連の処理としてまとめた関数agentを定義します。
from langchain_core.messages import HumanMessage, SystemMessage @mlflow.trace def agent(content): summary = call_summary_chain(content=content) return call_topic_chain(summary=summary)["topic"]
MLFlow traceのオートロギングも有効にし、文章を要約 -> トピックを抽出という処理ができているかLLMの入出力を確認します。
# Enable Autologging mlflow.langchain.autolog() # Run the agent for doc in split_docs: try: print(agent(doc.page_content)) except Exception as e: print(e) pass
traceの内容を確認します。
ドキュメントから要約処理をした際の入力

その出力

要約からトピック抽出時のトレース結果を示します。

ドキュメントから要約、トピック抽出まで問題なくできていることを確認しました。
評価用データセットの作成
先ほど生成したトレースからデータセットを作成し、LLM評価用のデータセットとして利用できます。ここでいう評価用データセットとは、後ほどプロンプト最適化実行時に参考とするデータです。
今回はトピック抽出のプロンプトを最適化するため、call_topic_chainスパンの入出力結果を評価用データとして利用します。
import mlflow # Extract the inputs and outputs of the second LLM call traces = mlflow.search_traces(extract_fields=[ "call_topic_chain.inputs", "call_topic_chain.outputs", ]) traces.head(10)
上記のトレースからデータセットを作成し、整形後にカタログに保存します。
from mlflow.genai import datasets EVAL_DATASET_NAME=f"{CATALOG}.{SCHEMA}.data" dataset = datasets.create_dataset(EVAL_DATASET_NAME) # エージェントの出力をデフォルトの期待値として扱い、データセットを作成します。 # カラム名を置き換えた後、「inputs」と「expectations」カラムのみを抽出します。 traces = traces.rename( columns={ "call_topic_chain.inputs": "inputs", "call_topic_chain.outputs": "expectations", } )[["inputs", "expectations"]] traces = traces.dropna() dataset.merge_records(traces)# traceデータフレームを評価用データセットに追加する
ラベリング
この後、評価用データセットにラベル付け(期待値の修正)を行い、プロンプト最適化時に参照します。
現在、データセットの「期待値」カラムにはエージェントの出力が自動で格納されていますが、プロンプト最適化の精度を高めるためには高品質なラベル(期待値)の修正が重要です。
「評価」タブ → 「データセット」タブに移動し、必要に応じて期待値を編集・修正してください。
traceから作成した評価用データセットは、画面上で直接修正できます。
ラベル付けが完了したら、以下のコマンドで評価用データセットをダウンロードできます。
dataset = datasets.get_dataset(EVAL_DATASET_NAME) dataset.merge_records([]) dataset = dataset.to_df() dataset.head()
プロンプト最適化
最後に、mlflow.genai.optimize_prompt を実行してプロンプトを最適化します。
import os from typing import Any import mlflow from mlflow.genai.scorers import Correctness from mlflow.genai.optimize import OptimizerConfig, LLMParams from mlflow.genai.scorers import scorer mlflow_creds = mlflow.utils.databricks_utils.get_databricks_host_creds() os.environ["OPENAI_API_KEY"] = mlflow_creds.token _correctness = Correctness() # プロンプト最適化のための評価関数(確からしさをテストする) @scorer def correctness(inputs, outputs, expectations): expectations = { "expected_response": expectations.get("topic") } return _correctness(inputs=inputs, outputs=outputs, expectations=expectations).value == "yes" # Optimize the prompt result = mlflow.genai.optimize_prompt( target_llm_params = LLMParams( model_name = "openai/databricks-llama-4-maverick", base_uri = f"{mlflow_creds.host}/serving-endpoints", ), prompt=topic_prompt, train_data=dataset, scorers=[correctness], optimizer_config=OptimizerConfig( num_instruction_candidates=8, max_few_show_examples=2, verbose=True, ) ) # The optimized prompt is automatically registered as a new version # Open the prompt registry web site to check the new prompt print(f"The new prompt URI: {result.prompt.uri}")
※ プロンプト最適化に使用するモデルはgpt-5-nanoを利用しようと思いましたが、まだOpenAIの新しいモデルに対応していないためかエラーが発生し利用できませんでした。対応策として、Databricksの基盤モデルを利用しました。
プロンプト最適化時のプロンプト品質を評価する関数として、@scorer以下のcorrectness関数をスコアラーとして利用しました。スコアラーは、LLMの出力結果が期待値(ラベル)にどれだけ一致しているかを評価するための関数・仕組みです。MLflowでは、正解率や品質など様々な観点でプロンプトの性能を自動評価でき、プロンプト最適化やモデル改善に活用されます。スコアラーについて詳しく知りたい方は、以下ドキュメントを参照ください。
https://learn.microsoft.com/ja-jp/azure/databricks/mlflow3/genai/eval-monitor/concepts/scorers
それではプロンプト最適化前と最適化後の結果を確認しましょう。
(画像左)プロンプト最適化前、(画像右)プロンプト最適化後
プロンプト最適化後は、Version 1のプロンプトと比較して入力と出力形式について最初に言及し、few shot promptingとして要約に対するトピックの抽出例がいくつか記載されています。これは、先ほど作成した評価用データセットをもとに例を用意しているかと思います。
この最適化処理されたプロンプトを利用し、LLMのトピック抽出結果を見てみましょう。
最適化前後プロンプトのLLM出力比較サンプル
この最適化処理されたプロンプトを利用し、LLMのトピック抽出結果を見てみましょう。 最適化処理前と後のプロンプトで要約ドキュメントに対するトピック抽出結果を比較します。
import mlflow from openai import OpenAI # Databricks LLMエンドポイントの認証情報取得 mlflow_creds = mlflow.utils.databricks_utils.get_databricks_host_creds() client = OpenAI( api_key=mlflow_creds.token, base_url=f"{mlflow_creds.host}/serving-endpoints" ) # プロンプトのバージョン1(最適化前)と最適化後プロンプトをロード first_prompt = mlflow.genai.load_prompt(f"prompts:/{CATALOG}.{SCHEMA}.topic_prompt/1") optimized_prompt = mlflow.genai.load_prompt(result.prompt.uri) def predict_topic(prompt, summary): formatted_prompt = prompt.format(summary=summary) response = client.chat.completions.create( model="databricks-llama-4-maverick", messages=[{"role": "user", "content": formatted_prompt}], ) return response.choices[0].message.content # 比較用の要約テキスト(例:split_docs[0].page_contentを要約したもの) example_summary = call_summary_chain(content=split_docs[0].page_content) for p, label in zip([first_prompt, optimized_prompt], ["最適化前", "最適化後"]): print(f"---{label}---") print(predict_topic(p, example_summary))
結果を見てみましょう。
最適化前のプロンプト利用時のトレース結果を示します。

最適化後のプロンプト利用時のトレース結果を示します。

OutputsのAssistantを見ると、まさかの出力結果に変化がありませんでした。 このような場合は、現状のプロンプトで十分と判断して運用を続けるか、あるいはラベリング(期待値の修正)を再度行い、より厳密な評価基準でプロンプト最適化をやり直すことが推奨されます。 特に業務要件や期待するアウトプットが明確な場合は、ラベルの品質を高めて再最適化することで、より望ましいLLMの挙動を引き出すことが可能です。
実務での注意点・アドバイス
本記事で紹介した機能を使う上での注意点とアドバイスを記載しました。
- プロンプト最適化の結果は必ず人によるレビューを行い、AIの出力が業務要件や期待値に合致しているか確認しましょう。
- プロンプトはできるだけ正確かつ具体的に記述し、曖昧な指示やAIへの過度な依存を避けることが重要です。そもそも最初のプロンプトがかなりシンプルでしたが、本来はプロンプト設計をしっかり行なった上でversion1のプロンプトを作成しましょう。version1でのプロンプトの完成度が低いと、そのプロンプト利用時のトレース結果をもとに最適化するため、最適化の効果は薄れます。
- プロンプトが最適化前後に利用するLLMは、条件を合わせて比較するという意味でモデルも共通させるべきです。本記事では検証の都合上前者に
gpt-nano, 後者にLlama 4 Maverickを利用しておりますが、プロンプトが同じでもLLMによって挙動が変わる場合もあります。 - ベータ版機能のため、予期せぬ挙動や仕様変更の可能性も考慮し、運用時は十分に検証・テストを行ってください。
- プロンプトやラベルの管理・変更履歴を残し、チームでの情報共有や再現性の確保に努めましょう。
おわりに
本記事では、MLflow 3.0のPrompt Registry機能とDatabricksとの統合を活用し、LLMプロンプトの管理・評価・最適化の一連の流れを解説しました。Azure OpenAIモデルを用いた実装例を通じて、プロンプトのバージョン管理、トレースによる入出力の可視化、評価用データセットの作成、スコアラーによる自動評価、そしてプロンプト最適化の実践方法を紹介しました。
プロンプト最適化は、LLMの品質向上や業務要件への適合に不可欠なプロセスです。ラベリングや評価基準の見直しを繰り返すことで、より高精度なLLM運用が可能となります。MLflowとDatabricksの連携により、チームでのプロンプト管理や実験の再現性も高まり、AI活用の現場で大きなメリットを得られるでしょう。
今後もMLflowやLLM関連機能の進化に注目し、より良いLLM開発・運用に役立てていきましょう。
私たちはDatabricksを用いたデータ分析基盤の導入から内製化支援等()幅広く支援をしております。もしご興味がある方は、お問い合わせ頂ければ幸いです。
また、一緒に働いていただける仲間も募集中です! エーピーコミュニケーションズやLakehouse部にご興味がある方は問い合わせいただければ幸いです。