LangChain4j Spring Boot Starter:自动配置与声明式 Bean 管理
Spring Boot 的魅力在于"约定优于配置"------LangChain4j 1.4.0 将这个理念贯彻到底,让 AI 能力的接入从"写一百行配置"变成"加几个注解"。
前言:为什么要有 Spring Boot Starter?
在没有 Starter 之前,集成 LangChain4j 到 Spring Boot 项目需要:
java
// 原始方式(每个用到的地方都要手动创建)
@Configuration
public class LangChainConfig {
@Bean
public ChatLanguageModel chatModel() {
return OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o")
.temperature(0.7)
.timeout(Duration.ofSeconds(60))
.maxTokens(2000)
.build();
}
@Bean
public EmbeddingModel embeddingModel() {
return OpenAiEmbeddingModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("text-embedding-3-small")
.build();
}
@Bean
public StreamingChatLanguageModel streamingModel() { ... }
@Bean
public ChatMemoryProvider memoryProvider() { ... }
// 还有向量存储、检索器等 10+ 个 Bean...
}
这段配置代码几乎每个项目都要写一遍,且容易出错。
Spring Boot Starter 的价值:
- 零配置启动 :加一个依赖,
application.yml里写几行,模型 Bean 自动注入 - 声明式 AI Service :接口加
@AiService注解,自动成为 Spring Bean - 多环境隔离:dev/test/prod 各用不同模型,配置文件切换
- 监控一体化:Micrometer 指标自动接入,Prometheus/Grafana 开箱即用
一、Starter 模块全景图
LangChain4j 1.4.0 提供了 6 个核心 Spring Boot 模块:
LangChain4j Spring Boot 模块体系:
+-----------------------------------------------------------+
| langchain4j-spring-boot-starter | <- 核心模块
| 提供:@AiService 声明式 API、ChatMemory 自动装配 |
+-----------------------------------------------------------+
|
| 模型 Starter(按需选择)
+-- langchain4j-open-ai-spring-boot-starter
+-- langchain4j-ollama-spring-boot-starter
+-- langchain4j-anthropic-spring-boot-starter
+-- langchain4j-azure-open-ai-spring-boot-starter
+-- langchain4j-google-ai-gemini-spring-boot-starter
|
| 存储 Starter
+-- langchain4j-pgvector-spring-boot-starter
+-- langchain4j-redis-spring-boot-starter
+-- langchain4j-elasticsearch-spring-boot-starter
|
| 监控 Starter
+-- langchain4j-micrometer (Micrometer 指标集成)
+-- langchain4j-observation (OpenTelemetry Tracing)
版本要求:
- Java 17+(LangChain4j 1.x 基线)
- Spring Boot 3.5+(暂不支持 Spring Boot 4.x)
二、快速搭建:从零到跑通
2.1 Maven 依赖
xml
<properties>
<java.version>17</java.version>
<spring-boot.version>3.5.0</spring-boot.version>
<langchain4j.version>1.0.0-alpha1</langchain4j.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- LangChain4j 核心 Starter(必须,提供 @AiService 注解支持) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<!-- OpenAI 集成 Starter(自动配置 ChatModel、EmbeddingModel 等) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<!-- 向量存储(可选,PgVector) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-pgvector-spring-boot-starter</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<!-- Micrometer 监控(可选) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter-test</artifactId>
<version>${langchain4j.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
2.2 最简配置(application.yml)
yaml
spring:
application:
name: langchain4j-demo
# LangChain4j OpenAI 自动配置
langchain4j:
open-ai:
chat-model:
api-key: ${OPENAI_API_KEY}
model-name: gpt-4o-mini # 推荐开发环境用 mini,成本低
temperature: 0.7
max-tokens: 2000
timeout: PT60S # ISO-8601 格式,60 秒
log-requests: true # 开发时开启,生产关闭
log-responses: true
embedding-model:
api-key: ${OPENAI_API_KEY}
model-name: text-embedding-3-small
# Spring Boot Actuator 暴露监控端点
management:
endpoints:
web:
exposure:
include: health,info,prometheus,metrics
2.3 最简 AI Service
java
// 第一步:声明 AI Service 接口(加 @AiService 注解即可)
import dev.langchain4j.service.spring.AiService;
@AiService
public interface Assistant {
@SystemMessage("你是一个专业、友好的 AI 助手,用中文回答所有问题。")
String chat(@UserMessage String message);
}
// 第二步:注入并使用(像普通 Spring Bean 一样)
@RestController
@RequestMapping("/api")
public class ChatController {
private final Assistant assistant;
public ChatController(Assistant assistant) {
this.assistant = assistant;
}
@PostMapping("/chat")
public String chat(@RequestBody String message) {
return assistant.chat(message);
}
}
就这些!加两个注解,写几行 YAML,一个 AI 对话接口就完成了。Spring Boot 会自动:
- 读取
application.yml创建ChatLanguageModelBean - 扫描到
@AiService接口,生成代理实现类 - 将代理类注册为 Spring Bean,支持
@Autowired注入
三、自动配置原理深挖
理解自动配置原理,才能在出问题时快速定位。
3.1 自动配置加载机制
Spring Boot 自动配置加载流程:
@SpringBootApplication
|
▼
@EnableAutoConfiguration
|
▼
spring.factories / AutoConfiguration.imports
|
▼
LangChain4jAutoConfiguration(核心配置类)
|
+-- 扫描 @AiService 接口
+-- 注册 ChatLanguageModel Bean
+-- 注册 EmbeddingModel Bean
+-- 注册 ChatMemoryProvider Bean
+-- 生成 AI Service 代理实现
3.2 条件装配(@ConditionalOnProperty)
LangChain4j Starter 利用 Spring Boot 的条件注解,只有配置了对应属性才创建 Bean:
java
// 模拟 LangChain4j 内部的条件配置(仅示意,非真实源码)
@AutoConfiguration
@ConditionalOnClass(OpenAiChatModel.class)
public class OpenAiAutoConfiguration {
@Bean
@ConditionalOnProperty(
prefix = "langchain4j.open-ai.chat-model",
name = "api-key"
)
@ConditionalOnMissingBean(ChatLanguageModel.class) // 用户自定义 Bean 优先
public ChatLanguageModel openAiChatModel(
OpenAiChatModelProperties properties
) {
return OpenAiChatModel.builder()
.apiKey(properties.getApiKey())
.modelName(properties.getModelName())
.temperature(properties.getTemperature())
.maxTokens(properties.getMaxTokens())
.timeout(properties.getTimeout())
.build();
}
}
关键点 :@ConditionalOnMissingBean 确保用户的自定义 Bean 优先级高于自动配置。
3.3 AI Service 代理生成
@AiService 工作原理:
@AiService
interface MyAssistant {
String chat(String msg);
}
|
▼ (启动时扫描)
AiServiceBeanDefinitionPostProcessor
|
▼ (分析接口)
解析:
- @SystemMessage → 系统提示词
- @MemoryId 参数 → ChatMemory 注入
- @UserMessage → 用户消息构造
- @Tool 方法 → 工具注册
|
▼ (生成实现)
AiServiceProxy(JDK 动态代理)
|
▼ (注册为 Bean)
@Bean("myAssistant")
MyAssistant myAssistantBean = AiServices.builder(MyAssistant.class)
.chatLanguageModel(autoWiredChatModel)
.chatMemory(autoWiredMemory)
.tools(autoWiredTools)
.build();
四、多模型配置:生产环境的必备能力
4.1 多环境模型隔离
不同环境使用不同配置,通过 Spring Profile 管理:
yaml
# application.yml(公共配置)
spring:
profiles:
active: dev # 默认开发环境
langchain4j:
open-ai:
embedding-model: # 嵌入模型各环境共用
api-key: ${OPENAI_API_KEY}
model-name: text-embedding-3-small
---
# application-dev.yml(开发环境)
spring:
config:
activate:
on-profile: dev
langchain4j:
open-ai:
chat-model:
api-key: ${OPENAI_API_KEY_DEV}
model-name: gpt-4o-mini # 便宜的 mini 模型
temperature: 0.9 # 开发时允许更有创意
log-requests: true # 开启日志方便调试
log-responses: true
---
# application-test.yml(测试环境)
spring:
config:
activate:
on-profile: test
langchain4j:
open-ai:
chat-model:
api-key: ${OPENAI_API_KEY_TEST}
model-name: gpt-4o-mini
log-requests: false # 关闭日志,减少测试输出
---
# application-prod.yml(生产环境)
spring:
config:
activate:
on-profile: prod
langchain4j:
open-ai:
chat-model:
api-key: ${OPENAI_API_KEY_PROD} # 生产 Key,权限更高
model-name: gpt-4o # 使用高质量模型
temperature: 0.7
max-tokens: 4000
timeout: PT120S # 生产允许更长超时
log-requests: false # 生产关闭日志(安全考虑)
log-responses: false
max-retries: 3 # 生产开启重试
4.2 同一应用多模型并用
有时需要在同一个应用中使用不同的模型(如:聊天用 GPT-4,嵌入用 text-embedding):
yaml
# 多模型共存配置
langchain4j:
open-ai:
# GPT-4o:用于高质量对话
chat-model:
api-key: ${OPENAI_API_KEY}
model-name: gpt-4o
# text-embedding-3-small:用于向量嵌入
embedding-model:
api-key: ${OPENAI_API_KEY}
model-name: text-embedding-3-small
# GPT-4o streaming:用于流式响应
streaming-chat-model:
api-key: ${OPENAI_API_KEY}
model-name: gpt-4o
# 本地 Ollama:用于内部文档处理(不外发数据)
ollama:
chat-model:
base-url: http://localhost:11434
model-name: llama3.1:8b
使用不同模型的 AI Service:
java
// 默认使用 OpenAI
@AiService
public interface PublicAssistant {
@SystemMessage("你是公共客服助手")
String chat(String message);
}
// 显式指定使用 Ollama(内部数据不外发)
@AiService(wiringMode = EXPLICIT)
public interface InternalDocumentAssistant {
@SystemMessage("你是内部文档助手,处理公司内部文件")
String analyze(String document);
}
// 在配置类中显式指定 Ollama 模型
@Configuration
public class InternalAssistantConfig {
@Bean
public InternalDocumentAssistant internalAssistant(
// 注入 Ollama 模型(Bean 名称 ollamaChatModel)
@Qualifier("ollamaChatModel") ChatLanguageModel ollamaModel
) {
return AiServices.builder(InternalDocumentAssistant.class)
.chatLanguageModel(ollamaModel)
.build();
}
}
4.3 动态模型切换(灰度发布)
java
@Service
public class DynamicModelRouter {
private final ChatLanguageModel gpt4oModel;
private final ChatLanguageModel gpt4oMiniModel;
// 灰度比例:10% 请求使用 GPT-4o,90% 使用 mini
private static final double GPT4O_RATIO = 0.1;
public ChatLanguageModel selectModel(String userId) {
// 基于用户 ID 哈希,保证同一用户始终用相同模型
int hash = Math.abs(userId.hashCode() % 100);
if (hash < (int)(GPT4O_RATIO * 100)) {
return gpt4oModel; // 高质量组(10%)
} else {
return gpt4oMiniModel; // 普通组(90%)
}
}
public String chat(String userId, String message) {
ChatLanguageModel model = selectModel(userId);
return model.generate(message);
}
}
五、声明式 AI Service 高级用法
5.1 完整注解体系
java
@AiService
public interface AdvancedAssistant {
// 基础:系统提示 + 用户消息
@SystemMessage("你是专业的技术顾问")
String basicChat(@UserMessage String message);
// 带记忆:多轮对话
@SystemMessage("你是一个有记忆的助手,记住之前的对话")
String chatWithMemory(
@MemoryId String sessionId, // 标识会话,相同 ID 共享记忆
@UserMessage String message
);
// 模板变量:@UserMessage + @V 注解
@SystemMessage("你是 {{role}} 领域的专家")
@UserMessage("请用 {{language}} 回答:{{question}}")
String expertAnswer(
@V("role") String role,
@V("language") String language,
@V("question") String question
);
// 结构化输出:直接返回 Java 对象
@UserMessage("分析以下商品评论,提取情感和关键问题:{{review}}")
ReviewAnalysis analyzeReview(@V("review") String review);
// 流式响应
@SystemMessage("你是助手")
TokenStream streamChat(@UserMessage String message);
}
// 结构化输出对象
public record ReviewAnalysis(
@Description("情感倾向:POSITIVE/NEGATIVE/NEUTRAL")
String sentiment,
@Description("评分,1-5分")
int score,
@Description("核心问题列表")
List<String> issues,
@Description("改进建议")
String suggestions
) {}
5.2 自动工具注入
java
// 定义工具类(加 @Component 注册为 Spring Bean)
@Component
public class WeatherTools {
@Tool("获取指定城市的天气")
public String getWeather(@P("城市名") String city) {
return weatherApiClient.getCurrentWeather(city);
}
@Tool("获取未来 N 天的天气预报")
public String getWeatherForecast(
@P("城市名") String city,
@P("天数,1-7") int days
) {
return weatherApiClient.getForecast(city, days);
}
}
@Component
public class CalendarTools {
@Tool("查询用户的日程安排")
public List<String> getSchedule(@P("日期,格式 yyyy-MM-dd") String date) {
return calendarService.getScheduleByDate(date);
}
@Tool("创建日程提醒")
public String createReminder(
@P("提醒内容") String content,
@P("提醒时间,格式 yyyy-MM-dd HH:mm") String time
) {
return calendarService.createReminder(content, time);
}
}
// AI Service 自动扫描并注入所有 @Component + @Tool 的工具
@AiService
public interface SmartAssistant {
@SystemMessage("""
你是一个智能个人助手,可以:
- 查询天气和预报
- 管理日程和提醒
根据用户需求调用合适的工具。
""")
String assist(@MemoryId String userId, @UserMessage String request);
}
注意 :
@AiService会自动扫描当前 Spring 上下文中所有包含@Tool方法的 Bean,无需手动注册。
5.3 ChatMemory 自动装配
java
// 基于 Redis 的持久化记忆(Spring Boot 自动装配)
@Configuration
public class ChatMemoryConfig {
/**
* 基于 Redis 的 ChatMemoryStore
* 会话记忆持久化,应用重启不丢失
*/
@Bean
public ChatMemoryStore redisChatMemoryStore(RedisTemplate<String, Object> redis) {
return new RedisChatMemoryStore(redis, Duration.ofDays(7));
}
/**
* ChatMemoryProvider:为每个 memoryId 提供独立的记忆实例
*/
@Bean
public ChatMemoryProvider chatMemoryProvider(ChatMemoryStore store) {
return memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(20) // 保留最近 20 条消息
.chatMemoryStore(store) // 使用 Redis 持久化
.build();
}
}
// Redis ChatMemoryStore 实现
@Component
public class RedisChatMemoryStore implements ChatMemoryStore {
private final RedisTemplate<String, Object> redis;
private final Duration ttl;
@Override
public List<ChatMessage> getMessages(Object memoryId) {
String key = "chat:memory:" + memoryId;
List<Object> raw = redis.opsForList().range(key, 0, -1);
if (raw == null) return List.of();
return raw.stream()
.map(obj -> (ChatMessage) obj)
.toList();
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
String key = "chat:memory:" + memoryId;
redis.delete(key);
if (!messages.isEmpty()) {
redis.opsForList().rightPushAll(key, messages.toArray());
redis.expire(key, ttl);
}
}
@Override
public void deleteMessages(Object memoryId) {
redis.delete("chat:memory:" + memoryId);
}
}
六、事务集成:保证数据一致性
AI Service 调用往往需要与数据库操作在同一事务中,LangChain4j Starter 与 Spring 事务完全兼容。
6.1 AI 对话与数据库操作事务
java
@Service
@Slf4j
public class ConversationService {
private final SmartAssistant assistant;
private final ConversationRepository conversationRepo;
private final MessageRepository messageRepo;
/**
* 处理用户消息,AI 回复和数据库记录在同一事务内
* 如果 AI 调用失败,数据库也不会有记录(回滚)
*/
@Transactional(rollbackFor = Exception.class)
public ConversationResponse handleMessage(
String userId,
String conversationId,
String userMessage
) {
// 1. 创建或获取会话记录
Conversation conversation = conversationRepo
.findById(conversationId)
.orElseGet(() -> {
Conversation newConv = new Conversation();
newConv.setId(conversationId);
newConv.setUserId(userId);
newConv.setCreatedAt(LocalDateTime.now());
return conversationRepo.save(newConv);
});
// 2. 记录用户消息(事务内)
Message userMsg = new Message();
userMsg.setConversationId(conversationId);
userMsg.setRole("user");
userMsg.setContent(userMessage);
userMsg.setTimestamp(LocalDateTime.now());
messageRepo.save(userMsg);
// 3. 调用 AI(如果这里抛出异常,上面的数据库操作也会回滚)
String aiResponse = assistant.assist(userId, userMessage);
// 4. 记录 AI 回复(事务内)
Message aiMsg = new Message();
aiMsg.setConversationId(conversationId);
aiMsg.setRole("assistant");
aiMsg.setContent(aiResponse);
aiMsg.setTimestamp(LocalDateTime.now());
messageRepo.save(aiMsg);
// 5. 更新会话最后活跃时间
conversation.setLastActiveAt(LocalDateTime.now());
conversationRepo.save(conversation);
return new ConversationResponse(conversationId, aiResponse);
}
}
6.2 使用 @TransactionalEventListener 处理异步后续
java
@Service
public class PostConversationProcessor {
/**
* 会话完成后,异步更新用户统计(不影响主流程事务)
*/
@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onConversationCompleted(ConversationCompletedEvent event) {
// 更新用户使用统计
userStatsService.incrementMessageCount(event.getUserId());
// 记录 Token 使用量(用于计费)
billingService.recordUsage(event.getUserId(), event.getTokenCount());
}
}
七、Micrometer 监控:生产级可观测性
7.1 自定义 ChatModel 监听器
java
@Component
@Slf4j
public class LangChain4jMetricsListener implements ChatModelListener {
private final MeterRegistry registry;
// 定义指标
private final Counter requestCounter;
private final Counter errorCounter;
private final DistributionSummary inputTokenSummary;
private final DistributionSummary outputTokenSummary;
private final Timer responseTimer;
public LangChain4jMetricsListener(MeterRegistry registry) {
this.registry = registry;
this.requestCounter = Counter.builder("langchain4j.chat.requests.total")
.description("Total chat model requests")
.register(registry);
this.errorCounter = Counter.builder("langchain4j.chat.errors.total")
.description("Total chat model errors")
.register(registry);
this.inputTokenSummary = DistributionSummary
.builder("langchain4j.chat.tokens.input")
.description("Input tokens per request")
.register(registry);
this.outputTokenSummary = DistributionSummary
.builder("langchain4j.chat.tokens.output")
.description("Output tokens per request")
.register(registry);
this.responseTimer = Timer.builder("langchain4j.chat.response.time")
.description("Chat model response time")
.publishPercentiles(0.5, 0.95, 0.99) // 记录 P50/P95/P99
.register(registry);
}
@Override
public void onRequest(ChatModelRequestContext context) {
requestCounter.increment();
// 记录开始时间(通过请求属性传递)
context.attributes().put("requestStartTime", System.currentTimeMillis());
}
@Override
public void onResponse(ChatModelResponseContext context) {
// 计算响应时间
Long startTime = (Long) context.attributes().get("requestStartTime");
if (startTime != null) {
long elapsed = System.currentTimeMillis() - startTime;
responseTimer.record(elapsed, TimeUnit.MILLISECONDS);
}
// 记录 Token 使用
TokenUsage usage = context.response().tokenUsage();
if (usage != null) {
inputTokenSummary.record(usage.inputTokenCount());
outputTokenSummary.record(usage.outputTokenCount());
// 成本估算(OpenAI gpt-4o 价格)
double cost = usage.inputTokenCount() * 0.000005 +
usage.outputTokenCount() * 0.000015;
Gauge.builder("langchain4j.chat.cost.estimate", () -> cost)
.description("Estimated API cost in USD")
.register(registry);
log.debug("AI 调用完成 - 输入:{} tokens,输出:{} tokens,成本估算:${}",
usage.inputTokenCount(), usage.outputTokenCount(),
String.format("%.6f", cost));
}
}
@Override
public void onError(ChatModelErrorContext context) {
errorCounter.increment();
log.error("AI 调用失败 - 错误:{}",
context.error().getMessage(), context.error());
// 按错误类型分类计数
String errorType = context.error().getClass().getSimpleName();
Counter.builder("langchain4j.chat.errors.by.type")
.tag("type", errorType)
.register(registry)
.increment();
}
}
7.2 将监听器注入模型
java
@Configuration
public class MonitoredModelConfig {
@Bean
public ChatLanguageModel monitoredChatModel(
LangChain4jMetricsListener metricsListener,
@Value("${langchain4j.open-ai.chat-model.api-key}") String apiKey,
@Value("${langchain4j.open-ai.chat-model.model-name}") String modelName
) {
return OpenAiChatModel.builder()
.apiKey(apiKey)
.modelName(modelName)
.listeners(List.of(metricsListener)) // 注入监听器
.build();
}
}
7.3 Prometheus + Grafana 可视化
yaml
# application-prod.yml 监控配置
management:
endpoints:
web:
exposure:
include: health,info,prometheus,metrics
endpoint:
health:
show-details: always
metrics:
export:
prometheus:
enabled: true
tags:
application: ${spring.application.name}
environment: prod
Prometheus 抓取配置(prometheus.yml):
yaml
scrape_configs:
- job_name: 'langchain4j-app'
metrics_path: '/actuator/prometheus'
scrape_interval: 15s
static_configs:
- targets: ['app-host:8080']
Grafana Dashboard 关键面板:
+------------------------------------------+
| LangChain4j 监控大盘 |
+------------------------------------------+
| 请求数(5min) | 错误率 | P95延迟 |
| 1,247 | 0.2% | 2.1s |
+------------------------------------------+
| |
| 响应时间分布(折线图) |
| P50: 0.9s | P95: 2.1s | P99: 4.5s |
| |
+------------------------------------------+
| Token 使用量(24h) 成本估算(今日) |
| Input: 1.2M $6.8 |
| Output: 380K (按小时展示) |
+------------------------------------------+
| 错误类型分布(饼图) |
| 超时: 45% | API限速: 30% | 其他: 25% |
+------------------------------------------+
八、Spring Boot 事务与 AI 的一致性保障
8.1 AI 调用失败的补偿机制
java
@Service
public class ResilientAiService {
private final SmartAssistant primaryAssistant;
private final SmartAssistant fallbackAssistant; // 降级模型
/**
* 带降级的 AI 调用
* 主模型失败时,自动切换到降级模型(如从 GPT-4o 降到 mini)
*/
public String chatWithFallback(String userId, String message) {
try {
return primaryAssistant.assist(userId, message);
} catch (Exception primaryEx) {
log.warn("主模型调用失败,降级到备用模型: {}",
primaryEx.getMessage());
try {
return fallbackAssistant.assist(userId, message);
} catch (Exception fallbackEx) {
log.error("降级模型也失败: {}", fallbackEx.getMessage());
return "抱歉,AI 服务暂时不可用,请稍后重试。";
}
}
}
/**
* 带重试的 AI 调用(适合网络抖动场景)
*/
@Retryable(
retryFor = {RuntimeException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000, multiplier = 2) // 指数退避:1s, 2s, 4s
)
public String chatWithRetry(String userId, String message) {
return primaryAssistant.assist(userId, message);
}
@Recover
public String recoverFromRetry(RuntimeException e, String userId, String message) {
log.error("重试 3 次后仍失败: {}", e.getMessage());
return "系统繁忙,请稍后重试。";
}
}
8.2 分布式锁保证 AI 调用幂等性
java
@Service
public class IdempotentAiService {
private final StringRedisTemplate redis;
private final SmartAssistant assistant;
/**
* 基于请求 ID 的幂等调用
* 相同 requestId 的请求只调用 AI 一次
*/
public String idempotentChat(String requestId, String userId, String message) {
String lockKey = "ai:lock:" + requestId;
String resultKey = "ai:result:" + requestId;
// 先检查是否已有结果
String cached = redis.opsForValue().get(resultKey);
if (cached != null) {
return cached;
}
// 尝试获取分布式锁
Boolean locked = redis.opsForValue()
.setIfAbsent(lockKey, "1", Duration.ofSeconds(30));
if (Boolean.FALSE.equals(locked)) {
// 等待其他节点完成
return waitForResult(resultKey);
}
try {
// 调用 AI
String result = assistant.assist(userId, message);
// 缓存结果(有效期 1 小时)
redis.opsForValue().set(resultKey, result, Duration.ofHours(1));
return result;
} finally {
redis.delete(lockKey);
}
}
}
九、完整项目示例:AI 驱动的客服系统
java
// 1. 主入口
@SpringBootApplication
public class CustomerServiceApplication {
public static void main(String[] args) {
SpringApplication.run(CustomerServiceApplication.class, args);
}
}
// 2. AI Service 接口
@AiService
public interface CustomerServiceAgent {
@SystemMessage("""
你是XX公司的专业客服代表,名叫"小智"。
你的职责:
1. 友好、专业地回答客户问题
2. 查询订单状态和物流信息
3. 处理退换货申请
4. 解答产品使用问题
注意事项:
- 始终保持友好和耐心
- 对于无法处理的问题,引导转接人工客服
- 不要承诺无法兑现的内容
""")
String handleInquiry(
@MemoryId String sessionId,
@UserMessage String customerMessage
);
}
// 3. 工具定义
@Component
public class CustomerServiceTools {
@Tool("查询订单详情,包括状态、物流信息")
public String queryOrder(@P("订单号,格式 ORD-XXXXXXXXXX") String orderId) {
Order order = orderService.findById(orderId);
if (order == null) return "未找到订单 " + orderId;
return String.format(
"订单号:%s,状态:%s,商品:%s,金额:%.2f元," +
"物流:%s(%s),预计送达:%s",
order.getId(), order.getStatus(), order.getProductName(),
order.getAmount(), order.getLogisticsCompany(),
order.getTrackingNumber(), order.getExpectedDeliveryDate()
);
}
@Tool("提交退换货申请")
public String submitReturnRequest(
@P("订单号") String orderId,
@P("退换货原因") String reason,
@P("处理方式:退款/换货") String action
) {
ReturnRequest request = returnService.create(orderId, reason, action);
return "退换货申请已提交,申请编号:" + request.getId() +
",预计 3-5 个工作日处理完毕。";
}
@Tool("查询产品使用说明和常见问题")
public String getProductHelp(@P("产品名称或关键词") String keyword) {
return knowledgeBase.search(keyword);
}
@Tool("转接人工客服")
public String transferToHuman(@P("转接原因") String reason) {
ticketService.createTicket(reason);
return "已为您转接人工客服,预计等待时间 3-5 分钟,请稍候。";
}
}
// 4. REST 接口
@RestController
@RequestMapping("/api/customer-service")
@Slf4j
public class CustomerServiceController {
private final CustomerServiceAgent agent;
private final ResilientAiService resilientService;
@PostMapping("/chat")
public ResponseEntity<ChatResponse> chat(
@RequestParam String sessionId,
@RequestBody ChatRequest request,
HttpServletRequest httpRequest
) {
log.info("客服请求 - Session: {}, Message: {}",
sessionId, request.getMessage().substring(0, Math.min(50, request.getMessage().length())));
String response = resilientService.chatWithFallback(
sessionId, request.getMessage()
);
return ResponseEntity.ok(new ChatResponse(response, sessionId));
}
@DeleteMapping("/sessions/{sessionId}")
public ResponseEntity<Void> clearSession(@PathVariable String sessionId) {
chatMemoryProvider.getMemory(sessionId).clear();
return ResponseEntity.noContent().build();
}
}
十、常见问题(FAQ)
Q1: 自动配置时报 "No qualifying bean of type 'ChatLanguageModel'"
原因 :缺少模型 Starter 或未配置 API Key
解决:
- 确认已添加
langchain4j-open-ai-spring-boot-starter依赖 - 确认
application.yml中配置了langchain4j.open-ai.chat-model.api-key
Q2: @AiService 扫描不到,Bean 注入失败
原因 :@AiService 接口不在 Spring Boot 扫描路径内
解决:
java
@SpringBootApplication
@AiServiceScan("com.example.services") // 显式指定扫描路径
public class Application {}
Q3: 如何在测试中 Mock AI 模型?
java
@SpringBootTest
class AssistantTest {
@MockBean // Mock 掉真实模型,避免测试时真实调用 API
private ChatLanguageModel chatModel;
@Autowired
private CustomerServiceAgent agent;
@Test
void testOrderQuery() {
// 配置 Mock 返回
given(chatModel.generate(any(List.class)))
.willReturn(new Response<>(new AiMessage("您的订单 ORD-001 正在配送中")));
String result = agent.handleInquiry("session-1", "查询订单 ORD-001");
assertThat(result).contains("配送中");
}
}
Q4: 生产环境 Bean 冲突怎么处理?
java
// 当有多个 ChatLanguageModel Bean 时,指定首选
@Bean
@Primary // 标记为首选 Bean
public ChatLanguageModel primaryModel() {
return OpenAiChatModel.builder()
.modelName("gpt-4o")
.build();
}
@Bean("fallbackModel") // 给备用模型命名
public ChatLanguageModel fallbackModel() {
return OpenAiChatModel.builder()
.modelName("gpt-4o-mini")
.build();
}
十一、总结
LangChain4j Spring Boot Starter 让 AI 能力接入 Spring Boot 变得极其简单:
核心价值总结
| 特性 | 效果 | 工作量减少 |
|---|---|---|
| 自动配置 | 配置文件写几行即可 | -80% 配置代码 |
| @AiService | 接口即服务 | -90% 胶水代码 |
| 多环境隔离 | Profile 切换模型 | -70% 环境配置 |
| Micrometer 集成 | 开箱即用的监控 | -85% 监控代码 |
生产环境 Checklist
☐ 使用环境变量管理 API Key(不要写死在配置文件)
☐ 生产环境关闭 log-requests/log-responses(安全考虑)
☐ 配置 maxRetries(应对 API 抖动)
☐ 设置 timeout(避免长时间阻塞)
☐ 启用 Micrometer 监控(实时了解成本和性能)
☐ 使用 Redis 持久化 ChatMemory(防止重启丢失会话)
☐ 配置 fallback 模型(主模型失败自动降级)
如果这篇文章对你有帮助,欢迎点赞、收藏!Spring Boot 集成问题欢迎评论区交流。