|
此版本仍在开发中,尚未被视为稳定版。如需最新的快照版本,请使用 Spring AI 1.1.3! |
Anthropic Chat
Anthropic Claude 是一系列基础人工智能模型,可以在多种应用中使用。 对于开发者和企业来说,您可以利用 API 访问并直接在 Anthropic 的 AI 基础设施 上构建。
Spring AI 支持 Anthropic 消息 API,用于同步和流式文本生成。
| Anthropic 的 Claude 模型也可通过 Amazon Bedrock Converse 获取。 Spring AI 提供了专门的 Amazon Bedrock Converse Anthropic 客户端实现。 |
前置条件
您需要在Anthropic门户上创建一个API密钥。
在Anthropic API 控制台注册账号,并在获取 API 密钥页面生成 API密钥。
The Spring AI项目定义了一个配置属性名为spring.ai.anthropic.api-key,你应该将其设置为从anthropic.com获取的API Key的值。
您可以在您的application.properties文件中设置此配置属性:
spring.ai.anthropic.api-key=<your-anthropic-api-key>
处理敏感信息(如API密钥)时,为了增强安全性,您可以使用Spring表达式语言(SpEL)引用自定义环境变量:
# In application.yml
spring:
ai:
anthropic:
api-key: ${ANTHROPIC_API_KEY}
# In your environment or .env file
export ANTHROPIC_API_KEY=<your-anthropic-api-key>
您也可以在应用程序代码中程序化地获取此配置:
// Retrieve API key from a secure source or environment variable
String apiKey = System.getenv("ANTHROPIC_API_KEY");
Auto-configuration
|
Spring AI自动配置和starter模块的artifact名称有了重大变化。 请参阅升级说明获取更多信息。 |
Spring AI 为 Anthropic 聊天客户端提供了 Spring Boot 自动配置。
要启用它,请将以下依赖项添加到项目中的 Maven pom.xml 或 Gradle build.gradle 文件中:
-
Maven
-
Gradle
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-anthropic</artifactId>
</dependency>
dependencies {
implementation 'org.springframework.ai:spring-ai-starter-model-anthropic'
}
| 请参阅依赖管理部分,将Spring AI BOM添加到您的构建文件中。 |
聊天属性
重试属性
spring.ai.retry 前缀用作属性前缀,允许您配置 Anthropic 对话模型的重试机制。
| <property> </property> | <description> </description> | 默认 |
|---|---|---|
spring.ai.retry.max-attempts |
最大重试尝试次数。 |
10 |
spring.ai.retry.backoff.initial-interval |
指数退避策略的初始睡眠时长。 |
2 秒。 |
spring.ai.retry.backoff.multiplier |
重试间隔倍数。 |
5 |
spring.ai.retry.backoff.max-interval |
最大退避持续时间。 |
3 min. |
spring.ai.retry.on-client-errors |
如果为假,则抛出一个NonTransientAiException,并且不尝试重试 |
false |
spring.ai.retry.exclude-on-http-codes |
不应当触发重试的HTTP状态码列表(例如,抛出NonTransientAiException)。 |
<p>空内容</p> |
spring.ai.retry.on-http-codes |
应触发重试的HTTP状态码列表(例如,抛出TransientAiException)。 |
<p>空内容</p> |
| 目前,重试策略不适用于流式API。 |
连接属性
使用前缀spring.ai.anthropic作为属性前缀,以便连接到Anthropic。
| <property> </property> | <description> </description> | 默认 |
|---|---|---|
spring.ai.anthropic.base-url |
连接的URL |
|
spring.ai.anthropic.completions-path |
在基础URL后面附加的路径。 |
|
spring.ai.anthropic.version |
Anthropic API 版本 |
2023-06-01 |
spring.ai.anthropic.api-key |
API密钥 |
- |
spring.ai.anthropic.beta-version |
启用新的/实验性的功能。如果设置为 |
|
配置属性
|
现在通过带有前缀 要启用,请设置:spring.ai.model.chat=anthropic (默认已启用) 要禁用,请设置 spring.ai.model.chat=none(或任何不匹配 Anthropic 的值) 此更改是为了允许配置多个模型。 |
spring.ai.anthropic.chat 前缀是属性前缀,让你可以配置与 Anthropic 对话模型相关的实现。
| <property> </property> | <description> </description> | 默认 |
|---|---|---|
spring.ai.anthropic.chat.enabled (已移除且不再有效) |
启用Anthropic聊天模型。 |
true |
spring.ai.model.chat |
启用Anthropic聊天模型。 |
Anthropic |
spring.ai.anthropic.chat.options.model |
这是要使用的 Anthropic 聊天模型。支持: |
|
spring.ai.anthropic.chat.options.temperature |
使用以控制生成完成内容的看似创造力的采样温度。较高值会使输出更具随机性,而较低值会使结果更集中和确定。不建议在同一完成请求中同时修改温度和top_p,因为这两个设置之间的交互难以预测。 |
0.8 |
spring.ai.anthropic.chat.options.max-tokens |
生成聊天补全时的最大token数。输入token和生成token的总长度受限于模型的上下文长度。 |
500 |
spring.ai.anthropic.chat.options.stop-sequence |
<p>自定义文本序列,这些序列会让模型停止生成。我们的模型通常会在自然完成其回合后停止生成,这会导致响应的 stop_reason 值为 |
- |
spring.ai.anthropic.chat.options.top-p |
使用核采样。在核采样中,我们按照递减概率顺序计算每个后续标记的所有选项的累积分布,并在达到由 top_p 指定的特定概率时停止。你应该仅更改温度或 top_p 中的一个,但不能同时更改两者。仅对高级用例推荐使用。通常情况下,你只需要使用温度。 |
- |
spring.ai.anthropic.chat.options.top-k |
仅从每个后续标记的前K个选项中取样。用于去除低概率响应中的“长尾”情况。详细了解技术细节请参阅此处。仅推荐在高级用例中使用。通常您只需要使用温度参数。 |
- |
spring.ai.anthropic.chat.options.tool-names |
使用名称标识的工具列表,以启用在单个提示请求中调用工具。这些名称中的工具必须存在于toolCallbacks注册表中。 |
- |
spring.ai.anthropic.chat.options.tool-callbacks |
使用回调注册与ChatModel相关的工具。 |
- |
spring.ai.anthropic.chat.options.toolChoice |
controls which(如果有的话)工具是由模型调用的。 |
- |
spring.ai.anthropic.chat.options.internal-tool-execution-enabled |
如果为假,Spring AI 将不会内部处理工具调用,而是将它们代理给客户端。然后需要由客户端负责处理这些工具调用、将其分派到适当的函数,并返回结果。如果为真(默认值),Spring AI 将会内部处理这些函数调用。仅适用于支持功能调用的聊天模型。 |
true |
spring.ai.anthropic.chat.options.http-headers |
添加到聊天补全请求中的可选HTTP标头。 |
- |
| 对于最新的模型别名及其描述列表,请参见官方Anthropic模型别名文档。 |
所有以spring.ai.anthropic.chat.options开头的属性可以在运行时通过向Prompt调用添加请求特定的运行时选项来覆盖。 |
运行时选项
The AnthropicChatOptions.java 提供了模型配置,例如要使用的模型、温度、最大token计数等。
启动时,可以使用AnthropicChatModel(api, options)构造函数或spring.ai.anthropic.chat.options.*属性来配置默认选项。
在运行时,您可以覆盖默认选项并通过向Prompt调用添加新的、针对请求的选项来实现。例如,要为特定请求覆盖默认模型和温度:
ChatResponse response = chatModel.call(
new Prompt(
"Generate the names of 5 famous pirates.",
AnthropicChatOptions.builder()
.model("claude-3-7-sonnet-latest")
.temperature(0.4)
.build()
));
| 此外,除了特定于模型的AnthropicChatOptions外,您还可以使用一个通用的ChatOptions 实例,该实例通过调用ChatOptions#builder() 创建。 |
提示缓存
Anthropic的提示缓存功能允许您缓存经常使用的提示,以减少成本并提高重复交互的响应速度。 当您缓存一个提示时,后续相同的请求可以重用缓存的内容,显著减少了处理的输入标记数量。
|
支持的模型 提示缓存目前在Claude Sonnet 4.5、Claude Opus 4.5、Claude Haiku 4.5、Claude Opus 4、Claude Sonnet 4、Claude Sonnet 3.7、Claude Sonnet 3.5、Claude Haiku 3.5、Claude Haiku 3 和 Claude Opus 3 中支持。 标签要求 不同模型的缓存有效性所需的最小标记阈值不同: - Claude Sonnet 4: 1024+ 标记 - Claude Haiku 模型: 2048+ 标记 - 其他模型: 1024+ 标记 |
缓存策略
Spring AI 通过 `0` 枚举提供了战略性的缓存放置。 每个策略都会自动在最优位置设置缓存断点,同时保持在 Anthropic 的 4 个断点限制之内。
| 策略 | 使用断点 | 用例 |
|---|---|---|
|
0 |
完全禁用提示缓存。 在请求是一次性的或内容太小而不适合缓存时使用。 |
|
1 |
缓存系统消息内容。 工具通过Anthropic的自动约20块回溯机制隐式缓存。 在系统提示较大且稳定,且工具数量少于20时使用。 |
|
1 |
仅缓存工具定义。系统消息不予缓存,并在每次请求时新鲜处理。 使用此方法的前提是工具定义庞大且稳定(5000+ tokens),而系统提示词则频繁变化或根据租户/上下文不同而异。 |
|
2 |
明确缓存工具定义(断点1)和系统消息(断点2)。 在您拥有超过20个工具(超出自动回溯范围)或希望确定性地缓存这两个组件时使用。 系统更改不会使工具缓存失效。 |
|
1-4 |
缓存整个对话历史记录,直到当前用户问题。 用于具有聊天记忆的多轮对话场景,其中对话历史会随着时间增长。 |
由于Anthropic的级联无效化,更改工具定义将使所有下游缓存断点(系统、消息)失效。
在使用SYSTEM_AND_TOOLS或CONVERSATION_HISTORY策略时,工具稳定性至关重要。 |
启用提示缓存
通过将cacheOptions设置在AnthropicChatOptions上并选择一个strategy来启用提示缓存。
系统专用缓存
最佳适用场景:稳定系统提示,工具数量<20(工具通过自动回溯隐式缓存)。
// Cache system message content (tools cached implicitly)
ChatResponse response = chatModel.call(
new Prompt(
List.of(
new SystemMessage("You are a helpful AI assistant with extensive knowledge..."),
new UserMessage("What is machine learning?")
),
AnthropicChatOptions.builder()
.model("claude-sonnet-4")
.cacheOptions(AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.SYSTEM_ONLY)
.build())
.maxTokens(500)
.build()
)
);
仅工具缓存
最佳适用场景:大型稳定工具集与动态系统提示(多租户应用、A/B 测试)。
// Cache tool definitions, system prompt processed fresh each time
ChatResponse response = chatModel.call(
new Prompt(
List.of(
new SystemMessage("You are a " + persona + " assistant..."), // Dynamic per-tenant
new UserMessage("What's the weather like in San Francisco?")
),
AnthropicChatOptions.builder()
.model("claude-sonnet-4")
.cacheOptions(AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.TOOLS_ONLY)
.build())
.toolCallbacks(weatherToolCallback) // Large tool set cached
.maxTokens(500)
.build()
)
);
系统和工具缓存
最佳用于:超过20种工具(超出自动回溯范围)或当两个组件应当独立缓存时。
// Cache both tool definitions and system message with independent breakpoints
// Changing system won't invalidate tool cache (but changing tools invalidates both)
ChatResponse response = chatModel.call(
new Prompt(
List.of(
new SystemMessage("You are a weather analysis assistant..."),
new UserMessage("What's the weather like in San Francisco?")
),
AnthropicChatOptions.builder()
.model("claude-sonnet-4")
.cacheOptions(AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.SYSTEM_AND_TOOLS)
.build())
.toolCallbacks(weatherToolCallback) // 20+ tools
.maxTokens(500)
.build()
)
);
对话历史记录缓存
// Cache conversation history with ChatClient and memory (cache breakpoint on last user message)
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultSystem("You are a personalized career counselor...")
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory)
.conversationId(conversationId)
.build())
.build();
String response = chatClient.prompt()
.user("What career advice would you give me?")
.options(AnthropicChatOptions.builder()
.model("claude-sonnet-4")
.cacheOptions(AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.CONVERSATION_HISTORY)
.build())
.maxTokens(500)
.build())
.call()
.content();
使用 ChatClient 节流 API
String response = ChatClient.create(chatModel)
.prompt()
.system("You are an expert document analyst...")
.user("Analyze this large document: " + document)
.options(AnthropicChatOptions.builder()
.model("claude-sonnet-4")
.cacheOptions(AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.SYSTEM_ONLY)
.build())
.build())
.call()
.content();
高级缓存选项
Per-Message TTL(5分钟或1小时)
默认情况下,缓存内容的TTL为5分钟。 您可以为特定消息类型设置1小时的TTL。 当使用1小时TTL时,Spring AI 会自动设置所需的Anthropic beta头部。
ChatResponse response = chatModel.call(
new Prompt(
List.of(new SystemMessage(largeSystemPrompt)),
AnthropicChatOptions.builder()
.model("claude-sonnet-4")
.cacheOptions(AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.SYSTEM_ONLY)
.messageTypeTtl(MessageType.SYSTEM, AnthropicCacheTtl.ONE_HOUR)
.build())
.maxTokens(500)
.build()
)
);
延展TTL使用Anthropic Beta功能 extended-cache-ttl-2025-04-11。 |
缓存有效性过滤器
通过设置最小内容长度和可选的基于Tokens的长度函数来控制何时使用缓存断点:
AnthropicCacheOptions cache = AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.CONVERSATION_HISTORY)
.messageTypeMinContentLength(MessageType.SYSTEM, 1024)
.messageTypeMinContentLength(MessageType.USER, 1024)
.messageTypeMinContentLength(MessageType.ASSISTANT, 1024)
.contentLengthFunction(text -> MyTokenCounter.count(text))
.build();
ChatResponse response = chatModel.call(
new Prompt(
List.of(/* messages */),
AnthropicChatOptions.builder()
.model("claude-sonnet-4")
.cacheOptions(cache)
.build()
)
);
工具定义始终会在使用SYSTEM_AND_TOOLS策略时被考虑进行缓存,无论内容长度如何。 |
使用示例
这里是一个完整的示例,演示了带有成本跟踪的提示缓存:
// Create system content that will be reused multiple times
String largeSystemPrompt = "You are an expert software architect specializing in distributed systems...";
// First request - creates cache
ChatResponse firstResponse = chatModel.call(
new Prompt(
List.of(
new SystemMessage(largeSystemPrompt),
new UserMessage("What is microservices architecture?")
),
AnthropicChatOptions.builder()
.model("claude-sonnet-4")
.cacheOptions(AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.SYSTEM_ONLY)
.build())
.maxTokens(500)
.build()
)
);
// Access cache-related token usage
AnthropicApi.Usage firstUsage = (AnthropicApi.Usage) firstResponse.getMetadata()
.getUsage().getNativeUsage();
System.out.println("Cache creation tokens: " + firstUsage.cacheCreationInputTokens());
System.out.println("Cache read tokens: " + firstUsage.cacheReadInputTokens());
// Second request with same system prompt - reads from cache
ChatResponse secondResponse = chatModel.call(
new Prompt(
List.of(
new SystemMessage(largeSystemPrompt),
new UserMessage("What are the benefits of event sourcing?")
),
AnthropicChatOptions.builder()
.model("claude-sonnet-4")
.cacheOptions(AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.SYSTEM_ONLY)
.build())
.maxTokens(500)
.build()
)
);
AnthropicApi.Usage secondUsage = (AnthropicApi.Usage) secondResponse.getMetadata()
.getUsage().getNativeUsage();
System.out.println("Cache creation tokens: " + secondUsage.cacheCreationInputTokens()); // Should be 0
System.out.println("Cache read tokens: " + secondUsage.cacheReadInputTokens()); // Should be > 0
Token 使用跟踪
The Usage 记录提供了与缓存相关的Tokens消耗的详细信息。
要访问 Anthropic 特定的缓存指标,请使用 getNativeUsage() 方法:
AnthropicApi.Usage usage = (AnthropicApi.Usage) response.getMetadata()
.getUsage().getNativeUsage();
特定于缓存的指标包括:
-
cacheCreationInputTokens(): 返回创建缓存条目时使用的标记数 -
cacheReadInputTokens(): 从现有缓存条目中读取的Tokens数量
当您首次发送缓存提示时:
- cacheCreationInputTokens() 将大于 0
- cacheReadInputTokens() 将为 0
当您再次发送相同的缓存提示时:
- cacheCreationInputTokens() 将是 0
- cacheReadInputTokens() 将大于 0
实际应用场景
法律文件分析
通过在多个问题中缓存文档内容来高效分析大型法律合同或合规文件:
// Load a legal contract (PDF or text)
String legalContract = loadDocument("merger-agreement.pdf"); // ~3000 tokens
// System prompt with legal expertise
String legalSystemPrompt = "You are an expert legal analyst specializing in corporate law. " +
"Analyze the following contract and provide precise answers about terms, obligations, and risks: " +
legalContract;
// First analysis - creates cache
ChatResponse riskAnalysis = chatModel.call(
new Prompt(
List.of(
new SystemMessage(legalSystemPrompt),
new UserMessage("What are the key termination clauses and associated penalties?")
),
AnthropicChatOptions.builder()
.model("claude-sonnet-4")
.cacheOptions(AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.SYSTEM_ONLY)
.build())
.maxTokens(1000)
.build()
)
);
// Subsequent questions reuse cached document - 90% cost savings
ChatResponse obligationAnalysis = chatModel.call(
new Prompt(
List.of(
new SystemMessage(legalSystemPrompt), // Same content - cache hit
new UserMessage("List all financial obligations and payment schedules.")
),
AnthropicChatOptions.builder()
.model("claude-sonnet-4")
.cacheOptions(AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.SYSTEM_ONLY)
.build())
.maxTokens(1000)
.build()
)
);
批量代码审查
使用一致的评审标准处理多个代码文件,并缓存评审准则:
// Define comprehensive code review guidelines
String reviewGuidelines = """
You are a senior software engineer conducting code reviews. Apply these criteria:
- Security vulnerabilities and best practices
- Performance optimizations and memory usage
- Code maintainability and readability
- Testing coverage and edge cases
- Design patterns and architecture compliance
""";
List<String> codeFiles = Arrays.asList(
"UserService.java", "PaymentController.java", "SecurityConfig.java"
);
List<String> reviews = new ArrayList<>();
for (String filename : codeFiles) {
String sourceCode = loadSourceFile(filename);
ChatResponse review = chatModel.call(
new Prompt(
List.of(
new SystemMessage(reviewGuidelines), // Cached across all reviews
new UserMessage("Review this " + filename + " code:\n\n" + sourceCode)
),
AnthropicChatOptions.builder()
.model("claude-sonnet-4")
.cacheOptions(AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.SYSTEM_ONLY)
.build())
.maxTokens(800)
.build()
)
);
reviews.add(review.getResult().getOutput().getText());
}
// Guidelines cached after first request, subsequent reviews are faster and cheaper
多租户 SaaS 与共享工具
构建一个多租户应用,在该应用中工具可以共享但每个租户的系统提示可以个性化定制:
// Define large shared tool set (used by all tenants)
List<FunctionCallback> sharedTools = Arrays.asList(
weatherToolCallback, // ~500 tokens
calendarToolCallback, // ~800 tokens
emailToolCallback, // ~700 tokens
analyticsToolCallback, // ~600 tokens
reportingToolCallback, // ~900 tokens
// ... 20+ more tools, totaling 5000+ tokens
);
@Service
public class MultiTenantAIService {
public String handleTenantRequest(String tenantId, String userQuery) {
// Get tenant-specific configuration
TenantConfig config = tenantRepository.findById(tenantId);
// Dynamic system prompt per tenant
String tenantSystemPrompt = String.format("""
You are %s's AI assistant. Company values: %s.
Brand voice: %s. Compliance requirements: %s.
""", config.companyName(), config.values(),
config.brandVoice(), config.compliance());
ChatResponse response = chatModel.call(
new Prompt(
List.of(
new SystemMessage(tenantSystemPrompt), // Different per tenant, NOT cached
new UserMessage(userQuery)
),
AnthropicChatOptions.builder()
.model("claude-sonnet-4")
.cacheOptions(AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.TOOLS_ONLY) // Cache tools only
.build())
.toolCallbacks(sharedTools) // Cached once, shared across all tenants
.maxTokens(800)
.build()
)
);
return response.getResult().getOutput().getText();
}
}
// Tools cached once (5000 tokens @ 10% = 500 token cost for cache hits)
// Each tenant's unique system prompt processed fresh (200-500 tokens @ 100%)
// Total per request: ~700-1000 tokens vs 5500+ without TOOLS_ONLY
知识库支持客户支持
创建一个客服系统,缓存您的产品知识库以提供一致且准确的响应:
// Load comprehensive product knowledge
String knowledgeBase = """
PRODUCT DOCUMENTATION:
- API endpoints and authentication methods
- Common troubleshooting procedures
- Billing and subscription details
- Integration guides and examples
- Known issues and workarounds
""" + loadProductDocs(); // ~2500 tokens
@Service
public class CustomerSupportService {
public String handleCustomerQuery(String customerQuery, String customerId) {
ChatResponse response = chatModel.call(
new Prompt(
List.of(
new SystemMessage("You are a helpful customer support agent. " +
"Use this knowledge base to provide accurate solutions: " + knowledgeBase),
new UserMessage("Customer " + customerId + " asks: " + customerQuery)
),
AnthropicChatOptions.builder()
.model("claude-sonnet-4")
.cacheOptions(AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.SYSTEM_ONLY)
.build())
.maxTokens(600)
.build()
)
);
return response.getResult().getOutput().getText();
}
}
// Knowledge base is cached across all customer queries
// Multiple support agents can benefit from the same cached content
最佳实践
-
选择合适的策略:
-
使用
SYSTEM_ONLY为稳定系统提示,工具数量少于20个(工具通过自动回溯隐式缓存) -
使用
TOOLS_ONLY为大型稳定工具集(包含5000多个token),并支持动态系统提示(多租户、A/B测试)。 -
当您拥有20个及以上工具(超出自动回溯范围)或希望两者独立缓存时,请使用
SYSTEM_AND_TOOLS。 -
使用
CONVERSATION_HISTORY与ChatClient内存进行多轮对话 -
使用
NONE显式禁用缓存
-
-
理解级联失效:Anthropic的缓存层次结构(
tools → system → messages)意味着更改会向下流动:-
更改工具会失效:工具 + 系统 + 消息(所有缓存)❌❌❌
-
更改 系统 会使以下内容无效:系统 + 消息(工具缓存保持有效)✅❌❌
-
更改消息会失效:仅消息(工具和系统缓存保持有效)✅✅❌
**Tool stability is critical** when using `SYSTEM_AND_TOOLS` or `CONVERSATION_HISTORY` strategies.
-
-
系统和工具独立性:使用
SYSTEM_AND_TOOLS时,更改系统的提示信息不会使工具缓存失效,即使系统提示不同,也能高效地重用缓存的工具。 -
满足Token要求:专注于缓存符合最小Token要求的内容(Sonnet 4模型需1024+ Token,Haiku模型需2048+ Token)。
-
重复使用相同内容:缓存最适合精确匹配提示内容的情况。即使是小的更改也需要一个新的缓存条目。
-
监控Tokens使用情况:使用缓存使用统计数据跟踪缓存有效性:
java AnthropicApi.Usage usage = (AnthropicApi.Usage) response.getMetadata().getUsage().getNativeUsage(); if (usage != null) { System.out.println("Cache creation: " + usage.cacheCreationInputTokens()); System.out.println("Cache read: " + usage.cacheReadInputTokens()); } -
战略缓存放置:实现会根据您选择的策略自动在最优位置放置缓存断点,确保符合Anthropic的4个断点限制。
-
缓存有效期:默认TTL为5分钟;通过
messageTypeTtl(…)为每种消息类型设置1小时的TTL。每次缓存访问会重置计时器。 -
工具缓存限制:请注意,基于工具的交互可能不会在响应中提供缓存使用元数据。
实施细节
Spring AI 框架中提示缓存的实现遵循以下关键设计原则:
-
战略缓存放置:根据选择的策略自动在最优位置放置断点,确保符合Anthropic的4个断点限制。
-
CONVERSATION_HISTORY在以下位置设置缓存断点:工具(如果存在),系统消息,以及最后用户消息 -
这使得Anthropic的前缀匹配能够增量地缓存不断增长的对话历史记录
-
每个回合都会在之前缓存的前缀基础上进行构建,以最大化缓存的重用
-
-
Provider Portability: 缓存配置是通过
AnthropicChatOptions完成的,而不是个别消息,从而在切换不同的AI提供商时保持兼容性。 -
线程安全: 缓存断点跟踪通过使用线程安全机制来正确处理并发请求。
-
自动内容排序: 实现确保了按照Anthropic的API要求,正确地在网络上传输JSON内容块和缓存控制。
-
聚合资格检查: 对于
CONVERSATION_HISTORY,实现会在确定联合内容是否满足缓存的最小Tokens阈值时考虑最近约20个内容块内的所有消息类型(用户、助手、工具)。
思考
Anthropic Claude 模型支持一种“思考”功能,允许模型在提供最终答案之前展示其推理过程。该功能使问题解决更加透明和详细,特别适用于需要逐步推理的复杂问题。
|
支持的模型 该思考功能由以下Claude模型支持:
模型能力:
API请求结构在所有支持的模型中是相同的,但输出行为会有所不同。 |
思考配置
要启用任何支持的Claude模型进行思考,请在请求中包含以下配置:
所需配置
-
强健的对象
thinking:-
"type": "enabled" -
budget_tokens: 基于推理的Tokens限制(建议从1024开始)
-
-
Token预算规则:
-
budget_tokens必须通常小于max_tokens -
Claude 可能会使用的Tokens少于分配的数量
-
更大预算可以增加推理深度,但可能会对延迟产生影响
-
当使用工具进行交错思考(仅限Claude 4)时,此约束将被放宽,但在Spring AI中尚未支持。
-
关键考虑事项
-
Claude 3.7 在响应中返回完整的思考内容
-
Claude 4 返回模型内部推理的摘要版本,以降低延迟并保护敏感内容
-
思考Tokens是可计费的,作为输出Tokens的一部分(即使在响应中并非全部可见)
-
交错思考仅在 Claude 4 模型上可用,并且需要使用测试版头
interleaved-thinking-2025-05-14
工具集成与交错思考
Claude 4 模型支持工具使用过程中的交错思考,允许模型在调用工具时进行推理。
|
当前的Spring AI 实现支持单独的基本思维和工具使用,但尚未支持在多次工具调用间进行交错思维(即思维跨越多个工具调用持续进行)。 |
在了解工具使用过程中的交错思考方法时,请参阅Anthropic 文档。
非流式示例
如何使用ChatClient API 在非流式请求中启用思考:<br />
ChatClient chatClient = ChatClient.create(chatModel);
// For Claude 3.7 Sonnet - explicit thinking configuration required
ChatResponse response = chatClient.prompt()
.options(AnthropicChatOptions.builder()
.model("claude-3-7-sonnet-latest")
.temperature(1.0) // Temperature should be set to 1 when thinking is enabled
.maxTokens(8192)
.thinking(AnthropicApi.ThinkingType.ENABLED, 2048) // Must be ≥1024 && < max_tokens
.build())
.user("Are there an infinite number of prime numbers such that n mod 4 == 3?")
.call()
.chatResponse();
// For Claude 4 models - thinking is enabled by default
ChatResponse response4 = chatClient.prompt()
.options(AnthropicChatOptions.builder()
.model("claude-opus-4-0")
.maxTokens(8192)
// No explicit thinking configuration needed
.build())
.user("Are there an infinite number of prime numbers such that n mod 4 == 3?")
.call()
.chatResponse();
// Process the response which may contain thinking content
for (Generation generation : response.getResults()) {
AssistantMessage message = generation.getOutput();
if (message.getText() != null) {
// Regular text response
System.out.println("Text response: " + message.getText());
}
else if (message.getMetadata().containsKey("signature")) {
// Thinking content
System.out.println("Thinking: " + message.getMetadata().get("thinking"));
System.out.println("Signature: " + message.getMetadata().get("signature"));
}
}
流式示例
您也可以使用带有流式响应的思考:<br>
ChatClient chatClient = ChatClient.create(chatModel);
// For Claude 3.7 Sonnet - explicit thinking configuration
Flux<ChatResponse> responseFlux = chatClient.prompt()
.options(AnthropicChatOptions.builder()
.model("claude-3-7-sonnet-latest")
.temperature(1.0)
.maxTokens(8192)
.thinking(AnthropicApi.ThinkingType.ENABLED, 2048)
.build())
.user("Are there an infinite number of prime numbers such that n mod 4 == 3?")
.stream();
// For Claude 4 models - thinking is enabled by default
Flux<ChatResponse> responseFlux4 = chatClient.prompt()
.options(AnthropicChatOptions.builder()
.model("claude-opus-4-0")
.maxTokens(8192)
// No explicit thinking configuration needed
.build())
.user("Are there an infinite number of prime numbers such that n mod 4 == 3?")
.stream();
// For streaming, you might want to collect just the text responses
String textContent = responseFlux.collectList()
.block()
.stream()
.map(ChatResponse::getResults)
.flatMap(List::stream)
.map(Generation::getOutput)
.map(AssistantMessage::getText)
.filter(text -> text != null && !text.isBlank())
.collect(Collectors.joining());
工具/函数调用
您可以通过AnthropicChatModel注册自定义Java工具,并让Anthropic Claude模型智能地选择输出一个包含调用已注册函数参数的JSON对象。
这是一种强大的技术,可以将LLM能力与外部工具和API连接起来。
更多关于工具调用的内容,请参阅相关文档。
工具选择
tool_choice 参数允许您控制模型如何使用提供的工具。此功能为您提供对工具执行行为的精细控制。
对于完整的API细节,请参阅Anthropic tool_choice 文档。
工具选择选项
Spring AI 通过 AnthropicApi.ToolChoice 接口提供了四种工具选择策略:
-
ToolChoiceAuto(默认):模型自动决定是否使用工具或以文本形式作答 -
ToolChoiceAny: 模型必须使用至少一个可用的工具 -
ToolChoiceTool: 模型必须使用特定名称的工具 -
ToolChoiceNone: 该模型无法使用任何工具
使用示例
自动模式(默认行为)
让模型决定是否使用工具:
ChatResponse response = chatModel.call(
new Prompt(
"What's the weather in San Francisco?",
AnthropicChatOptions.builder()
.toolChoice(new AnthropicApi.ToolChoiceAuto())
.toolCallbacks(weatherToolCallback)
.build()
)
);
强制使用工具(任意)
要求模型至少使用一个工具:
ChatResponse response = chatModel.call(
new Prompt(
"What's the weather?",
AnthropicChatOptions.builder()
.toolChoice(new AnthropicApi.ToolChoiceAny())
.toolCallbacks(weatherToolCallback, calculatorToolCallback)
.build()
)
);
强制特定工具
要求模型使用特定名称的工具:
ChatResponse response = chatModel.call(
new Prompt(
"What's the weather in San Francisco?",
AnthropicChatOptions.builder()
.toolChoice(new AnthropicApi.ToolChoiceTool("get_weather"))
.toolCallbacks(weatherToolCallback, calculatorToolCallback)
.build()
)
);
禁用工具使用
防止模型使用任何工具:<br>
ChatResponse response = chatModel.call(
new Prompt(
"What's the weather in San Francisco?",
AnthropicChatOptions.builder()
.toolChoice(new AnthropicApi.ToolChoiceNone())
.toolCallbacks(weatherToolCallback)
.build()
)
);
禁用并行工具使用
强制模型每次只使用一个工具:
ChatResponse response = chatModel.call(
new Prompt(
"What's the weather in San Francisco and what's 2+2?",
AnthropicChatOptions.builder()
.toolChoice(new AnthropicApi.ToolChoiceAuto(true)) // disableParallelToolUse = true
.toolCallbacks(weatherToolCallback, calculatorToolCallback)
.build()
)
);
多模态
多模态指的是模型同时理解并处理来自多种源的信息的能力,包括文本、PDF、图像和数据格式。
图片
目前,Anthropic Claude 3 支持 base64 源类型用于 images,以及 image/jpeg、image/png、image/gif 和 image/webp 媒体类型。
请查阅 视觉指南 以获取更多信息。
Anthropic Claude 3.5 Sonnet 还支持 pdf 源类型用于 application/pdf 文件。
Spring AI的Message接口通过引入Media类型支持多模态AI模型。
这种类型包含消息中媒体附件的数据和信息,使用Spring的org.springframework.util.MimeType和一个java.lang.Object来承载原始媒体数据。
以下是一个从AnthropicChatModelIT.java提取的简单代码示例,展示了用户文本与图片的结合。
var imageData = new ClassPathResource("/multimodal.test.png");
var userMessage = new UserMessage("Explain what do you see on this picture?",
List.of(new Media(MimeTypeUtils.IMAGE_PNG, this.imageData)));
ChatResponse response = chatModel.call(new Prompt(List.of(this.userMessage)));
logger.info(response.getResult().getOutput().getText());
它将输入multimodal.test.png图像:
<p>along with the text message "Explain what do you see on this picture?", 和生成类似下面的响应:</p>
The image shows a close-up view of a wire fruit basket containing several pieces of fruit. ...
使用Sonnet 3.5,PDF支持(beta)已提供。
使用application/pdf媒体类型将PDF文件附加到消息中:
var pdfData = new ClassPathResource("/spring-ai-reference-overview.pdf");
var userMessage = new UserMessage(
"You are a very professional document summarization specialist. Please summarize the given document.",
List.of(new Media(new MimeType("application", "pdf"), pdfData)));
var response = this.chatModel.call(new Prompt(List.of(userMessage)));
引用
Anthropic的引文API允许Claude在生成响应时引用提供的文档中的特定部分。 当包含引文文档的提示时,Claude可以引用原始材料,并且在响应元数据中返回引文元数据(字符范围、页码或内容块)。
引用可以帮助改善:
-
准确度验证:用户可以将Claude的回答与原始材料进行比对。
-
透明性:查看生成回复时参考了文档中的哪些部分
-
合规性: 满足受监管行业对源归属的要求
-
信任: 通过展示信息来源来建立信心
|
支持的模型 Claude 3.7 Sonnet 和 Claude 4 模型(Opus 和 Sonnet)支持引用。 文档类型 支持三种引用文献类型:
|
创建引用文件
使用CitationDocument构建器创建可以引用的文档:
文本文件
CitationDocument document = CitationDocument.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
CitationDocument document = CitationDocument.builder()
.pdfFile("path/to/document.pdf")
.title("Technical Specification")
.citationsEnabled(true)
.build();
// From byte array
byte[] pdfBytes = loadPdfBytes();
CitationDocument document = CitationDocument.builder()
.pdf(pdfBytes)
.title("Product Manual")
.citationsEnabled(true)
.build();
自定义内容块
对精细引用控制,请使用自定义内容块:
CitationDocument document = CitationDocument.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-3-7-sonnet-latest")
.maxTokens(1024)
.citationDocuments(document)
.build()
)
);
多份文档
您可以为Claude提供多个参考文档:
CitationDocument parisDoc = CitationDocument.builder()
.plainText("Paris is the capital city of France with a population of 2.1 million.")
.title("Paris Information")
.citationsEnabled(true)
.build();
CitationDocument eiffelDoc = CitationDocument.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-3-7-sonnet-latest")
.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();
}
引用类型
引用根据文档类型包含不同的位置信息:
字符位置(纯文本)
对于纯文本文档,引用包括字符索引:<br>
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
CitationDocument document = CitationDocument.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-3-7-sonnet-latest")
.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.");
}
最佳实践
-
使用描述性标题:为引用文档提供有意义的标题,帮助用户识别引文中的来源。
-
检查引用是否为空:并非所有响应都会包含引用,因此在访问引用元数据之前,请始终验证其是否存在。
-
考虑文档大小:较大的文档提供更多的上下文,但会消耗更多的输入标记,并可能影响响应时间。
-
利用多个文档:在回答涉及多个来源的问题时,将所有相关文档在一个请求中提供,而不是多次调用。
-
选择合适的文档类型:对于简单的内容,请选择纯文本;对于现有的文档,请选择PDF;当您需要对引用粒度进行精细控制时,请使用自定义内容块。
实际应用场景
法律文件分析
分析合同和法律文件的同时保留源引用信息:
CitationDocument contract = CitationDocument.builder()
.pdfFile("merger-agreement.pdf")
.title("Merger Agreement 2024")
.citationsEnabled(true)
.build();
ChatResponse response = chatModel.call(
new Prompt(
"What are the key termination clauses in this contract?",
AnthropicChatOptions.builder()
.model("claude-sonnet-4")
.maxTokens(2000)
.citationDocuments(contract)
.build()
)
);
// Citations will reference specific pages in the PDF
客户支持知识库
使用可验证来源提供准确的客户支持答案:
CitationDocument kbArticle1 = CitationDocument.builder()
.plainText(loadKnowledgeBaseArticle("authentication"))
.title("Authentication Guide")
.citationsEnabled(true)
.build();
CitationDocument kbArticle2 = CitationDocument.builder()
.plainText(loadKnowledgeBaseArticle("billing"))
.title("Billing FAQ")
.citationsEnabled(true)
.build();
ChatResponse response = chatModel.call(
new Prompt(
"How do I reset my password and update my billing information?",
AnthropicChatOptions.builder()
.model("claude-3-7-sonnet-latest")
.citationDocuments(kbArticle1, kbArticle2)
.build()
)
);
// Citations show which KB articles were referenced
研究与合规
生成需要来源引用的合规报告:
CitationDocument clinicalStudy = CitationDocument.builder()
.pdfFile("clinical-trial-results.pdf")
.title("Clinical Trial Phase III Results")
.citationsEnabled(true)
.build();
CitationDocument regulatoryGuidance = CitationDocument.builder()
.plainText(loadRegulatoryDocument())
.title("FDA Guidance Document")
.citationsEnabled(true)
.build();
ChatResponse response = chatModel.call(
new Prompt(
"Summarize the efficacy findings and regulatory implications.",
AnthropicChatOptions.builder()
.model("claude-sonnet-4")
.maxTokens(3000)
.citationDocuments(clinicalStudy, regulatoryGuidance)
.build()
)
);
// Citations provide audit trail for compliance
引用文档选项
上下文字段
Optionally provide context about the document that won’t be cited but can guide Claude’s understanding:
CitationDocument document = CitationDocument.builder()
.plainText("...")
.title("Legal Contract")
.context("This is a merger agreement dated January 2024 between Company A and Company B")
.build();
控制引用
默认情况下,所有文档禁用了引用(采用启用模式)。
要启用引用,请明确设置 citationsEnabled(true):
CitationDocument document = CitationDocument.builder()
.plainText("The Eiffel Tower was completed in 1889...")
.title("Historical Facts")
.citationsEnabled(true) // Explicitly enable citations for this document
.build();
您可以提供没有引用的文档以供背景信息参考:
CitationDocument backgroundDoc = CitationDocument.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的
技能解决了传统LLMs的一项基本限制:
-
传统Claude: "这里是你销售报告的样子……"(仅文本描述)
-
带有技能: 创建一个实际的
sales_report.xlsx文件,您可以下载并用Excel打开
|
支持的模型 技能在Claude Sonnet 4、Claude Sonnet 4.5、Claude Opus 4及后续模型中受到支持。 要求
|
预构建的Anthropic技能
Spring AI通过AnthropicSkill枚举提供了类型安全访问Anthropic预构建技能的方式:
| 技能 | <description> </description> | 生成的文件类型 |
|---|---|---|
|
Excel电子表格生成和操作 |
|
|
PowerPoint演示文稿创建 |
|
|
生成字文档 |
|
|
PDF 文档创建 |
|
基本用法
通过将它们添加到您的AnthropicChatOptions来启用技能:
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("claude-sonnet-4-5")
.maxTokens(4096)
.skill(AnthropicApi.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("claude-sonnet-4-5")
.maxTokens(8192)
.skill(AnthropicApi.AnthropicSkill.XLSX)
.skill(AnthropicApi.AnthropicSkill.PPTX)
.build()
)
);
使用SkillContainer进行高级配置
使用SkillContainer直接获得更多的控制权:
AnthropicApi.SkillContainer container = AnthropicApi.SkillContainer.builder()
.skill(AnthropicApi.AnthropicSkill.XLSX)
.skill(AnthropicApi.AnthropicSkill.PPTX, "20251013") // Specific version
.build();
ChatResponse response = chatModel.call(
new Prompt(
"Generate the quarterly report",
AnthropicChatOptions.builder()
.model("claude-sonnet-4-5")
.maxTokens(4096)
.skillContainer(container)
.build()
)
);
使用 ChatClient 节流 API
技能可以无缝地与ChatClient流畅API配合使用:
String response = ChatClient.create(chatModel)
.prompt()
.user("Create a PowerPoint presentation about Spring AI with 3 slides: " +
"Title, Key Features, and Getting Started")
.options(AnthropicChatOptions.builder()
.model("claude-sonnet-4-5")
.maxTokens(4096)
.skill(AnthropicApi.AnthropicSkill.PPTX)
.build())
.call()
.content();
流式传输与技能
技能与流式响应工作:
Flux<ChatResponse> responseFlux = chatModel.stream(
new Prompt(
"Create a Word document explaining machine learning concepts",
AnthropicChatOptions.builder()
.model("claude-sonnet-4-5")
.maxTokens(4096)
.skill(AnthropicApi.AnthropicSkill.DOCX)
.build()
)
);
responseFlux.subscribe(response -> {
String content = response.getResult().getOutput().getText();
System.out.print(content);
});
下载生成的文件
当Claude使用Skills生成文件时,响应中包含可用于通过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);
}
获取文件元数据
下载前,您可以检索文件元数据:
@Autowired
private AnthropicApi anthropicApi;
// Get metadata for a specific file
String fileId = fileIds.get(0);
AnthropicApi.FileMetadata metadata = anthropicApi.getFileMetadata(fileId);
System.out.println("Filename: " + metadata.filename()); // e.g., "sales_report.xlsx"
System.out.println("Size: " + metadata.size() + " bytes"); // e.g., 5082
System.out.println("MIME Type: " + metadata.mimeType()); // e.g., "application/vnd..."
下载文件内容
// Download file content as bytes
byte[] fileContent = anthropicApi.downloadFile(fileId);
// Save to local file system
Path outputPath = Path.of("downloads", metadata.filename());
Files.write(outputPath, fileContent);
System.out.println("Saved file to: " + outputPath);
方便的方法:下载所有文件
AnthropicSkillsResponseHelper 提供了一种方便的方法,一次性下载所有生成的文件:
// Download all files to a target directory
Path targetDir = Path.of("generated-files");
Files.createDirectories(targetDir);
List<Path> savedFiles = AnthropicSkillsResponseHelper.downloadAllFiles(response, anthropicApi, targetDir);
for (Path file : savedFiles) {
System.out.println("Downloaded: " + file.getFileName() +
" (" + Files.size(file) + " bytes)");
}
全量文件下载示例
这里是一个完整的示例,展示了如何使用Skills进行文件下载:
@Service
public class DocumentGenerationService {
private final AnthropicChatModel chatModel;
private final AnthropicApi anthropicApi;
public DocumentGenerationService(AnthropicChatModel chatModel, AnthropicApi anthropicApi) {
this.chatModel = chatModel;
this.anthropicApi = anthropicApi;
}
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("claude-sonnet-4-5")
.maxTokens(4096)
.skill(AnthropicApi.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 the generated file
String fileId = fileIds.get(0);
AnthropicApi.FileMetadata metadata = anthropicApi.getFileMetadata(fileId);
byte[] content = anthropicApi.downloadFile(fileId);
// Save to output directory
Path outputPath = outputDir.resolve(metadata.filename());
Files.write(outputPath, content);
return outputPath;
}
}
文件 API 操作
The AnthropicApi 提供直接访问 Files API 的功能:
| 方法 | <description> </description> |
|---|---|
|
获取元数据包括文件名、大小、MIME类型和过期时间 |
|
下载文件内容为字节数组 |
|
支持分页的文件列表 |
|
立即删除文件(文件自动过期后24小时) |
列表文件
// List files with pagination
AnthropicApi.FilesListResponse files = anthropicApi.listFiles(20, null);
for (AnthropicApi.FileMetadata file : files.data()) {
System.out.println(file.id() + ": " + file.filename());
}
// Check for more pages
if (files.hasMore()) {
AnthropicApi.FilesListResponse nextPage = anthropicApi.listFiles(20, files.nextPage());
// Process next page...
}
最佳实践
-
使用合适的模型:技能最好与Claude Sonnet 4及其之后的模型配合使用。请确保您正在使用的是一款受支持的模型。
-
设置足够的最大Tokens数:文档生成可能需要大量的Tokens。对于复杂的文档,请使用
maxTokens(4096)或更高值。 -
在提示中要具体明确: 提供关于文档结构、内容和格式的清晰详细的指令。
-
及时处理文件下载:生成的文件在24小时后过期。请在生成后尽快下载文件。
-
检查文件ID: 在尝试下载之前,始终验证是否返回了文件ID。某些提示可能会导致文本响应而没有文件生成。
-
使用防御性错误处理:将文件操作包在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 {
byte[] content = anthropicApi.downloadFile(fileIds.get(0));
// Process file...
} catch (Exception e) {
log.error("Failed to download file: {}", e.getMessage());
}
实际应用场景
自动化报告生成
生成格式化的业务报告:
@Service
public class ReportService {
private final AnthropicChatModel chatModel;
private final AnthropicApi anthropicApi;
public byte[] generateMonthlyReport(SalesData data) throws IOException {
String prompt = String.format(
"Create a PowerPoint presentation summarizing monthly sales performance. " +
"Total Revenue: $%,.2f, Total Expenses: $%,.2f, Net Profit: $%,.2f. " +
"Include charts and key insights. Create 5 slides: " +
"1) Title, 2) Revenue Overview, 3) Expense Breakdown, " +
"4) Profit Analysis, 5) Recommendations.",
data.revenue(), data.expenses(), data.profit()
);
ChatResponse response = chatModel.call(
new Prompt(prompt,
AnthropicChatOptions.builder()
.model("claude-sonnet-4-5")
.maxTokens(8192)
.skill(AnthropicApi.AnthropicSkill.PPTX)
.build()
)
);
List<String> fileIds = AnthropicSkillsResponseHelper.extractFileIds(response);
return anthropicApi.downloadFile(fileIds.get(0));
}
}
数据导出服务
导出结构化数据为Excel格式:
@RestController
public class ExportController {
private final AnthropicChatModel chatModel;
private final AnthropicApi anthropicApi;
private final CustomerRepository customerRepository;
@GetMapping("/export/customers")
public ResponseEntity<byte[]> exportCustomers() throws IOException {
List<Customer> customers = customerRepository.findAll();
String dataDescription = customers.stream()
.map(c -> String.format("%s, %s, %s", c.name(), c.email(), c.tier()))
.collect(Collectors.joining("\n"));
ChatResponse response = chatModel.call(
new Prompt(
"Create an Excel spreadsheet with customer data. " +
"Columns: Name, Email, Tier. Format the header row with bold text. " +
"Data:\n" + dataDescription,
AnthropicChatOptions.builder()
.model("claude-sonnet-4-5")
.maxTokens(4096)
.skill(AnthropicApi.AnthropicSkill.XLSX)
.build()
)
);
List<String> fileIds = AnthropicSkillsResponseHelper.extractFileIds(response);
byte[] content = anthropicApi.downloadFile(fileIds.get(0));
AnthropicApi.FileMetadata metadata = anthropicApi.getFileMetadata(fileIds.get(0));
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + metadata.filename() + "\"")
.contentType(MediaType.parseMediaType(metadata.mimeType()))
.body(content);
}
}
多格式文档生成
生成单个请求即可创建多种文档格式:
public Map<String, byte[]> generateProjectDocumentation(ProjectInfo project) throws IOException {
ChatResponse response = chatModel.call(
new Prompt(
"Create project documentation for: " + project.name() + "\n" +
"Description: " + project.description() + "\n\n" +
"Generate:\n" +
"1. An Excel file with the project timeline and milestones\n" +
"2. A PowerPoint overview presentation (3-5 slides)\n" +
"3. A Word document with detailed specifications",
AnthropicChatOptions.builder()
.model("claude-sonnet-4-5")
.maxTokens(16384)
.skill(AnthropicApi.AnthropicSkill.XLSX)
.skill(AnthropicApi.AnthropicSkill.PPTX)
.skill(AnthropicApi.AnthropicSkill.DOCX)
.build()
)
);
Map<String, byte[]> documents = new HashMap<>();
List<String> fileIds = AnthropicSkillsResponseHelper.extractFileIds(response);
for (String fileId : fileIds) {
AnthropicApi.FileMetadata metadata = anthropicApi.getFileMetadata(fileId);
byte[] content = anthropicApi.downloadFile(fileId);
documents.put(metadata.filename(), content);
}
return documents;
}
结合技能与其他功能
技能可以与其他Anthropic功能(如提示缓存)结合使用:
ChatResponse response = chatModel.call(
new Prompt(
List.of(
new SystemMessage("You are an expert data analyst and document creator..."),
new UserMessage("Create a financial summary spreadsheet")
),
AnthropicChatOptions.builder()
.model("claude-sonnet-4-5")
.maxTokens(4096)
.skill(AnthropicApi.AnthropicSkill.XLSX)
.cacheOptions(AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.SYSTEM_ONLY)
.build())
.build()
)
);
自定义技能
除了预构建的技能,Anthropic 还支持您创建自定义技能以用于专门的文档模板、格式化规则或特定领域的行为。
自定义技能是 SKILL.md 文件,您可以将包含指令的这些文件上传到您的 Anthropic 工作区。
一旦上传,您就可以在 Spring AI 中与预构建的技能一起使用它们。
自定义技能适用于:
-
企业品牌化: 应用一致的页眉、页脚、标志和色彩方案
-
强制性要求:添加必要的免责声明、保密声明或审计追踪
-
文档模板: 遵循特定的结构规范报告、提案或规格说明
-
领域专长:包含行业特定的术语、计算规则或格式要求
在创建自定义技能方面,请参阅Anthropic Skills API 文档。
上传自定义技能
使用Anthropic API上传您的技能。
注意files[]参数的具体格式要求:
curl -X POST "https://api.anthropic.com/v1/skills" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: skills-2025-10-02" \
-F "display_title=My Custom Skill" \
-F "files[][email protected];filename=my-skill-name/SKILL.md"
|
响应包含您的技能ID:
{
"id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
"display_title": "My Custom Skill",
"source": "custom",
"latest_version": "1765845644409101"
}
使用自定义技能在Spring AI
使用 .skill() 方法通过其ID引用您的自定义技能:
ChatResponse response = chatModel.call(
new Prompt(
"Create a quarterly sales report",
AnthropicChatOptions.builder()
.model("claude-sonnet-4-5")
.maxTokens(4096)
.skill("skill_01AbCdEfGhIjKlMnOpQrStUv")
.build()
)
);
结合预构建和自定义技能
您可以在同一个请求中使用预构建技能和自定义技能。 这使得您可以利用Anthropic的文档生成能力,同时应用您组织的具体要求:
ChatResponse response = chatModel.call(
new Prompt(
"Create a sales report spreadsheet",
AnthropicChatOptions.builder()
.model("claude-sonnet-4-5")
.maxTokens(4096)
.skill(AnthropicApi.AnthropicSkill.XLSX) // Pre-built
.skill("skill_01AbCdEfGhIjKlMnOpQrStUv") // Your custom skill
.build()
)
);
使用 SkillContainer 配置自定义技能
使用 `0` 直接获得更详细的技能版本控制:
AnthropicApi.SkillContainer container = AnthropicApi.SkillContainer.builder()
.skill(AnthropicApi.AnthropicSkill.XLSX)
.skill("skill_01AbCdEfGhIjKlMnOpQrStUv") // Uses latest version
.skill("skill_02XyZaBcDeFgHiJkLmNoPq", "1765845644409101") // Specific version
.build();
ChatResponse response = chatModel.call(
new Prompt(
"Generate the report",
AnthropicChatOptions.builder()
.model("claude-sonnet-4-5")
.maxTokens(8192)
.skillContainer(container)
.build()
)
);
更新自定义技能
要更新现有技能,请将新版本上传到 /versions 端点:
curl -X POST "https://api.anthropic.com/v1/skills/YOUR_SKILL_ID/versions" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: skills-2025-10-02" \
-F "files[][email protected];filename=my-skill-name/SKILL.md"
当使用版本latest(默认值)时,会自动选择新的版本。
完全自定义技能示例
这里是一个完整的示例,展示了一个可选地应用自定义品牌技能的服务:
@Service
public class BrandedDocumentService {
private static final String BRANDING_SKILL_ID = "skill_01AbCdEfGhIjKlMnOpQrStUv";
private final AnthropicChatModel chatModel;
private final AnthropicApi anthropicApi;
public BrandedDocumentService(AnthropicChatModel chatModel, AnthropicApi anthropicApi) {
this.chatModel = chatModel;
this.anthropicApi = anthropicApi;
}
public byte[] generateReport(String prompt, boolean includeBranding) throws IOException {
// Build options with document skill
AnthropicChatOptions.Builder optionsBuilder = AnthropicChatOptions.builder()
.model("claude-sonnet-4-5")
.maxTokens(8192)
.skill(AnthropicApi.AnthropicSkill.XLSX);
// Add custom branding skill if requested
if (includeBranding) {
optionsBuilder.skill(BRANDING_SKILL_ID);
}
ChatResponse response = chatModel.call(
new Prompt(prompt, optionsBuilder.build())
);
// Extract and download the generated file
List<String> fileIds = AnthropicSkillsResponseHelper.extractFileIds(response);
if (fileIds.isEmpty()) {
throw new RuntimeException("No file was generated");
}
return anthropicApi.downloadFile(fileIds.get(0));
}
}
样本控制器
创建一个新的Spring Boot项目,并在pom(或gradle)依赖中添加spring-ai-starter-model-anthropic。
在src/main/resources目录下添加一个application.properties文件,以启用并配置Anthropic聊天模型:
spring.ai.anthropic.api-key=YOUR_API_KEY
spring.ai.anthropic.chat.options.model=claude-3-5-sonnet-latest
spring.ai.anthropic.chat.options.temperature=0.7
spring.ai.anthropic.chat.options.max-tokens=450
请用你的Anthropic凭据替换api-key。 |
这将创建一个AnthropicChatModel实现,你可以在你的类中注入。
以下是一个使用聊天模型进行文本生成的简单@Controller类示例。
@RestController
public class ChatController {
private final AnthropicChatModel chatModel;
@Autowired
public ChatController(AnthropicChatModel chatModel) {
this.chatModel = chatModel;
}
@GetMapping("/ai/generate")
public Map generate(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
return Map.of("generation", this.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 this.chatModel.stream(prompt);
}
}
手动配置
The AnthropicChatModel 实现了ChatModel 和 StreamingChatModel,并使用低级 AnthropicApi 客户端 连接到 Anthropic 服务。
将如下的spring-ai-anthropic依赖添加到项目中Maven的pom.xml文件中:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-anthropic</artifactId>
</dependency>
请将以下内容添加到您的Gradle build.gradle 构建文件中。
dependencies {
implementation 'org.springframework.ai:spring-ai-anthropic'
}
| 请参阅依赖管理部分,将Spring AI BOM添加到您的构建文件中。 |
接下来,创建一个AnthropicChatModel 并用于文本生成:
var anthropicApi = new AnthropicApi(System.getenv("ANTHROPIC_API_KEY"));
var anthropicChatOptions = AnthropicChatOptions.builder()
.model("claude-3-7-sonnet-20250219")
.temperature(0.4)
.maxTokens(200)
.build()
var chatModel = AnthropicChatModel.builder().anthropicApi(anthropicApi)
.defaultOptions(anthropicChatOptions).build();
ChatResponse response = this.chatModel.call(
new Prompt("Generate the names of 5 famous pirates."));
// Or with streaming responses
Flux<ChatResponse> response = this.chatModel.stream(
new Prompt("Generate the names of 5 famous pirates."));
The AnthropicChatOptions 提供了聊天请求的配置信息。
The AnthropicChatOptions.Builder 是流畅选项构建器。
低级 AnthropicApi 客户端
The AnthropicApi 提供了一个轻量级的 Java 客户端用于 Anthropic Message API 。
以下类图说明了AnthropicApi聊天接口和构建块:
这里是如何通过程序方式使用API的一个简单示例:
AnthropicApi anthropicApi =
new AnthropicApi(System.getenv("ANTHROPIC_API_KEY"));
AnthropicMessage chatCompletionMessage = new AnthropicMessage(
List.of(new ContentBlock("Tell me a Joke?")), Role.USER);
// Sync request
ResponseEntity<ChatCompletionResponse> response = this.anthropicApi
.chatCompletionEntity(new ChatCompletionRequest(AnthropicApi.ChatModel.CLAUDE_OPUS_4_5.getValue(),
List.of(this.chatCompletionMessage), null, 100, 0.8, false));
// Streaming request
Flux<StreamResponse> response = this.anthropicApi
.chatCompletionStream(new ChatCompletionRequest(AnthropicApi.ChatModel.CLAUDE_OPUS_4_5.getValue(),
List.of(this.chatCompletionMessage), null, 100, 0.8, true));
跟随AnthropicApi.java的JavaDoc以获得更多信息。
低级API示例
-
The AnthropicApiIT.java 测试提供了一些关于如何使用轻量级库的通用示例。