AI TOOLS

EP01. "Bedrock API 基础入门:从请求到多轮对话"

首页 › AI 工具 › Claude · Developer and Platform · Amazon Bedrock · EP01
约 29 分钟· #EP01#Claude#Amazon Bedrock
🔒 登录后可标记已读
  • 同一个 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、system prompt、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」这一层:

  1. 用户在网页界面输入文字(例如 "Define quantum computing")、点发送
  2. Server 收到这段文字
  3. Server 用 Bedrock client 向 AWS Bedrock 发请求
  4. 请求里包含 user message 和 model ID(例如 Claude Haiku 或 Claude Sonnet)
  5. 被选中的模型处理请求、生成文字
  6. AWS Bedrock 把生成的 assistant message 传回 server
  7. 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

官方文档:

  1. Claude with Amazon Bedrock(Claude Academy 课程)— https://academy.claude.com/courses/claude-with-amazon-bedrock