AIに決まった形で返させても、読めるかどうかと、中身が正しいかどうかは別です。JSONとして成立していても、金額が負だったりします。
応答10件に検証を当てたところ、JSONとして読めたのは8件でした。ところがそのうち6件は中身が壊れています。
応答10件に検証を実際に当てました。JSONとして読めたのは8件で、そのうち検証を通ったのは2件です。
Pydantic AIが前提にしている「型で検証してから受け取る」という形が、どれだけ効くのかを実際に確かめました。用意したのは氏名と金額と日付を返させたときの応答10件です。
検証は本当に実行しています。応答も検証の条件もコードに書いてあり、通ったかどうかを機械で判定しました。
function validate(raw) {
let o;
try { o = JSON.parse(raw); } catch { return { ok: false, why: 'JSONとして読めない' }; }
if (typeof o.name !== 'string' || o.name.length === 0) return { ok: false, why: '氏名が空か文字列でない' };
if (typeof o.amount !== 'number') return { ok: false, why: '金額が数値でない' };
if (!Number.isInteger(o.amount) || o.amount <= 0) return { ok: false, why: '金額が正の整数でない' };
if (typeof o.date !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(o.date)) return { ok: false, why: '日付の形式が違う' };
// 存在しない日付を弾く
const [y, m, d] = o.date.split('-').map(Number);
const dt = new Date(Date.UTC(y, m - 1, d));
if (dt.getUTCFullYear() !== y || dt.getUTCMonth() + 1 !== m || dt.getUTCDate() !== d) {
return { ok: false, why: '存在しない日付' };
}
return { ok: true, value: o };
}
応答(先頭40文字) 検証
{"name":"佐藤","amount":12000,"date":"20… 通る
{"name":"佐藤","amount":"12000","date":"… 弾く: 金額が数値でない
{"name":"佐藤","amount":12000,"date":"20… 弾く: 日付の形式が違う
{"name":"","amount":12000,"date":"2026… 弾く: 氏名が空か文字列でない
{"name":"佐藤","amount":-500,"date":"202… 弾く: 金額が正の整数でない
{"name":"佐藤","amount":12000} 弾く: 日付の形式が違う
{"name":"佐藤","amount":12000,"date":"20… 弾く: JSONとして読めない
はい、こちらが結果です。{"name":"佐藤","amount":1200… 弾く: JSONとして読めない
{"name":"佐藤","amount":12000,"date":"20… 弾く: 存在しない日付
{"name":"佐藤","amount":1.2e4,"date":"20… 通る
検証を通った: 2 / 10
検証で弾いた: 8 / 10
検証しない場合、そのまま次の処理に流れる件数
JSONとして読めた件数: 8 / 10
そのうち中身が壊れているもの: 6件
ここが要点です。JSONとして読めた8件のうち、6件は中身が壊れています。読めることは、正しいことを保証しません。
読めなかった2件は、その場で例外になります。ですから気づけます。
困るのは残りです。amount が -500 でも、date が 2026-13-45 でも、JSONとしては成立します。検証しなければ、そのまま次の処理に流れます。
面白かったのが最後の1件です。1.2e4 という指数表記が通りました。読み込むと12000という整数になるためで、検証としては正しい判定です。
見た目は違っても値は同じなので、通ってよい例になります。逆に言えば、見た目を見ているのか値を見ているのかを、検証の条件で決めているということです。
読めることと正しいことは別。差の6件が黙って流れる。
Pydantic AI is the Python AI SDK: a typed, extensible agent loop with every model a string swap away.原文pydantic/pydantic-ai README この内容の有効期限2027-02-18
返ってくる形を型として宣言し、検証を通ったものだけを受け取ります。型がそのまま検証の条件になります。
Pydantic AIは、返ってくる形を型として書いておき、その型に合うものだけを受け取る枠組みです。READMEでは、型のついた、拡張できるエージェントの繰り返しだと説明されています。
特徴は、検証の条件を別に書かなくてよい点です。型を書けば、その型に合うかどうかが自動で確かめられます。
前の節では検証を手で書きましたが、あの条件のうち型と必須の部分は、型の宣言から出てきます。範囲や実在の確認だけを足す形になります。
READMEでは、型のついた単純なデータの取り出しから、長く動く複数エージェントの協働までを対象に挙げています。取り出しだけの用途でも使える、という位置づけです。
返す形を宣言する考え方そのものはFunction callingの記事で扱っています。Pydantic AIは、そこに検証と再実行まで含めた形になります。
同じREADMEでは、モデルを文字列の入れ替えで差し替えられるとも書かれています。共通の口をはさむ考え方はLangChainの記事でも扱っており、そちらでは1社増やすときの変更が5か所から1か所になりました。
使ってみて感じたのは、検証の条件を書く作業が、そのまま仕様を決める作業になることでした。金額は正の整数か、0を許すか。日付は実在を確かめるか。曖昧なまま進めていた部分が、型を書く段階で全部表に出てきます。
What are you building? From simple typed data extraction to complex, long-running multi-agent collaboration, Pydantic AI and Pydantic AI Harness have got you covered.原文pydantic/pydantic-ai README この内容の有効期限2027-02-18
1回挟むだけで通る割合は3倍以上になります。ただし回数を増やすほど伸びは鈍り、呼び出しは増えます。
Pydantic AIのような検証を入れると、次に決めるのが弾いたあとどうするかです。何が悪かったかを伝えて再実行させるのが一般的な形になります。
どこまで挟む価値があるのかを、10,000回の試行で確かめました。「弾いて指摘して再実行すると1回あたり60%で直る」という前提は置いた値です。
再実行の上限 最終的に通る割合 呼び出し回数の平均
0回 20.2% 1.00回
1回 68.2% 1.80回
2回 87.0% 2.12回
3回 95.2% 2.24回
1回挟むだけで効きます。20.2%が68.2%ですから、3倍以上です。
2回目以降は伸びが落ちます。68.2%から87.0%、そこから95.2%。上限を1回から2回に増やすより、0回から1回にするほうがはるかに効きます。
呼び出し回数のほうは1.80回から2.24回で、あまり増えません。1回目で通った分は再実行しないためです。
3番目が改善につながります。同じ条件でばかり落ちているなら、指示のほうを直すほうが速いためです。依頼文を詰める作業はプロンプト設計の記事で扱っています。
0回から1回への1歩がいちばん効く。そこから先は鈍る。
Pydantic AI Harness has everything an agent needs for complex, long-running work, snapped on as capabilities , from memory , sub-agents , and context management to a complete coding agent .原文pydantic/pydantic-ai README この内容の有効期限2027-02-18
同じ課題を持つ会社にとって、動いている設定は「作る時間」を買えるということです。ServiceDockは自作のワークフローやテンプレートを出品できるマーケットプレイスです。手数料や出品の流れは出品者向けページにまとまっています。
出品の仕組みを見る