Vision APIにコード画像を食わせてリファクタリング案を自動生成する
Claude Opus 4.6のVision APIでスクリーンショットや写真のコードを読み取り、リファクタリング済みコードを返すスクリプトの実装手順
Slackで「ここのコード直したいんだけど」とスクリーンショットが飛んでくることがある。テキストで貼ってくれよ、と思いつつ、相手はデザイナーだったりクライアントだったりする。コードをコピーできない場面は意外と多い。ホワイトボードに書いた擬似コード、紙に印刷されたレビュー資料、他人のブログのコード部分のキャプチャ。
これを手で書き写してからリファクタリングするのが面倒すぎて、Claude Opus 4.6のVision APIに画像を渡して「読み取ったうえで改善案を出して」と頼むスクリプトを書いた。
画像をAPIに渡す基本の形
Vision APIにコード画像を送る方法は3つある。base64埋め込み、URL指定、Files API経由のfile_id参照。スクリーンショットをローカルから送る用途ならbase64で十分。
import Anthropic from "@anthropic-ai/sdk";
import { readFileSync } from "fs";
const client = new Anthropic();
async function readCodeFromImage(imagePath: string) {
const imageData = readFileSync(imagePath).toString("base64");
const ext = imagePath.split(".").pop();
const mediaType = ext === "png" ? "image/png" : "image/jpeg";
const message = await client.messages.create({
model: "claude-opus-4-6",
max_tokens: 4096,
messages: [
{
role: "user",
content: [
{
type: "image",
source: { type: "base64", media_type: mediaType, data: imageData },
},
{
type: "text",
text: `この画像に写っているコードを正確に読み取り、以下の形式で返してください:
1. 【読み取り結果】画像のコードをそのままテキストとして出力
2. 【問題点】コードの問題点を箇条書き
3. 【リファクタリング案】改善後のコード全文`,
},
],
},
],
});
return message.content[0].type === "text" ? message.content[0].text : "";
}
公式ドキュメントにある通り、画像はテキストより前に置くほうが結果がよい。content配列の中で画像ブロックを先に、テキストブロックを後に並べるのがポイント。
画像の解像度とトークンコスト
ここが一番ハマった部分。Claudeは画像を28×28ピクセルのパッチ単位で処理する。トークン数の計算式は ⌈width / 28⌉ × ⌈height / 28⌉ で、Opus 4.6はStandard tierなので長辺1568pxを超えると自動的にリサイズされる。
コードのスクリーンショットで試した実感値:
| 画像サイズ | トークン数 | Opus 4.6での入力コスト |
|---|---|---|
| 800×600 | 約620 | 約$0.003 |
| 1920×1080 | 約1560(リサイズ後) | 約$0.008 |
| 3840×2160 | 約1560(リサイズ後) | 約$0.008 |
4Kスクリーンショットも1920×1080もリサイズ後のトークン数はほぼ同じ。つまり巨大な画像を送っても帯域を無駄に食うだけで、認識精度もコストも変わらない。コードを読ませるなら、長辺1568px以下にリサイズしてから送るのが正解。
Opus 4.6の料金は入力$5/MTok・出力$25/MTok。画像1枚あたりの入力コストは数円程度で、出力のリファクタリング案のほうがコストとしては大きい。500トークンの出力で$0.0125。画像の入力と合わせて1回あたり2〜3円くらいの感覚。
コード認識の精度はどうか
手元にあった5枚のスクリーンショットで試した感触。
VS Codeのスクリーンショット(ダークテーマ、フォントサイズ14px相当)は、変数名・インデントまでほぼ完璧に読めた。シンタックスハイライトの色分けがむしろヒントになっているのか、プレーンテキストの画像より精度が高い気がする。
ホワイトボードに手書きした擬似コードは、さすがに文字の崩れがひどい部分は誤認識した。ただ「これはforループでiを0からnまで回している」くらいの意図は正しく汲んでくれて、ちゃんとしたPythonコードに直して返してくれた。
一番厳しかったのは、解像度の低いブログ記事のスクリーンショット。フォントが小さくてJPEG圧縮のアーティファクトが乗っていると、lと1、Oと0の区別が怪しくなる。公式ドキュメントでも「小さいテキストを読ませるなら原寸で送れ」と書いてあるが、そもそも元の画像が潰れていたらどうしようもない。
リファクタリングの質を上げるプロンプト設計
単に「リファクタリングして」だけだと、Claudeは過剰に書き換えてしまう。元のコードが何をしていたか不明な状態で大改修すると、意図しない動作変更が入りやすい。
実際に使って落ち着いた形がこれ。
const refactorPrompt = `この画像のコードを読み取ってリファクタリングしてください。
制約:
- 外部APIの仕様変更や動作変更を伴う変更は避ける
- 変数名・関数名の改善、型の明示、早期リターンへの書き換えに留める
- 元コードの意図が不明な部分はコメントで「要確認」と注記する
- 言語が判別できない場合は、最も可能性の高い言語を明示する
出力形式:
\`\`\`[言語名]
// リファクタリング後のコード
\`\`\`
変更点の要約(箇条書き3行以内)`;
「動作変更を伴う変更は避ける」の一文が効く。これがないと、エラーハンドリングの追加や引数の型変更まで勝手にやってくれて、レビューの手間が増える。
複数画像を一度に送る
コードが1画面に収まらない場合、分割スクリーンショットを複数枚まとめて送れる。API上は1リクエストに最大100枚(200Kコンテキストの場合)。各画像にテキストラベルを付けると、Claudeがファイルの前後関係を理解してくれる。
const content = [
{ type: "text" as const, text: "Image 1: (上半分)" },
{
type: "image" as const,
source: { type: "base64" as const, media_type: "image/png" as const, data: topHalf },
},
{ type: "text" as const, text: "Image 2: (下半分)" },
{
type: "image" as const,
source: { type: "base64" as const, media_type: "image/png" as const, data: bottomHalf },
},
{
type: "text" as const,
text: "上記2枚は1つのファイルの連続したコードです。結合してリファクタリングしてください。",
},
];
ただ、画像が増えるとトークン消費が当然増える。2枚で3000トークン超え、5枚なら8000トークン前後。コスト面では、テキストで貼れるならそのほうが圧倒的に安い。画像を使うのは「テキストで貼れない場面」に限定すべき。
Batch APIで大量の画像を処理する
レガシーコードの棚卸しのような場面では、画像が数十枚になることもある。リアルタイムで返す必要がなければBatch APIで50%引きにできる。Opus 4.6のBatch料金は入力$2.50/MTok・出力$12.50/MTok。
import { readFileSync, writeFileSync } from "fs";
// バッチリクエストのJSONL作成
function createBatchFile(imagePaths: string[]) {
const lines = imagePaths.map((path, i) => {
const data = readFileSync(path).toString("base64");
return JSON.stringify({
custom_id: `refactor-${i}`,
params: {
model: "claude-opus-4-6",
max_tokens: 4096,
messages: [
{
role: "user",
content: [
{
type: "image",
source: { type: "base64", media_type: "image/png", data },
},
{ type: "text", text: "このコードを読み取ってリファクタリングしてください。" },
],
},
],
},
});
});
writeFileSync("batch_requests.jsonl", lines.join("\n"));
}
20枚ほどのスクリーンショットをBatchで処理したとき、全件返ってくるまで数時間かかった。急ぎでなければこれで十分。
実際のワークフローに組み込んで感じたこと
このスクリプトをSlack Botに繋いで、画像が投稿されたらリファクタリング案を返す仕組みにしてみた。便利ではある。ただ、実用上の限界もはっきり見えた。
認識精度は画像品質に完全に依存する。「この画像からコードを読み取って」と頼んでいる以上、元の画像がぼやけていたら話にならない。OCR専用サービスのように前処理でシャープネスをかけたりはしない。
もうひとつ、リファクタリング案はあくまで「画像から読み取ったコード」に対するもの。プロジェクト全体のコンテキストがないので、「この変数名はプロジェクトの命名規則に合っているか」のような判断はできない。ファイル全体のテキストを渡せるならそのほうが精度は高い。
結局、Vision APIでのコードリファクタリングが本当に活きるのは「テキストで取得できないコード」に限られる。それでも、手書き写し→手動リファクタリングの手間を考えれば、1回2〜3円で済むのは十分ありがたい。