LLMに画像を読ませる:Vision APIの使い方(無料モデルで試せます)
LLMに画像を読ませるには、messages の content を文字列ではなく配列にして、image_url を追加します。 それだけです。エンドポイントも認証も、テキストだけのリクエストと同じです。
この記事では実際に動くコードと、あまり書かれていない画像のトークンコストを実測値で示します。FastMetalには無料の対応モデルがあるので、残高ゼロでも試せます。
contentを配列にする
テキストだけのリクエストでは content は文字列です。
{"role": "user", "content": "この画像を説明して"}
画像を渡すときは、これをパーツの配列に変えます。
{
"role": "user",
"content": [
{"type": "text", "text": "この画像を説明して"},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,iVBORw0KG..."}}
]
}
type は text か image_url のどちらかです。順序は自由で、画像を先に置いても構いません。テキストパーツを省略して画像だけ送ることもできますが、何をしてほしいかは書いたほうが結果が安定します。
画像の渡し方は2通り
image_url.url には2つの形式が使えます。
| 形式 | 書き方 | 向いている場面 |
|---|---|---|
| data URI | data:image/png;base64,<base64文字列> | ローカルのファイル、非公開の画像 |
| 公開URL | https://example.com/photo.jpg | すでにWeb上にある画像 |
data URIのほうが確実です。 公開URLはモデル提供元のサーバーがその画像を取得できる必要があり、認証付きURLや社内ネットワークの画像は当然読めません。ローカルのファイルを扱うなら data URI 一択です。
data URIの image/png の部分は実際の形式に合わせてください(image/jpeg、image/webp など)。ここが実体とずれると弾かれることがあります。
動くコード
Python
import base64
from openai import OpenAI
client = OpenAI(
api_key="<FASTMETAL_API_KEY>",
base_url="https://api.fastmetal.ai/v1",
)
with open("photo.png", "rb") as f:
b64 = base64.b64encode(f.read()).decode()
resp = client.chat.completions.create(
model="gemini-flash-lite-free",
max_tokens=300,
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "この画像に写っているものを日本語で説明して"},
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}},
],
}
],
)
print(resp.choices[0].message.content)
print(resp.usage)
curl
B64=$(base64 -i photo.png | tr -d '\n')
curl https://api.fastmetal.ai/v1/chat/completions \
-H "Authorization: Bearer $FASTMETAL_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"model\": \"gemini-flash-lite-free\",
\"max_tokens\": 300,
\"messages\": [{
\"role\": \"user\",
\"content\": [
{\"type\": \"text\", \"text\": \"この画像を説明して\"},
{\"type\": \"image_url\", \"image_url\": {\"url\": \"data:image/png;base64,$B64\"}}
]
}]
}"
macOSの base64 は -i でファイルを指定します。Linuxでは base64 -w0 photo.png としてください。
画像はトークンをかなり消費します
ここが実務でいちばん効いてくる部分です。画像はテキストに変換されてモデルに渡るため、画像トークンとして課金されます。
実際に測ってみます。8×8ピクセルという極端に小さいPNGを1枚送ったときの usage がこれです(2026年8月29日、gemini-flash-lite-free で実測)。
{
"prompt_tokens": 1098,
"completion_tokens": 1,
"prompt_tokens_details": {
"text_tokens": 9,
"image_tokens": 1089
}
}
テキストは9トークン、画像は1,089トークンです。8×8ピクセルの画像でこれだけかかります。画像はピクセル数に比例して素直に増えるわけではなく、1枚あたりの下限がかなり大きいと考えたほうが実態に合います。
つまり、
- 画像1枚は、日本語の文章にすると数百字ぶんのコストに相当します
- 会話履歴に画像を残したまま何ターンも続けると、毎回その画像トークンを再送することになります(Chat Completions APIはステートレスなので)
- 画像を大量に扱うなら、必要なターンだけ画像を含め、以降は最初の応答テキストを履歴に残す設計が効きます
prompt_tokens_details.image_tokens はレスポンスに実際に入っている値なので、自分のユースケースで一度測ってください。トークンとコストの関係はトークンとはにまとめています。
複数の画像を渡す
content 配列に image_url を複数入れるだけです。「2枚を比較して」といった指示に使えます。
content = [{"type": "text", "text": "2枚の違いを挙げて"}]
for path in ["before.png", "after.png"]:
with open(path, "rb") as f:
b64 = base64.b64encode(f.read()).decode()
content.append({"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}})
当然ながらトークンも枚数ぶん増えます。
対応しているモデルの見分け方
すべてのモデルが画像を読めるわけではありません。 非対応のモデルに画像を送ると、エラーになるか、画像が無視されてテキストだけで答えられます。後者のほうが厄介です。
FastMetalではモデルカタログで画像入力への対応を確認できます。2026年8月時点で、Claude系(Opus 5 / Sonnet 5 / Haiku 4.5 など)、Gemini系、GPT-5.6系、Grok、Qwen系など複数のモデルが対応しています。無料の gemini-flash-lite-free も画像入力に対応しているので、残高ゼロのまま挙動を確かめられます。
なお、モデル本体が画像対応をうたっていても、経由する提供元のエンドポイントが画像入力を提供していない場合があります。この構造はProvider Routingの記事で解説しました。カタログの表示を信じる前に、実際に1枚投げて確かめるのがいちばん確実です。
つまずきやすい点
content を文字列のままにしている
image_url を足したいだけでも、content は配列に変える必要があります。文字列のままでは画像を入れる場所がありません。
data URIのプレフィックスを忘れる
data:image/png;base64, を付けずにbase64文字列だけを渡すと弾かれます。カンマの後にスペースを入れないよう注意してください。
改行が混入している
base64 コマンドの出力は折り返されることがあります。tr -d '\n'(macOS)や -w0(Linux)で1行にしてください。
画像が大きすぎる 高解像度の写真をそのまま送ると、トークンも転送量も増えます。読み取りたい情報が残る範囲で縮小してから送るのが基本です。
PDFは画像とは別扱い チャットUIではPDFの添付にも対応していますが(お知らせ)、APIから扱う場合の形式はモデルによって異なります。
次のステップ
画像を読ませた結果をそのままプログラムで使いたい場合は、出力をJSONで受け取る構造化出力と組み合わせると扱いやすくなります。逆に画像を生成したい場合は画像生成APIの使い方を参照してください。
エージェントから画像を扱いたい場合は、MCPサーバーの analyze_image ツールが同じことをしてくれます。
よくある質問
Q. 画像入力は無料で試せますか?
はい。gemini-flash-lite-free は画像入力に対応した無料モデルなので、残高ゼロのアカウントでも試せます。ただし無料モデルにはレート制限があります。
Q. 画像1枚でどれくらいのトークンを消費しますか?
モデルによりますが、小さい画像でも1,000トークン前後かかることがあります(8×8のPNGで1,089トークンを実測)。レスポンスの prompt_tokens_details.image_tokens で実際の値を確認してください。
Q. 画像のURLを渡す方法とbase64はどちらがいいですか? 非公開の画像やローカルファイルはbase64(data URI)が確実です。公開URLはモデル提供元のサーバーからアクセスできる必要があります。
Q. 画像を送ったのにテキストしか見ていないような応答が返ります。 そのモデルが画像入力に対応していない可能性があります。モデルカタログで対応状況を確認してください。
まとめ
画像入力は content を配列にして image_url を足すだけで、エンドポイントもSDKもテキストと同じです。押さえるべきはトークンコストで、小さな画像でも1,000トークン規模を消費し、会話履歴に残せば毎ターン再送されます。
無料モデルで対応しているので、まずAPIキーを取得して1枚投げてみてください。対応モデルの一覧はモデルカタログにあります。