はじめに

日々の業務では、メール、議事録、問い合わせログといった非構造化テキストデータが溢れています。これらのデータから「顧客名」「製品番号」「対応期日」などの重要な情報を手作業で抜き出すのは、時間と手間がかかるだけでなく、ヒューマンエラーの原因にもなり得ます。

この課題を解決する強力な武器が、大規模言語モデル(LLM)の「構造化出力」機能です。LLMに単なる文章を生成させるのではなく、JSONやXMLといった決まった形式で情報を抽出させることで、データ活用プロセスを劇的に自動化できます。

この記事では、LLMを用いて非構造化テキストから構造化データを抽出するための具体的な手法を、基本的なプロンプトエンジニアリングから、より確実性を高めるライブラリの活用まで、初中級者向けに分かりやすく解説します。

LLMにおける構造化出力とは?

LLMにおける構造化出力とは、モデルの応答を特定のデータ構造(スキーマ)に強制する技術のことです。最も一般的に使われるフォーマットはJSONですが、XMLやYAMLなども指定可能です。

なぜ構造化出力が重要なのでしょうか。それは、LLMの出力をプログラムで扱いやすくなるからです。例えば、抽出したデータをデータベースに登録したり、別のAPIに渡したりする場合、出力形式が保証されていると後続の処理が非常にシンプルになります。これにより、LLMを単なるチャットボットではなく、自動化システムの堅牢な一部として組み込むことが可能になります。

構造化出力を実現する方法はいくつかあります。単純な方法としてはプロンプトで出力形式を指示するやり方があります。より高度で確実な方法として、OpenAIのFunction Calling(現在はTool Useに統合)のようなAPI機能や、InstructorやLangChainといった専用ライブラリを利用する方法が挙げられます。

基本的なプロンプトによる構造化出力

最も手軽に始められるのが、プロンプトで直接JSONフォーマットを指示する方法です。システムプロンプトやユーザープロンプトの中で、どのようなキーを持つJSONを期待しているか、例(Few-shot)を交えて示すことで、LLMはそれに倣って出力しようとします。

例えば、顧客からの問い合わせメールから情報を抽出するシナリオを考えてみましょう。以下のようなプロンプトを作成します。

import openai

# APIキーは環境変数などで設定してください
client = openai.OpenAI()

response = client.chat.completions.create(
  model="gpt-4-turbo",
  messages=[
    {"role": "system", "content": "あなたはメールの内容を分析し、指定されたJSON形式で情報を抽出するアシスタントです。"},
    {"role": "user", "content": """
以下のメールから情報を抽出してください。

件名:緊急:製品Aの納期について
お世話になっております。ABC商事の佐藤です。
先日注文いたしました製品A(注文番号: #12345)の納期について確認させてください。
来週のイベントで使用するため、至急ご回答いただけますと幸いです。

---
出力形式:
{
  "customer_name": "顧客名",
  "product_name": "製品名",
  "query_type": "問い合わせ種別(例: 納期確認, 在庫確認)",
  "urgency": "緊急度(高, 中, 低)"
}
"""}
  ],
  response_format={ "type": "json_object" } # JSONモードを有効化
)

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

この方法の利点は、特別なライブラリが不要で手軽に試せる点です。しかし、LLMの出力は確率的なため、必ずしも指示通りの完璧なJSONが返ってくるとは限りません。キー名が違ったり、JSON形式が壊れていたりすることもあり、アプリケーション側でパースエラーのハンドリングやリトライ処理を実装する必要が出てきます。

ライブラリ活用で確実性と開発効率を高める

プロンプトによる指示だけでは出力が不安定な問題を解決し、より堅牢なシステムを構築するために、PydanticとInstructorというライブラリの組み合わせが非常に有効です。

PydanticはPythonのデータバリデーションライブラリで、クラスを用いてデータのスキーマを厳密に定義できます。一方、InstructorはPydanticモデルを使ってLLMの出力をバリデーションし、指定したスキーマに準拠したPythonオブジェクトを直接返してくれるライブラリです。これにより、LLMの出力が壊れたJSONであったり、スキーマと異なっていたりした場合に自動でリトライを行ってくれます。

以下のコードは、先ほどのメール抽出をInstructorとPydanticで実装した例です。出力スキーマをMailInfoクラスとして定義し、それをresponse_modelに指定するだけで、型安全なデータ抽出が実現できます。

import openai
import instructor
from pydantic import BaseModel, Field
from typing import Literal

# Pydanticモデルで出力スキーマを厳密に定義
class MailInfo(BaseModel):
    customer_name: str = Field(description="メール送信者の会社名または氏名")
    product_name: str = Field(description="問い合わせ対象の製品名")
    query_type: Literal["納期確認", "在庫確認", "技術的な質問", "その他"]
    urgency: Literal["高", "中", "低"]

# OpenAIクライアントをInstructorでパッチする
client = instructor.patch(openai.OpenAI())

# response_modelにPydanticモデルを指定する
extracted_data: MailInfo = client.chat.completions.create(
    model="gpt-4-turbo",
    response_model=MailInfo,
    messages=[
        {"role": "user", "content": """
        件名:緊急:製品Aの納期について
        お世話になっております。ABC商事の佐藤です。
        先日注文いたしました製品A(注文番号: #12345)の納期について確認させてください。
        来週のイベントで使用するため、至急ご回答いただけますと幸いです。
        """}
    ]
)

# Pydanticモデルのインスタンスが直接得られる
assert isinstance(extracted_data, MailInfo)
print(extracted_data.model_dump_json(indent=2, ensure_ascii=False))

このアプローチにより、開発者はLLMからの出力のパースやバリデーション処理を自前で実装する必要がなくなり、本来のビジネスロジックに集中できます。コードの可読性や保守性も大幅に向上します。

まとめ

本記事では、LLMの構造化出力機能を用いて、非構造化テキストから業務データを自動抽出する手法を解説しました。簡単なプロンプトによる指示から始め、PydanticとInstructorライブラリを組み合わせることで、より堅牢で信頼性の高いデータ抽出システムを構築できることを示しました。

要点のまとめ:

  • 構造化出力はLLMを業務アプリケーションに組み込むための鍵となる技術です。
  • 簡単なユースケースでは、プロンプトでのJSON形式の指示とresponse_format指定が有効です。
  • 高い信頼性と開発効率が求められる場合は、Pydanticでスキーマを定義し、InstructorでLLMの出力を制御する方法が強力です。

この技術を活用すれば、これまで手作業で行っていた多くのデータ入力・転記作業を自動化し、生産性を飛躍的に向上させることができます。ぜひ、ご自身の業務で活用できる場面を探し、小さな自動化から試してみてはいかがでしょうか。