LangChain4j Spring Boot Starter:自动配置与声明式 Bean 管理

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 会自动:

  1. 读取 application.yml 创建 ChatLanguageModel Bean
  2. 扫描到 @AiService 接口,生成代理实现类
  3. 将代理类注册为 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
解决

  1. 确认已添加 langchain4j-open-ai-spring-boot-starter 依赖
  2. 确认 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 集成问题欢迎评论区交流。

相关推荐
沐硕1 小时前
《基于改进协同过滤与多目标优化的健康饮食推荐系统设计与实现》
java·python·算法·fastapi·多目标优化·饮食推荐·改进协同过滤
披着羊皮不是狼2 小时前
AI计算系统实战:从算子实现到GPU性能调优
人工智能
yhole2 小时前
springboot 修复 Spring Framework 特定条件下目录遍历漏洞(CVE-2024-38819)
spring boot·后端·spring
大师影视解说2 小时前
2026 短剧出海:百亿市场洗牌,自动化翻译与工程本地化
人工智能·视频技术·短剧出海·短剧行业趋势·短剧翻译·视频翻译技术·行业观察
大傻^2 小时前
Spring AI 2.0 MCP 协议实战:Model Context Protocol SDK 与多服务器编排
服务器·人工智能·spring
哈哈很哈哈2 小时前
深度学习中的分布式并行策略和内存优化技术
人工智能·语言模型
摩尔元数2 小时前
2026年PLC控制器工厂选MES,厂商推荐
人工智能·低代码·制造·mes
愣头不青2 小时前
560.和为k的子数组
java·数据结构
Web3VentureView2 小时前
倒计时 12 小时,SYNBO 主网即将上线!
大数据·人工智能·金融·web3·区块链