Pythonなのに中身はRust!?pydanticの実力とテスト自動化&OpenAI活用術

PythonでAPIを作ったり、外部サービスとやりとりしていると、こんな悩みありませんか?

  • 入力値がちゃんと型に合ってるか不安。。
  • 毎回バリデーションを書くのが面倒。。
  • テストでもう一度条件を書くのがつらい。。

そんなときに使いたいのが、pydantic。 そしてその最新バージョン(v2〜)は、中身がRust製というちょっとビックリな進化を遂げています。。

今回は、文系SEの私が体験したpydanticの力と、テストやOpenAI APIの活用にまで広がる使い方をご紹介!

(この記事は広告を含みます。)

skyticketプレミアム


pydanticとは?

pydanticは、Pythonのクラスベースでデータ検証(バリデーション)ができるライブラリです。 簡単に言うと、「Pythonで型を守ってくれる親切な仕組み」。

docs.pydantic.dev

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の出力制御にもめちゃ便利
リテラル型とは?

こちらを参照

kunio-ud-all.com

【実例】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と相性が良いので、使っていきます。

kunio-ud-all.com

kunio-ud-all.com