はじめに
Instractor は LLM への問い合わせ結果を Ecto のスキーマとして構造化してくれます
本記事では Livebook で Instractor を使用し、 Ollama からの応答を構造化します
Ollama はローカルマシン上で起動しており、 Livebook はコンテナで起動しているものとします
実装したノートブックはこちら
セットアップ
セットアップセルで Instractor をインストールします
Mix.install([
{:instructor, "~> 0.1.0"}
])
スキーマ定義
Ollama からの応答を格納するスキーマを定義します
@llm_doc
で各フィールドの説明を書くことで、 LLM が説明に沿った内容を格納してくれます
また、バリデーションによって正しいスキーマになっているか検証し、間違っていればリトライします
defmodule Judgement do
use Ecto.Schema
use Instructor
@llm_doc """
## Field Descriptions:
- class: 文章が肯定的か否定的か.
- reason: 判定した理由.
- score: 肯定的の度合いを示す 0.0 から 1.0 の値.
"""
@primary_key false
embedded_schema do
field(:class, Ecto.Enum, values: [:positive, :negative])
field(:reason, :string)
field(:score, :float)
end
@impl true
def validate_changeset(changeset) do
changeset
|> Ecto.Changeset.validate_number(:score,
greater_than_or_equal_to: 0.0,
less_than_or_equal_to: 1.0
)
end
end
LLM への問い合わせ
モデル、応答を格納するスキーマ、メッセージを指定して LLM を呼び出します
第2引数のキーワードリストで Ollama を使うように指定しています
また、今回は Livebook をコンテナ上で起動しているため、 api_url
に "http://host.docker.internal:11434"
を指定しています
モデルには日本語も理解できる Phi-4 を使用します
judge = fn text ->
Instructor.chat_completion(
[
model: "phi4",
mode: :json,
response_model: Judgement,
max_retries: 3,
messages: [
%{
role: "user",
content: """
文章が肯定的か否定的か判定してください
<文章>
#{text}
</文章>
"""
}
]
],
[
adapter: Instructor.Adapters.Ollama,
api_url: "http://host.docker.internal:11434"
]
)
end
「出掛けるのも吝かではない」という文章が肯定的か、否定的か判定してもらいましょう
judge.("出掛けるのも吝かではない")
実行結果
{:ok,
%Judgement{
class: :positive,
reason: "文章は「出掛けるのも吝かではない」とあり、外に出ることや行動を肯定的に捉えています。この表現から前向きで楽観的なニュアンスが感じられます。",
score: 0.8
}}
結果は :positive
で、正しく判定できています
しっかりスキーマにも格納されています
続いて「あなたの希望には添えかねます」という文章が肯定的か、否定的か判定してもらいましょう
judge.("あなたの希望には添えかねます")
実行結果
{:ok,
%Judgement{
class: :negative,
reason: "文章は、願いや要求が叶うことを明示的に否定する形で提示されており、「希望には添えかねます」という表現から否定的な印象が生まれるため。",
score: 0.2
}}
こちらも正しく判定でき、指定したスキーマの通りに返ってきました
まとめ
Instractor を使うことで LLM の応答をスキーマに格納することができました
システムに LLM を組み込む場合、出力形式が安定することは必須条件と言えます
Ecto スキーマに格納することで、単なる JSON 形式ではなく、各フィールドの型や選択肢、範囲なども制約できるのは良いですね