メインコンテンツへジャンプ

バッチおよびエージェントワークフローのための構造化出力の紹介

ジェフリー・チェン
ベイ・フォーリー・コックス
アフマド・ビラル
マーガレット・チェン
Share this post

Summary

  • 多くのAIのユースケースは、非構造化入力を構造化データに変換することに依存しています。
  • Mosaic AI Model ServingにStructured Outputsを導入することを発表しました。
  • Structured Outputsは、提供されたJSONスキーマにオプションで準拠できるJSONオブジェクトを生成するための統一されたAPIです。

Generated by AI

多くのAIのユースケースは、非構造化入力を構造化データに変換することに依存しています。開発者はますます、LLMを利用して生のドキュメントから構造化データを抽出し、APIソースからデータを取得するアシスタントを構築し、行動を起こすエージェントを作成しています。これらの各ユースケースでは、モデルが構造化された形式に従った出力を生成する必要があります。

 

今日、私たちは Structured OutputsをMosaic AI Model Servingに導入することを発表します。これは、提供されたJSONスキーマにオプションで準拠できるJSONオブジェクトを生成するための統一されたAPIです。この新機能は、LlamaのようなオープンなLLM、ファインチューニングされたモデル、OpenAIのGPT-4oのような外部LLMを含むすべてのタイプのモデルをサポートし、特定のユースケースに最適なモデルを選択する柔軟性を提供します。 Structured Outputsは、新たに導入されたresponse_formatとともにバッチ構造生成にも、関数呼び出しを用いたエージェントアプリケーションの作成にも使用できます。

なぜ構造化出力なのか?

構造化出力により、2つの主要なユースケースで品質と一貫性が大幅に向上します。

構造化出力を使ったバッチ生成(response_format)

バッチ推論による特徴抽出は、時には数百万のデータポイントを対象とするため、厳密なスキーマに準拠した完全なJSONオブジェクトを確実に出力するのは困難です。しかし、構造化出力を使用することで、顧客はデータベース内の各ドキュメントに関連する情報を簡単にJSONオブジェクトに埋め込むことができます。バッチ特徴抽出は、Databricks FMAPIプラットフォーム上のすべてのLLM(ファインチューニングされたモデルも含む)で動作するresponse_format APIフィールドを介して利用可能です。

機能呼び出しを活用したエージェント構築

エージェントワークフローでは、機能呼び出しとツールの利用が成功の鍵を握ります。構造化出力により、LLMが外部APIや内部で定義されたコードへの機能呼び出しを一貫して出力できるようになります。FMAPIにおける関数呼び出し機能は、2024年のData + AI Summitで発表され、その後すぐにMosaic AIエージェントフレームワークが導入されました。関数呼び出し機能はtools APIフィールドを通じてユーザーが利用できます。機能呼び出し品質の評価に関する詳細はこちらのブログをご覧ください。

なお、tools APIフィールドは現在、Llama 3 70BおよびLlama 3 405Bでのみ動作します。

構造化された出力をどのように使用しますか?

response_format を使用すると、モデルの出力をどのような構造に制約するかを詳細に指定できます。サポートされている3つの出力フォーマットは以下の通りです:

  1. Text: プロンプトに基づいてモデルが出力する非構造化テキスト。
  2. Json_object: モデルがプロンプトから直感的にスキーマを推測して生成するJSONオブジェクト。
  3. Json_schema: APIに適用されるJSONスキーマに準拠したJSONオブジェクトを出力。

後者の2つ(Json_objectJson_schema)を使用することで、特定のユースケースに応じた信頼性の高いJSON出力を得ることができます。

response_format フィールドのユースケース例

  • 賃貸契約書から法的情報やPOC(Point of Contact)情報を抽出
  • 投資家や財務アドバイザーとの会話記録から投資リスクを抽出
  • 研究論文を解析してキーワード、トピック、著者の連絡先を抽出

JSONスキーマに準拠したカレンダーイベントの抽出例

以下は、プロンプトからカレンダーイベントを抽出するためにJSONスキーマを適用した例です。OpenAI SDKを使用すると、Pydanticを活用してオブジェクトスキーマを簡単に定義し、それをモデルに渡すことができます。これにより、明示的なJSONスキーマの代わりに柔軟で効率的な方法でスキーマを指定することが可能です。

python
from pydantic import BaseModel
from openai import OpenAI

DATABRICKS_TOKEN = os.environ.get('YOUR_DATABRICKS_TOKEN')
DATABRICKS_BASE_URL = os.environ.get('YOUR_DATABRICKS_BASE_URL')

client = OpenAI(
  api_key=DATABRICKS_TOKEN,
  base_url=DATABRICKS_BASE_URL
  )

class CalendarEvent(BaseModel):
    name: str
    date: str
    participants: list[str]

completion = client.beta.chat.completions.parse(
    model="databricks-meta-llama-3-1-70b-instruct",
    messages=[
        {"role": "system", "content": "イベント情報を抽出します。"},
        {"role": "user", "content": "アリスとボブは金曜日に科学展に行く予定です。"},
    ],
    response_format=CalendarEvent,
)

print(completion.choices[0].message.parsed)
#name='science fair' date='Friday' participants=['Alice', 'Bob']

関数呼び出しを持つエージェントの構築 

tools および tool_choice を使用することで、LLMが関数呼び出しをどのように行うかを詳細に指定できます。tools パラメータを使用すると、LLMが呼び出す可能性のあるツールのリストを指定できます。それぞれのツールは、名前、説明、およびJSONスキーマ形式のパラメータを持つ関数として定義されます。

その後、tool_choice を使用して、ツールがどのように呼び出されるかを決定できます。選択肢は以下の通りです:

  • none: ツールリストにあるツールは一切呼び出されません。
  • auto: ツールリスト内のツールを呼び出すべきかどうかをモデルが判断します。ツールが呼び出されない場合、モデルは通常通り非構造化テキストを出力します。
  • required: 関連性にかかわらず、ツールリストの中から必ず1つのツールが呼び出されます。
  • {"type": "function", "function": {"name": "my_function"}}: ツールリスト内に有効な関数名「my_function」があれば、モデルはその関数を強制的に選択します。

例: ツールの選択

モデルが get_delivery_dateget_relevant_products という2つのツールを選択する状況を想定します。以下のコードスニペットでは、モデルが get_relevant_products を呼び出すべきケースが示されています。

python
from openai import OpenAI

DATABRICKS_TOKEN = os.environ.get('あなたのDATABRICKS_TOKEN')
DATABRICKS_BASE_URL = os.environ.get('あなたのDATABRICKS_BASE_URL')

client = OpenAI(
  api_key=DATABRICKS_TOKEN,
  base_url=DATABRICKS_BASE_URL
  )

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_delivery_date",
            "description": "顧客の注文の配送日を取得します。例えば、顧客が'私のパッケージはどこにありますか'と尋ねたときや、配送日を知る必要があるときにこれを呼び出します。",
            "parameters": {
                "type": "object",
                "properties": {
                    "order_id": {
                        "type": "string",
                        "description": "顧客の注文ID。",
                    },
                },
                "required": ["order_id"],
            },
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_relevant_products",
            "description": "指定された検索クエリで販売されている関連製品のリストを返します。例えば、顧客が'販売しているラップトップは何ですか?'と尋ねた場合、これを呼び出します。",
            "parameters": {
                "type": "object",
                "properties": {
                    "search_query": {
                        "type": "string",
                        "description": "検索する製品のカテゴリ。",
                    },
                    "number_of_items": {
                        "type": "integer",
                        "description": "検索応答で返すアイテムの数。デフォルトは5で、最大は20です。",
                    },
                },
                "required": ["search_query"],
            },
        }
    }
]


response = client.chat.completions.create(
    model="databricks-meta-llama-3-1-70b-instruct",
    messages=[
        {"role": "user", "content": "キーボードを販売していますか?"}],
    tools=tools,
    tool_choice="auto",
)

print(response.choices[0].message.tool_calls)

内部メカニズム

構造化出力を実現する鍵となるのが「制約付きデコード」です。制約付きデコードは、モデルがトークンを生成する各ステップで、期待される構造フォーマットに基づいて生成可能なトークンのセットを制限する技術です。例えば、JSONオブジェクトの冒頭部分を考えてみましょう。JSONオブジェクトは常に左中括弧で始まるため、トークン生成時には左中括弧で始まるトークンのみを考慮するように制限をかけます。これは単純な例ですが、この考え方は、必須のキーや特定のキー-値ペアの型など、JSONオブジェクト内の他の構造要素にも適用できます。出力の各位置でスキーマに準拠するトークンのセットを特定し、それに基づいてサンプリングを行います。技術的には、LLMが出力する生のロジットのうちスキーマに合致しないものは、サンプリング前に各タイムステップでマスキングされます。

制約付きデコードを使用することで、十分なトークンが生成される限り、モデルの出力が提供されたJSONスキーマに準拠するJSONオブジェクトになることが保証されます。これにより、構文エラーや型エラーが排除されます。制約付きデコードを活用することで、顧客は一貫性があり信頼性の高いLLMの出力を得ることができ、数百万のデータポイントにもスケール可能です。そのため、カスタムのリトライロジックやパーシングロジックを書く必要がなくなります。

制約付きデコードにはオープンソースでも多くの関心が寄せられており、OutlinesGuidanceといった人気ライブラリがその例です。Databricksでは、制約付きデコードを大規模に適用する際の品質とパフォーマンスへの影響を調査し、さらに優れた方法を模索しています。

制約のヒント

上記の例に加え、バッチ推論作業の品質を最大化するためのヒントとコツを以下に紹介します。

シンプルなJSONスキーマは、複雑なJSONスキーマよりも高品質な出力を生成します。

  • 深いネストを含むJSONスキーマの使用は避けましょう。 モデルが推論しにくくなるためです。ネストされたスキーマがある場合は、可能な限りフラット化してください!
  • JSONスキーマにキーを詰め込みすぎないようにしましょう。 不要なキーは削除し、キーを簡潔に保つことが重要です。
  • シンプルで正確なスキーマを使用することで、品質向上に加え、パフォーマンスがわずかに向上し、コストも削減できます。
  • 直感を活用しましょう。 JSONスキーマが目視で複雑すぎると感じる場合は、スキーマを最適化することで改善の余地があります。

明確で簡潔なパラメータの説明と名前を設定しましょう。

  • モデルは、どのような制約を課されているのか、なぜそれが必要なのかを理解すると、推論能力が向上します。これにより、抽出の品質が大幅に向上します。

JSONスキーマの機能を活用しましょう。

  • プロパティを必須としてマークしたり、enum 機能でフィールドを特定の値のセットに制限することができます。少なくとも1つのプロパティを必須に設定することをお勧めします。


入力データに関連するスキーマを使用して制約を設定しましょう。

  • たとえば、Wikipediaの記事から名前やイベントを抽出する場合、ページのHTMLマークアップではなく、実際のテキストを渡すことでデータの範囲を絞り込むと効果的です。

システムプロンプトに成功例を追加すると効果があります。

  • LLMは、顧客が成功とみなす抽出例を与えられるとパフォーマンスが向上します。ただし、これが常に有効とは限らないため、試行錯誤することが重要です。

以下に例を示します。たとえば、賃貸契約書から法的情報やPOC情報を抽出する際に、次のスキーマを使用する場合を考えます:

python
{
        "name": "extract",
        "schema": {
          "type": "object",
          "properties": {
		"dates" : {
		  "type": "object",
		  "properties": {
			"start_date": { "type": "string" },
			"end_date": { "type": "string" },
"sign": { "type": "string" },
			"expire" : { "type" : "string" },
   }
		},
		"people" : {
		  "type": "object",
		  "properties": {
			"lessee": { "type": "string" },
			"lessor": { "type": "string" },
   }
		},
		"terms_of_payment": { "type": "string"},
            	"if_pets": { "type": "boolean" },
		"pets" : {
		  "type": "array",
		  "items": {
		    "type": "object",
		    "properties": {
		       "animal" : { "type": "string" },
			 "name" : { "type": "string" }
          },
        },
        "strict": True
      }

上記の制約設定のヒントを活用して、最適なスキーマを設計することができます。まず、不要なキーを削除し、スキーマをフラット化します。例えば、ペットのフィールドの長さをチェックできる場合は、if_pets フィールドは不要です。また、モデルが認識しやすいように、すべての名前をより明確に変更することができます。次に、各プロパティに適切な型を指定し、役立つ説明を追加します。最後に、どのキーが必須であるかをマークし、ユースケースに最適なJSONスキーマを作成します。

以下は、最適化後のスキーマを使用して構造化出力を実行するための完全なコードです。

python
import os
import json
from openai import OpenAI

DATABRICKS_TOKEN = os.environ.get('YOUR_DATABRICKS_TOKEN')
DATABRICKS_BASE_URL = os.environ.get('YOUR_DATABRICKS_BASE_URL')

client = OpenAI(
  api_key=DATABRICKS_TOKEN,
  base_url=DATABRICKS_BASE_URL
  )

response_format = {
      "type": "json_schema",
      "json_schema": {
        "name": "extract_lease_information",
	  "description": "リース契約から法的およびPOC情報を抽出する", 
        "schema": {
          "type": "object",
          "properties": {
   "start_date": { 
     "type": "date", 
     "description": "リースの開始日。" 
   },
		"end_date": { 
  "type": "date", 
  "description": "リースの終了日。" 
},
   "signed_date": { 
     "type": "date",
     "description": "リースが貸主と借主の両方によって署名された日付"
   },
	      "expiration_date" : { 
              "type" : "date",
              "description": "リースが満了する日付"
             },
	      "lessee": { 
     "type": "string",
     "description": "リース契約を署名した借主の名前(および可能性としては住所)。", 
   },
		"lessor": { 
               "type": "string",
		   "description": "リース契約を署名した貸主の名前(および可能性としては住所)。"
             },
		"terms_of_payment": { 
               "type": "string",
               "description": "支払い条件の説明。"
             },
		"pets" : {
		  "type": "array",
               "description": "リースに記載されている借主が所有するペットのリスト。"
		  "items": {
		    "type": "object",
		    "properties": {
		       "animal" : { 
                     "type": "string",
                     "description": "ペットの種類、猫、犬、鳥など。 それ以外のペットは許可されません。",
			  "enum": ["dog", "cat", "bird"]
                    },
			 "name" : { 
                      "type": "string",
                      "description": "ペットの名前。"
                    }
          },
	   "required": ["start_date", "end_date", "signed_date", "expiration_date", "lessee", "lessor", "terms_of_payment"]
        },
        "strict": True
      }
    }

messages = [{
        "role": "system",
        "content": "あなたは構造化データ抽出の専門家です。 リースから非構造化テキストを与えられ、それを指定された構造に変換する必要があります。"
      },
      {
        "role": "user",
        "content": "..."
      }]

response = client.chat.completions.create(
    model="databricks-meta-llama-3-1-70b-instruct",
    messages=messages,
    response_format=response_format
)

print(json.dumps(json.loads(response.choices[0].message.model_dump()['content']), indent=2))

今後について

今後も構造化出力の利用に関する最新情報をお見逃しなく!構造化出力は間もなく ai_query で利用可能になります。これにより、数百万行のバッチ推論をワンコマンドで簡単に実行できるようになります。

Databricks 無料トライアル

関連記事

シンプル・高速・スケーラブル!Mosaic AIで実現するバッチ LLM 推論

長年にわたり、企業は膨大な量の非構造化テキストデータ(文書、報告書、メールなど)を蓄積してきましたが、そこから意味のあるインサイトを抽出することは依然として課題でした。現在、大規模言語モデル(LLM)を活用することで、このデータをスケーラブルに分析する方法が実現しており、バッチ推論が最も効率的な解決策となっています。しかし、多くのツールはオンライン推論に焦点を当てており、バッチ処理機能の充実にはまだ課題が残されています。 本日、大規模文書に LLM を適用するための、よりシンプルで高速、かつスケーラブルな方法を発表します。これまでのようにデータを CSV ファイルとして 未管理の場所にエクスポートする必要はありません。今では、Unity Catalog による完全なガバナンスのもと、ワークフロー内でバッチ推論を直接実行できます。 以下の SQL クエリを記述し、ノートブックやワークフローで実行するだけで完了します。 ai_query を使用すれば、前例のない速度で大規模なデータセットを処理することが可能になり、最

DatabricksのモザイクAIを用いて複合AIシステムをより高速に構築!

多くのお客様が、一般的なモデルを使用したモノリシックなプロンプトから、製品準備完了のGenAIアプリに必要な品質を達成するための特化した複合AIシステムへと移行しています。 7月には、 エージェントフレームワークとエージェント評価を立ち上げ 、多くの企業がエージェントアプリケーションを作成するために使用しています。その一例が Retrieval Augmented Generation (RAG) です。今日、私たちはエージェントフレームワークに新機能を追加し、複雑な推論を行い、サポートチケットの開設、メールへの返信、予約の取得などのタスクを実行するエージェントの構築プロセスを簡素化することを発表します。これらの機能には以下のものが含まれます: 構造化されたエンタープライズデータと非構造化エンタープライズデータを共有可能で管理された AIツールを通じてLLMに接続します。 新しいプレイグラウンド体験を使って、エージェントを素早く実験し評価します 。 新しい ワンクリックコード生成 オプションを使用して、プレイグラ
生成 AI一覧へ