📋

Outlines で LLM の構造化出力を100%成功させる

に公開

Outlines のサムネ

忙しい人向けのまとめ

LLM の出力を確実に JSON にしたい!! → Outlines が使えるぞ!

はじめに

LLM を使ったアプリケーションを開発していると、出力を JSON や Pydantic などの決まった形式で受け取りたい場面がよくあります。しかし、LLMが勝手に説明文を付け加えたり、形式を微妙に変えたりして、パースエラーや予期しない出力に悩まされることも多いのではないでしょうか。

従来はこうした問題に対して正規表現での整形やリトライ処理など、本来のロジックとは関係ない処理を書く必要がありました。

Outlines は、これらの問題を解決し、LLM の出力をシンプルなコードで確実に構造化出力を成功させることのできる便利なライブラリです。Outlines は OpenAI の API や transformers, Ollama, vLLM といった様々なバックエンドで利用可能です。

https://github.com/dottxt-ai/outlines

インストール

以下のコマンドでインストールできます。

pip install outlines 

# uv の場合
uv add outlines

使用例

基本的な使い方

outlines を使って構造化出力を得る際に、パース処理や正規表現を使ったコードを書く必要はなく、基本的に以下のようなシンプルなコードで実現可能です。

model("Prompt", output_type)

モデルをインスタンス化する際は、Outlines の from_* 関数でラップする必要があります。
モデルのインスタンス化
様々な Outlines の from_* 関数

Pydantic を使った構造化出力

Outlines のドキュメント に従って、OpenAI API を使って構造化出力を試してみます。

from outlines import from_openai
import openai
from pydantic import BaseModel

class ClubActivity(BaseModel):
    name: str
    category: str
    established_year: int

# Create the model
model = from_openai(
    openai.OpenAI(),
    "gpt-4o"
)

prompt = "架空の部活「ちくわ部」について教えて"
output = model.generate(prompt, ClubActivity)
print(output)
print(type(output))

以下の出力を得ました。

{"name":"ちくわ部","category":"食文化研究会","established_year":2020}
<class 'str'>

様々なデータ型での出力

Outlines では output_type (第二引数) を指定することで、int などの Python 標準な型や、Pydantic のクラス、選択肢からのトークン選択など様々な出力制限が可能です。

model("How many minutes are there in one hour", int) # "60"
model("Pizza or burger", Literal["pizza", "burger"]) # "pizza"
model("Create a character", Character, max_new_tokens=100) # '{"name": "James", "birth_date": "1980-05-10)", "skills": ["archery", "negotiation"]}'

正規表現での出力制限

正規表現のような自由な出力形式も指定可能です。

from outlines.types import Regex

regex = r"[0-9]{3}"
output_type = Regex(regex)

マルチモーダル・チャット入力

画像、音声などのマルチモーダルな入力形式やチャット形式の入力なども可能です。

import io
import requests
import PIL
import outlines
import openai
from outlines.inputs import Image

# Create the model
model = outlines.from_openai(
    openai.OpenAI(),
    "gpt-4o"
)

# Function to get an image
def get_image(url):
    r = requests.get(url)
    return PIL.Image.open(io.BytesIO(r.content))

# Create the prompt containing the text and the image
prompt = [
    "Describe the image",
    Image(get_image("https://picsum.photos/id/237/400/300"))
]

# Call the model to generate a response
response = model(prompt, max_tokens=50)
print(response) # 'This is a picture of a black dog.'

チャット形式の入力も可能です。

from outlines.inputs import Chat, Image

# Create the chat input
prompt = Chat([
    {"role": "system", "content": "You are a helpful assistant."},
    {
        "role": "user",
        "content": ["Describe the image", Image(get_image("https://picsum.photos/id/237/400/300"))]
    },
])
print(prompt)
# {'role': 'system', 'content': 'You are a helpful assistant.'}
# {'role': 'user', 'content': ['Describe the image', Image(image=<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=400x300 at 0x7FFA526CCC40>)]}

まとめ

このように、Outlines を使うと構造化出力にまつわる複雑なコーディングやモデル側の生成/パース失敗を気にする必要がなくなり、かなり便利です。OpenAI API の場合はネイティブの Structured Output を使っても使用感は変わらないかもしれません。

余談ですが、Outlines は DeNA さんの DeNA × AI Talks #1 - AIスペシャリストが語る、最新技術 -で知りました。面白い勉強会をありがとうございます。

参考文献

Discussion