【Bug已解决】Getting Malformed input request when trying to use Claude Sonet Model by Bedrock 解决方案
一、现象长什么样
你在 Bedrock 上用 Claude Sonnet(原文拼写为 Sonet)模型,请求被拒:
Malformed input request(输入请求格式错误);- 或
ValidationException: Malformed input request; - 你照着 Anthropic 官方文档写的 body(带
max_tokens、messages、system),却在这报错; - 用
invoke_model直接传 JSON 时尤其容易踩; - 有时是字段名不对(Bedrock 用
maxTokens而非max_tokens),有时是缺anthropic_version; - 同样的字段在直连 Anthropic 时没事,一上 Bedrock 就 malformed。
一句话:Bedrock 对 Claude 的请求体有自己的一套字段命名/结构(驼峰式maxTokens、通过inferenceConfig传参数、需要anthropic_version),你用了 Anthropic 原生的蛇形命名(max_tokens),于是被判定为格式错误。
二、背景
Bedrock 的InvokeModel接受的是原始 JSON body,而 Claude on Bedrock 的 body 格式与 Anthropic 官方 API 不同:
| Anthropic 原生 | Bedrock (invoke_model) |
|---|---|
max_tokens | max_tokens其实也接受,但inferenceConfig.maxTokens更标准 |
system(字符串) | system是文本块数组[{"text": "..."}] |
请求头anthropic-version | body 里的"anthropic_version": "bedrock-2023-05-31" |
最常见的"malformed"来源:
- 用了
max_tokens但放在错误层级;Bedrock 推荐放在inferenceConfig: { maxTokens: 256 }; system写成字符串而非文本块数组;- 漏了
anthropic_version字段; messages[].content结构不符合 Bedrock 的 content block 规范。
而ConverseAPI 则更不同(前面 930/933 已讲),用inferenceConfig.maxTokens、system作为参数。
三、根因
根因是请求体字段不符合 Bedrock 的 Claude 格式契约:
// 错误(Anthropic 原生风格,Bedrock 不接受) { "model": "anthropic.claude-3-5-sonnet-...", "max_tokens": 256, "system": "你是助手", "messages": [{"role": "user", "content": "hi"}] } // -> Malformed input request// 正确(Bedrock Claude 格式) { "anthropic_version": "bedrock-2023-05-31", "max_tokens": 256, "system": [{"text": "你是助手"}], "messages": [{"role": "user", "content": [{"text": "hi"}]}] }注意:即便用invoke_model的body,max_tokens顶层也可接受(Claude on Bedrock 容忍),但更稳妥的是按 Bedrock 约定。关键是system必须是文本块数组,anthropic_version必填。
四、最小可运行复现
下面用 boto3 演示"错误结构 vs 正确结构":
import boto3 import json client = boto3.client("bedrock-runtime", region_name="us-west-2") MODEL = "anthropic.claude-3-5-sonnet-20241022-v2:0" def call(body: dict): try: client.invoke_model( modelId=MODEL, contentType="application/json", accept="application/json", body=json.dumps(body).encode(), ) return "ok" except client.exceptions.ValidationException as e: return f"Malformed: {e}" # 错误:system 是字符串 bad = { "anthropic_version": "bedrock-2023-05-31", "max_tokens": 256, "system": "你是助手", # 应为 [{text:...}] "messages": [{"role": "user", "content": [{"text": "hi"}]}], } print(call(bad)) # Malformed # 正确 good = { "anthropic_version": "bedrock-2023-05-31", "max_tokens": 256, "system": [{"text": "你是助手"}], "messages": [{"role": "user", "content": [{"text": "hi"}]}], } print(call(good)) # ok运行后system为字符串时报 Malformed,改为数组后通过。
五、解决方案(第一层:最小直接修复)
最小修复是按 Bedrock 的 Claude 格式构造 body:
import boto3, json client = boto3.client("bedrock-runtime", region_name="us-west-2") body = { "anthropic_version": "bedrock-2023-05-31", "max_tokens": 256, "system": [{"text": "你是乐于助人的助手"}], # 文本块数组 "messages": [ {"role": "user", "content": [{"text": "你好"}]}, ], } resp = client.invoke_model( modelId="anthropic.claude-3-5-sonnet-20241022-v2:0", contentType="application/json", accept="application/json", body=json.dumps(body).encode(), ) print(json.loads(resp["body"].read()))若改用ConverseAPI,则参数走inferenceConfig:
client.converse( modelId=MODEL, system=[{"text": "你是助手"}], # Converse 的 system 也是数组 messages=[{"role": "user", "content": [{"text": "hi"}]}], inferenceConfig={"maxTokens": 256}, # 驼峰 maxTokens )六、解决方案(第二层:结构化改进)
把"Bedrock Claude 请求体规范化"做成策略,统一system数组化、补全anthropic_version:
from dataclasses import dataclass, field from typing import Dict, List @dataclass(frozen=True) class BedrockMalformedInputPolicy: """Bedrock Claude 请求体策略:消除 Malformed input request。 规则: - anthropic_version 必填 - system 必须是文本块数组(非字符串) - messages content 必须是块数组 """ anthropic_version: str = "bedrock-2023-05-31" def normalize_system(self, system) -> List[Dict]: if isinstance(system, str): return [{"text": system}] if isinstance(system, list): return system return [{"text": str(system)}] def build_body(self, *, system, messages, max_tokens: int = 256) -> Dict: if not messages: raise ValueError("messages 不能为空") return { "anthropic_version": self.anthropic_version, "max_tokens": max_tokens, "system": self.normalize_system(system), "messages": messages, } def demo() -> None: policy = BedrockMalformedInputPolicy() body = policy.build_body( system="你是助手", messages=[{"role": "user", "content": [{"text": "hi"}]}], ) assert body["system"] == [{"text": "你是助手"}] print("body 规范化 OK") if __name__ == "__main__": demo()七、解决方案(第三层:断言 / CI 守护)
import pytest from your_module import BedrockMalformedInputPolicy def test_system_string_to_array(): policy = BedrockMalformedInputPolicy() assert policy.normalize_system("hi") == [{"text": "hi"}] def test_system_array_passthrough(): policy = BedrockMalformedInputPolicy() assert policy.normalize_system([{"text": "x"}]) == [{"text": "x"}] def test_body_has_version(): policy = BedrockMalformedInputPolicy() b = policy.build_body(system="x", messages=[{"role": "user", "content": [{"text": "y"}]}]) assert b["anthropic_version"] == "bedrock-2023-05-31" def test_body_rejects_empty_messages(): policy = BedrockMalformedInputPolicy() with pytest.raises(ValueError): policy.build_body(system="x", messages=[]) def test_messages_content_is_blocks(): policy = BedrockMalformedInputPolicy() b = policy.build_body(system="x", messages=[{"role": "user", "content": [{"text": "y"}]}]) assert isinstance(b["messages"][0]["content"], list)CI 里加一条:构造请求体后断言system是数组、anthropic_version存在,避免Malformed input request。
八、排查清单
system是否写成了字符串?Bedrock 要求是文本块数组[{text:...}]。- 是否漏了
anthropic_version: bedrock-2023-05-31? - 是否混用了 Anthropic 原生
max_tokens与 BedrockinferenceConfig.maxTokens?两者层级不同。 messages[].content是否是块数组(如[{text:...}])?- 用
Converse还是invoke_model?两者参数位置不同。 - 是否从 Anthropic 官方文档直接搬了 body?Bedrock 字段命名有差异。
九、小结
Bedrock 上用 Claude Sonnet 报Malformed input request,根因是请求体不符合 Bedrock 的 Claude 格式契约——最常见是system写成字符串而非文本块数组、漏了anthropic_version、或max_tokens层级放错。最小修复是按 Bedrock 约定构造(system数组化、补全anthropic_version、参数走inferenceConfig);结构化做法是抽成BedrockMalformedInputPolicy,统一规范化请求体;最后用 pytest 守护"system是数组、anthropic_version存在",杜绝 malformed。