BAML vs Instructor:構造化されたLLM出力

BAMLとInstructorによる型安全なLLM出力

目次

プロダクション環境で大規模言語モデル(LLM)を扱う際、構造化され、型安全性のある出力を得ることが極めて重要です。 この問題の解決策として、2つの人気フレームワーク—BAMLとInstructor—がそれぞれ異なるアプローチを採用しています。

この比較ガイドは、PythonのLLMアプリケーションに適したツールを選択するお手伝いをします。

circular-flow

構造化出力の課題を理解する

LLMは本来、非構造化のテキストを生成しますが、現代のアプリケーションには予測可能でパース可能なデータが必要です。チャットボット、データ抽出パイプライン、またはAIエージェントを構築する場合、自由形式の応答ではなく、JSONオブジェクト、検証済みのデータ型、およびエラー処理が必要となります。

BAMLとInstructorはどちらもこの課題に対処していますが、根本的に異なる哲学に基づいています。BAMLはコード生成を用いた契約ファースト(Contract-First)アプローチを採用する一方、InstructorはPythonの型システムとランタイム検証を活用しています。異なるLLMプロバイダー間の構造化出力アプローチに関するより広い文脈に興味がある場合、これらのフレームワークを理解することはさらに価値が高まります。 検証、拒否処理、およびフレームワーク層以下のpytestフィクスチャについては、Pythonで堅牢なLLM構造化出力の検証をご参照ください。

BAML: LLMのためのドメイン固有言語

BAML(BoundaryMLの言語)は、LLMとのインタラクションを定義するための専用ドメイン固有言語(DSL)を導入しています。.bamlファイルにプロンプト、型、関数を宣言し、BAMLがPythonを含む複数の言語用の型安全なクライアントコードを生成します。

BAMLの主な特徴

言語横断的な型安全性: BAMLは同じ.baml定義からPython、TypeScript、Ruby用のクライアントを生成し、スタック全体で一貫性を保ちます。

プロンプトのバージョン管理: プロンプトは.bamlファイルに格納されるため、アプリケーションコードとは独立して追跡、レビュー、テストが容易になります。

内蔵テストフレームワーク: BAMLにはテストツールが含まれており、デプロイ前にプロンプトの動作を検証し、開発の早い段階で問題を発見できます。

プレイグラウンドインターフェース: BAMLのプレイグラウンドでは、プロンプトを視覚的に反復処理し、即時のフィードバックを得られるため、開発サイクルを加速できます。

BAMLの実装例

# まず、.bamlファイルでスキーマを定義します:
# persona.baml

class Person {
  name string
  age int
  occupation string
  skills string[]
}

function ExtractPerson(text: string) -> Person {
  client GPT4
  prompt #"
    次の情報から人物情報を抽出してください{{ text }}
    構造化されたデータを返してください
  "#
}

生成されたPythonクライアントは型安全なアクセスを提供します:

from baml_client import b
from baml_client.types import Person

# 生成されたクライアントを使用
text = "John Smith, 34, software engineer skilled in Python and Go"
result: Person = b.ExtractPerson(text)

print(f"{result.name}{result.age}歳です")
print(f"スキル: {', '.join(result.skills)}")

BAMLのアプローチは、複数のサービスが同じLLM契約を使用する場合、または言語境界を越えてデータ形状について強い保証が必要な場合にその真価を発揮します。

Instructor: PydanticネイティブなPythonフレームワーク

InstructorはPythonファーストのアプローチを取り、PydanticモデルにLLM機能を拡張します。すでにPydanticを検証や型ヒントのために使用しているPython開発者にとって、自然な感覚で扱えます。

Instructorの主な特徴

ボイラープレート不要: Instructorは既存のPydanticモデルを単純なデコレーターを使って直接使用します。コード生成やビルドステップは不要です。

豊富な検証機能: Pydanticの検証エコシステム全体を活用できます—カスタムバリデーター、フィールド制約、計算フィールド、複雑なネスト構造など。

マルチプロバイダーサポート: OpenAI、Anthropic、Google、Ollamaと統合されたインターフェースを通じてシームレスに動作します。

ストリーミングサポート: 増分Pydanticモデル更新を伴うストリーミング応答をファーストクラスでサポートします。

リトライロジック: 指数バックオフとバリデーターベースのエラー回復を備えた内蔵リトライメカニズムを提供します。

Instructorの実装例

from pydantic import BaseModel, Field
from instructor import from_openai
from openai import OpenAI

# Pydanticモデルを定義
class Person(BaseModel):
    name: str = Field(description="人物のフルネーム")
    age: int = Field(ge=0, le=120, description="年齢(年)")
    occupation: str
    skills: list[str] = Field(description="専門スキルのリスト")

# OpenAIクライアントをパッチ適用
client = from_openai(OpenAI())

# 構造化データを抽出
text = "John Smith, 34, software engineer skilled in Python and Go"
result = client.chat.completions.create(
    model="gpt-4",
    response_model=Person,
    messages=[
        {"role": "user", "content": f"人物情報を抽出してください: {text}"}
    ]
)

print(f"{result.name}{result.age}歳です")
print(f"スキル: {', '.join(result.skills)}")

Instructorの強みは、そのシンプルさとPythonエコシステムとの統合にあります。すでにPydanticを使用している場合、学習曲線は最小限です。Pythonに新しい開発者や、Python固有のパターンに関するクイックリファレンスが必要な開発者のために、当サイトのPythonチートシートが、これらのフレームワークと併せて有用な構文リマインダーを提供しています。

詳細比較: BAML vs Instructor

開発体験

BAMLでは、追加のビルドステップとツール設定が必要です。.bamlファイルを作成し、ジェネレーターを実行し、生成されたコードをインポートします。これにより、プロンプトエンジニアリングとアプリケーションロジック間に明確な分離が生まれるため、大規模なチームでは有益です。

Instructorはセットアップの摩擦がゼロです—pip installするだけで準備完了です。プロンプトはコードと一緒に存在するため、小規模プロジェクトやプロトタイピングでは迅速な反復が容易になります。

型安全性と検証

BAMLは生成されたコードでコンパイル時の型チェックを提供します。実行前にIDEが利用可能なフィールドを正確に認識できます。同じ.bamlファイルがサポートされるすべての言語のクライアントを生成するため、言語横断的な一貫性が保証されます。

InstructorはPydanticを通じてランタイム検証を提供します。Pythonの型ヒントはIDEサポートを提供しますが、エラーは実行中に表面化します。これはPythonの標準的な挙動ですが、BAMLの生成コードほどの静的保証はありません。

ローカルLLMとの連携

両フレームワークともローカルモデルをサポートしており、プライバシー、コスト管理、オフライン開発において重要です。Ollamaや他のローカルLLMプロバイダーを使用する場合、外部APIへの依存なしに同じ構造化出力の利点を維持できます。Ollama、Qwen3、PythonまたはGoを用いた構造化出力によるLLMの制約について深く掘り下べる場合、これらのフレームワークは低レベルAPIの上にプロダクションレディな抽象化を提供します。

BAML.bamlファイルでクライアントを設定することでOllamaに接続します:

# .bamlファイル内で:
client OllamaLocal {
  provider ollama
  options {
    model "llama2"
    base_url "http://localhost:11434"
  }
}

InstructorはOpenAI互換APIを通じてOllamaと連携します:

from openai import OpenAI
from instructor import from_openai

client = from_openai(OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama"  # ダミーキー
))

ローカルモデルを使用する場合、すべてのモデルが同様の信頼性で構造化出力を処理するわけではないため、OllamaおよびGPT-OSSモデルにおける構造化出力の潜在的な問題に留意する必要があります。

エラー処理とリトライ

BAMLは、構成可能な戦略でフレームワークレベルでリトライを処理します。スキーマ検証でのエラーは、エラーコンテキストを伴う自動的な再プロンプトをトリガーします。

Instructorはカスタム動作のためのフックを備えたデコレータベースのリトライロジックを提供します。修正されたプロンプトでリトライをトリガーするバリデーターを定義できます:

from instructor import patch
from tenacity import retry, stop_after_attempt

@retry(stop=stop_after_attempt(3))
def extract_with_retry(text: str) -> Person:
    return client.chat.completions.create(
        model="gpt-4",
        response_model=Person,
        messages=[{"role": "user", "content": text}]
    )

テストと可観測性

BAMLにはテストフレームワークが含まれており、.bamlファイル内でテストケースを直接記述し、異なる入力に対するプロンプトの動作を検証できます。プレイグラウンドは視覚的なデバッグを提供します。

Instructorは標準的なPythonテストフレームワークと統合されています。通常のPythonコードと同様に、pytestフィクスチャ、モッキングライブラリ、アサーションヘルパーを使用できます。

パフォーマンス上の考慮事項

ランタイムパフォーマンスは比較可能です—両フレームワークは最終的に同じLLM API呼び出しを行います。検証とパースのオーバーヘッドは、ネットワーク遅延やモデル推論時間と比較して無視できるレベルです。

開発速度には顕著な違いがあります:

  • BAMLのコード生成は、より良いオートコンプリートと早期エラー検出を意味しますが、ビルドステップを必要とします
  • Instructorのデコレーターアプローチは、より速い反復を意味しますが、ランタイムでのエラー発見となります

数百万のリクエストを処理するプロダクションシステムでは、両フレームワークは同等に負荷を処理します。選択はパフォーマンス特性よりも、開発ワークフローの好みにより依存します。

BAMLを選ぶべき場合

以下の要件がある場合にBAMLを選択してください:

  • マルチ言語サポート: Python、TypeScript、Rubyサービスから同じLLM契約へのアクセス
  • 契約ファースト開発: 実装前にLLMインターフェースが設計されるAPIスタイルの開発
  • チームコラボレーション: プロンプトエンジニアリングワークフローをアプリケーション開発から分離
  • 強い型保証: スタック全体でのコンパイル時のチェック
  • 視覚的なプロンプト開発: プレイグラウンド駆動によるプロンプトの反復処理

Instructorを選ぶべき場合

以下のことを求める場合にInstructorを選択してください:

  • Pythonオンリープロジェクト: 言語横断的な一貫性の必要性がない場合
  • 迅速なプロトタイピング: 構造化出力を動作させるための最小限のセットアップ
  • Pydantic統合: 既存のPydanticモデルとバリデーターの活用
  • シンプルなデプロイ: 管理が必要なビルドステップや生成コードがない
  • 豊富なPythonエコシステム: Python固有のライブラリとパターンの使用

アプローチの組み合わせ

いくつかのプロジェクトでは、両フレームワークを使用することで恩恵を受けることができます。例えば、言語横断的なクライアントが必要な顧客向けAPIにはBAMLを使用し、迅速な反復が必要な内部PythonサービスにはInstructorを使用するといった具合です。

プロジェクトが成熟するにつれてフレームワーク間の移行も可能です—Instructorでクイックな検証から始め、広範な言語サポートやより厳格な契約が必要になった時点でBAMLに移行することができます。

実世界のユースケース

データ抽出パイプライン(BAML)

ドキュメント処理システムがBAMLを使用して、請求書、契約書、レシートから構造化データを抽出します。.baml定義はMLチームとバックエンドサービス間の契約として機能し、WebダッシュボードにはTypeScriptクライアント、バッチ処理にはPythonクライアントを使用します。

カスタマーサポートボット(Instructor)

サポートボットがInstructorを使用してチケットを分類し、ユーザーの意図を抽出し、応答を生成します。チームはPydanticモデルを使用してプロンプトを迅速に反復処理し、バリデーターが抽出された電話番号、メール、チケットIDがフォーマット要件を満たすことを保証します。

マルチモーダルAIエージェント(両方)

AIエージェントシステムが、分散システム全体での型安全性を確保するためにコアのエージェント間通信契約にBAMLを使用し、各エージェントがユーザー入りの柔軟なPythonネイティブな処理のために内部でInstructorを使用します。同様のパターンは、PythonでMCPサーバーを構築する際に適用され、構造化出力によりAIアシスタントとの信頼性の高いツール統合を可能にします。

移行と統合パス

すでにLLMで基本的なJSONパースを使用している場合、両フレームワークは straightforwardな移行パスを提供します:

JSONからBAMLへ: JSONスキーマをBAML型定義に変換し、プロンプトを.bamlファイルへ移動し、クライアントを生成し、手動パースを生成された型に置き換えます。

JSONからInstructorへ: JSON構造に一致するPydanticモデルを追加し、instructorをインストールし、OpenAIクライアントをパッチ適用し、JSONパースをresponse_modelパラメータに置き換えます。

両方の移行は段階的に行うことができます—一度にコードベース全体を変換する必要はありません。

将来の展望とコミュニティ

両フレームワークは活発に開発されており、強力なコミュニティを持っています:

BAML(BoundaryML)は、言語サポートの拡張、プレイグラウンドの改善、テスト機能の強化に注力しています。商業的なバックアップは長期的な安定性を示唆しています。

Instructorは、頻繁なアップデート、広範なドキュメント、そして拡大する採用を伴い、強力なオープンソースプレゼンスを維持しています。プロジェクトはJason Liuとコントリビューターによって適切に維持されています。

結論

BAMLとInstructorは、構造化LLM出力に対する2つの優れており、かつ明確に異なるアプローチを表しています。BAMLの契約ファースト、マルチ言語哲学は、厳格な型要件を持つ分散システムを構築するチームに適しています。InstructorのPythonネイティブ、Pydanticベースのアプローチは、迅速な開発とPython中心のスタックに適合します。

どちらかが普遍的に優れているわけではありません—選択はチームの規模、言語の好み、開発ワークフロー、型安全性の要件に依存します。多くのチームは、プロトタイピングにはInstructorから始め、プロダクションのマルチサービスアーキテクチャにはBAMLを採用することで、両者の最良の側面を得られることを発見するでしょう。

有用なリンク

当サイトの記事

外部参照

購読する

システム、インフラ、AIエンジニアリングの新記事をお届けします。