結論から:Jev単体では画像を認識できない
前回の記事で紹介したTypeSafe AIの「Jev」は、ソフトウェアが直接使える型付きの判断を返すモデルです。「写真に人が写っているか判断させたい」と考えるのは自然な発想ですが、結論から言うとJevはテキスト専用モデルで、画像をそのまま渡すことはできません。
公式ドキュメントにはこう明記されています。
Jev accepts text only. State must be a string, JSON object, or array of text values. Images, audio, and video are not supported (yet).
同時に、公式は代わりの方法もはっきり示しています。
Pre-process non-text inputs (images, audio, video, binaries) into text or structured fields before sending them as \
state\.
つまり、画像を先にVision対応モデルで文章に変換し、その文章をJevの\state\として渡すのが公式の推奨パターンです。この記事では、この2段構成で「写真に人がいるかいないか」を判定するプログラムを作ります。
全体の設計:認識はVisionに、判断はJevに
処理を2つの役割に分けます。
- 認識(Vision):画像に何が写っているかを、判断を交えずに事実だけ文章化する
- 判断(Jev):その文章を材料に、型付きの答え(Yes/No、選択肢)と確信度を返す
この分業には理由があります。Jevのドキュメントは、Jevに渡す指示は「明確で文字通り」であるべきだと繰り返し述べています。もしVisionモデルに「人がいるか教えて」と直接聞いてしまうと、Yes/Noの判断基準や確信度の出し方がモデルごとにバラバラになり、後工程のコードで扱いにくくなります。Visionモデルには事実の描写だけをさせ、「人物が写っているかどうか」という判断そのものはJevの型付きの質問(Noul・Choice)に任せることで、確信度による自動化のしきい値設計が可能になります。
このパターンは、TypeSafeの公式クックブックにある「SDE Cascade(抽出モデルで下書きし、Jevの確信度で検証・エスカレーションする)」とも同じ考え方です。抽出とJevによる判断を分けるという設計思想は共通しています。
使うもの
- Claude API(Vision対応モデル):画像を文章に変換する
- TypeSafe AIのJev(\
typesafe-sdk\):文章から型付きの判断を出す
Claude APIのインストールとセットアップは以下の通りです。
\\\`bash
pip install anthropic typesafe-sdk
\\\`
Claude APIキー(\ANTHROPIC_API_KEY\)とTypeSafeのAPIキーをそれぞれ環境変数に設定しておいてください。
ステップ1:画像を文章化する
まず、画像をBase64エンコードしてClaude APIに送り、写っているものを客観的に描写させます。ポイントは、プロンプトで「人がいるか判断して」とは聞かず、「見えたものをそのまま説明して」と頼むことです。
\\\`python
import base64
from anthropic import Anthropic
anthropic_client = Anthropic()
def describe_photo(image_path: str) -> str:
with open(image_path, "rb") as f:
image_data = base64.standard_b64encode(f.read()).decode("utf-8")
media_type = "image/jpeg" if image_path.lower().endswith((".jpg", ".jpeg")) else "image/png"
response = anthropic_client.messages.create(
model="claude-opus-5",
max_tokens=1024,
messages=[{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": media_type,
"data": image_data,
},
},
{
"type": "text",
"text": (
"この写真に写っているものを、判断を交えず事実だけ箇条書きで説明してください。"
"人物が写っていれば、人数・服装・姿勢など見た目の特徴も含めてください。"
"写っていない場合は無理に人物について触れる必要はありません。"
),
},
],
}],
)
return "".join(block.text for block in response.content if block.type == "text")\\\`
ここで返ってくるのは、あくまで「見えたものの説明文」です。「人がいます」「いません」という最終判断はまだ出していません。
ステップ2:文章をJevに渡して型付きに判断する
次に、この説明文をJevの\state\として渡し、2つの質問を投げます。
- Noul:「この説明は、写真に少なくとも1人の人物が写っていることを示しているか」(0〜1の確率)
- Choice:「写っている人数はどれに当てはまるか」(0人・1人・複数人)
\\\`python
from typesafe_sdk import Choice, Noul, TypeSafeClient
typesafe_client = TypeSafeClient()
def check_person_in_photo(image_path: str) -> dict:
description = describe_photo(image_path)
result = typesafe_client.system_one(
state=description,
questions={
"has_person": Noul(
instructions="この説明は、写真に少なくとも1人の人物が写っていることを示している",
),
"person_count": Choice(
instructions="写真に写っている人物の人数はどれに当てはまるか",
criteria={
"none": "人物は写っていない",
"one": "1人だけ写っている",
"multiple": "2人以上写っている",
},
),
},
)
has_person = result.answers["has_person"]
person_count = result.answers["person_count"]
return {
"description": description,
"has_person": has_person.noul >= 0.5,
"confidence": has_person.confidence,
"person_count": person_count.choice,
"person_count_confidence": person_count.confidence,
# 確信度が低い場合は自動判定せず人の目でのチェックに回す
"needs_review": has_person.confidence < 0.7,
}\\\`
ステップ3:確信度でルーティングする
最後に、確信度に応じて挙動を分けます。しきい値は例なので、実際の用途(誤検知の許容度)に合わせて調整してください。
\\\`python
if __name__ == "__main__":
result = check_person_in_photo("sample.jpg")
if result["needs_review"]:
print(f"⚠️ 確信度が低いため要確認(確信度: {result['confidence']:.2f})")
print(f" Visionの説明: {result['description']}")
elif result["has_person"]:
print(f"✅ 人物あり({result['person_count']}, 確信度: {result['confidence']:.2f})")
else:
print(f"➖ 人物なし(確信度: {result['confidence']:.2f})")\\\`
実行結果のイメージは次のようになります。
\\\`
✅ 人物あり(one, 確信度: 0.97)
\\\`
\\\`
⚠️ 確信度が低いため要確認(確信度: 0.42)
Visionの説明: - 屋外の風景写真。手前に木々のシルエット...
\\\`
確信度が高ければ自動判定に任せ、低ければ人手のチェックに回す――これは前回の記事でも紹介した、Jevの基本的な運用パターンです。破壊的な操作(例:自動で写真を削除する、通知を送るなど)に使う場合は、しきい値をより高く設定してください。
なぜ「Visionだけ」で済ませないのか
ここまで読むと、「Claudeに直接『人はいますか?』と聞けば済むのでは」と思うかもしれません。単純な1枚の判定であれば、その通りです。この2段構成が効いてくるのは、次のような場面です。
- 大量の写真を一括処理したい:Jevは1回の呼び出しで複数の質問を並列に評価でき、質問を増やしても応答時間がほとんど変わりません。人数の区分やシーン分類など、判断項目を増やしてもコストの伸びを抑えられます
- 確信度に基づいて自動化と人手確認を切り分けたい:Vision単体の自由な文章では、しきい値による機械的な分岐が組みにくくなります
- 判断ロジックをコード側に残しておきたい:Visionの描写とJevの判断基準を分けておくことで、「どういう写真を人物ありとみなすか」の基準を、プロンプトではなくJevの\
criteria\として明示的に管理できます
逆に、1枚だけをその場で確認するようなカジュアルな用途では、Vision単体で十分な場合が多いです。
注意点
- Visionモデルの説明が誤っていれば、Jevの判断もその誤りを引き継ぎます。Jevは文章に対する判断は較正されていますが、そもそもの画像認識精度はVisionモデル側に依存します
- Jevは算数や数え上げが苦手です。今回は人数を「0人・1人・複数人」という選択式(Choice)にすることで、この弱点を回避しています。「正確に何人か」を数えさせたい場合は、Jevではなく別の手段(物体検出モデルなど)を検討してください
- 確信度のしきい値は用途に応じて調整が必要です。プライバシーに関わる用途(人物が写っている写真を自動でぼかす、など)では、見逃しのリスクを考慮して慎重に設定してください
まとめ
- Jevはテキスト専用モデルで、画像を直接扱うことはできない
- 公式が推奨するのは、Visionモデルで画像を文章化してからJevに渡す構成
- Visionには「事実の描写」だけをさせ、判断はJevの型付きの質問(Noul・Choice)に任せるのがポイント
- 確信度をしきい値として使い、自動判定と人手確認を切り分けることで、大量の画像処理にも展開しやすくなる
「AIに画像を見せて何かを判断させる」という要件は、AIモデル1つで完結させず、認識と判断を役割分担すると設計しやすくなります。今回の「人物がいるか」の判定は最小限の例ですが、シーン分類やコンテンツモデレーションなど、他の画像判定タスクにも同じ構成を応用できます。
※本記事のコード例は、公開されているAnthropicおよびTypeSafe AIの公式ドキュメントの仕様に基づいています。実行には別途Claude APIキーとTypeSafe AIのAPIキーが必要です。