Skip to content
Spring AI 文档版本 2.1.0-SNAPSHOT 源快照 5f3f777a 更新于 2026-10-16

Anthropic 聊天

Spring AI 通过官方的 Anthropic Java SDK 支持 Anthropic 的 Claude 模型,经由 Anthropic 的 API 访问 Claude。

前置条件

在 Anthropic Console 创建一个账户,并在 API Keys 页 生成一个 API 密钥。

添加仓库与 BOM

Spring AI 的制品发布在 Maven Central 和 Spring Snapshot 仓库中。 参见 制品仓库 一节,把这些仓库添加到你的构建系统。

为便于依赖管理,Spring AI 提供了一个 BOM(物料清单),以确保整个项目中使用一致版本的 Spring AI。参见 依赖管理 一节,把 Spring AI BOM 添加到你的构建系统。

自动配置

通过 spring-ai-starter-model-anthropic starter 可获得 Spring Boot 自动配置。

Add it to your project's Maven pom.xml file:

xml
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-anthropic</artifactId>
</dependency>

TIP

参见 依赖管理 一节,把 Spring AI BOM 添加到你的构建文件。

配置属性

使用 spring.ai.anthropic.* 属性来配置 Anthropic 连接和聊天选项:

配置项说明默认值
spring.ai.anthropic.api-keyAnthropic API key-
spring.ai.anthropic.base-urlAPI 基础 URLhttps://api.anthropic.com
spring.ai.anthropic.timeout构造期写入共享 HTTP 客户端的默认超时。可在单次调用上通过设置 AnthropicChatOptions.timeout()(挂在 ChatClient 或逐次请求的 options)覆盖。 | 60sspring.ai.anthropic.max-retries
失败请求的最大重试次数2spring.ai.anthropic.custom-headers.*
为所有 Anthropic 客户端请求附加的自定义 HTTP 头-spring.ai.anthropic.chat.model
模型名claude-haiku-4-5spring.ai.anthropic.chat.max-tokens
最大 token 数4096spring.ai.anthropic.chat.temperature
采样温度-spring.ai.anthropic.chat.top-p
Top-p 采样-spring.ai.anthropic.chat.top-k
Top-k 采样-spring.ai.anthropic.chat.cache-options.strategy
使用的缓存策略NONEspring.ai.anthropic.chat.cache-options.multi-block-system-caching
每条系统消息使用独立的块falsespring.ai.anthropic.chat.http-headers
逐次请求的 HTTP 头,传给单个 API 调用。-spring.ai.anthropic.chat.inference-geo
请求处理的地理区域(us 或 eu)-spring.ai.anthropic.chat.web-search-tool.max-uses
每次请求最多进行几次网页搜索-spring.ai.anthropic.chat.web-search-tool.allowed-domains
以逗号分隔的域名列表,用于限定搜索结果范围-spring.ai.anthropic.chat.web-search-tool.blocked-domains
以逗号分隔的域名列表,用于从搜索结果中排除-spring.ai.anthropic.chat.web-search-tool.user-location.city
用于本地化搜索结果的 cities-spring.ai.anthropic.chat.web-search-tool.user-location.country
ISO 3166-1 alpha-2 国家代码-spring.ai.anthropic.chat.web-search-tool.user-location.region
地区或州-spring.ai.anthropic.chat.web-search-tool.user-location.timezone
IANA 时区标识-spring.ai.anthropic.chat.service-tier
容量路由:auto(有优先容量就用)或 standard_only(始终标准)。参见 Service Tiers.-

手动配置

AnthropicChatModel 实现了 ChatModel 接口,并使用官方的 Anthropic Java SDK 连接到 Claude。

Add the spring-ai-anthropic dependency to your project's Maven pom.xml file:

xml
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-anthropic</artifactId>
</dependency>

TIP

参见 依赖管理 一节,把 Spring AI BOM 添加到你的构建文件。

认证

以编程方式或通过环境变量配置你的 API 密钥:

java
var chatOptions = AnthropicChatOptions.builder()
    .model("claude-sonnet-4-20250514")
    .maxTokens(1024)
    .apiKey(System.getenv("ANTHROPIC_API_KEY"))
    .build();

var chatModel = AnthropicChatModel.builder()
    .options(chatOptions)
    .build();

或设置环境变量并让 SDK 自动检测它:

bash
export ANTHROPIC_API_KEY=<your-api-key>
java
// API key will be detected from ANTHROPIC_API_KEY environment variable
var chatModel = AnthropicChatModel.builder()
    .options(AnthropicChatOptions.builder()
        .model("claude-sonnet-4-20250514")
        .maxTokens(1024)
        .build())
    .build();

基本用法

java
ChatResponse response = chatModel.call(
    new Prompt("Generate the names of 5 famous pirates."));

// Or with streaming responses
Flux<ChatResponse> stream = chatModel.stream(
    new Prompt("Generate the names of 5 famous pirates."));

运行时选项

AnthropicChatOptions.java 类提供模型配置,例如要使用的模型、温度、最大令牌数等。

在启动时,用 AnthropicChatModel.builder().defaultOptions(options).build() 构造器配置默认选项。

在运行时,你可以通过向 Prompt 调用添加新的、请求特定的选项来覆盖默认选项。 例如,要为某个特定请求覆盖默认的模型和温度:

java
ChatResponse response = chatModel.call(
    new Prompt(
        "Generate the names of 5 famous pirates.",
        AnthropicChatOptions.builder()
            .model("claude-sonnet-4-20250514")
            .temperature(0.4)
        .build()
    ));

聊天选项

选项说明默认值
model要使用的 Claude 模型名。 可选模型包括 claude-sonnet-4-20250514、claude-opus-4-20250514、claude-3-5-sonnet-20241022、claude-3-5-haiku-20241022 等,详见 Claude Models。claude-sonnet-4-20250514
maxTokens响应中最多生成的 token 数。4096
temperature控制响应的随机性。值越大输出越随机,越小越确定。范围 0.0–1.01.0
topP核采样参数,模型考虑累计概率达 top_p 的 token。 仅 Claude Opus 4.6 及更早模型支持。Anthropic 已在 4.6 之后的模型(如 claude-opus-4-7)上废弃:< 0.99 会返回 HTTP 400,>= 0.99 仅为向后兼容而接受(实际不做过滤)。-
topK每个 token 只从前 K 个候选中采样。 仅 Claude Opus 4.6 及更早模型支持。Anthropic 已在 4.6 之后的模型(如 claude-opus-4-7)上废弃:在这些模型上传任意值都会返回 HTTP 400。-
stopSequences自定义停止序列,模型生成到这些序列时停止。-
apiKey用于认证的 API key。 未设置时会自动从 ANTHROPIC_API_KEY 环境变量读取。-
baseUrlAnthropic API 的基础 URL。https://api.anthropic.com
timeout单次调用的超时覆盖。通过 ChatClient.Builder.defaultOptions() 或逐次请求的 ChatClient.prompt().options(...) 设置时,只约束该次调用,并覆盖 spring.ai.anthropic.timeout 的构造期默认值。 | 60 secondsmaxRetries
失败请求的最大重试次数。2proxy
HTTP 客户端的代理设置。-customHeaders
所有请求都带上的自定义 HTTP 头(客户端级)。-httpHeaders
逐次请求的 HTTP 头。 这些头通过 MessageCreateParams.putAdditionalHeader() 加到单个 API 调用上,适用于请求级追踪、beta API 头或路由。-cacheOptions
配置提示词缓存行为的选项,包括缓存策略、多块缓存、TTL 与内容长度门槛。-thinking
思考(thinking)配置。可用便捷构建方法 thinkingEnabled(budgetTokens), thinkingEnabled(budgetTokens, display), thinkingAdaptive(), thinkingAdaptive(display), 或 thinkingDisabled(), 或直接传入原始 ThinkingConfigParam. The display 参数控制思考内容在响应中如何呈现: SUMMARIZED (精简摘要) 或 OMITTED (隐藏正文,仅留签名).-outputConfig
结构化输出(JSON schema)与 effort 控制的输出配置。 使用 outputConfig(OutputConfig) 可完全控制,或用便捷方法 outputSchema(String) and effort(OutputConfig.Effort). 需要 claude-sonnet-4-6 及更新版本。-inferenceGeo
控制请求在哪个地理区域处理,支持 us、eu,用于满足数据驻留合规要求。 Configurable via spring.ai.anthropic.chat.inference-geo.-serviceTier
控制请求的容量路由。 用 MessageCreateParams.ServiceTier.AUTO 尽量使用优先容量,或用 STANDARD_ONLY 始终使用标准容量。参见 Service Tiers.-

工具调用选项

选项描述默认值
toolChoice控制模型调用哪个(若有)工具。使用 ToolChoiceAuto、ToolChoiceAny、ToolChoiceTool 或 ToolChoiceNone。AUTO
toolCallbacks要注册到模型的工具回调列表。-
disableParallelToolUse为 true 时,模型每次响应至多使用一个工具。false

TIP

除模型特定的 AnthropicChatOptions 外,你还可以使用一个可移植的 ChatOptions 实例,通过 ChatOptions#builder() 创建。

速率限制元数据

Anthropic API 在每个响应中包含速率限制头,报告你当前的请求与令牌预算,以及每个窗口何时重置。 Spring AI 通过标准的 ChatResponseMetadata#getRateLimit() 访问器暴露这些数据,它返回一个由响应头填充的 AnthropicRateLimit。

可移植的 RateLimit 接口暴露请求和令牌两类:

java
ChatResponse response = chatModel.call(prompt);

RateLimit rateLimit = response.getMetadata().getRateLimit();
rateLimit.getRequestsLimit();      // overall request ceiling
rateLimit.getRequestsRemaining();  // requests left in the current window
rateLimit.getRequestsReset();      // time until the request window resets

rateLimit.getTokensLimit();        // overall token ceiling
rateLimit.getTokensRemaining();    // tokens left in the current window
rateLimit.getTokensReset();        // time until the token window resets

Anthropic 还为输入令牌和输出令牌报告各自独立的桶。这些由 AnthropicRateLimit 具体类型暴露:

java
if (response.getMetadata().getRateLimit() instanceof AnthropicRateLimit anthropic) {
    anthropic.getInputTokensLimit();
    anthropic.getInputTokensRemaining();
    anthropic.getInputTokensReset();

    anthropic.getOutputTokensLimit();
    anthropic.getOutputTokensRemaining();
    anthropic.getOutputTokensReset();
}

如果 Anthropic 在某个响应上不返回任何速率限制头,getRateLimit() 会转而返回一个 EmptyRateLimit 实例,符合其他 Spring AI 服务商所用的约定。

速率限制元数据在同步 call(Prompt) 与流式 stream(Prompt) 两条路径上都会被填充。 对于流式响应,头会在流开始时读取一次,并附加到携带最终用量那个响应块上,因此那里 getRateLimit() 会解析为一个已填充的 AnthropicRateLimit。

NOTE

为读取响应头,流式路径会在一个 Schedulers.boundedElastic() 工作线程上消费 Anthropic SDK 的阻塞流,因此一次流式调用会在整个流期间占用一个工作线程。

工具调用

AnthropicChatModel 支持工具调用。Claude 可以请求执行工具,但它自己并不运行工具——由 Spring AI 处理执行。

对于大多数应用,请搭配自动注册的 ToolCallingAdvisor 使用 ChatClient —— 参见工具调用。若需要在由你自己驱动该循环的底层控制下(例如把工具调用与提示词缓存结合时),参见 ChatModel 工具调用。

工具选择选项

用 toolChoice 选项控制 Claude 选择哪个工具:

java
import com.anthropic.models.messages.ToolChoiceAny;
import com.anthropic.models.messages.ToolChoiceTool;
import com.anthropic.models.messages.ToolChoiceNone;

// Force Claude to use any available tool
var options = AnthropicChatOptions.builder()
    .toolChoice(ToolChoiceAny.builder().build())
    .toolCallbacks(...)
    .build();

// Force Claude to use a specific tool
var options = AnthropicChatOptions.builder()
    .toolChoice(ToolChoiceTool.builder().name("getCurrentWeather").build())
    .toolCallbacks(...)
    .build();

// Prevent tool use entirely
var options = AnthropicChatOptions.builder()
    .toolChoice(ToolChoiceNone.builder().build())
    .toolCallbacks(...)
    .build();

TIP

Anthropic Java SDK 为常见工具选择提供了便捷的静态工厂方法,能让你的代码更简洁:

  • 用 ToolChoice.auto() 可代替 ToolChoice.ofAuto(...)。
  • 用 ToolChoice.any() 可代替 ToolChoice.ofAny(...)。
  • 用 ToolChoice.none() 可代替 ToolChoice.ofNone(...)。

流式传输

Anthropic SDK 模块同时支持同步与流式响应。流式让 Claude 能在生成的同时增量返回响应。

java
Flux<ChatResponse> stream = chatModel.stream(new Prompt("Tell me a story"));

stream.subscribe(response -> {
    String content = response.getResult().getOutput().getContent();
    if (content != null) {
        System.out.print(content);
    }
});

扩展思考

Anthropic Claude 模型支持一个“思考”特性,让模型能在给出最终答案前展示其推理过程。这对于需要分步推理的复杂问题尤其有用,例如数学、逻辑和分析任务。

NOTE

受支持的模型 思考特性受以下 Claude 模型支持:

  • Claude 4 模型(claude-opus-4-20250514、claude-sonnet-4-20250514)
  • Claude 3.7 Sonnet(claude-3-7-sonnet-20250219) 模型能力:
  • Claude 3.7 Sonnet:返回完整的思考输出。
  • Claude 4 模型:支持摘要式思考和增强的工具集成。 API 请求结构在所有受支持模型间相同,但输出行为各异。

思考配置

要启用思考,配置以下内容:

  1. 设置思考预算:budgetTokens 必须 >= 1024 且小于 maxTokens。
  2. 把温度设为 1.0:启用思考时必需。

便捷构建器方法

AnthropicChatOptions.Builder 为思考配置提供便捷方法:

java
// Enable thinking with a specific token budget
var options = AnthropicChatOptions.builder()
    .model("claude-sonnet-4-20250514")
    .temperature(1.0)
    .maxTokens(16000)
    .thinkingEnabled(10000L)    // budget must be >= 1024 and < maxTokens
    .build();

// Let Claude adaptively decide whether to think
var options = AnthropicChatOptions.builder()
    .model("claude-sonnet-4-20250514")
    .thinkingAdaptive()
    .build();

// Explicitly disable thinking
var options = AnthropicChatOptions.builder()
    .model("claude-sonnet-4-20250514")
    .thinkingDisabled()
    .build();

你也可以直接使用原始的 SDK ThinkingConfigParam:

java
import com.anthropic.models.messages.ThinkingConfigParam;
import com.anthropic.models.messages.ThinkingConfigEnabled;

var options = AnthropicChatOptions.builder()
    .thinking(ThinkingConfigParam.ofEnabled(
        ThinkingConfigEnabled.builder().budgetTokens(10000L).build()))
    .build();

思考显示设置

默认情况下,完整的思考输出会在响应中返回。你可以用 display 参数控制它以降低令牌成本:

  • SUMMARIZED —— Claude 仍会完全思考,但返回一个精简摘要而非原始思维链。在仍提供推理洞见的同时减少输出令牌。
  • OMITTED —— 会进行思考但完全从响应中涂黑。只返回一个加密签名(多轮连续性所需)。输出令牌成本最低。
java
import com.anthropic.models.messages.ThinkingConfigEnabled;
import com.anthropic.models.messages.ThinkingConfigAdaptive;

// Enabled thinking with summarized display
var options = AnthropicChatOptions.builder()
    .model("claude-sonnet-4-20250514")
    .temperature(1.0)
    .maxTokens(16000)
    .thinkingEnabled(10000L, ThinkingConfigEnabled.Display.SUMMARIZED)
    .build();

// Enabled thinking with omitted display
var options = AnthropicChatOptions.builder()
    .model("claude-sonnet-4-20250514")
    .temperature(1.0)
    .maxTokens(16000)
    .thinkingEnabled(10000L, ThinkingConfigEnabled.Display.OMITTED)
    .build();

// Adaptive thinking with summarized display
var options = AnthropicChatOptions.builder()
    .model("claude-sonnet-4-20250514")
    .temperature(1.0)
    .maxTokens(16000)
    .thinkingAdaptive(ThinkingConfigAdaptive.Display.SUMMARIZED)
    .build();

NOTE

显示设置不影响最终答案的质量——无论如何在 Claude 都执行相同量的思考。它只控制在响应中返回哪些思考内容。

非流式示例

java
var options = AnthropicChatOptions.builder()
    .model("claude-sonnet-4-20250514")
    .temperature(1.0)
    .maxTokens(16000)
    .thinkingEnabled(10000L)
    .build();

ChatResponse response = chatModel.call(
    new Prompt("Are there an infinite number of prime numbers such that n mod 4 == 3?", options));

// The response contains multiple generations:
// - ThinkingBlock generations (with "signature" in metadata)
// - TextBlock generations (with the final answer)
for (Generation generation : response.getResults()) {
    AssistantMessage message = generation.getOutput();
    if (message.getMetadata().containsKey("signature")) {
        // This is a thinking block - contains Claude's reasoning
        System.out.println("Thinking: " + message.getText());
        System.out.println("Signature: " + message.getMetadata().get("signature"));
    }
    else if (message.getMetadata().containsKey("data")) {
        // This is a redacted thinking block (safety-redacted reasoning)
        System.out.println("Redacted thinking data: " + message.getMetadata().get("data"));
    }
    else if (message.getText() != null && !message.getText().isBlank()) {
        // This is the final text response
        System.out.println("Answer: " + message.getText());
    }
}

流式示例

思考在流式模式下被完全支持。思考增量与签名增量会在到达时发出:

java
var options = AnthropicChatOptions.builder()
    .model("claude-sonnet-4-20250514")
    .temperature(1.0)
    .maxTokens(16000)
    .thinkingEnabled(10000L)
    .build();

Flux<ChatResponse> stream = chatModel.stream(
    new Prompt("Are there an infinite number of prime numbers such that n mod 4 == 3?", options));

stream.subscribe(response -> {
    Generation generation = response.getResult();
    AssistantMessage message = generation.getOutput();

    if (message.getMetadata().containsKey(AnthropicChatModel.THINKING_METADATA_KEY)) {
        // Thinking delta — getText() returns null so thinking does not mix into the
        // answer; the incremental thinking text is in the THINKING_TEXT_METADATA_KEY.
        System.out.print(message.getMetadata().get(AnthropicChatModel.THINKING_TEXT_METADATA_KEY));
    }
    else if (message.getMetadata().containsKey("signature")) {
        // Thinking block signature (emitted at end of thinking)
        System.out.println("\nSignature: " + message.getMetadata().get("signature"));
    }
    else if (message.getText() != null) {
        // Final text content
        System.out.print(message.getText());
    }
});

响应结构

当启用思考时,响应包含不同类型的内容:

内容类型元数据键描述
思考块signatureClaude 的推理文本带一个加密签名。在同步模式下,思考文本在 getText(),签名在 getMetadata().get("signature")。
涂黑的思考data被安全涂黑的推理。只含一个 data 标记,无可见文本。
签名(流式)signature在流式模式下,签名作为思考块末尾的一个独立增量到达。
思考增量(流式)THINKING_METADATA_KEY、THINKING_TEXT_METADATA_KEY流式期间的增量思考块。THINKING_METADATA_KEY("thinking")被设为 true,THINKING_TEXT_METADATA_KEY("thinkingText")持有增量思考文本。这些块上 getText() 返回 null,因此思考不会被聚合进答案文本;在聚合响应上也可通过 AnthropicAssistantMessage.getThinkingContents() 获取完整思考文本。
文本块(无)getText() 中的最终答案文本。

思考与工具调用并用

当启用思考且 Claude 决定调用一个工具时,该响应中的思考块会被自动保留,并在随工具结果一起发起的后续请求中重放。 这是 Anthropic API 的要求——重放时省略思考块会导致 API 错误。

无需特殊配置。 无论使用自动管理的工具循环(带 ToolCallingAdvisor 的 ChatClient)还是通过 ToolCallingManager 手动驱动该循环,Spring AI 都会透明地处理重放。

多模态支持

Anthropic SDK 模块支持多模态输入,让你能在提示词中随文本一并发图像和 PDF 文档。

图像输入

使用 Media 类向 Claude 发送图像以供分析:

java
var imageResource = new ClassPathResource("/test-image.png");

var userMessage = UserMessage.builder()
    .text("What do you see in this image?")
    .media(List.of(new Media(MimeTypeUtils.IMAGE_PNG, imageResource)))
    .build();

ChatResponse response = chatModel.call(new Prompt(List.of(userMessage)));

受支持的图像格式:PNG、JPEG、GIF、WebP。图像可以如下形式提供:

  • 字节数组(自动 base64 编码)
  • HTTPS URL(直接传给 API)

PDF 文档输入

发送 PDF 文档供 Claude 分析:

java
var pdfResource = new ClassPathResource("/document.pdf");

var userMessage = UserMessage.builder()
    .text("Please summarize this document.")
    .media(List.of(new Media(new MimeType("application", "pdf"), pdfResource)))
    .build();

ChatResponse response = chatModel.call(new Prompt(List.of(userMessage)));

多个媒体项

你可以在单条消息中包含多个图像或文档:

java
var userMessage = UserMessage.builder()
    .text("Compare these two images.")
    .media(List.of(
        new Media(MimeTypeUtils.IMAGE_PNG, image1Resource),
        new Media(MimeTypeUtils.IMAGE_PNG, image2Resource)))
    .build();

引用

Anthropic 的引用 API 让 Claude 在生成响应时能引用所提供文档的特定部分。 当引用文档被包含在提示词中时,Claude 能引用源材料,且引用元数据(字符范围、页码或内容块)会在响应元数据中返回。

引用有助于提升:

  • 准确性校验:用户能对照源材料验证 Claude 的响应
  • 透明度:确切看到文档的哪些部分为响应提供了信息
  • 合规:满足受监管行业中关于来源标注的要求
  • 信任:通过展示信息来源建立信心

NOTE

受支持的模型 引用在 Claude 3.7 Sonnet 及 Claude 4 模型(Opus 和 Sonnet)上受支持。 文档类型 支持三种引用文档类型:

  • 纯文本:带字符级引用的文本内容
  • PDF:带页级引用的 PDF 文档
  • 自定义内容:带块级引用的用户自定义内容块

创建引用文档

使用 AnthropicCitationDocument 构建器创建可被引用的文档:

纯文本文档

java
AnthropicCitationDocument document = AnthropicCitationDocument.builder()
    .plainText("The Eiffel Tower was completed in 1889 in Paris, France. " +
               "It stands 330 meters tall and was designed by Gustave Eiffel.")
    .title("Eiffel Tower Facts")
    .citationsEnabled(true)
    .build();

PDF 文档

java
// From file path
AnthropicCitationDocument document = AnthropicCitationDocument.builder()
    .pdfFile("path/to/document.pdf")
    .title("Technical Specification")
    .citationsEnabled(true)
    .build();

// From byte array
byte[] pdfBytes = loadPdfBytes();
AnthropicCitationDocument document = AnthropicCitationDocument.builder()
    .pdf(pdfBytes)
    .title("Product Manual")
    .citationsEnabled(true)
    .build();

自定义内容块

要获得细粒度的引用控制,使用自定义内容块:

java
AnthropicCitationDocument document = AnthropicCitationDocument.builder()
    .customContent(
        "The Great Wall of China is approximately 21,196 kilometers long.",
        "It was built over many centuries, starting in the 7th century BC.",
        "The wall was constructed to protect Chinese states from invasions."
    )
    .title("Great Wall Facts")
    .citationsEnabled(true)
    .build();

在请求中使用引用

把引用文档包含进你的聊天选项:

java
ChatResponse response = chatModel.call(
    new Prompt(
        "When was the Eiffel Tower built and how tall is it?",
        AnthropicChatOptions.builder()
            .model("claude-sonnet-4-20250514")
            .maxTokens(1024)
            .citationDocuments(document)
            .build()
    )
);

多个文档

你可以提供多个文档供 Claude 参考:

java
AnthropicCitationDocument parisDoc = AnthropicCitationDocument.builder()
    .plainText("Paris is the capital city of France with a population of 2.1 million.")
    .title("Paris Information")
    .citationsEnabled(true)
    .build();

AnthropicCitationDocument eiffelDoc = AnthropicCitationDocument.builder()
    .plainText("The Eiffel Tower was designed by Gustave Eiffel for the 1889 World's Fair.")
    .title("Eiffel Tower History")
    .citationsEnabled(true)
    .build();

ChatResponse response = chatModel.call(
    new Prompt(
        "What is the capital of France and who designed the Eiffel Tower?",
        AnthropicChatOptions.builder()
            .model("claude-sonnet-4-20250514")
            .citationDocuments(parisDoc, eiffelDoc)
            .build()
    )
);

访问引用

引用在响应元数据中返回:

java
ChatResponse response = chatModel.call(prompt);

// Get citations from metadata
@SuppressWarnings("unchecked")
List<Citation> citations = (List<Citation>) response.getMetadata().get("citations");

// Optional: Get citation count directly from metadata
Integer citationCount = (Integer) response.getMetadata().get("citationCount");
System.out.println("Total citations: " + citationCount);

// Process each citation
for (Citation citation : citations) {
    System.out.println("Document: " + citation.getDocumentTitle());
    System.out.println("Location: " + citation.getLocationDescription());
    System.out.println("Cited text: " + citation.getCitedText());
    System.out.println("Document index: " + citation.getDocumentIndex());
    System.out.println();
}

引用类型

依据文档类型,引用包含不同的位置信息:

字符位置(纯文本)

对于纯文本文档,引用包含字符索引:

java
Citation citation = citations.get(0);
if (citation.getType() == Citation.LocationType.CHAR_LOCATION) {
    int start = citation.getStartCharIndex();
    int end = citation.getEndCharIndex();
    String text = citation.getCitedText();
    System.out.println("Characters " + start + "-" + end + ": " + text);
}

页位置(PDF)

对于 PDF 文档,引用包含页码:

java
Citation citation = citations.get(0);
if (citation.getType() == Citation.LocationType.PAGE_LOCATION) {
    int startPage = citation.getStartPageNumber();
    int endPage = citation.getEndPageNumber();
    System.out.println("Pages " + startPage + "-" + endPage);
}

内容块位置(自定义内容)

对于自定义内容,引用指向特定的内容块:

java
Citation citation = citations.get(0);
if (citation.getType() == Citation.LocationType.CONTENT_BLOCK_LOCATION) {
    int startBlock = citation.getStartBlockIndex();
    int endBlock = citation.getEndBlockIndex();
    System.out.println("Content blocks " + startBlock + "-" + endBlock);
}

完整示例

下面是一个演示引用用法的完整示例:

java
// Create a citation document
AnthropicCitationDocument document = AnthropicCitationDocument.builder()
    .plainText("Spring AI is an application framework for AI engineering. " +
               "It provides a Spring-friendly API for developing AI applications. " +
               "The framework includes abstractions for chat models, embedding models, " +
               "and vector databases.")
    .title("Spring AI Overview")
    .citationsEnabled(true)
    .build();

// Call the model with the document
ChatResponse response = chatModel.call(
    new Prompt(
        "What is Spring AI?",
        AnthropicChatOptions.builder()
            .model("claude-sonnet-4-20250514")
            .maxTokens(1024)
            .citationDocuments(document)
            .build()
    )
);

// Display the response
System.out.println("Response: " + response.getResult().getOutput().getText());
System.out.println("\nCitations:");

// Process citations
List<Citation> citations = (List<Citation>) response.getMetadata().get("citations");

if (citations != null && !citations.isEmpty()) {
    for (int i = 0; i < citations.size(); i++) {
        Citation citation = citations.get(i);
        System.out.println("\n[" + (i + 1) + "] " + citation.getDocumentTitle());
        System.out.println("    Location: " + citation.getLocationDescription());
        System.out.println("    Text: " + citation.getCitedText());
    }
} else {
    System.out.println("No citations were provided in the response.");
}

最佳实践

  1. 使用描述性标题:为引用文档提供有意义的标题,帮助用户在引用中识别来源。
  2. 检查引用是否为 null:并非所有响应都会包含引用,因此在访问前务必校验引用元数据存在。
  3. 考虑文档大小:较大的文档提供更多上下文但消耗更多输入令牌,并可能影响响应时间。
  4. 善用多个文档:回答跨越多来源的问题时,在单次请求中提供所有相关文档,而非发起多次调用。
  5. 使用合适的文档类型:简单内容选纯文本,既有文档选 PDF,当你需要对引用粒度做细粒度控制时用自定义内容块。

引用文档选项

上下文字段

可选地提供关于文档的上下文,它不会被引用,但能引导 Claude 的理解:

java
AnthropicCitationDocument document = AnthropicCitationDocument.builder()
    .plainText("...")
    .title("Legal Contract")
    .context("This is a merger agreement dated January 2024 between Company A and Company B")
    .build();

控制引用

默认情况下,所有文档的引用都是禁用的(选择性启用行为)。 要启用引用,显式设置 citationsEnabled(true):

java
AnthropicCitationDocument document = AnthropicCitationDocument.builder()
    .plainText("The Eiffel Tower was completed in 1889...")
    .title("Historical Facts")
    .citationsEnabled(true)  // Explicitly enable citations for this document
    .build();

你也可以提供不带引用的文档作为背景上下文:

java
AnthropicCitationDocument backgroundDoc = AnthropicCitationDocument.builder()
    .plainText("Background information about the industry...")
    .title("Context Document")
    // citationsEnabled defaults to false - Claude will use this but not cite it
    .build();

NOTE

Anthropic 要求一个请求内所有文档的引用设置保持一致。 你不能在同一请求中混用启用引用与禁用引用的文档。

提示词缓存

Anthropic 的提示词缓存 通过在各 API 调用间缓存重复的上下文来降低成本与延迟。Anthropic SDK 模块支持带可配置策略、TTL 和按消息类型设置的提示词缓存。

缓存策略

通过 AnthropicCacheStrategy 有五种缓存策略可用:

策略描述
NONE不缓存(默认)。不添加缓存控制头。
SYSTEM_ONLY缓存系统消息内容。使用 1 个缓存断点。
TOOLS_ONLY只缓存工具定义。使用 1 个缓存断点。
SYSTEM_AND_TOOLS缓存系统消息和工具定义两者。使用 2 个缓存断点。
CONVERSATION_HISTORY缓存系统消息、工具定义和对话消息。使用至多 4 个缓存断点。

NOTE

Anthropic 每请求允许最多 4 个缓存断点。实现会跟踪断点用量,达到上限后停止添加缓存控制。

基本用法

java
var options = AnthropicChatOptions.builder()
    .model("claude-sonnet-4-20250514")
    .maxTokens(1024)
    .cacheOptions(AnthropicCacheOptions.builder()
        .strategy(AnthropicCacheStrategy.SYSTEM_ONLY)
        .build())
    .build();

ChatResponse response = chatModel.call(
    new Prompt(List.of(
        new SystemMessage("You are an expert assistant with deep domain knowledge..."),
        new UserMessage("What is the capital of France?")),
        options));

缓存配置选项

AnthropicCacheOptions 为缓存行为提供细粒度控制:

java
var cacheOptions = AnthropicCacheOptions.builder()
    .strategy(AnthropicCacheStrategy.SYSTEM_AND_TOOLS)
    .messageTypeTtl(MessageType.SYSTEM, AnthropicCacheTtl.ONE_HOUR)     // 1 hour TTL
    .messageTypeMinContentLength(MessageType.SYSTEM, 100)                   // Min 100 chars
    .multiBlockSystemCaching(true)                                          // Per-block caching
    .build();
选项描述默认值
strategy要使用的缓存策略。NONE
messageTypeTtl每种消息类型的 TTL。可用值:FIVE_MINUTES、ONE_HOUR。所有类型为 FIVE_MINUTES
messageTypeMinContentLength缓存某消息类型前所需的最小内容长度。1
contentLengthFunction计算内容长度的自定义函数(例如令牌计数)。String::length
multiBlockSystemCaching为 true 时,每条系统消息成为一个可独立缓存的块;缓存控制施加在倒数第二块(静态前缀模式)。为 false 时,所有系统消息拼接为一个块。false
cacheToolResults为 true 时,会在请求最后一个工具结果消息上放置一个缓存断点,使工具输出在各工具调用轮间被缓存。在 CONVERSATION_HISTORY 下生效。false

缓存工具结果

当一个回合触发工具调用时,每一轮会追加一个助手 tool_use 消息和一个用户 tool_result 消息,而不断增长的历史会在每一轮被发回 Anthropic。默认情况下,CONVERSATION_HISTORY 断点落在最后一个用户消息上,所以工具结果——它们在其后被追加——落在所有断点之外,在每一轮都被计为未缓存输入的费用。当工具输出很大时这很浪费。

启用 cacheToolResults 会在请求的最后一个工具结果块上放置一个断点。下一轮时,Anthropic 从缓存读取上一轮的工具结果而非重新处理它们:

java
var cacheOptions = AnthropicCacheOptions.builder()
    .strategy(AnthropicCacheStrategy.CONVERSATION_HISTORY)
    .cacheToolResults(true)
    .build();

该断点在每一轮移到最新的工具结果,因此各轮早前的工具输出从缓存读取,而最新的被写入。这会额外占用一个缓存断点(Anthropic 每请求允许最多四个),在 CONVERSATION_HISTORY 下为工具、系统和最后用户断点留出空间。TTL 与最小内容长度可通过 messageTypeTtl(MessageType.TOOL, ...) 和 messageTypeMinContentLength(MessageType.TOOL, ...) 配置。

多块系统缓存

当你既有静态系统提示词又有动态指令时,使用多块系统缓存只缓存静态部分:

java
var cacheOptions = AnthropicCacheOptions.builder()
    .strategy(AnthropicCacheStrategy.SYSTEM_ONLY)
    .multiBlockSystemCaching(true)
    .build();

ChatResponse response = chatModel.call(
    new Prompt(List.of(
        new SystemMessage("You are an expert knowledge base assistant..."),  // Static (cached)
        new SystemMessage("Today's date is 2025-02-23. User timezone: PST"), // Dynamic
        new UserMessage("What are the latest updates?")),
        AnthropicChatOptions.builder()
            .model("claude-sonnet-4-20250514")
            .cacheOptions(cacheOptions)
            .build()));

Spring Boot 配置

通过 application.properties 或 application.yml 配置提示词缓存:

properties
spring.ai.anthropic.chat.cache-options.strategy=SYSTEM_AND_TOOLS
spring.ai.anthropic.chat.cache-options.multi-block-system-caching=true

访问缓存令牌用量

缓存令牌指标可通过原生 SDK 的 Usage 对象获得:

java
ChatResponse response = chatModel.call(prompt);

com.anthropic.models.messages.Usage sdkUsage =
    (com.anthropic.models.messages.Usage) response.getMetadata().getUsage().getNativeUsage();
long cacheCreation = sdkUsage.cacheCreationInputTokens().orElse(0L);
long cacheRead = sdkUsage.cacheReadInputTokens().orElse(0L);

System.out.println("Cache creation tokens: " + cacheCreation);
System.out.println("Cache read tokens: " + cacheRead);

在首个请求上,cacheCreationInputTokens 会非零(写入缓存的令牌)。在带相同缓存前缀的后续请求上,cacheReadInputTokens 会非零(以降低成本从缓存读取的令牌)。

对话历史缓存

CONVERSATION_HISTORY 策略缓存整个对话上下文,包括系统消息、工具定义和最后一个用户消息。这对多轮对话很有用,否则不断增长的上下文会在每个请求被重新处理:

java
var cacheOptions = AnthropicCacheOptions.builder()
    .strategy(AnthropicCacheStrategy.CONVERSATION_HISTORY)
    .build();

var options = AnthropicChatOptions.builder()
    .model("claude-sonnet-4-20250514")
    .cacheOptions(cacheOptions)
    .build();

// First turn
ChatResponse response1 = chatModel.call(
    new Prompt(List.of(
        new SystemMessage("You are a helpful assistant."),
        new UserMessage("What is machine learning?")),
        options));

// Second turn - previous context is cached
ChatResponse response2 = chatModel.call(
    new Prompt(List.of(
        new SystemMessage("You are a helpful assistant."),
        new UserMessage("What is machine learning?"),
        new AssistantMessage(response1.getResult().getOutput().getText()),
        new UserMessage("Can you give me an example?")),
        options));

结构化输出

结构化输出把 Claude 约束为产出符合某个 JSON 模式的响应。Anthropic SDK 模块还支持 Anthropic 的力度控制,用于在响应质量与速度之间调优。

NOTE

模型要求 结构化输出和力度控制需要 claude-sonnet-4-6 或更新版本。像 claude-sonnet-4-20250514 这样的旧模型不支持这些特性。 模式要求 使用 JSON 模式输出时,Anthropic 要求模式中所有对象类型都带 "additionalProperties": false。

JSON 模式输出

用 outputSchema 便捷方法把 Claude 的响应约束到某个特定的 JSON 模式:

java
var options = AnthropicChatOptions.builder()
    .model("claude-sonnet-4-6")
    .outputSchema("""
        {
            "type": "object",
            "properties": {
                "name": {"type": "string"},
                "capital": {"type": "string"},
                "population": {"type": "integer"}
            },
            "required": ["name", "capital"],
            "additionalProperties": false
        }
        """)
    .build();

ChatResponse response = chatModel.call(new Prompt("Tell me about France.", options));
// Response text will be valid JSON conforming to the schema

力度控制

控制 Claude 在其响应上投入多少计算。较低力度意味着更快更便宜的响应;较高力度意味着更彻底的推理。

力度等级描述
LOW快速简洁的响应,推理最少
MEDIUM在速度与彻底性之间权衡
HIGH更彻底的推理与更详细的响应
MAX为最尽可能彻底的响应投入最大计算
java
var options = AnthropicChatOptions.builder()
    .model("claude-sonnet-4-6")
    .effort(OutputConfig.Effort.LOW)
    .build();

ChatResponse response = chatModel.call(new Prompt("What is the capital of France?", options));

组合模式与力度

你可以把 JSON 模式输出与力度控制相结合:

java
var options = AnthropicChatOptions.builder()
    .model("claude-sonnet-4-6")
    .outputSchema("""
        {
            "type": "object",
            "properties": {
                "answer": {"type": "integer"},
                "explanation": {"type": "string"}
            },
            "required": ["answer", "explanation"],
            "additionalProperties": false
        }
        """)
    .effort(OutputConfig.Effort.HIGH)
    .build();

ChatResponse response = chatModel.call(
    new Prompt("What is 15 * 23? Show your reasoning.", options));

直接使用 OutputConfig

要完全控制,直接使用 SDK 的 OutputConfig:

java
import com.anthropic.models.messages.OutputConfig;
import com.anthropic.models.messages.JsonOutputFormat;
import com.anthropic.core.JsonValue;

var outputConfig = OutputConfig.builder()
    .effort(OutputConfig.Effort.HIGH)
    .format(JsonOutputFormat.builder()
        .schema(JsonOutputFormat.Schema.builder()
            .putAdditionalProperty("type", JsonValue.from("object"))
            .putAdditionalProperty("properties", JsonValue.from(Map.of(
                "name", Map.of("type", "string"))))
            .putAdditionalProperty("additionalProperties", JsonValue.from(false))
            .build())
        .build())
    .build();

var options = AnthropicChatOptions.builder()
    .model("claude-sonnet-4-6")
    .outputConfig(outputConfig)
    .build();

ChatResponse response = chatModel.call(new Prompt("Tell me about France.", options));

StructuredOutputChatOptions 接口

AnthropicChatOptions 实现了 StructuredOutputChatOptions 接口,它提供可移植的 getOutputSchema() 方法。这让结构化输出能与 Spring AI 的通用结构化输出基础设施协作。

服务层

Anthropic 提供不同的服务层,控制 API 请求的容量路由。你可以用 AnthropicServiceTier.AUTO 在可用时伺机使用优先容量(更低延迟),或用 STANDARD_ONLY 始终使用标准容量。

通过 Spring Boot 属性:

properties
spring.ai.anthropic.chat.service-tier=auto

或以编程方式逐请求:

java
var options = AnthropicChatOptions.builder()
    .serviceTier(AnthropicServiceTier.AUTO)
    .build();

ChatResponse response = chatModel.call(new Prompt("Hello", options));

逐请求 HTTP 头

Anthropic SDK 模块支持逐请求 HTTP 头,它们会被注入到单个 API 调用。这不同于 customHeaders(后者在客户端层为所有请求设置)。

逐请求头对以下用途很有用:

  • 请求跟踪:为每个请求添加关联 ID 或追踪头
  • Beta API 访问:为特定请求包含 beta 特性头
  • 路由:为负载均衡添加路由或优先级头
java
var options = AnthropicChatOptions.builder()
    .httpHeaders(Map.of(
        "X-Request-Id", "req-12345",
        "X-Custom-Tracking", "my-tracking-value"))
    .build();

ChatResponse response = chatModel.call(new Prompt("Hello", options));

NOTE

httpHeaders 是逐请求的,通过 MessageCreateParams.putAdditionalHeader() 设置。它们不影响其他请求。对于应适用于所有请求的头,请改用 customHeaders。

示例控制器

下面是一个简单 @RestController 类的示例,它使用聊天模型进行文本生成:

java
@RestController
public class ChatController {

    private final AnthropicChatModel chatModel;

    public ChatController() {
        var options = AnthropicChatOptions.builder()
            .model("claude-sonnet-4-20250514")
            .maxTokens(1024)
            .apiKey(System.getenv("ANTHROPIC_API_KEY"))
            .build();
        this.chatModel = AnthropicChatModel.builder()
            .options(options)
            .build();
    }

    @GetMapping("/ai/generate")
    public Map<String, String> generate(
            @RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
        return Map.of("generation", chatModel.call(message));
    }

    @GetMapping("/ai/generateStream")
    public Flux<ChatResponse> generateStream(
            @RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
        Prompt prompt = new Prompt(new UserMessage(message));
        return chatModel.stream(prompt);
    }
}

访问原始响应

完整的 Anthropic SDK Message 对象可在响应元数据中 "anthropic-response" 键下获得。它提供对 Spring AI 抽象未显式映射的任何字段的访问:

java
ChatResponse response = chatModel.call(new Prompt("Hello"));

com.anthropic.models.messages.Message rawMessage =
    (com.anthropic.models.messages.Message) response.getMetadata().get("anthropic-response");

// Access native SDK fields
rawMessage.stopReason();    // Optional<StopReason>
rawMessage.content();       // List<ContentBlock>
rawMessage.usage();         // Usage with cache token details

NOTE

原始响应仅对同步调用可用。流式响应不包含它。

技能

Anthropic 的 Skills API 通过专业化、预打包的文档生成能力扩展 Claude 的能力。 技能让 Claude 能创建真正可下载的文件——Excel 电子表格、PowerPoint 演示文稿、Word 文档和 PDF——而非仅仅描述这些文档可能包含什么。

NOTE

受支持的模型 技能在 Claude Sonnet 4、Claude Sonnet 4.5、Claude Opus 4 及更新模型上受支持。 要求

  • 技能需要代码执行能力(当配置技能时由 Spring AI 自动启用)
  • 每请求最多 8 个技能
  • 生成的文件通过 Files API 可下载,保留 24 小时

预置的 Anthropic 技能

Spring AI 通过 AnthropicSkill 枚举为 Anthropic 的预置技能提供类型安全的访问:

技能描述生成文件类型
XLSXExcel 电子表格生成与操作.xlsx(Microsoft Excel)
PPTXPowerPoint 演示文稿创建.pptx(Microsoft PowerPoint)
DOCXWord 文档生成.docx(Microsoft Word)
PDFPDF 文档创建.pdf(便携文档格式)

基本用法

把它们添加到你的 AnthropicChatOptions 以启用技能:

java
ChatResponse response = chatModel.call(
    new Prompt(
        "Create an Excel spreadsheet with Q1 2025 sales data. " +
        "Include columns for Month, Revenue, and Expenses with 3 rows of sample data.",
        AnthropicChatOptions.builder()
            .model(Model.CLAUDE_SONNET_4_5)
            .maxTokens(4096)
            .skill(AnthropicSkill.XLSX)
            .build()
    )
);

// Claude will generate an actual Excel file
String responseText = response.getResult().getOutput().getText();
System.out.println(responseText);
// Output: "I've created an Excel spreadsheet with your Q1 2025 sales data..."

多个技能

你可以在单次请求中启用多个技能(最多 8 个):

java
ChatResponse response = chatModel.call(
    new Prompt(
        "Create a sales report with both an Excel file containing the raw data " +
        "and a PowerPoint presentation summarizing the key findings.",
        AnthropicChatOptions.builder()
            .model(Model.CLAUDE_SONNET_4_5)
            .maxTokens(8192)
            .skill(AnthropicSkill.XLSX)
            .skill(AnthropicSkill.PPTX)
            .build()
    )
);

使用 AnthropicSkillContainer 做高级配置

要对技能类型和版本获得更多控制,直接使用 AnthropicSkillContainer:

java
AnthropicSkillContainer container = AnthropicSkillContainer.builder()
    .skill(AnthropicSkill.XLSX)
    .skill(AnthropicSkill.PPTX, "20251013") // Specific version
    .build();

ChatResponse response = chatModel.call(
    new Prompt(
        "Generate the quarterly report",
        AnthropicChatOptions.builder()
            .model(Model.CLAUDE_SONNET_4_5)
            .maxTokens(4096)
            .skillContainer(container)
            .build()
    )
);

下载生成的文件

当 Claude 使用技能生成文件时,响应包含可用于通过 Files API 下载实际文件的文件 ID。 Spring AI 提供 AnthropicSkillsResponseHelper 工具类用于提取文件 ID 和下载文件。

提取文件 ID

java
import org.springframework.ai.anthropic.AnthropicSkillsResponseHelper;

ChatResponse response = chatModel.call(prompt);

// Extract all file IDs from the response
List<String> fileIds = AnthropicSkillsResponseHelper.extractFileIds(response);

for (String fileId : fileIds) {
    System.out.println("Generated file ID: " + fileId);
}

下载所有文件

AnthropicSkillsResponseHelper 提供一个便捷方法以一次性下载所有生成的文件。这需要 AnthropicClient 实例(与创建聊天模型所用的是同一个):

java
import com.anthropic.client.AnthropicClient;

@Autowired
private AnthropicClient anthropicClient;

// Download all files to a target directory
Path targetDir = Path.of("generated-files");
Files.createDirectories(targetDir);

List<Path> savedFiles = AnthropicSkillsResponseHelper.downloadAllFiles(
        response, anthropicClient, targetDir);

for (Path file : savedFiles) {
    System.out.println("Downloaded: " + file.getFileName() +
                       " (" + Files.size(file) + " bytes)");
}

提取容器 ID

对于带技能的多轮对话,你可以提取容器 ID 以供复用:

java
String containerId = AnthropicSkillsResponseHelper.extractContainerId(response);

if (containerId != null) {
    System.out.println("Container ID for reuse: " + containerId);
}

完整示例

下面是一个展示技能连同样文件下载的完整示例:

java
@Service
public class DocumentGenerationService {

    private final AnthropicChatModel chatModel;
    private final AnthropicClient anthropicClient;

    public DocumentGenerationService(AnthropicChatModel chatModel,
                                     AnthropicClient anthropicClient) {
        this.chatModel = chatModel;
        this.anthropicClient = anthropicClient;
    }

    public Path generateSalesReport(String quarter, Path outputDir) throws IOException {
        // Generate Excel report using Skills
        ChatResponse response = chatModel.call(
            new Prompt(
                "Create an Excel spreadsheet with " + quarter + " sales data. " +
                "Include Month, Revenue, Expenses, and Profit columns.",
                AnthropicChatOptions.builder()
                    .model(Model.CLAUDE_SONNET_4_5)
                    .maxTokens(4096)
                    .skill(AnthropicSkill.XLSX)
                    .build()
            )
        );

        // Extract file IDs from the response
        List<String> fileIds = AnthropicSkillsResponseHelper.extractFileIds(response);

        if (fileIds.isEmpty()) {
            throw new RuntimeException("No file was generated");
        }

        // Download all generated files
        List<Path> savedFiles = AnthropicSkillsResponseHelper.downloadAllFiles(
                response, anthropicClient, outputDir);

        return savedFiles.get(0);
    }
}

最佳实践

  1. 使用合适的模型:技能在 Claude Sonnet 4 及更新模型上表现最佳。确保你使用的是受支持的模型。

  2. 设置足够的最大令牌数:文档生成可能需要大量令牌。对复杂文档使用 maxTokens(4096) 或更高。

  3. 在提示词中具体明确:就文档结构、内容和格式提供清晰详细的指令。

  4. 及时处理文件下载:生成的文件 24 小时后过期。生成后尽快下载文件。

  5. 检查文件 ID:在尝试下载前务必核实是否返回了文件 ID。某些提示词可能产生不含文件生成的文本响应。

  6. 使用防御性错误处理:把文件操作包在 try-catch 块中,以优雅处理网络问题或过期文件。

java
List<String> fileIds = AnthropicSkillsResponseHelper.extractFileIds(response);

if (fileIds.isEmpty()) {
    // Claude may have responded with text instead of generating a file
    String text = response.getResult().getOutput().getText();
    log.warn("No files generated. Response: {}", text);
    return;
}

try {
    List<Path> files = AnthropicSkillsResponseHelper.downloadAllFiles(
            response, anthropicClient, targetDir);
    // Process files...
} catch (IOException e) {
    log.error("Failed to download file: {}", e.getMessage());
}

网络搜索

Anthropic 的 Web Search 工具允许 Claude 在对话期间搜索网络并用结果生成带引用的响应。

NOTE

  • 网络搜索是一个内建的服务端工具——不需要外部工具回调
  • 网络搜索结果自动包含带 URL 的引用
  • 你可以在同一请求中把网络搜索与其他工具(函数调用、代码执行、技能)结合

基本用法

向你的聊天选项添加一个 AnthropicWebSearchTool 以启用网络搜索:

java
var webSearch = AnthropicWebSearchTool.builder().build();

ChatResponse response = chatModel.call(
    new Prompt("What is the latest released version of Spring AI?",
        AnthropicChatOptions.builder()
            .webSearchTool(webSearch)
            .build()));

String answer = response.getResult().getOutput().getText();

配置选项

选项描述默认值
maxUses每次请求 Claude 可执行网络搜索的最大次数-
allowedDomains只把搜索结果限制在这些域名-
blockedDomains从搜索结果中排除这些域名-
userLocation用于本地化结果的大致用户位置(城市、国家、区域、时区)-

域名过滤

限制或从搜索结果中排除特定域名:

java
var webSearch = AnthropicWebSearchTool.builder()
    .allowedDomains(List.of("docs.spring.io", "github.com"))
    .blockedDomains(List.of("example.com"))
    .maxUses(5)
    .build();

用户位置

提供大致位置以本地化搜索结果:

java
var webSearch = AnthropicWebSearchTool.builder()
    .userLocation("San Francisco", "US", "California", "America/Los_Angeles")
    .build();

访问网络搜索结果

网络搜索结果和引用可在响应元数据中获得:

java
ChatResponse response = chatModel.call(
    new Prompt("What happened in tech news today?",
        AnthropicChatOptions.builder()
            .webSearchTool(AnthropicWebSearchTool.builder().build())
            .build()));

// Get web search results
@SuppressWarnings("unchecked")
List<AnthropicWebSearchResult> results =
    (List<AnthropicWebSearchResult>) response.getMetadata().get("web-search-results");

if (results != null) {
    for (AnthropicWebSearchResult result : results) {
        System.out.println("Title: " + result.title());
        System.out.println("URL: " + result.url());
        System.out.println("Page age: " + result.pageAge());
    }
}

// Get web search citations
@SuppressWarnings("unchecked")
List<Citation> citations =
    (List<Citation>) response.getMetadata().get("citations");

if (citations != null) {
    for (Citation citation : citations) {
        if (citation.getType() == Citation.LocationType.WEB_SEARCH_RESULT_LOCATION) {
            System.out.println("Source: " + citation.getUrl());
            System.out.println("Title: " + citation.getDocumentTitle());
            System.out.println("Cited text: " + citation.getCitedText());
        }
    }
}

Spring Boot 配置

通过 application.properties 或 application.yml 配置网络搜索:

properties
spring.ai.anthropic.chat.web-search-tool.max-uses=5
spring.ai.anthropic.chat.web-search-tool.allowed-domains=docs.spring.io,github.com
spring.ai.anthropic.chat.web-search-tool.user-location.city=San Francisco
spring.ai.anthropic.chat.web-search-tool.user-location.country=US

可观测性

对每次 Anthropic 调用,Spring AI 在两层发出 Micrometer 观测:

  • 聊天模型层。 一个 gen_ai.client.operation 观测包裹着对 AnthropicChatModel.call(...) 或 .stream(...) 的每次调用,携带请求参数、响应元数据和令牌用量。标签与指标的完整集合参见可观测性参考。
  • HTTP 层。 一个 okhttp.requests 观测包裹着对 Anthropic API 的每次 HTTP 尝试,携带 HTTP 方法、URI、状态码和异常标签。每个请求还会在线路上向任何下游服务(AI 网关、OpenAI 兼容推理服务器、代理)传播 traceparent。

在一个带 spring-boot-starter-actuator 和追踪桥(micrometer-tracing-bridge-otel 或 micrometer-tracing-bridge-brave)的 Spring Boot 应用中,两层都会被自动装配——无需显式开启。 对于非 Boot 应用,把一个 ObservationRegistry 传给 AnthropicChatModel.builder():

java
AnthropicChatModel chatModel = AnthropicChatModel.builder()
    .options(...)
    .observationRegistry(observationRegistry)
    .build();

连接池指标(选择性启用)

Spring AI 还能把 OkHttp 连接池 gauge(带 state=active|idle 和 client.kind=sync|async 的 okhttp.pool.connection.count)绑定到应用的 MeterRegistry。 这些是次级遥测——对容量调优有用,但对追踪或每请求延迟并非必需——因此默认禁用。

要在 Spring Boot 应用中启用它们:

properties
spring.ai.anthropic.chat.connection-pool-metrics-enabled=true

对于非 Boot 应用,把一个 MeterRegistry 直接传给构建器;只要提供了注册表,gauge 就会绑定:

java
AnthropicChatModel chatModel = AnthropicChatModel.builder()
    .options(...)
    .observationRegistry(observationRegistry)
    .meterRegistry(meterRegistry)
    .build();

HTTP 调度执行器(高级)

默认情况下 Spring AI 管理 OkHttp 调度器的执行器——一个无界的平台线程池,复刻 SDK 的原生行为。 对于高 HTTP 并发的负载,或为了利用 Java 21+ 虚拟线程,你可以提供自己的 ExecutorService:

java
AnthropicChatModel chatModel = AnthropicChatModel.builder()
    .options(...)
    .dispatcherExecutor(Executors.newVirtualThreadPerTaskExecutor())
    .build();

同一个执行器同时支撑同步和异步(流式)客户端。 当你提供自己的执行器时,你就拥有它的生命周期——Spring AI 绝不会对它调用 shutdown()。 当省略时,内部执行器会被自动创建与清理。

WARNING

流式 HTTP 观测并不作为聊天模型观测的子级 对于同步调用,okhttp.requests span 正确地嵌套在 gen_ai.client.operation span 之下——完整追踪树如期形成。 对于流式调用,okhttp.requests span 会触发并被记录进注册表,计时器被记录进 MeterRegistry,traceparent 仍在出站请求上传播——但该 span 不会嵌套在聊天模型 span 之下。 这是因为 Anthropic SDK 的异步流式实现在调用 Spring AI 的 HTTP 客户端前会跳到 ForkJoinPool.commonPool(),从而在该边界丢弃调用线程的观测上下文。 Spring AI 无法在其 HTTP 客户端包装器内部恢复该上下文。 如果你需要在追踪 UI 中把流式 HTTP span 与父聊天模型 span 关联,可按 span 名 okhttp.requests 且 host api.anthropic.com 过滤,并用追踪 ID 或时间戳范围连接到对应的 gen_ai.client.operation span。 流式的内置父级关联正在 Anthropic Java SDK 上游推进。

日志

通过设置环境变量启用 SDK 日志:

bash
export ANTHROPIC_LOG=debug

限制

以下特性暂不受支持:

  • Amazon Bedrock 后端
  • Google Vertex AI 后端

这些特性计划在未来版本推出。

定制 HTTP 客户端

Spring AI 底层使用官方的 anthropic-java SDK,并用 SpringAiAnthropicHttpClient.Builder 构建的一个自定义 OkHttp 客户端来配置其 HTTP 传输。 通过暴露一个或多个 AnthropicHttpClientBuilderCustomizer bean,你可以在底层 OkHttpClient 被创建之前拦截那个构建器。

java
@FunctionalInterface
public interface AnthropicHttpClientBuilderCustomizer {
    void customize(SpringAiAnthropicHttpClient.Builder builder);
}

典型用例包括:

  • 注册 OkHttp 的 Interceptor 实例(身份认证、传播头、自定义日志);
  • 更换调度器的 ExecutorService(例如,把异步 I/O 路由到虚拟线程);
  • 配置代理、SSL、主机名验证,或构建器暴露的连接池大小。

当应用上下文中存在多个定制器 bean 时,它们会按 @Order / @Priority 顺序、在 Spring AI 自己的默认值之后应用,因此用户代码优先。

NOTE

每个定制器在每个 AnthropicChatModel 上会被调用两次——一次为该模型支撑的同步客户端,一次为异步客户端。

实现应当是幂等的(即多次调用也安全)。

当通过 AnthropicChatModel.Builder 手动装配模型时,同样的钩子也可用。在这种情况下,定制器按注册顺序应用;在 Spring 的 bean 容器之外,@Order 注解不起作用:

java
var chatModel = AnthropicChatModel.builder()
    .options(AnthropicChatOptions.builder().model("claude-sonnet-4-20250514").build())
    .httpClientBuilderCustomizer(myCustomizer)
    .build();

其他资源

BytesRouter 是社区翻译站点,所有译文仅供参考,一切以官方英文原文为准。