「あのJR東のロボット、うちでも作れないんですかね」
先週、ある製造業のクライアントから半分冗談で言われた。文脈は、JR東日本が線路内を自律走行するロボットで点検作業を進めるという11月の発表だ。クマ遭遇リスクの高い区間や夜間作業を機械に置き換える、という話である。
正直なところ、自律ロボット本体を内製するのは無理だ。だが、その先にある「画像で異常を見つけて、文書化する」というワークフローだけ切り出せば、Vision LLM と数十行のスクリプトで近いものは作れる。製造設備の日常点検、物流倉庫の棚チェック、店舗什器の状態確認 — どれも構造は同じだ。
本記事は、今回の案件で実際に組んだ最小構成と、ハマった3点をメモとして残す。

前提・環境
Python 3.11
openai 1.x (Claude API でも同じ構造で書ける)
gpt-4o (画像入力対応モデル)
入力: スマホで撮った点検対象の写真 1〜数枚
出力: 点検箇所・状態・異常の有無・推奨アクション を含む JSON
pip install openai pydantic実装ステップ
Step 1: 出力スキーマを Pydantic で定義する
LLM に自由文を返させると、後段のシステムでパースできない。最初に「返してほしい JSON の形」を Pydantic で書いて、API の structured output 機能に渡すのが定石である。
from pydantic import BaseModel, Field
from typing import Literal
class InspectionFinding(BaseModel):
location: str = Field(..., description="点検箇所(例: 配電盤の右下端子)")
status: Literal["正常", "要観察", "異常"]
description: str = Field(..., description="観察事実(主観を入れない)")
recommended_action: str | None = None
class InspectionReport(BaseModel):
site: str
findings: list[InspectionFinding]
overall_risk: Literal["低", "中", "高"]
「異常」「要観察」「正常」を Literal で縛っておくと、後段のダッシュボードで集計しやすい。自由文で「やや問題あり」「気になる」が混ざってくると、それだけで集計コードが汚れる。
Step 2: 画像を送る最小コード
import base64
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY")
def encode_image(path: str) -> str:
with open(path, "rb") as f:
return base64.b64encode(f.read()).decode("utf-8")
def inspect(image_paths: list[str], site: str) -> InspectionReport:
content = [
{"type": "text", "text": f"以下は{site}の点検写真である。各写真について、点検箇所・状態・異常の有無を観察し、JSON で返す。"},
]
for p in image_paths:
content.append({
"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{encode_image(p)}"}
})
completion = client.beta.chat.completions.parse(
model="gpt-4o",
messages=[{"role": "user", "content": content}],
response_format=InspectionReport,
)
return completion.choices[0].message.parsed
Step 3: ここで30分ハマった — 「異常なし」しか返さない問題
最初の動作確認で、明らかにサビが浮いた金属パネルの写真を入れたのに、LLM は「特に異常は確認できません」と返してきた。Vision LLM は概して「断定を避ける」方向にチューニングされている節がある。点検という用途では、これは致命的だ。
対策は few-shot を1〜2例プロンプトに入れることだった。実際の異常写真と、そのときの正解 JSON を例示として置く。
SYSTEM_PROMPT = """あなたは現場点検の補助である。観察された事実のみを記録し、判断に迷う場合は「要観察」を選ぶ。「異常なし」は、画像内に明確な点検対象が存在し、かつ目立った変色・損傷・漏れ・歪みが見られない場合にのみ使う。
例:
入力: 配管接合部に水滴の跡が見える写真
出力: {"location":"配管接合部","status":"要観察","description":"接合部周辺に水滴の跡。乾燥か継続漏れか不明","recommended_action":"24時間後に再撮影"}
"""
これだけで「要観察」が適切に出るようになった。30%を「要観察」にできるだけでも人間の判断負荷はかなり減る。100枚の写真なら、断定が必要なのは70枚程度に絞れる、という言い方もできる。
Step 4: 複数画像とトークン爆発
スマホで点検場所を一周撮ると、10〜20枚はすぐに溜まる。gpt-4o は画像1枚あたり概ね数百〜千トークン消費するので、20枚をそのまま投げると入力だけで2万トークン近い。
実装上の工夫は2点だけだ。1つは撮影時にサムネイルサイズ(長辺1024px程度)にリサイズしてから送ること、もう1つは1リクエストあたり最大5枚に分割して、最後にレポートをマージすることである。リサイズだけで体感3〜5倍は安くなった。
動作確認
ローカルで配管・配電盤・床面の写真3枚を投入した時のログ。
$ python inspect.py samples/
{
"site": "工場B棟2階",
"findings": [
{"location":"配電盤右下","status":"要観察","description":"端子付近に変色。腐食初期の可能性","recommended_action":"次回点検時に再確認"},
{"location":"配管接合部","status":"正常","description":"水滴・変形なし","recommended_action":null},
{"location":"床面","status":"異常","description":"オイル状の液体痕","recommended_action":"早期清掃と漏れ元特定"}
],
"overall_risk":"中"
}
業務システムに流し込める JSON がそのまま返ってきている。あとは Slack に投げるなり、点検台帳DBに INSERT するなりは別の話だ。
応用・発展
物流倉庫の棚卸し — SKU と数量の組を返すスキーマに差し替えれば、画像棚卸しが作れる
小売店の什器チェック — 商品の陳列状態・POP の有無を Literal で型付けする
建設現場の進捗記録 — 日次写真から「前日比の変化」を抽出するには、直前の画像も同時に入力する構成にする
アノテーション補助 — 「LLM の初稿 → 人間が修正」のフローで、一次レポート作成時間を半分以下に短縮できる
オフライン化 — 機密性が高い現場では、ローカル動作する Vision モデル(Qwen2-VL など)で同じ構造を組む手もある
まとめ
JR東のような自律ロボット導入は大規模投資が必要だが、「現場の写真を AI に見せて構造化する」部分だけなら、数十行のコードで動くものは作れる。鍵は出力スキーマの設計と、few-shot で「異常なし」逃避を抑えることだ。
本実装では、点検レポート作成にかかっていた1日2時間の事務作業を、現場担当者がスマホで撮るだけのフローに置き換えた。完全自動ではない。だが、人間の判断は最終確認だけになった。
筆者は 5years+ で韓国・日本の中小企業向けに AI/LLM の業務応用を担当している。