人工智能

AI 智能体的《土拨鼠之日》:在 Amazon Bedrock 上使用 OpenAI 模型的提示词缓存模式

每次你启动智能体时,它都必须重新阅读自己的完整经历,然后才能执行操作。下面介绍如何让它记住这些内容,而无需再次向它提供相同的上下文。

在电影 《土拨鼠之日》中,比尔·默里饰演的角色每天都会重新经历同一个2月2日。他在同一家旅馆、同一个小镇醒来,收音机里播放着同一首歌,日复一日,一些影迷估计这样的循环持续了34年。

他前一天取得的任何成果都不会延续下来,除非他从头重新推导一遍。AI 智能体生活在一个《土拨鼠之日》式的版本里。每当智能体采取一步行动(调用工具、读取结果、决定下一步做什么),它都必须把到目前为止的完整经历发送给模型,以保持连续性:工具定义、指令、此前每一次工具调用以及每一个结果。 模型只会根据你提供的输入预测下一个 token。它在请求之间没有内部记忆,因此必须重新读取全部内容。到编码会话的第 20 轮时,模型会重新读取第 1–19 轮,而你要为其中每一个 token 反复付费。

提示词缓存打破了这一循环。该服务会存储你的输入提示词(指令、工具和内容),并在下一次请求时以输入价格的一小部分将其复用。

在这篇文章中,我将带你了解一个智能体的一生,从它的第一次交互开始,一直到它获得新工具并生成辅助智能体。在此过程中,我会向你展示提示词缓存如何降低成本,并让智能体保持响应迅速和一致。

智能体向其 LLM 发送的内容

在讨论缓存之前,先看看一个典型智能体首次调用大语言模型(LLM)时都包含哪些内容会很有帮助:

  • 工具列表 ——智能体可调用的每个函数的定义。每次发布新版本时都会变化。
  • 指令 —— 开发者提示词:规则、人设、策略、示例。也会随版本发布而变化。
  • 历史记录 ——本次会话到目前为止发生的所有内容:任务、模型的工具调用以及工具结果(每轮都会增长)。
  • 新一轮 ——最新的工具结果或用户消息(每次可能不同)。

如果不设置缓存,智能体每一轮都会向模型发送的内容:

Five growing agent requests, with tools, instructions, and earlier history marked in red and each new turn in yellow.

图 1:智能体每一轮向模型发送的内容

提示词缓存在底层是如何工作的

当模型读取提示词时,它首先执行一次称为 预填充(prefill)的过程。在预填充期间,它会处理每一个输入 token,并构建其键值向量(也称为 key-value,或 KV),这些向量构成模型的工作状态。这发生在模型写出第一个 token 之前。提示词缓存会保存该状态,其中包含系统提示词、工具、内容、历史记录等。当后续请求以相同的输入 token(即相同的文本)开头时,提示词缓存服务会加载已保存的状态,而不是重新计算。随后它只处理在预填充期间,它会处理每一个输入 token,并构建其键值向量(也称为 key-value,或 KV),这些向量构成模型的工作状态。这发生在模型写出第一个 token 之前。

提示词缓存会保存该状态,其中包含系统提示词、工具、内容、历史记录等。当后续请求以相同的输入 token(即相同的文本)开头时,提示词缓存服务会加载已保存的状态,而不是重新计算。随后它只处理下一个发送给模型的载荷中新增的内容。 提示词缓存可以是隐式的,即由 Amazon Bedrock 选择可复用前缀的结束位置;也可以是显式的,即由你的代码使用 prompt_cache_breakpoint你可以将这两种方法结合使用,我们也建议这样做。

第 1 轮与第 2 轮的对比

Turn 1 writes the tools and instructions through a breakpoint. Turn 2 reads that same prefix from cache and processes its new question.

图 2:第 1 轮保存断点之前的提示词前缀,第 2 轮复用它,并且只为新问题支付全价

隐式还是显式,为什么两者都需要?

对于智能体工作负载,隐式缓存让 Bedrock 随着对话增长自行选择要缓存的内容。使用显式缓存,你可以标记共享内容(例如工具和指令),以便在跨会话以及启动辅助智能体时复用。你并不总是需要同时使用这两种缓存形式,但将二者结合可以为你提供一个可复用的起点,同时 Bedrock 会自动管理不断增长的对话。

新会话也可以复用这个共享起点,同时每个对话都基于各自缓存的历史继续构建。在本文中,我们先从第一轮开始,然后展示如何在后续轮次、新增工具以及跨子智能体之间传递缓存内容。

第一个收益点是缓存提示词中的静态部分:即在各请求之间保持不变的各类工具和指令。这些示例使用你现有的 Amazon Bedrock 客户端、 和 。请将缩短的 字符串替换为你真实的策略和示例。可复用的前缀必须达到 1,024 个 token 才符合条件。缓存静态内容时,请使用显式模式:选择已保存前缀的结束位置。 会告知服务哪些请求应共享同一缓存。我们会在讲到 时再回来讨论如何使用它。

第一个收益点是缓存提示词中的静态部分:即在各请求之间保持不变的各类工具和指令。这些示例使用你现有的 Amazon Bedrock 客户端、 TOOLS,以及 task请将缩短的 INSTRUCTIONS 字符串替换为你真实的策略和示例。可复用的前缀必须达到 1,024 个 token 才符合条件。

缓存静态内容时,请使用显式模式:选择已保存前缀的结束位置。

KEY = "support-agent:v7:gpt-6-astra"
INSTRUCTIONS = "Your full support policies and examples..."

# Rendered context: [provider context][TOOLS][INSTRUCTIONS] | [task]
# Cache starts at the beginning; the marker below chooses its end.
resp = client.responses.create(
    model="us.openai.gpt-6-astra",
    store=False,  # Response storage; prompt caching still works.
    include=["reasoning.encrypted_content"],
    prompt_cache_key=KEY,
    prompt_cache_options={"mode": "explicit", "ttl": "30m"},
    tools=TOOLS,  # Rendered before instructions; keep order stable.
    input=[
        {
            "role": "developer",
            "content": [{
                "type": "input_text",
                "text": INSTRUCTIONS,
                # Save through this whole block, including TOOLS.
                "prompt_cache_breakpoint": {"mode": "explicit"},
            }],
        },
        # User message is processed but not cached.
        {"role": "user", "content": task},
    ],
)

这个 prompt_cache_key 会告知服务哪些请求应共享同一缓存。我们会在讲到在新会话中读取缓存时再回来讨论如何使用它。在我们使用显式缓存的一项测试中,约 4,000 token 的工具与指令前缀在首次请求时写入了 4,120 个 token,并在后续请求中读取了这 4,120 个 token。这个 task (或用户请求)出现在缓存边界之后,因此这不会触发对缓存的写入。

请记住,提示词缓存仅在传入完全相同的文本时才有效。请确保智能体的指令和工具列表每次都以相同方式序列化。模型上下文协议(MCP)服务器及其他形式的工具交付方式需要建立索引,以确保它们保持一致。请将任何会变化的内容(例如日期、用户名或请求 ID)放在已缓存的指令之后。

会在你的应用程序中保留对话历史。这不会禁用缓存。当下一轮继续同一任务时,这很有用。不同的任务无法通过旧任务匹配前缀,而隐式写入也不一定会仅为工具和指令单独创建一个条目。当你需要让这个较短的共享前缀可跨任务复用时,请在那里使用显式标记。当智能体既需要共享起点又需要不断增长的历史记录时,请同时使用两者。从标记的 store=False 会在你的应用程序中保留对话历史。这不会禁用缓存。

def ask_with_implicit_cache(history):
    return client.responses.create(
        model="us.openai.gpt-6-astra",
        store=False,
        include=["reasoning.encrypted_content"],
        prompt_cache_key=KEY,
        prompt_cache_options={"mode": "implicit", "ttl": "30m"},
        tools=TOOLS,
        input=history,  # Everything gets cached
    )

history = [
    {"role": "developer", "content": INSTRUCTIONS},
    {"role": "user", "content": task},
]
# Automatic endpoint: the end of the user message.
resp = ask_with_implicit_cache(history)

当下一轮继续同一任务时,这很有用。不同的任务无法通过旧任务匹配前缀,而隐式写入也不一定会仅为工具和指令单独创建一个条目。当你需要让这个较短的共享前缀可跨任务复用时,请在那里使用显式标记。

当智能体既需要共享起点又需要不断增长的历史记录时,请同时使用两者。从以下代码示例中标记的开发者代码块开始,并调用该函数 ask_with_implicit_cache请求级别的模式保持隐式。块级标记会在指令之后添加一个显式保存点。服务还可以通过最新的符合条件的消息进行保存。这样,在对话不断增长的同时,共享前缀仍然可用。

history = [
    {
        "role": "developer",
        "content": [{
            "type": "input_text",
            "text": INSTRUCTIONS,
            "prompt_cache_breakpoint": {"mode": "explicit"}, 
        }],
    },
    {"role": "user", "content": task},
]
# Still implicit mode, now with an explicit startup save point in the history.
resp = ask_with_implicit_cache(history)

缓存对话历史和工具结果

模型按名称请求工具并提供其参数。你的应用程序必须执行这些工具。 execute_function 是你构建的一个辅助函数,用于将这些请求路由到匹配的 Python 函数并返回其结果。下面的循环使用这个辅助函数来运行每个请求的工具。它将模型的输出和所有工具结果追加到 history 列表中,然后再次发送该列表,直到模型返回最终答案。在隐式模式下,Amazon Bedrock 可以复用未更改的前缀,并缓存从开头到最后一条工具结果之间新增的内容。保留较早的消息和初始 TOOLS 保持不变。

import json

# Map tool names to their Python implementations.
TOOL_HANDLERS = {
    "get_owner": get_owner,
    # Register your other initial tools here.
}

def execute_function(name, arguments):
    """Run the requested tool and return its result."""
    handler = TOOL_HANDLERS.get(name)
    if handler is None:
        return {"error": f"Unknown tool: {name}"}
    return handler(**arguments)

while True:
    # Preserve all output items, including encrypted reasoning.
    history.extend(resp.output)
    calls = [x for x in resp.output if x.type == "function_call"]
    if not calls:
        break
    for call in calls:
        result = execute_function(call.name, json.loads(call.arguments))
        history.append({
            "type": "function_call_output",
            "call_id": call.call_id,
            "output": json.dumps(result),
        })
    # The last result in this group supplies the automatic endpoint.
    resp = ask_with_implicit_cache(history)

对于后续请求,追加一条新的用户消息并再次调用该辅助函数。保持较早的消息不变,以便其缓存的端点仍然可用。

滚动断点:每一轮都会读取此前所有内容

Rolling breakpoints, where each turn reads all earlier turns and writes only its own tool result.

图 3:滚动断点,其中每一轮都会读取此前所有内容,并且只写入自己的工具结果

对于单独的仅显式运行,请从组合的 Hello 缓存世界示例中重建这两条消息的历史记录。此循环将工具结果包装在 input_text 块,并在每个批次中标记最终结果。较早的标记会保留在 history请参阅 当前的查找规则 以支持长对话。

import json

while True:
    resp = client.responses.create(
        model="us.openai.gpt-6-astra", store=False,
        include=["reasoning.encrypted_content"],
        tools=TOOLS, prompt_cache_key=KEY, input=history,
        prompt_cache_options={"mode": "explicit", "ttl": "30m"},
    )
    d = resp.usage.input_tokens_details
    print("read", d.cached_tokens, "write", d.cache_write_tokens)
    history.extend(resp.output)  # Includes encrypted reasoning.
    calls = [x for x in resp.output if x.type == "function_call"]
    if not calls:
        break
    batch = []
    for call in calls:
        result = execute_function(call.name, json.loads(call.arguments))
        batch.append({
            "type": "function_call_output", "call_id": call.call_id,
            "output": [{"type": "input_text", "text": json.dumps(result)}],
        })
    # Add the newest boundary; retain every earlier marker.
    batch[-1]["output"][-1]["prompt_cache_breakpoint"] = {"mode": "explicit"}
    history.extend(batch)

GPT-6 Astra 在此六轮工作负载中取得了并列最佳的隐式缓存结果:输入成本比不使用缓存运行时低约 62%。在同一模型上,显式滚动断点节省了约 61%。随着对话增长,两者都复用了更多历史记录。这些是工作负载结果。请测量你自己智能体的节省情况。

Bar chart comparing the input cost of each caching setup on GPT-6 Astra, relative to no caching.

图 4:在 GPT-6 Astra 上,各缓存配置相对于无缓存的输入成本

注意事项:

  • 仅显式模式下的静态断点 ——工具和指令会被缓存,但不断增长的历史记录在每一轮都会重新处理(0.61)。这一限制不适用于组合示例,后者也会通过符合条件的对话结束点进行保存。
  • 移动单个断点 可能会阻止缓存复用,并在仅显式模式下导致重复写入。同时保留上一轮的断点。
  • ——通过变化的任务进行隐式写入 ——新任务可能共享工具和指令,但不共享之前保存的端点。如果不同任务需要复用这个较短的前缀,请在共享指令之后添加一个显式标记。

当工具发生变化时

有时智能体的工具列表会发生变化,或者会新增工具。请保持初始列表稳定:添加、删除或重新排序定义都可能导致缓存前缀失效。请选择适合工具可用方式的方法。

当已知工具在智能体流程的不同步骤中被调用时: 发送相同的已排序 TOOLS ,并使用 allowed_tools 以限制模型可以调用的内容。使用 allowed_tools, "mode": "auto" 让模型决定是否调用允许的工具,并且 "mode": "required" 需要至少一个允许的工具调用。将此作为 tool_choice这一限制不会改变已缓存的目录:

tool_choice={"type": "allowed_tools", "mode": "auto",
             "tools": [{"type": "function", "name": "get_owner"}]}

会话中途新增工具 Amazon Bedrock Runtime 和 Amazon Bedrock Mantle 端点都接受一种名为 additional_tools 适用于 GPT-6 Astra、GPT-6.1 Sol 和 GPT-5.6 Luna。有关各 AWS 区域支持的模型,请参阅 Amazon Bedrock 中各 AWS 区域支持的模型注册新工具 get_due_date ,然后将其 schema 追加到智能体循环中已完成的历史记录之后。保持初始的 不变: execute_function,然后将其 schema 追加到智能体循环中已完成的历史记录之后。保持初始的 TOOLS 会话中途新增工具图 5:追加工具可保持此前的前缀可复用,示例计数来自 Amazon Bedrock Runtime 上的 GPT-6 Astra。使用新响应继续工具执行循环。该循环会将模型的输出添加到 ,运行任何被请求的工具,并追加它们的结果。在后续请求中保留 项,以便新工具保持可用。在隐式模式下,Amazon Bedrock 可以缓存到新的用户消息为止,包括其前面的工具定义。在显式

TOOL_HANDLERS["get_due_date"] = get_due_date
NEW_TOOL = {
    "type": "function", "name": "get_due_date",
    "description": "Return the due date for a support case.",
    "strict": True,
    "parameters": {
        "type": "object",
        "properties": {"case_id": {"type": "string"}},
        "required": ["case_id"], "additionalProperties": False,
    },
}
history.append({
    "type": "additional_tools", "role": "developer",
    "tools": [NEW_TOOL],
})
history.append({
    "role": "user", "content": "Find the due date for case C-123.",
})
# Implicit mode saves through this new user message, including NEW_TOOL.
resp = ask_with_implicit_cache(history)
d = resp.usage.input_tokens_details
print("old prefix read", d.cached_tokens, "new write", d.cache_write_tokens)

# Example output:
# old prefix read 2048 new write 256

会话中途新增工具

Native additional_tools loading preserves the earlier cached prefix. The GPT-6 Astra Runtime test reads 2,068 tokens and writes a 145-token extension. Editing the initial catalog causes a cache miss.

图 5:追加工具可保持此前的前缀可复用,示例计数来自 Amazon Bedrock Runtime 上的 GPT-6 Astra

使用新响应继续工具执行循环。该循环会将模型的输出添加到 history,运行任何被请求的工具,并追加它们的结果。保留 additional_tools 项,以便新工具保持可用。在隐式模式下,Amazon Bedrock 可以缓存到新的用户消息为止,包括其前面的工具定义。在仅显式模式下,保留较早的断点,并在该用户消息中的 input_text 项本身无法设置断点。不同角色,不同目录。当不同的智能体角色需要不同的工具时,在每次会话开始时选择一个稳定的工具配置,例如支持或计费。每个配置都有自己的缓存前缀。标记共享指令,以便之后使用该配置的任务可以复用它们,然后随着对话增长使用任一模式。缓存的工具配置图 6:每个工具配置都有自己的缓存前缀,可由匹配的会话复用。子智能体和侧 additional_tools 项本身无法设置断点。

不同角色,不同目录。 当不同的智能体角色需要不同的工具时,在每次会话开始时选择一个稳定的工具配置,例如支持或计费。每个配置都有自己的缓存前缀。标记共享指令,以便之后使用该配置的任务可以复用它们,然后随着对话增长使用任一模式。

缓存的工具配置

Support and billing sessions each reuse the cache for their fixed tool profile.

图 6:每个工具配置都有自己的缓存前缀,可由匹配的会话复用。

子智能体与侧调用

委派任务不应让每个辅助智能体都重新读取相同的背景信息。共享前缀,然后在末尾添加每个辅助智能体的任务。请注意,当你将缓存与 AI 智能体一起使用时,工具和系统提示词可能会被泄露或利用。建立负责任的 AI 实践,并将你的提示词缓存实现纳入该治理体系。

先预热一次,然后再分发。 先发送一个普通请求,等待其完成后再启动辅助智能体。在每个请求中显式标记共享上下文,即使模式是隐式的:

shared = {"role": "developer", "content": [{
    "type": "input_text", "text": INSTRUCTIONS,
    "prompt_cache_breakpoint": {"mode": "explicit"},
}]}
warm = client.responses.create(
    model="us.openai.gpt-6-astra", store=False,
    tools=TOOLS, prompt_cache_key=KEY,
    prompt_cache_options={"mode": "implicit", "ttl": "30m"},
    max_output_tokens=16,
    input=[shared, {"role": "user", "content": "Reply READY."}],
)
d = warm.usage.input_tokens_details
if not (d.cache_write_tokens or d.cached_tokens):
    raise RuntimeError("Shared prefix was not cached.")
from concurrent.futures import ThreadPoolExecutor

# helper_tasks contains the task text for each helper.
helper_inputs = [
    [shared, {"role": "user", "content": task}]
    for task in helper_tasks
]
with ThreadPoolExecutor(max_workers=4) as pool:
    helpers = list(pool.map(ask_with_implicit_cache, helper_inputs))

辅助智能体使用相同的 TOOLS、模型以及 KEY 作为预热。即使预热在输出上限处结束,仍可填充缓存。用量检查会在辅助智能体启动前确认缓存活动。

子智能体:先预热一次共享前缀,然后再分发

An ordinary warm-up request populates the shared prefix before four subagents start.

图 7:在启动辅助智能体之前先预热共享前缀,随后每个辅助智能体再添加自己的任务

让侧调用拥有相同的开头。 智能体循环会构建 history 其前面的工具定义。在仅显式模式下,保留较早的断点,并在该用户消息中的 项本身无法设置断点。不同角色,不同目录。当不同的智能体角色需要不同的工具时,在每次会话开始时选择一个稳定的工具配置,例如支持或计费。每个配置都有自己的缓存前缀。标记共享指令,以便之后使用该配置的任务可以复用它们,然后随着对话增长使用任一模式。缓存的工具配置图 6:每个工具配置都有自己的缓存前缀,可由匹配的会话复用。子智能体和侧 项本身无法设置断点。不同角色,不同目录。 当不同的智能体角色需要不同的工具时,在每次会话开始时选择一个稳定的工具配置,例如支持或计费。每个配置都有自己的缓存前缀。标记共享指令,以便之后使用该配置的任务可以复用它们,然后随着对话增长使用任一模式。缓存的工具配置图 6:每个工具配置都有自己的缓存前缀,可由匹配的会话复用。子智能体与侧调用委派任务不应让每个辅助智能体都重新读取相同的背景信息。共享前缀,然后在末尾添加每个辅助智能体的任务。请注意,当你将缓存与 AI 智能体一起使用时,工具和系统提示词可能会被泄露或利用。建立负责任的 AI 实践,并将你的提示词缓存实现纳入该治理体系。先预热一次,然后再分发。 先发送一个普通请求,等待其完成后再启动辅助智能体。在每个请求中显式标记共享上下文,即使模式是隐式的:辅助智能体使用相同的 、模型以及 作为预热。即使预热在输出上限处结束,仍可填充缓存。用量检查会在辅助智能体启动前确认缓存活动。子智能体:先预热一次共享前缀,然后再分发图 7:在启动辅助智能体之前先预热共享前缀,随后每个辅助智能体再添加自己的任务让侧调用拥有相同的开头。 智能体循环会构建 及其匹配的工具结果,包括加密的推理内容。父级完成后,将该完整列表复制给摘要器或评判器。同一个辅助智能体保持工具、模型和密钥不变:该辅助智能体可以复用 的缓存部分,但追加的开发者消息不会自动将缓存扩展到包含摘要指令。如果后续请求复用相同的历史记录和指令,请将该指令放入带有 的 块中。对于一次性摘要,无需额外的标记。 resp.output 及其匹配的工具结果,包括加密的推理内容。父级完成后,将该完整列表复制给摘要器或评判器。同一个辅助智能体保持工具、模型和密钥不变:

from copy import deepcopy

# history is the completed history built by the agent loop above.
# Copy it so the helper can add instructions without changing the parent.
helper_history = deepcopy(history)
helper_history.append({
    "role": "developer",
    "content": "Summarize this conversation in one sentence. Do not call tools.",
})
summary = ask_with_implicit_cache(helper_history)
d = summary.usage.input_tokens_details
print("history read", d.cached_tokens, "write", d.cache_write_tokens)
print(summary.output_text)

# Example output (assumed values and conversation):
# history read 4096 write 32
# Case C-123 is due on October 31, 2026.

该辅助智能体可以复用以下内容的缓存部分: history,但追加的开发者消息不会自动将缓存扩展到包含摘要指令。如果后续请求复用相同的历史记录和指令,请将该指令放入一个带有 input_text 块中。对于一次性摘要,无需额外的标记。暂停、重启与缓存键暂停。缓存前缀的最短存活时间(TTL)为 30 分钟,成功复用会刷新该时间,也可能保留更久。如果会话很快恢复,普通预热可以在前缀过期前刷新它。请将该请求的成本与让缓存过期进行比较。有关 TTL 规则,请参阅 Amazon Bedrock 文档。新会话。对于旨在共享前缀的请求,请保持 稳定。一个会话 prompt_cache_breakpoint对于一次性摘要,无需额外的标记。

暂停、重启与缓存键

暂停。 缓存前缀的最短存活时间(TTL)为 30 分钟,成功复用会刷新该时间,也可能保留更久。如果会话很快恢复,普通预热可以在前缀过期前刷新它。请将该请求的成本与让缓存过期进行比较。有关 TTL 规则,请参阅 Amazon Bedrock 文档。

新会话。 保持 prompt_cache_key 对于旨在共享前缀的请求保持稳定。会话专属键即使在提示词匹配时也能隔离会话。在以下示例中,切换到共享键使会话能够复用该前缀。

新会话只有在共享密钥时才会复用缓存

The original experiment compares per-session cache keys with a stable shared key.

图 8:会话专属键将缓存条目分隔开,而共享键则让匹配的会话复用这些条目

复用现有缓存内容需要相同的模型、AWS 账户和区域,以及可用的匹配前缀。以下示例将显式启动标记与隐式模式结合使用。每个会话都以相同的指令和各自的任务开始。两者使用相同的计算键:

import hashlib

def cache_key(app, model, prompt_version, instructions):
    digest = hashlib.sha256(instructions.encode()).hexdigest()[:12]
    return f"{app}:{prompt_version}:{model}:{digest}"

KEY = cache_key("support-agent", "us.openai.gpt-6-astra", "v7", INSTRUCTIONS)
prefix = {"role": "developer", "content": [{
    "type": "input_text", "text": INSTRUCTIONS,
    "prompt_cache_breakpoint": {"mode": "explicit"},
}]}
for label, question in [
    ("session A", "Explain the refund policy without calling tools."),
    ("session B", "Explain the escalation policy without calling tools."),
]:
    # A fresh conversation for each session; the startup prefix matches.
    session_history = [prefix, {"role": "user", "content": question}]
    result = ask_with_implicit_cache(session_history)
    d = result.usage.input_tokens_details
    print(label, "read", d.cached_tokens, "write", d.cache_write_tokens)

在冷启动时,会话 1 写入共享前缀。会话 2 即使任务不同也能读取它。每个任务也可以被隐式缓存。检查使用计数器:匹配的键和标记使复用成为可能,但缓存条目仍必须可用。

提示词缓存能带来什么

成本。 对于一个由 GPT-6 Astra 驱动的智能体,其启动前缀为 8,000 个 token,六轮对话每轮增加 2,000 个 token,一次性静态缓存将成本降至 0.50 美元,而滚动断点将建模输入成本降低了 64%:从每次会话 0.87 美元降至 0.32 美元。

Line chart of cumulative input cost per session for the support-agent example on GPT-6 Astra.

图 9:支持智能体示例的每次会话累计输入成本,使用 GPT-6 Astra 的 Amazon Bedrock 按需价格计算

如何判断缓存是否生效

从响应使用情况开始。 cached_tokens 报告从缓存读取的输入,并且 cache_write_tokens 报告写入其中的输入。随着对话增长,检查后续轮次是否复用较早的历史,以及新的写入是否反映正在添加的内容。

要在 Amazon Bedrock Runtime 端点上进行生产监控,请使用 CacheReadInputTokenCount 以及 CacheWriteInputTokenCount Amazon CloudWatch 中的指标。当您需要单个请求记录时,请启用模型调用日志记录,然后使用 Amazon CloudWatch Logs Insights 查询这些记录。对于 Amazon Bedrock Mantle 端点,请在应用程序日志中记录响应使用情况字段。其 Amazon CloudWatch 命名空间目前没有单独的缓存计数器。

要检查成本,请筛选 AWS Cost Explorer ,并按 使用类型按模型分别列出缓存读取、缓存写入和未缓存输入。计算它们占输入成本的比例时,排除输出 token 条目。

一份简短的检查清单

  • 保持可复用前缀稳定。 将共享工具和指令放在最前面,然后追加对话历史和新轮次。更改时间戳、消息或工具定义会导致该更改之后的内容无法复用。如果较短的 prefix 拥有自己保存的 endpoint,它仍然可以被复用。
  • 围绕你想要复用的内容选择边界。 对不断增长的历史记录使用隐式缓存,对共享前缀使用显式标记,或两者兼用。仅显式模式可避免写入一次性后缀。在扩展仅显式对话时,保留较早的标记,以便后续请求能够找到已保存的前缀。
  • 保持初始工具目录不变。 使用 allowed_tools 来限制哪些工具可被调用。将已知定义追加到 additional_tools,或通过带命名空间的客户端 tool_search当不同智能体角色需要不同的目录时,选择一个稳定的工具配置文件。
  • 检查前缀大小和生命周期。 可缓存前缀必须至少包含 1,024 个 token。缓存前缀在其最近一次写入或成功复用后,至少 30 分钟内仍可被复用。
  • 在启动辅助程序之前,先预热共享内容。 在预热请求和辅助请求中都标记共享前缀,包括使用隐式模式时也是如此。在开始并行调用之前先完成预热请求。
  • 在每个共享组内保持缓存键稳定。 希望复用某个前缀的请求必须使用相同的键。不同的键可以让不同客户或用户之间的缓存核算保持独立。
  • 衡量整个工作流程中的节省情况。 缓存写入费用是正常输入费率的 1.25 倍。读取价格取决于模型。在开发过程中使用响应使用情况数据,在生产环境中使用监控和账单数据,将写入成本与读取节省一并跟踪。

在2月3日醒来

比尔·默里只有在不再重复同样的错误、并开始把每天学到的东西带到下一天时,才逃出了《土拨鼠之日》。智能体也以类似的方式做到这一点,只是没那么浪漫:保持提示词开头逐字节稳定,并随着历史增长使用自动或显式的缓存边界。

在这篇文章中,我们介绍了如何缓存共享工具和指令、如何让缓存在智能体的多个轮次之间增长,以及在添加工具、启动辅助程序或开启新会话时如何保持复用。保持共享前缀稳定,并随着工作流的推进追加新信息。对不断增长的对话使用隐式缓存,对共享内容使用显式断点。 当两者都需要时可将它们组合使用,或者当变化的后缀不太可能被复用时选择仅显式模式。比较缓存读取、写入、总输入成本和延迟,找出最适合你的智能体的方案。要应用这些模式,请遵循 Amazon Bedrock 上 OpenAI 模型的提示词缓存文档 并检查 按 AWS 区域和端点划分的模型可用性从单一工作流开始,并随着对话的增长衡量节省效果。


关于作者