EP01. "Bedrock API 基础入门:从请求到多轮对话"
🔒 登录后可标记已读- 同一个 Claude 模型,透过 AWS Bedrock 呼叫时用的是
boto3+converse(),跟直接用 Anthropic API 的client.messages.create()长得不一样——参数名称、回应结构都不同,混着抄代码会直接报错 - Bedrock 有个 Anthropic API 没有的坑:模型不是每个 AWS region 都有,region 选错会得到一个看不出原因的「找不到模型」错误,解法是用 inference profile ID 而不是普通 model ID
- Claude(不管走哪条 API)都不存对话记录,每次请求都是独立的,要自己在代码里维护 messages 列表、每次把完整历史一起传过去
temperature、systemprompt、streaming、prefill + stop sequence 这几个参数/技巧,决定了回答的语气、可预测性和输出格式,是接下来所有课程内容的地基- 前置知识:会写 Python,已经有 AWS 账号并且在 Bedrock 开通了 Claude 模型的访问权限
重点内容
三个模型怎么选:Opus / Sonnet / Haiku
Claude 有三个模型家族,共享文字生成、写代码、看图片这些核心能力,差别在「智能 / 速度 / 成本」这三者怎么取舍:
| 模型 | 特点 | 适合场景 |
|---|---|---|
| Opus | 智能最高,能独立处理复杂多步骤项目,少人工介入也能规划到位;支持 reasoning,简单任务快答、复杂问题会多花时间想 | 需要深度推理/规划的复杂任务 |
| Sonnet | 智能、速度、成本三者平衡的「甜蜜点」,写代码能力强、生成速度快,能在不破坏原有功能的前提下精准修改复杂代码库 | 大部分实际应用的主力模型 |
| Haiku | 最快的模型,优先考虑响应速度和成本;Haiku 4.5 开启 extended thinking(给它 token budget)也能想难题,但不会像 Opus/Sonnet 那样自己判断要不要推理 | 实时、面向用户的场景,偶尔遇到难题再开 extended thinking |
📌 实务上很多团队三个模型混着用:Haiku 处理面向用户、要求速度的互动;Sonnet 撑主业务逻辑;Opus 处理需要深度推理的复杂任务——这样每个环节都用对应最划算的模型,整体成本和体验都能兼顾。
这门课后续的代码示例统一用 Claude Haiku 4.5。
一次请求怎么流转(AWS Bedrock 版本)
跟直接用 Anthropic API 比,Bedrock 多了「自己的 server 还要再转一手给 AWS Bedrock」这一层:
- 用户在网页界面输入文字(例如 "Define quantum computing")、点发送
- Server 收到这段文字
- Server 用 Bedrock client 向 AWS Bedrock 发请求
- 请求里包含 user message 和 model ID(例如 Claude Haiku 或 Claude Sonnet)
- 被选中的模型处理请求、生成文字
- AWS Bedrock 把生成的 assistant message 传回 server
- Server 再把结果传回用户的浏览器
用 boto3 连接 Bedrock Runtime
Bedrock 没有专门的 Anthropic SDK,走的是 AWS 官方的 boto3:
import boto3
client = boto3.client("bedrock-runtime", region_name="us-west-2")
Region 与 Inference Profile:为什么会「找不到模型」
📌 不是每个模型在每个 AWS region 都能用。 如果 Claude Sonnet 只在 us-west-2 有,你却在 us-east-1 发请求,请求会失败,而且错误信息看不出「是 region 不对」这个真正原因。
解法:用 inference profile,不要直接用 model ID。 Inference profile 会自动把请求路由到这个模型实际存在的 region,就算你是从别的 region 连过去的也一样,不用自己手动去记哪个模型在哪个 region。
- 去 AWS Bedrock 控制台找 inference profile ID:Cross-region inference(不是主模型目录页那个 model ID)
- 示例:Claude Haiku 4.5 的 inference profile ID 是
us.anthropic.claude-haiku-4-5-20251001-v1:0
发送第一个请求
User message 的结构:
user_message = {
"role": "user",
"content": [
{"text": "What's 1+1?"}
]
}
💡 content 是一个 list,因为一条消息可以塞不同类型的内容(文字、图片等),这个结构天生就是为多模态请求设计的。
发请求,用的是 converse(),不是 messages.create():
response = client.converse(
modelId=model_id,
messages=[user_message]
)
拿生成的文字:
response["output"]["message"]["content"][0]["text"]
Assistant message(模型生成的内容)跟 user message 用的是同一套结构(role + content list),这样交替拼起来就能组成一段对话。
多轮对话:自己维护 messages 历史
📌 Bedrock 跟 Claude 都不存任何消息——发出去的、收回来的,都不会被保留,每次 API 调用完全独立。要让 Claude「记得」上一句,得自己在代码里维护完整的 messages 列表,每次请求整包带上。
def add_user_message(messages, text):
user_message = {
"role": "user",
"content": [
{"text": text}
]
}
messages.append(user_message)
def add_assistant_message(messages, text):
assistant_message = {
"role": "assistant",
"content": [
{"text": text}
]
}
messages.append(assistant_message)
def chat(messages):
response = client.converse(
modelId=model_id,
messages=messages
)
return response["output"]["message"]["content"][0]["text"]
完整流程示范:
messages = []
add_user_message(messages, "What's 1+1?")
answer = chat(messages)
add_assistant_message(messages, answer)
add_user_message(messages, "And 3 more added to that?")
answer = chat(messages)
print(answer)
⚠️ 消息顺序必须是 user → assistant → user → assistant 交替,不能连续出现两条 user 或两条 assistant,这是 API 的硬性要求。
System Prompt:给 Claude 定角色
在每条 user message 里塞规则的做法有明显问题:要预判所有可能的问题和边界情况、规则列表会越写越乱、用户还会看到这些内部指令。
更好的做法是用 system prompt 给 Claude 指定一个专业角色,好处:
- Claude 会照着这个角色一致地回答,不用逐条列规则
- 回答自动保持在角色范围内、不跑题
system_prompt = """
You are an AWS cloud support specialist. Your job is to answer user queries related
to cloud hosting services on AWS.
"""
response = client.converse(
modelId=model_id,
messages=messages,
system=[{"text": system_prompt}]
)
📌 效果对比:没给 system prompt 时,Claude 会连别的云平台一起讲;配上「AWS 云支持专员」的 system prompt 后,回答只聚焦 RDS、Aurora、EC2 这类 AWS 方案,问到题外话也会礼貌地拒答、守住角色设定。
写成可复用的 chat() 函数,system 可选:
def chat(messages, system=None):
params = {"modelId": model_id, "messages": messages}
if system:
params["system"] = [{"text": system}]
response = client.converse(**params)
return response["output"]["message"]["content"][0]["text"]
⚠️ 几个技术细节:
system不能传空字符串system参数要传成[{"text": ...}]这样的 list,不是直接传字符串- system prompt 会先于 user messages 被处理
- Amazon Bedrock 不支持「对话中途插入 system message」——只能在每次请求的顶层设一个
system,可以每次请求换内容,但不能塞进 messages 列表中间
Temperature:可预测 vs 有创意
Claude 生成文字分三步:Tokenization(把输入切成小块)→ Prediction(算出下一个 token 的各种概率,例如 "about" 30%、"would" 20%)→ Sampling(照概率选一个 token 出来),重复直到生成完成。
temperature 是 0 到 1 之间的小数,直接影响这个「选 token」的概率分布,可以理解成一个「创意旋钮」:
- 接近 0:最高概率的 token 会被大幅放大选中机会,趋近于「每次都选最保险的那个」
- 接近 1:概率被拉得更平均,低概率 token 也有机会被选中
💡 默认 temperature 是 1.0,也就是默认就是「最大创意」——想要稳定输出,反而要自己调低。
| 区间 | 效果 | 适合用在 |
|---|---|---|
| 0.0 – 0.3 | 结果稳定、可预测 | 事实性回答、写代码、数据抽取、内容审核 |
| 0.4 – 0.7 | 平衡 | 摘要、教育类内容、解题、有限制的创意写作 |
| 0.8 – 1.0 | 输出更多变、更有创意 | 头脑风暴、创意写作、营销文案、讲笑话 |
Bedrock 的 converse() 里,temperature 要塞进 inferenceConfig,不是顶层参数:
def chat(messages, system=None, temperature=1.0):
params = {
"modelId": model_id,
"messages": messages,
"inferenceConfig": {"temperature": temperature}
}
if system:
params["system"] = [{"text": system}]
response = client.converse(**params)
return response["output"]["message"]["content"][0]["text"]
课程用「生成一句电影点子」示范差异:temperature 1.0 每次都给出天马行空、互不相同的点子(比如「隐居的折纸大师发现自己的作品会在夜里活过来」);temperature 0.0 反复跑同样的 prompt,主题会一直重复类似的套路(比如老是出现「穿越时空的考古学家」)。
Streaming:converse_stream() 边生成边显示
一般回答要等 10-30 秒才能生成完,用户只能盯着 loading。Bedrock 对应的做法是用 converse_stream() 而不是 converse():
messages = []
add_user_message(messages, "Write a 1 sentence description of a fake database")
response = client.converse_stream(messages=messages, modelId=model_id)
for event in response["stream"]:
print(event)
调用后立刻拿到一个含 stream 的 response,stream 是个 generator,会陆续吐出事件。
📌 事件顺序固定:messageStart → 多个 contentBlockDelta(真正要显示的文字片段就在这里)→ contentBlockStop → messageStop → metadata。只要显示文字,只需要处理 contentBlockDelta。
text = ""
for event in response["stream"]:
if "contentBlockDelta" in event:
chunk = event["contentBlockDelta"]["delta"]["text"]
print(chunk, end="")
text += chunk
print("\n\nTotal Message:\n" + text)
end="" 是为了去掉 print 默认加的换行。
真实项目里,通常会:把每个文字片段透过 WebSocket / Server-Sent Events 传给前端、即时更新 UI、streaming 结束后把完整消息存进数据库、处理 streaming 过程中可能出现的错误。
精准控制输出:Prefill + Stop Sequence
除了改善 prompt 本身,还有两个手法能更精准地控制 Claude 的输出。
Prefilled assistant message(预填开头):不让 Claude 自己决定怎么开头,直接给它一段「已经写了一半」的 assistant message,Claude 会从这段文字后面接着写,不会重复你写的部分。
messages = []
add_user_message(messages, "Is coffee or tea better for breakfast?")
add_assistant_message(messages, "Coffee is better because")
chat(messages)
回应大概是 "it has more caffeine.",跟 prefill 拼起来就是完整句子。换成 "Tea is better because" 会导向挺茶的回答,换成 "They are the same because" 会导向中立回答。
Stop sequence(停止序列):给一个字符串列表,Claude 一旦生成到列表里任何一个字符串就立刻停止,这个字符串本身不会出现在返回结果里。
def chat(messages, system=None, temperature=1.0, stop_sequences=[]):
params = {
"modelId": model_id,
"messages": messages,
"inferenceConfig": {
"temperature": temperature,
"stopSequences": stop_sequences
},
}
messages = []
add_user_message(messages, "Count from 1 to 10")
chat(messages, stop_sequences=["5"])
结果是 "1, 2, 3, 4,",在 "5" 出现前就被截断。可以传多个 stop sequence,Claude 碰到哪个先出现就在哪里停。
用途:控制回答长度、在结构化输出的自然断点处停下、避免生成跑过某个分隔符。
Structured Data:只要 JSON,不要废话
Claude 默认回答结构化数据时会带标题、解释文字、markdown 格式,例如:
# EventBridge Rule
{ "source": ["aws.ec2"], "detail-type": ["EC2 Instance State-change Notification"], "detail": {"state": ["running"]} }
This rule captures EC2 instance state changes when instances start running or stop.
写文档时这样很好,但程序要直接拿 JSON 用时反而碍事。
解法:prefill + stop sequence 组合拳——预填markdown代码块的开头符号,再用对应的收尾符号当 stop sequence:
messages = []
add_user_message(messages, "Generate a very short event bridge rule as json")
add_assistant_message(messages, "```json")
text = chat(messages, stop_sequences=["```"])
原理:Claude 收到这条预填了 `json 的 assistant message 后,会认为自己已经开了代码块,不会再另外加标题和开头符号,直接继续写 JSON 内容;等它想收尾写 ` 时,stop sequence 立刻截断生成——拿到的就是干净的 JSON,不带任何解释文字。
拿到文字后记得处理换行/空白:
import json
parsed_data = json.loads(text.strip())
💡 这招不只能用在 JSON——Python 代码、bullet list、CSV,任何「只要内容、不要解释」的格式都适用,把 prefill 换成对应的开头符号就行。
常见错误
- ❌ 直接拿模型目录页的 model ID 当 model ID 用——region 不对会报一个看不出原因的错,应该去 Cross-region inference 页面找 inference profile ID
- ❌ 以为 Bedrock 会帮忙存对话记录——它跟 Anthropic API 一样完全无状态,不自己维护并传完整的
messages列表,后续问题 Claude 根本接不上 - ❌ 把
temperature/stopSequences当成顶层参数直接塞给converse()——Bedrock 这两个参数要包进inferenceConfig,跟直接用 Anthropic API 的写法不一样 - ❌ 想在对话中途插入一条新的 system message——Amazon Bedrock 不支持,只能在每次请求的顶层重设
system - ❌
system传空字符串或直接传裸字符串——要用if system:判断要不要传,传的时候要包成[{"text": ...}]
Sources
官方文档:
- Claude with Amazon Bedrock(Claude Academy 课程)— https://academy.claude.com/courses/claude-with-amazon-bedrock