Visual Store を更新した。

今回は、スクリーンショットを実際に開く前の判断と、開いた後の確認結果を画像ごとに残すJudgment Layerを追加した。

前回の記事「コーディングエージェントに画像を溜め込まないためのVisual Storeを作った」では、画像を保存して必要な一枚だけ取り出す仕組みを書いた。

背景

Visual Storeは、デバッグ用のスクリーンショットを会話へそのまま入れず、ローカルへ保存して必要な一枚だけ後から取り出すために作った。

保存した画像が増えると、次はどれを開くかを決める必要がある。labelやnoteだけでも候補は絞れるが、UIを操作した直後なら、画像を見なくても次のような情報を持っていることがある。

操作: login button clicked
遷移前のroute: /login
遷移後のroute: /dashboard
console error: なし
画面に出ている文字: Dashboard, Welcome back
前frameとの差分: pixelは一致しない

この情報を使わず、保存したスクリーンショットを順番に開くのは少しもったいない。

仮説

UI確認で取ったスクリーンショットの大半は、画像そのものを見なくても確認対象から外せるのではないか、と考えた。

例えば、routeが期待どおり変わり、console errorがなく、想定した文字が出ていて、前frameとも異なっているなら、ひとまず問題なさそうに見える。反対にrouteが変わらない、errorがある、材料が足りない、といったものは画像を見た方がよさそうである。

もちろん、この条件だけで画面が正しいとは決められない。ここでやりたいのは成功を自動判定することではなく、画像を見る優先度を付けることになる。

方法

画像を開く前に取れる軽い情報と、呼び出し側が持っている操作履歴やログを使って、視覚確認が必要そうな画像だけを選ぶことにした。

スクリーンショットをVisual Storeへ保存する
hash、寸法、直前frameとの差分を取得する
route、console error、表示テキストなどを合わせる
Jevで「画像確認が必要か」を一次判断する
不確かなものだけ画像を開く
Vision LLMまたは人が最終確認する

Jevは、前の記事「Jev MCPに評価用JSONLと運用向けの仕組みを追加した」でも使ったTypeSafe AIの判断モデルである。渡したstateに対して、Yes/Noの確率、選択肢からの選択、段階評価を構造化して返す。

ここではJevに画像を見せない。すでにあるテキストと数値だけで、視覚確認まで進めるべきかを判断させる。Jevが問題なさそうと返しても最終結果ではない。不確かなものや確認が必要とされたものだけ、画像を取り出す。

実装

features

vstore features REFを追加した。

vstore features 'visual://STORE/images/IMAGE'

返すのは、storeがすでに持っている元ファイルとdecoded pixelのhash、幅と高さ、元ファイルと現在の保存表現のサイズ、同じrun・streamの直前frameとpixelが完全に同じか、といった情報だけである。

PNGをexportしないし、pack済みのVP9 segmentを復号もしない。OCRやネットワークアクセスもしない。features自体は判断を返すものではなく、判断の材料を返す。

changed pixel ratioやperceptual hashはまだ入れていない。二枚の画像を復号するコストや、alphaをどう扱うか、アルゴリズムの版をどう管理するかを決めてから追加することにした。

judgment

format version 3では、画像ごとに複数のjudgmentを追記できる。

vstore judgment add 'visual://STORE/images/IMAGE' \
  --kind needs_visual_inspection \
  --producer jev \
  --model jev-1.13.0 \
  --value false \
  --confidence 0.96

一件のjudgmentには、kindproducer、任意のmodel名、producer側のschema version、JSONのvalue、任意のprobabilityconfidence、metadata、時刻を保存する。

valueをbooleanに固定しなかった。needs_visual_inspectionならtruefalseだが、visual_changeなら"expected""unexpected"のような文字列を入れたいかもしれない。ルールベースの結果ならJSON objectでもよい。

producerもJev専用ではない。

rule
classical
jev
vision-llm
human

のように、何が判断したかを残せる。

ここは上書きではなく追記にした。Jevが問題なさそうと返したあと、人が画像を見てエラー画面だと確認することもある。前の結果を消さずに残しておけば、あとでどこで判断が外れたかを見られる。

Jevとの境界

Visual Store本体はJevを呼ばない。APIキーを持たず、JevのSDKを依存に入れず、putの途中でネットワークにも出ない。

Jevを呼ぶのはエージェント側である。Skillには、infofeatures、過去のjudgmentを読み、route、操作内容、console error、visible textを呼び出し側で集めてからjev.batchへ渡す手順を書いた。

JevのstateにはPNG bytes、Base64、data URL、画像を取り出したローカルパスを入れない。visual://の参照、featuresの結果、すでにあるテキストと数値だけで判断する。

JevのChoiceなら、選ばれた値をvalueへ、選択肢の確率をprobabilityへ、返されたconfidenceをconfidenceへ入れられる。Scoreも同様に保存できる。NoulはYesの確率をprobabilityとして残せるが、独立したconfidenceがないのでVisual Store側で勝手に作らない。

判断結果から画像を選ぶ

judgment listjudgment searchで、同じ画像の判断履歴や、store全体の確認対象を探せる。

vstore judgment search \
  --kind needs_visual_inspection \
  --value true

vstore judgment search \
  --producer jev \
  --confidence-below 0.70

valueはcanonical JSONの完全一致で検索する。confidence-belowも、指定した値より低い記録を探すだけで、Visual Storeがconfidenceの意味を再解釈するものではない。

結果と未検証事項

今回追加したのは、画像を開く前の判断と、開いた後の結論を保存できるところまでである。実際にどのくらい画像確認を減らせるか、Jevの判断がどの程度使えるかはまだ測っていない。

confidence: 0.96だから正しい、という意味でもない。Jevの場合は回答分布が集中している度合いなので、どの値で画像確認へ進めるかは、画面や用途ごとに実際の結果を見ながら決める必要がある。

judgmentの追加・検索、typed JSONの検証、featuresが画像を取り出さないこと、v2からv3への移行と中断復旧はテストに入れている。手元ではJudgment Layerと移行に関する12件のテストを実行して成功した。

まとめ

VP9 packは、連続したスクリーンショットを小さく保存するためのものだった。今回のJudgment Layerは、保存済みの画像のうち、どれを実際に見るかを後から絞るためのものになる。

画像ファイルが小さくなった分だけ画像トークンが減るわけではないし、一度CodexやVision LLMへ表示した画像をVisual Storeが会話履歴から消せるわけでもない。

まずはUI確認で使ってみて、仮説どおり画像確認を減らせるのか、どんなkindやthresholdが役に立つのかを見ていこうと思う。