Ohhnews

分类导航

$ cd ..
Baeldung原文

Spring AI 集成 Anthropic Claude 的提示缓存支持

#spring ai#anthropic claude#提示缓存#性能优化#缓存策略

[LOADING...]

1. 概述

当处理大型提示时,反复向模型发送相同的上下文会增加延迟和成本。当应用在多个请求之间复用大型系统指令、文档或对话上下文时,这一点尤其明显。

Spring AI 与 Anthropic 中的提示缓存通过允许频繁复用的提示部分被缓存并在请求之间复用来解决这一问题,从而减少模型每次需要处理的工作量。这可以改善响应延迟,同时降低输入 token 成本。

在本教程中,我们将解释提示缓存的工作原理、不同 Claude 模型的限制和要求,以及如何在 Spring AI 中使用它。我们还将介绍在应用中应用缓存时的主要配置选项和实践注意事项。

2. 依赖

让我们先定义演示 Spring AI 中提示缓存所需的最小依赖。 我们只需要 spring-boot-starter-webspring-ai-starter-model-anthropic。前者用于基本的 Spring Boot 应用和自动配置。后者包含我们本文要探索的工具,包括 Spring AI。提示缓存的最低版本是 1.0.3,因此我们可以使用:

$ xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.5.13</version>
</dependency>
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-anthropic</artifactId>
    <version>2.0.1</version>
</dependency>

3. 提示缓存

让我们先了解什么是提示缓存。提示缓存允许 Claude 复用提示中先前已处理的前缀,而不是在每个请求中从头处理相同的内容。 这对于大型系统指令、工具定义、文档以及其他在请求之间保持不变的内容特别有用。

我们获得的主要好处是降低延迟和输入 token 成本。对于大多数 Claude 模型,Anthropic 目前对缓存读取收取常规输入 token 价格的 10%。虽然对于 5 分钟缓存,初始缓存写入成本比基础输入价格高 25%,但随着同一提示前缀被复用,节省会逐渐增加。

不过,也有一些限制需要考虑:

  • 最小可缓存提示大小取决于模型
  • 缓存使用有限的 TTL
  • 最多只能定义四个缓存断点
  • 对缓存前缀所做的更改可能会使缓存失效
  • 缓存条目只有在第一次响应开始后才可用,这在发送并发请求时很重要

接下来,我们需要理解提示缓存层级,以便其他概念(如策略)能够说得通。提示缓存遵循层级:工具 → 系统 → 消息。Claude 按此顺序处理这些部分,并且每一层都建立在前一层之上。

失效也遵循这一层级。如果我们更改工具,这会使工具缓存及其下面的所有内容失效。如果我们更改系统提示,工具缓存保持不变,但系统缓存和消息缓存会失效。当我们更改消息时,只有消息缓存会失效。

因此,思路很简单:将稳定内容尽可能放在层级的较高位置,将频繁变化的内容尽可能放在较低位置。这样可以让请求之间尽可能大的提示部分保持缓存。

4. 提示缓存策略

层级和失效规则自然会导致不同的缓存策略。目标是将缓存断点放在能够带来最大复用收益的位置,同时尽量减少内容变更时失效的范围。

缓存断点告诉 Anthropic 在何处创建缓存条目。每个断点会按照请求层级工具 → 系统 → 消息,缓存到该点为止的提示内容。Anthropic 目前允许每个请求最多有四个缓存断点,因此应有意识地放置它们,而不是在每个可能的部分都放置。

Spring AI 通过 AnthropicCacheStrategy 提供五种提示缓存策略:

策略断点缓存内容典型用例
NONE0一次性请求、测试
SYSTEM_ONLY1工具 + 系统消息稳定的系统提示
TOOLS_ONLY1工具定义大型共享工具与动态系统提示
SYSTEM_AND_TOOLS2工具 + 系统消息工具与系统提示需要独立缓存
CONVERSATION_HISTORY1–4对话历史多轮对话

断点的数量和位置也会影响失效。例如,在系统消息处只有一个断点时,对工具的更改会使整个缓存前缀失效。使用 SYSTEM_AND_TOOLS 时,Spring AI 会在工具和系统消息之后分别放置断点,这样当只有系统提示发生变化时,工具缓存仍然保持有效。

因此,选择正确的策略取决于请求各部分有多稳定。某个部分变化越频繁,就越应该谨慎地将其断点与稳定内容分开。

5. 提示缓存实践

最后,让我们把目前所学付诸实践。首先,我们将在 Spring Boot 应用中应用这些理论,然后运行一些测试来观察提示缓存的实际效果。

5.1. 使用提示缓存的 Spring Boot 应用

如前所述,我们可以使用 Spring 属性来设置模型:

$ config
spring:
  ai:
    anthropic:
      api-key: ${ANTHROPIC_API_KEY}
      chat:
        options:
          model: claude-sonnet-4-6
          max-tokens: 500
        cache-options:
          strategy: SYSTEM_ONLY

目前,3.5 之前的 claude-sonnet 模型已不可用。我们将模型设置为 4-6,并将 max-tokens 设为 500 作为控制成本的保护措施,并在应用中使用 SYSTEM_ONLY 作为 strategyclaude-sonnet-4-6 的最小可缓存提示为 1024 个 token。

然后,我们只需要一个使用该模型的服务:

$ java
public class ChatWithPromptCachingService {
    private final ChatClient chatClient;
    // ... constructors, etc
    public ChatResponseWithMetadataDto chat(String userMessage, String systemPrompt) {
        ChatResponse response = chatClient
          .prompt()
          .system(systemPrompt)
          .user(userMessage)
          .call()
          .chatClientResponse()
          .chatResponse();
        if (response == null || response.getResult() == null) {
            throw new RuntimeException("Client response has no results");
        }
        return ChatResponseWithMetadataDto.fromChatResponse(response);
    }
}

chat() 方法接收系统提示和用户消息,并请求我们的 chat-client。然后返回 ChatResponseWithMetadataDto

$ java
public record ChatResponseWithMetadataDto(
  String responseText,
  Integer promptTokens,
  Integer completionTokens,
  Long cacheReadInputTokens,
  Long cacheWriteInputTokens) {
    public static ChatResponseWithMetadataDto fromChatResponse(ChatResponse chatResponse) {
        // ... implementation of the mapper
    }
}

ChatResponseWithMetadataDto 只保存我们需要的信息:responseTextpromptTokenscompletionTokenscacheReadInputTokenscacheWriteInputTokens

5.2. 测试提示缓存应用

为了确保提示确实被缓存,我们先以 strategy 设置为 NONE 运行一个测试:

$ java
@Test
void chat_whenPromptCachingDisabled_returnsResponse() {
    String chatMessage = "hello there";
    ChatResponseWithMetadataDto response = service.chat(chatMessage, PromptsUtils.LONG_SYSTEM_PROMPT);
    assertThat(response).isNotNull();
    assertThat(response.promptTokens()).isGreaterThan(1030);
    assertThat(response.cacheReadInputTokens()).isEqualTo(0);
    response = service.chat(chatMessage + " again", PromptsUtils.LONG_SYSTEM_PROMPT);
    assertThat(response).isNotNull();
    assertThat(response.promptTokens()).isGreaterThan(1030);
    assertThat(response.cacheReadInputTokens()).isEqualTo(0);
}

在这个测试中,我们请求一条虚拟消息以及默认的 PromptsUtils.LONG_SYSTEM_PROMPT,它仅比 1024 个 token 稍长。正如预期,对于缓存策略 NONE,我们看到提示 token 数完整为 1030,并且没有记录缓存 token。

接下来,让我们使用应用的默认缓存策略(SYSTEM_ONLY)尝试同样的测试:

$ java
@Test
void chat_whenPromptCachingEnabled_returnsResponse() {
    String chatMessage = "hello there";
    UUID testId = UUID.randomUUID();
    ChatResponseWithMetadataDto response = service.chat(
      chatMessage,
      PromptsUtils.LONG_SYSTEM_PROMPT + "\nTEST_ID=" + testId);
    assertThat(response).isNotNull();
    assertThat(response.promptTokens()).isLessThan(50);
    assertThat(response.cacheWriteInputTokens()).isGreaterThan(1000);
    assertThat(response.cacheReadInputTokens()).isEqualTo(0);
    response = service.chat(chatMessage + " again", PromptsUtils.LONG_SYSTEM_PROMPT + "\nTEST_ID=" + testId);
    assertThat(response).isNotNull();
    assertThat(response.promptTokens()).isLessThan(50);
    assertThat(response.cacheWriteInputTokens()).isEqualTo(0);
    assertThat(response.cacheReadInputTokens()).isGreaterThan(1000);
}

现在我们看到了提示缓存的效果!在第一个请求中,响应告诉我们 cacheWriteInputTokens 超过 1000,这意味着它刚刚写入了缓存。此外,promptTokens 很低,仅为用户消息,而 cacheReadInputTokens 为 0,因为没有缓存命中。

在第二个请求中,promptTokens 很低,但这次 cacheWriteInputTokens 为 0(没有新 token 被缓存)。cacheReadInputTokens 现在超过 1000,这表明发生了缓存命中!

此测试中的提示还使用了 TEST_ID,以确保之前的测试不会影响下一次执行。这是一种在不同测试执行之间使用不同缓存的技巧。

6. 结论

在本文中,我们解释了 Spring AI 与 Anthropic Claude 中的提示缓存支持如何工作。我们介绍了其工作原理、配置选项和限制。然后我们了解了提示缓存策略。最后,我们使用 Spring AI 在实践中进行了演示。

一如既往,示例的源代码可以在 GitHub 上找到。