跳到主要内容
版本:2.0.1

Anthropic 聊天

QWen 3.8 中英对照 Anthropic Anthropic Chat

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

先决条件​

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

添加仓库和 BOM​

Spring AI 构件已发布在 Maven Central 和 Spring Snapshot 仓库中。请参见 Artifact Repositories 部分,以将这些仓库添加到你的构建系统中。

为了辅助依赖管理,Spring AI 提供了一个 BOM(物料清单),以确保在整个项目中统一使用相同版本的 Spring AI。请参考 依赖管理 部分,将 Spring AI BOM 添加到你的构建系统中。

自动配置​

Spring Boot 自动配置可通过 spring-ai-starter-model-anthropic 启动器提供。

将其添加到项目的 Maven pom.xml 文件中:

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

请参见 依赖管理 章节,将 Spring AI BOM 添加到你的构建文件中。

配置属性​

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

属性描述默认值
spring.ai.anthropic.api-keyAnthropic API 密钥-
spring.ai.anthropic.base-urlAPI 基础 URL[api.anthropic.com](https://api.anthropic.com)
spring.ai.anthropic.timeout启动时注入到共享 HTTP 客户端中的构建时默认超时时间。可通过设置在 ChatClient 或每次请求的提示选项上的 AnthropicChatOptions.timeout() 进行每次调用覆盖。60s
spring.ai.anthropic.max-retries失败请求的最大重试次数2
spring.ai.anthropic.custom-headers.*添加到所有 Anthropic 客户端请求的自定义 HTTP 头-
spring.ai.anthropic.chat.model模型名称claude-haiku-4-5
spring.ai.anthropic.chat.max-tokens最大 Token 数4096
spring.ai.anthropic.chat.temperature采样温度-
spring.ai.anthropic.chat.top-pTop-p 采样-
spring.ai.anthropic.chat.top-kTop-k 采样-
spring.ai.anthropic.chat.cache-options.strategy要使用的缓存策略NONE
spring.ai.anthropic.chat.cache-options.multi-block-system-caching为每个系统消息使用单独的块false
spring.ai.anthropic.chat.http-headers传递给单个 API 调用的每次请求 HTTP 头。-
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用于本地化搜索结果的市名-
spring.ai.anthropic.chat.web-search-tool.user-location.countryISO 3166-1 alpha-2 国家代码-
spring.ai.anthropic.chat.web-search-tool.user-location.region地区或州-
spring.ai.anthropic.chat.web-search-tool.user-location.timezoneIANA 时区标识符-
spring.ai.anthropic.chat.service-tier容量路由:auto(如果可用则使用优先级)或 standard_only(始终使用标准)。参见 Service Tiers。-

手动配置​

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

将 spring-ai-anthropic 依赖项添加到您项目的 Maven pom.xml 文件中:

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

参考依赖管理部分,将 Spring AI BOM 添加到你的构建文件中。

身份验证​

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

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 自动检测:

export ANTHROPIC_API_KEY=<your-api-key>
// 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();

基础用法​

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 类提供模型配置,例如要使用的模型、温度、最大 tokens 等。

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

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

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 模型。claude-sonnet-4-20250514
maxTokens响应中允许生成的最大 token 数量。4096
temperature控制响应的随机性。值越高,输出越随机;值越低,输出越确定。取值范围:0.0–1.01.0
topP核采样参数。模型将仅考虑概率质量前 top_p 的 token。仅支持 Claude Opus 4.6 及更早版本的模型。对于 Claude Opus 4.6 之后发布的模型(如 claude-opus-4-7),Anthropic 已弃用该参数;若值 < 0.99,将返回 HTTP 400 错误;若值 >= 0.99,则作为向后兼容的空操作被接受(不会实际进行过滤)。-
topK仅从每个 token 的前 K 个候选项中进行采样。仅支持 Claude Opus 4.6 及更早版本的模型。对于 Claude Opus 4.6 之后发布的模型(如 claude-opus-4-7),Anthropic 已弃用该参数;在这些模型上,任何值都会导致 HTTP 400 错误。-
stopSequences自定义停止序列,触发后模型将停止生成。-
apiKey用于身份验证的 API 密钥。若未显式设置,将自动从环境变量 ANTHROPIC_API_KEY 中检测。-
baseUrlAnthropic API 的基础 URL。api.anthropic.com
timeout单次调用的超时时间覆盖值。通过 ChatClient.Builder.defaultOptions() 或单请求的 ChatClient.prompt().options(…​) 设置时,仅限制该次调用,并覆盖构建时的 spring.ai.anthropic.timeout 默认值。60 秒
maxRetries失败请求的最大重试次数。2
proxyHTTP 客户端的代理设置。-
customHeaders包含在所有请求中的自定义 HTTP 头(客户端级别)。-
httpHeaders单请求的 HTTP 头。这些头通过 MessageCreateParams.putAdditionalHeader() 添加到各个 API 调用中。适用于请求级跟踪、Beta API 头或路由场景。-
cacheOptions配置提示缓存行为的选项。包括缓存策略、多块缓存、TTL 和内容长度要求。-
thinking思维配置。可使用便捷构建器 thinkingEnabled(budgetTokens)、thinkingEnabled(budgetTokens, display)、thinkingAdaptive()、thinkingAdaptive(display) 或 thinkingDisabled(),或直接传入原始的 ThinkingConfigParam。display 参数控制思维内容在响应中的呈现方式:SUMMARIZED(摘要)或 OMITTED(隐藏,仅保留签名)。-
outputConfig结构化输出(JSON Schema)和力度控制的输出配置。使用 outputConfig(OutputConfig) 可完全控制,或使用便捷方法 outputSchema(String) 和 effort(OutputConfig.Effort)。需要 claude-sonnet-4-6 或更新版本。-
inferenceGeo控制请求处理的地理区域。支持的值:us、eu。用于数据驻留合规。可通过 spring.ai.anthropic.chat.inference-geo 配置。-
serviceTier控制请求的容量路由。使用 MessageCreateParams.ServiceTier.AUTO 可机会性地使用优先容量,或使用 STANDARD_ONLY 始终使用标准容量。详见 Service Tiers。-

工具调用选项​

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

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

速率限制元数据​

Anthropic API 会在每个响应中包含 速率限制头,用于报告当前的请求和 Token 预算,以及每个窗口的重置时间。Spring AI 通过标准的 ChatResponseMetadata#getRateLimit() 访问器暴露这些数据,该访问器返回一个从响应头中填充的 AnthropicRateLimit 对象。

便携式 RateLimit 接口公开了请求和令牌族:

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 具体类型公开:

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。

备注

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

工具调用​

AnthropicChatModel 支持工具调用(tool calling)。Claude 可以请求执行工具,但其本身不会运行工具 —— Spring AI 负责处理执行。

对于大多数应用,请配合自动注册的 ToolCallingAdvisor 使用 ChatClient — 参见 Tool Calling。若需进行低级控制并由你自行驱动循环(例如,将工具调用与 prompt caching 结合使用时),请参见 ChatModel Tool Calling。

工具选择选项​

使用 toolChoice 选项控制 Claude 选择的工具:

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();
提示

Anthropic Java SDK 为常用的工具选择提供了便捷的静态工厂方法,可以使你的代码更加简洁:

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

流式传输​

Anthropic SDK 模块支持同步和流式响应。流式功能允许 Claude 在响应生成过程中增量返回内容。

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 模型支持“thinking”功能,允许模型在提供最终答案前展示其推理过程。这对于需要逐步推理的复杂问题尤为有用,例如数学、逻辑和分析任务。

备注

支持模型

思考功能由以下 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. 将 temperature 设置为 1.0:启用思考功能时为必需设置。

便捷构建器方法​

AnthropicChatOptions.Builder 提供了用于配置思考行为的便捷方法:

// 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:

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 参数来控制这一点,以降低 token 成本:

  • SUMMARIZED — Claude 仍会进行完整思考,但返回的是精简后的摘要,而非原始的思维链(chain-of-thought)。在提供推理洞察的同时,减少了输出 token 数量。

  • OMITTED — 思考过程会执行,但会被完全从响应中删除。仅返回一个加密签名(用于多轮对话的连续性)。输出 token 成本最低。

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();
备注

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

非流式示例​

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());
}
}

流式示例​

在流式模式下完全支持 Thinking。Thinking 增量(Thinking deltas)和签名增量(signature deltas)会在到达时实时发出:

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("thinking")) {
// Thinking delta — getText() returns null; the full accumulated thinking text
// is available via AnthropicAssistantMessage.getThinkingContents() on the
// aggregated response after streaming completes.
System.out.println("[thinking...]");
}
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());
}
});

响应结构​

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

内容类型元数据键描述
思维块signature带有加密签名的 Claude 推理文本。在同步模式下,思维文本位于 getText(),签名位于 getMetadata().get("signature")。
已编辑的思维data经过安全审查的推理内容。仅包含一个 data 标记,没有可见文本。
签名(流式传输)signature在流式传输模式下,签名作为单独的增量部分出现在思维块的末尾。
思维增量(流式传输)thinking流式传输过程中的增量思维片段。thinking 元数据键被设置为 true。在这些片段上,getText() 返回 null;完整的思维文本可以通过聚合响应中的 AnthropicAssistantMessage.getThinkingContents() 获取。
文本块(无)位于 getText() 中的最终回答文本。

借助工具调用进行思考​

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

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

多模态支持​

Anthropic SDK 模块支持多模态输入,允许您在提示词中同时发送图像、PDF 文档和文本。

图像输入​

使用 Media 类将图像发送给 Claude 以进行分析:

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 进行分析:

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)));

多个媒体项​

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

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 的 Citations API 允许 Claude 在生成响应时引用所提供文档中的特定部分。当提示中包含引用文档时,Claude 可以引用源材料,并在响应元数据中返回引用元数据(字符范围、页码或内容块)。

引用有助于改善:

  • 准确性验证:用户可以将 Claude 的响应与源材料进行核对

  • 透明性:查看文档中哪些具体部分影响了响应内容

  • 合规性:满足受监管行业对信息来源标注的要求

  • 信任:通过展示信息来源来建立信心

备注

支持的模型

引用功能在 Claude 3.7 Sonnet 和 Claude 4 模型(Opus 和 Sonnet)上受支持。

文档类型

支持三种类型的引用文档:

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

创建引文文档​

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

纯文本文档​

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 文档​

// 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();

自定义内容块​

用于细粒度引用控制,请使用自定义内容块:

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();

在请求中使用引用​

在聊天选项中包含引用文档:

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 提供多个文档以供参考:

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()
)
);

访问引文​

响应元数据中会返回引用信息:

ChatResponse response = chatModel.call(prompt);

// Get citations from metadata
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();
}

引用类型​

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

字符位置(纯文本)​

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

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);
}

Page Location (PDF)​

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

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);
}

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

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

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);
}

完整示例​

下面是一个完整示例,演示引文的使用:

// 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. 检查空引用:并非所有响应都会包含引用,因此,在访问引用元数据之前,务必验证其是否存在。

  3. 考虑文档大小:较大的文档提供更多的上下文,但会消耗更多的输入 token,并可能影响响应时间。

  4. 利用多个文档:当回答跨越多个来源的问题时,应在单次请求中提供所有相关文档,而不是进行多次调用。

  5. 使用适当的文档类型:对于简单内容选择纯文本,对于现有文档选择 PDF,当需要对引用粒度进行细粒度控制时,使用自定义内容块。

引用文档选项​

上下文字段​

可选择性地提供关于文档的背景信息,这些信息不会被引用,但有助于 Claude 的理解:

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):

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

您也可以提供不包含引用的文档作为背景上下文:

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();
备注

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

提示词缓存​

Anthropic 的 Prompt Caching 通过在 API 调用间缓存重复的上下文来降低成本和延迟。Anthropic SDK 模块支持 Prompt Caching,并允许配置策略、TTL 以及针对每种消息类型的设置。

缓存策略​

AnthropicCacheStrategy 提供了五种缓存策略:

策略描述
NONE无缓存(默认值)。不添加任何缓存控制头。
SYSTEM_ONLY缓存系统消息内容。使用 1 个缓存断点。
TOOLS_ONLY仅缓存工具定义。使用 1 个缓存断点。
SYSTEM_AND_TOOLS同时缓存系统消息和工具定义。使用 2 个缓存断点。
CONVERSATION_HISTORY缓存系统消息、工具定义和对话消息。最多使用 4 个缓存断点。
备注

Anthropic 允许每个请求最多使用 4 个缓存断点。该实现会跟踪断点的使用情况,并在达到限制时停止添加缓存控制。

基本用法​

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 提供对缓存行为的细粒度控制:

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用于计算内容长度的自定义函数(例如,token 计数)。String::length
multiBlockSystemCaching当设置为 true 时,每条系统消息都会成为一个独立的可缓存块;缓存控制应用于倒数第二个块(静态前缀模式)。当设置为 false 时,所有系统消息将被合并为一个块。false
cacheToolResults当设置为 true 时,会在请求的最后一条工具结果消息上放置缓存断点,从而在工具调用轮次之间缓存工具输出。需与 CONVERSATION_HISTORY 配合生效。false

缓存工具结果​

当回合触发工具调用时,每一轮都会追加一条助手的 tool_use 消息和一条用户的 tool_result 消息,并且不断增长的历史记录会在每一轮中重新发送给 Anthropic。默认情况下,CONVERSATION_HISTORY 断点位于最后一条用户消息处,因此追加在其后的工具结果将落在所有断点之外,在每一轮中都会被计费为未缓存的输入。当工具输出较大时,这种开销是浪费的。

启用 cacheToolResults,可在请求的最后一个工具结果块上设置断点。下一轮中,Anthropic 将从缓存中读取上一轮的工具结果,而不是重新处理它们:

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

断点在每一轮都移动到最新的工具结果上,因此前几轮的工具输出会从缓存中读取,而最新的结果则会被写入。这会额外消耗一个缓存断点(Anthropic 允许每个请求最多四个),从而为 CONVERSATION_HISTORY 下的工具、系统和最后一条用户消息断点留出空间。TTL 和最小内容长度可通过 messageTypeTtl(MessageType.TOOL, …​) 和 messageTypeMinContentLength(MessageType.TOOL, …​) 进行配置。

多块系统缓存​

当同时具有静态系统提示和动态指令时,请使用多块系统缓存,仅对静态部分进行缓存:

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 配置提示词缓存:

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

访问缓存令牌使用情况​

缓存 Token 指标可通过原生 SDK 的 Usage 对象获取:

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 将为非零值(写入缓存的 token 数)。在后续具有相同缓存前缀的请求中,cacheReadInputTokens 将为非零值(以较低成本从缓存读取的 token 数)。

对话历史记录缓存​

CONVERSATION_HISTORY 策略会缓存整个对话上下文,包括系统消息、工具定义和最后一条用户消息。这对于多轮对话非常有用,否则随着上下文的增长,每次请求都需要重新处理:

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 Schema。Anthropic SDK 模块还支持 Anthropic 的 effort control,用于调节响应质量与速度之间的平衡。

备注

模型要求

结构化输出和 effort 控制需要 claude-sonnet-4-6 或更新版本。旧模型如 claude-sonnet-4-20250514 不支持这些功能。

Schema 要求

使用 JSON schema 输出时,Anthropic 要求 schema 中所有对象类型均包含 "additionalProperties": false。

JSON Schema 输出​

使用 outputSchema 便捷方法,将 Claude 的响应约束为特定的 JSON Schema:

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 在响应上投入的计算量。较低的 effort 意味着响应更快、成本更低;较高的 effort 意味着推理更加深入。

努力程度描述
LOW快速简洁的响应,推理最少
MEDIUM速度与详尽程度之间的平衡
HIGH更深入的推理和详细的响应
MAX最大算力,提供最详尽的响应
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 Schema 输出与 effort control:

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));

Direct OutputConfig​

如需完全控制,请直接使用 SDK 的 OutputConfig:

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 提供了不同的服务层级(service tiers),用于控制 API 请求的容量路由。你可以使用 AnthropicServiceTier.AUTO,在可用时机会性地使用优先容量(低延迟),也可以使用 STANDARD_ONLY 以始终使用标准容量。

通过 Spring Boot 属性:

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

或者按请求以编程方式:

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

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

每请求 HTTP 标头​

Anthropic SDK 模块支持按请求设置 HTTP 请求头,这些请求头会注入到单独的 API 调用中。这与 customHeaders(在客户端级别为所有请求设置)不同。

针对每个请求的头信息(Per-request headers)适用于以下场景:

  • Request tracking(请求追踪): 为每个请求添加关联 ID 或跟踪头
  • Beta API access(Beta API 访问): 为特定请求包含 Beta 功能头
  • Routing(路由): 添加用于负载均衡的路由头或优先级头
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));
备注

httpHeaders 是针对单个请求的,通过 MessageCreateParams.putAdditionalHeader() 设置。它们不会影响其他请求。对于需要应用于所有请求的标头,请改用 customHeaders。

示例控制器​

以下是使用聊天模型进行文本生成的简单 @RestController 类的示例:

@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 抽象中未显式映射的任何字段的能力:

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
备注

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

技能​

Anthropic 的 Skills API 通过提供针对文档生成的专用且预打包的能力,扩展了 Claude 的功能。Skills 使 Claude 能够创建实际可下载的文件——包括 Excel 电子表格、PowerPoint 演示文稿、Word 文档和 PDFs——而不仅仅是描述这些文档可能包含的内容。

备注

支持的模型

Skills 在 Claude Sonnet 4、Claude Sonnet 4.5、Claude Opus 4 及之后的模型上得到支持。

要求

  • Skills 需要代码执行能力(当配置 skills 时,Spring AI 会自动启用)

  • 每个请求最多支持 8 个 skills

  • 生成的文件可通过 Files API 下载,有效期为 24 小时

预置的 Anthropic 技能​

Spring AI 通过 AnthropicSkill 枚举提供对 Anthropic 预构建技能(skills)的类型安全访问:

技能描述生成的文件类型
XLSX生成和操作电子表格.xlsx(Microsoft Excel)
PPTX创建 PowerPoint 演示文稿.pptx(Microsoft PowerPoint)
DOCX生成 Word 文档.docx(Microsoft Word)
PDF创建 PDF 文档.pdf(便携式文档格式)

基础用法​

通过将 skills 添加到您的 AnthropicChatOptions 中,启用 skills:

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 个):

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:

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 使用 Skills 生成文件时,响应中包含文件 ID,可通过 Files API 使用这些 ID 下载实际文件。Spring AI 提供了 AnthropicSkillsResponseHelper 工具类,用于提取文件 ID 并下载文件。

提取文件 ID​

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 实例(与创建聊天模型时使用的实例相同):

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​

对于使用 Skills 进行的多轮对话,你可以提取容器 ID 以供复用:

String containerId = AnthropicSkillsResponseHelper.extractContainerId(response);

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

完整示例​

下面是一个展示如何使用 Skills 进行文件下载的完整示例:

@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. 使用合适的模型:Skills 在 Claude Sonnet 4 及后续模型上表现最佳。请确保您使用的是受支持的模型。

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

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

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

  5. 检查文件 ID:在尝试下载之前,始终验证是否返回了文件 ID。某些提示词可能只会产生文本响应,而不会生成文件。

  6. 使用防御性错误处理:在 try-catch 块中包装文件操作,以妥善处理网络问题或文件过期的情况。

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 的网页搜索工具允许 Claude 在对话过程中搜索网络,并利用搜索结果生成带引用的回复。

备注
  • Web 搜索是一个内置的服务器端工具——无需使用外部工具回调
  • Web 搜索结果会自动包含带有 URL 的引用
  • 你可以在同一个请求中将 Web 搜索与其他工具(函数调用、代码执行、技能)结合使用

基本用法​

通过向您的聊天选项中添加 AnthropicWebSearchTool 来启用网络搜索:

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用户的大致位置,用于本地化搜索结果(城市、国家、地区、时区)-

域名过滤​

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

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

用户位置​

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

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

访问 Web 搜索结果​

响应元数据中提供了网络搜索结果和引用信息:

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

// Get web search results
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
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 配置 web search:

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

可观测性​

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

  • Chat-model 层。 gen_ai.client.operation observation 封装了对 AnthropicChatModel.call(…​) 或 .stream(…​) 的每次调用,携带请求参数、响应元数据和 token 使用情况。有关完整的 tags 和 metrics 集合,请参阅 observability reference。

  • HTTP 层。 okhttp.requests observation 封装了对 Anthropic API 的每次 HTTP 尝试,携带 HTTP 方法、URI、状态码和异常 tags。每个请求还会通过 traceparent 在线路上将追踪信息传播到任何下游服务(AI 网关、OpenAI 兼容的推理服务器、代理)。

在同时使用 spring-boot-starter-actuator 和追踪桥接器(micrometer-tracing-bridge-otel 或 micrometer-tracing-bridge-brave)的 Spring Boot 应用中,这两个层会自动完成配置——无需手动启用。对于非 Boot 应用,需将 ObservationRegistry 传递给 AnthropicChatModel.builder():

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

连接池指标(可选)​

Spring AI 还可以将 OkHttp 连接池的仪表盘指标(okhttp.pool.connection.count,其中 state=active|idle 和 client.kind=sync|async)绑定到应用程序的 MeterRegistry。这些是次要的遥测数据——对于容量调优很有用,但对于跟踪或每次请求的延迟并非必需——因此默认情况下是禁用的。

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

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

对于非 Boot 应用,直接将 MeterRegistry 传递给 builder;只要提供了 registry,gauge 就会绑定:

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

HTTP 调度器执行器(高级)​

默认情况下,Spring AI 管理 OkHttp 调度器的执行器,即一个无界平台线程池,以复制 SDK 的默认行为。对于具有高 HTTP 并发的负载,或者希望利用 Java 21+ 虚拟线程的场景,您可以提供自己的 ExecutorService:

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

相同的执行器(executor)同时支撑同步和异步(流式)客户端。当您提供自己的执行器时,您需要负责其生命周期管理——Spring AI 绝不会对其调用 shutdown()。如果未提供,内部执行器将自动创建并自动清理。

important

流式 HTTP 观测不会作为聊天模型观测的子节点

对于同步调用,okhttp.requests span 正确地嵌套在 gen_ai.client.operation span 下——完整的追踪树按预期形成。

对于流式调用,okhttp.requests span 会被触发并记录在 registry 中,计时器会被记录在 MeterRegistry 中,并且 traceparent 仍然会在出站请求中传播——但该 span 没有嵌套在聊天模型 span 之下。这是因为 Anthropic SDK 的异步流式实现在调用 Spring AI 的 HTTP 客户端之前,会切换到 ForkJoinPool.commonPool(),从而在该边界处丢失了调用线程的观测上下文。Spring AI 无法从其 HTTP 客户端包装器内部恢复该上下文。

如果你需要在追踪 UI 中将流式 HTTP span 与其父聊天模型 span 进行关联,可以按 span 名称 okhttp.requests 和主机名 api.anthropic.com 进行过滤,并通过 trace ID 或时间戳范围与对应的 gen_ai.client.operation span 进行关联。流式调用的内置父子关联正在 Anthropic Java SDK 上游被推进。

日志记录​

设置以下环境变量以启用 SDK 日志记录:

export ANTHROPIC_LOG=debug

限制​

以下功能尚不支持:

  • Amazon Bedrock 后端

  • Google Vertex AI 后端

这些功能计划在后续版本中发布。

自定义 HTTP 客户端​

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

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

典型的用例包括:

  • 注册 OkHttp Interceptor 实例(身份验证、传播头、自定义日志记录);

  • 替换调度器的 ExecutorService(例如,将异步 I/O 路由至虚拟线程);

  • 配置由 Builder 暴露的代理、SSL、主机名验证或连接池大小。

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

备注

每个自定义器(customizer)在每个 AnthropicChatModel 中会被调用 两次——一次用于同步客户端,一次用于异步客户端(该异步客户端作为模型的支撑)。实现应当是幂等的(即可以安全地多次调用)。

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

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

其他资源​