【Bug已解决】How to send a base64 image to Bedrock via ConverseCommand as bytes 解决方案
一、现象长什么样
你在 AWS Bedrock 用Converse/ConverseCommand(SDK v3)给 Claude 传一张图,想把本地图片以 base64 形式发过去,却遇到:
- 报错说
image块的source格式不对; - 或你传了 base64 字符串,但 Bedrock 期望的是
bytes(二进制),两者混用报错; - 文档里写
source: { bytes: Uint8Array },但你手里是 base64 文本,不知道怎么转; - 你试过直接把 base64 字符串塞进
bytes字段,报ValidationException; - 图片相关的
format(png/jpeg)也填错,导致模型收不到图。
一句话:BedrockConverse的图片块要求source.bytes是一个二进制字节流(如Uint8Array/bytes),而不是 base64 字符串——你需要先把 base64 解码成字节,再作为bytes传入,并正确声明format。
二、背景
Bedrock 的ConverseAPI(以及 boto3 的converse、JS SDK 的ConverseCommand)对多模态图片有统一结构:
{ "image": { "format": "png", "source": { "bytes": <二进制字节> } } }source有两种:bytes(直接二进制)或s3Location(存在 S3 的图片)。没有base64这个字段——Bedrock 不接收 base64 字符串,它要的是解码后的字节。
所以"我有一张 base64 图"的处理链路是:base64 文本 →base64.b64decode/Buffer.from(b64, 'base64')→ 字节 → 放进source.bytes。很多人卡在"直接把 base64 字符串当 bytes 传",于是校验失败。
三、根因
根因是把 base64 字符串直接当bytes传,而不是先解码成二进制:
# 错误:base64 字符串不是 bytes block = {"image": {"format": "png", "source": {"bytes": "iVBORw0KGgo..."}}} # 字符串! -> 报错 # 正确:先解码 import base64 raw = base64.b64decode("iVBORw0KGgo...") block = {"image": {"format": "png", "source": {"bytes": raw}}} # bytes!修复方向:在发送前把 base64 解码为字节对象(bytes/Uint8Array),并声明正确的format。
四、最小可运行复现
下面用 Python(boto3)演示完整链路:
import base64 import boto3 # 假设你有一张 base64 编码的 PNG b64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+M8AAAMBAQDJ/pLvAAAAAElFTkSuQmCC" # 1) 解码成 bytes(关键) image_bytes = base64.b64decode(b64) # 2) 构造 image 块 image_block = { "image": { "format": "png", "source": {"bytes": image_bytes}, # 必须是 bytes,不是字符串 } } # 3) 发送 client = boto3.client("bedrock-runtime", region_name="us-west-2") resp = client.converse( modelId="anthropic.claude-3-5-sonnet-20241022-v2:0", messages=[{ "role": "user", "content": [ image_block, {"text": "这张图里有什么?"}, ], }], inferenceConfig={"maxTokens": 256}, ) print(resp["output"]["message"]["content"])JS SDK(ConverseCommand)等价写法:
import { BedrockRuntimeClient, ConverseCommand } from "@aws-sdk/client-bedrock-runtime"; const b64 = "iVBORw0KGgo..."; const bytes = Uint8Array.from(Buffer.from(b64, "base64")); // base64 -> bytes const cmd = new ConverseCommand({ modelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", messages: [{ role: "user", content: [ { image: { format: "png", source: { bytes } } }, { text: "这张图里有什么?" }, ], }], }); const res = await new BedrockRuntimeClient({ region: "us-west-2" }).send(cmd);五、解决方案(第一层:最小直接修复)
最小修复是发送前把 base64 解码为字节,并声明format:
import base64 def b64_to_image_block(b64: str, fmt: str = "png") -> dict: return { "image": { "format": fmt, "source": {"bytes": base64.b64decode(b64)}, } } # 用法 block = b64_to_image_block(my_b64, fmt="jpeg")要点:
source.bytes必须是解码后的二进制,不是 base64 字符串;format必须与实际图片格式一致(png/jpeg/gif/webp等);- 若图片已在 S3,直接用
source: {"s3Location": {"uri": "s3://..."}},连解码都省了。
六、解决方案(第二层:结构化改进)
把"图片源构造"做成策略,统一处理 base64 / 文件路径 / S3 三种来源:
from dataclasses import dataclass import base64 from pathlib import Path from typing import Union @dataclass(frozen=True) class BedrockImageBytesPolicy: """Bedrock 图片块构造策略:base64/文件/S3 -> 合规 image 块。 规则: - base64: 解码成 bytes 再放 source.bytes - 文件: 读二进制 - s3: 用 s3Location - format 必须显式声明 """ def from_base64(self, b64: str, fmt: str) -> dict: return {"image": {"format": fmt, "source": {"bytes": base64.b64decode(b64)}}} def from_file(self, path: Union[str, Path], fmt: str) -> dict: data = Path(path).read_bytes() return {"image": {"format": fmt, "source": {"bytes": data}}} def from_s3(self, uri: str) -> dict: return {"image": {"source": {"s3Location": {"uri": uri}}}} def demo() -> None: policy = BedrockImageBytesPolicy() blk = policy.from_base64("iVBOR", "png") assert blk["image"]["source"]["bytes"] == base64.b64decode("iVBOR") print("base64 -> bytes 块 OK") if __name__ == "__main__": demo()七、解决方案(第三层:断言 / CI 守护)
import base64 import pytest from your_module import BedrockImageBytesPolicy def test_from_base64_decodes(): policy = BedrockImageBytesPolicy() blk = policy.from_base64("aGVsbG8=", "png") # "hello" base64 assert blk["image"]["source"]["bytes"] == b"hello" assert blk["image"]["format"] == "png" def test_from_base64_not_string(): policy = BedrockImageBytesPolicy() blk = policy.from_base64("aGVsbG8=", "png") # bytes 字段必须是 bytes 而非 str assert isinstance(blk["image"]["source"]["bytes"], bytes) assert not isinstance(blk["image"]["source"]["bytes"], str) def test_from_s3_shape(): policy = BedrockImageBytesPolicy() blk = policy.from_s3("s3://bucket/k.png") assert blk["image"]["source"]["s3Location"]["uri"].startswith("s3://") def test_from_file(tmp_path): policy = BedrockImageBytesPolicy() p = tmp_path / "x.png" p.write_bytes(b"\x89PNG") blk = policy.from_file(p, "png") assert blk["image"]["source"]["bytes"] == b"\x89PNG" def test_format_declared(): policy = BedrockImageBytesPolicy() blk = policy.from_base64("aGk=", "jpeg") assert blk["image"]["format"] == "jpeg"CI 里加一条:对任何图片输入,断言source.bytes是字节(非字符串),避免把 base64 当 bytes 传导致ValidationException。
八、排查清单
- 是否把 base64 字符串直接塞进了
source.bytes?必须先b64decode成字节。 format是否与实际图片格式一致(png/jpeg/...)?- 图片在 S3 吗?可直接用
s3Location,免解码。 - JS 侧是否用
Uint8Array.from(Buffer.from(b64,'base64'))? source字段名是否拼对?Bedrock 用bytes/s3Location,没有base64字段。- 多图是否都在同一条
user消息的content里?
九、小结
在 Bedrock 用Converse传 base64 图片报格式错误,根因是 Bedrock 的图片块要求source.bytes是解码后的二进制字节,而你直接把 base64 字符串当bytes传了——Bedrock 没有base64这个 source 字段。最小修复是发送前base64.b64decode成字节并声明format;结构化做法是抽成BedrockImageBytesPolicy,统一处理 base64/文件/S3 三种来源;最后用 pytest 守护"source.bytes必为字节(非字符串)",避免校验失败。