PythonでAPIを作ったり、外部サービスとやりとりしていると、こんな悩みありませんか?
- 入力値がちゃんと型に合ってるか不安。。
- 毎回バリデーションを書くのが面倒。。
- テストでもう一度条件を書くのがつらい。。
そんなときに使いたいのが、pydantic。 そしてその最新バージョン(v2〜)は、中身がRust製というちょっとビックリな進化を遂げています。。
今回は、文系SEの私が体験したpydanticの力と、テストやOpenAI APIの活用にまで広がる使い方をご紹介!
(この記事は広告を含みます。)
- pydanticとは?
- Rust製になって、なにが変わった?
- 文系SE的pydanticの「ここがスゴい!」
- 【実例】Literalで値の種類をシバる!
- 【実例】OpenAI APIの出力構造をpydanticで
- 【実例】テストコードとの相性も抜群だ!
- まとめ:pydanticは型と安心をくれる
pydanticとは?
pydanticは、Pythonのクラスベースでデータ検証(バリデーション)ができるライブラリです。 簡単に言うと、「Pythonで型を守ってくれる親切な仕組み」。
from pydantic import BaseModel class User(BaseModel): id: int name: str age: int
このモデルに対して、文字列が来ても自動で型変換してくれたり、間違った値には怒ってくれたりします。
Rust製になって、なにが変わった?
pydanticはv1まではPythonだけで動いていましたが、v2から内部のコア処理がRustで実装されています。
**v2のポイント** - バリデーション処理がRustで爆速化! - 型変換&検証の処理がより正確に - `pydantic-core` というRustライブラリが裏で活躍
表向きはPythonのまま、でも中身は最強なRustさんが支えてくれてる…みたいなイメージですかね。
文系SE的pydanticの「ここがスゴい!」
- 自動で型変換してくれる(str → intなど)
- わかりやすいエラーメッセージ
- Pythonのクラスを書く感覚でバリデーションができる
- テストコードと再利用しやすい
- OpenAI APIのレスポンス構造にもぴったり!
- さらに!Literal型で値の「種類」まで制限できる
【実例】Literalで値の種類をシバる!
from pydantic import BaseModel from typing import Literal class Task(BaseModel): title: str status: Literal['todo', 'doing', 'done'] # ← この3つ以外はNG! task = Task(title="書く", status="doing") # OK Task(title="書く", status="hold") # → ValidationError!
Literalのメリット
- たとえば「ステータスは3種類だけ」みたいなルールを、コードに明示
- フロントエンドからの入力値のミスを自動でキャッチ
- API設計やLLMの出力制御にもめちゃ便利
リテラル型とは?
こちらを参照
【実例】OpenAI APIの出力構造をpydanticで
OpenAIの出力も、構造を定義すれば型安全に扱えるように!
from pydantic import BaseModel, Field from langchain.output_parsers import PydanticOutputParser from langchain.prompts import PromptTemplate from langchain.llms import OpenAI # 出力の構造を定義した Pydantic モデル class Output(BaseModel): answer: str = Field(..., description="LLMの返答") score: float = Field(..., description="返答の信頼度") # モデルに基づくパーサーを作成 parser = PydanticOutputParser(pydantic_object=Output) # プロンプトテンプレートに、パーサーのフォーマット指示を注入 prompt = PromptTemplate( template="以下の質問に回答してください。\n{format_instructions}\n質問: {question}", input_variables=["question"], partial_variables={"format_instructions": parser.get_format_instructions()}, ) # LLM(例:OpenAI のモデル)を指定してチェーンを構築 llm = OpenAI(temperature=0.3) chain = prompt | llm | parser # チェーンを実行すると、LLMの出力がOutput型にパースされる result = chain.invoke({"question": "今日の天気はどうですか?"}) print(result)
自然言語を「構造化データ」に変える時代に必須な道具
【実例】テストコードとの相性も抜群だ!
バツグンだ!とかポケモン風ですね。子どもが良く話しているのを聞くので、ついつい。。。
import pytest from pydantic import ValidationError from models import Task def test_valid_task(): task = Task(title="記事を書く", status="todo") assert task.status == "todo" @pytest.mark.parametrize("valid_status", ["todo", "doing", "done"]) def test_task_valid_statuses(valid_status): task = Task(title="記事を書く", status=valid_status) assert task.status == valid_status def test_invalid_status(): with pytest.raises(ValidationError): Task(title="記事を書く", status="waiting") # 無効なstatusでエラー発生を確認 def test_task_empty_title(): with pytest.raises(ValidationError): Task(title="", status="todo")
まとめ:pydanticは型と安心をくれる
- 型の力で安心感が得られる
- コードがスッキリ&安全になる
- Rustのパワーでパフォーマンスも安心
- テストでもOpenAIでも活躍してくれる
- Literalで「選択肢をしばる」という安全性も追加!
FastAPIと相性が良いので、使っていきます。