最近在尝试将AI大模型能力集成到Java后端项目中,发现市面上的资料要么是Python生态的,要么就是概念讲解居多,真正能跑通、能落地的Java+AI整合方案少之又少。尤其是在Spring Boot项目中,如何优雅地接入OpenAI、DeepSeek等大模型,并实现RAG、智能体(Agent)等高级功能,往往需要自己摸索很久。

本文将以Spring Boot 3.x为基础,整合Spring AI、Spring AI Alibaba以及LangChain4j三大主流Java AI框架,通过一个完整的智能客服Agent案例,手把手带你从零搭建一个可运行、可扩展的Java AI应用。无论你是想为现有系统增加AI对话能力,还是探索RAG知识库问答,这篇文章都能提供一套可直接复用的代码模板和避坑指南。

1. 背景与核心概念:为什么Java开发者需要关注AI大模型?

在AI浪潮下,Python凭借其丰富的库(如LangChain、LlamaIndex)在AI应用开发中占据主导。然而,企业级后端服务大量基于Java(特别是Spring Boot)构建。让Java后端直接调用Python服务会引入额外的复杂度、网络延迟和运维成本。因此,在Java生态中直接集成AI能力,成为了降本增效的关键需求。

核心框架解析:

  1. Spring AI :Spring官方推出的AI应用框架。它提供了统一的 ChatClient EmbeddingClient 等抽象接口,让开发者能以类似使用 JdbcTemplate 的方式操作不同的大模型(OpenAI、Azure OpenAI、Ollama等)。其核心理念是“约定优于配置”,通过简单的依赖和配置即可快速接入。
  2. Spring AI Alibaba :阿里巴巴基于Spring AI规范开发的扩展套件。它最大的价值在于提供了对阿里云灵积模型服务平台(DashScope)上通义千问等模型的直接支持,并且集成了阿里云OSS向量存储等能力,对于国内开发者而言,访问速度更快、合规性更友好。
  3. LangChain4j :Java版的LangChain。它提供了极其丰富和灵活的AI应用构建模块,如链(Chain)、智能体(Agent)、工具(Tool)、记忆(Memory)和检索器(Retriever)。其设计更偏向于灵活的编程模型,适合构建复杂的AI工作流。

如何选择?

  • 追求快速集成、统一接口 :首选Spring AI。
  • 主要使用国内模型(通义千问) :选择Spring AI Alibaba。
  • 需要构建复杂Agent、自定义工作流 :LangChain4j功能更强大。
  • 全都要 :在实际项目中,它们可以共存。例如,用Spring AI做基础的对话,用LangChain4j构建高级Agent。

本文将演示三者如何在一个项目中协同工作。

2. 环境准备与版本说明

在开始编码前,请确保你的开发环境符合以下要求。版本兼容性是成功的第一步。

基础环境:

  • 操作系统 :Windows 10/11, macOS, 或 Linux (Ubuntu 20.04+)。本文演示基于macOS/Windows。
  • Java :JDK 17 或 21(推荐17,长期支持版)。Spring Boot 3.x必须使用JDK 17+。
  • 构建工具 :Apache Maven 3.6+ 或 Gradle 7.x+。本文使用Maven。
  • IDE :IntelliJ IDEA(推荐)或 VS Code with Java插件。

核心依赖版本: 这是最容易出错的环节,务必核对。以下版本为2024年中旬的稳定组合。

<!-- 在项目的 pom.xml 中 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.5</version> <!-- 使用稳定的3.2.x版本 -->
    <relativePath/>
</parent>

<properties>
    <java.version>17</java.version>
    <!-- 重要:Spring AI 版本与 Boot 版本有对应关系 -->
    <spring-ai.version>0.8.1</spring-ai.version>
</properties>

<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Spring AI OpenAI (接入OpenAI/DeepSeek) -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
    </dependency>
    <!-- Spring AI Alibaba (接入通义千问) -->
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-ai-spring-boot-starter</artifactId>
        <version>2023.0.1.0</version> <!-- 注意此版本号独立 -->
    </dependency>
    <!-- LangChain4j 核心 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
        <version>0.31.0</version>
    </dependency>
    <!-- LangChain4j OpenAI 适配器 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai</artifactId>
        <version>0.31.0</version>
    </dependency>
    <!-- 测试 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencyManagement>
<!-- 必须:Spring AI 的依赖管理 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>${spring-ai.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

项目结构预览: 创建完成后,你的项目结构应类似于:

java-ai-demo
├── src/main/java/com/example/ai
│   ├── config          // 配置类
│   ├── controller      // 控制器
│   ├── service         // 业务层
│   │   ├── impl
│   │   └── agent       // Agent相关服务
│   ├── tool            // Agent工具定义
│   └── JavaAiDemoApplication.java // 启动类
├── src/main/resources
│   ├── application.yml // 主配置文件
│   └── documents       // 存放用于RAG的文档
└── pom.xml

3. 核心配置与多模型接入

我们将配置两个模型:一个国际模型(DeepSeek,性价比高)和一个国内模型(通义千问)。 application.yml 是配置的核心。

3.1 配置文件详解

src/main/resources/application.yml 中,进行如下配置:

# 应用基础配置
spring:
  application:
    name: java-ai-demo

# Spring AI - OpenAI 兼容配置 (用于DeepSeek)
# DeepSeek的API与OpenAI兼容,base-url指向其端点
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY:sk-your-deepseek-api-key-here} # 从环境变量读取,优先使用环境变量保证安全
      base-url: https://api.deepseek.com # DeepSeek API地址
      chat:
        options:
          model: deepseek-chat # 使用的模型名称
          temperature: 0.7 # 创造性,0-2之间,越高越随机
          max-tokens: 2000 # 单次回复最大token数

# Spring AI Alibaba - 通义千问配置
# 注意:这里的配置项前缀与Spring AI OpenAI不同
alibaba:
  ai:
    dashscope:
      api-key: ${DASHSCOPE_API_KEY:sk-your-dashscope-api-key-here} # 阿里云灵积API Key
      chat:
        options:
          model: qwen-max # 可选 qwen-turbo, qwen-plus, qwen-max
          temperature: 0.8

# LangChain4j 配置(部分通过代码配置)
langchain4j:
  open-ai:
    api-key: ${OPENAI_API_KEY} # 可以复用同一个key
    base-url: ${spring.ai.openai.base-url} # 复用base-url
    model-name: ${spring.ai.openai.chat.options.model} # 复用模型名

关键点说明:

  1. API密钥安全 ${VARIABLE_NAME:default-value} 语法表示优先从系统环境变量读取,找不到则使用默认值。 强烈建议 OPENAI_API_KEY DASHSCOPE_API_KEY 设置为环境变量,避免密钥硬编码在代码中泄露。
  2. 模型端点 :DeepSeek完全兼容OpenAI API协议,所以 base-url 改为其官方端点即可。其他国产模型如智谱GLM、月之暗面Kimi也有类似兼容方案。
  3. 配置隔离 :Spring AI Alibaba使用了独立的配置前缀 alibaba.ai.dashscope ,与 spring.ai.openai 并列,避免了冲突。

3.2 配置类与Bean注入

为了让不同框架的客户端在Spring容器中可用,我们需要一个配置类。

// 文件路径:src/main/java/com/example/ai/config/AiConfig.java
package com.example.ai.config;

import org.springframework.ai.alibaba.dashscope.AlibabaDashCopeChatModel;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;

@Configuration
public class AiConfig {

    @Value("${spring.ai.openai.api-key}")
    private String openaiApiKey;

    @Value("${spring.ai.openai.base-url}")
    private String openaiBaseUrl;

    @Value("${spring.ai.openai.chat.options.model}")
    private String openaiModel;

    @Value("${alibaba.ai.dashscope.api-key}")
    private String dashscopeApiKey;

    /**
     * 配置 Spring AI 的 OpenAiChatModel (用于DeepSeek)
     * 这是一个标准的Spring AI ChatModel Bean
     */
    @Bean
    @Qualifier("deepSeekChatModel") // 使用限定符区分Bean
    public ChatModel deepSeekChatModel() {
        OpenAiApi openAiApi = new OpenAiApi(openaiBaseUrl, openaiApiKey, RestClient.builder());
        OpenAiChatOptions options = OpenAiChatOptions.builder()
                .model(openaiModel)
                .temperature(0.7)
                .maxTokens(2000)
                .build();
        return new OpenAiChatModel(openAiApi, options);
    }

    /**
     * 配置 Spring AI Alibaba 的 ChatModel (用于通义千问)
     */
    @Bean
    @Qualifier("qwenChatModel")
    public ChatModel qwenChatModel() {
        // AlibabaDashCopeChatModel 实现了 Spring AI 统一的 ChatModel 接口
        return new AlibabaDashCopeChatModel(dashscopeApiKey);
    }

    /**
     * 配置 LangChain4j 的 OpenAiChatModel
     * 注意:这是LangChain4j自己的类,与Spring AI的无关
     */
    @Bean
    @Qualifier("langChainOpenAiModel")
    public dev.langchain4j.model.openai.OpenAiChatModel langChainOpenAiModel() {
        return dev.langchain4j.model.openai.OpenAiChatModel.builder()
                .apiKey(openaiApiKey)
                .baseUrl(openaiBaseUrl)
                .modelName(openaiModel)
                .temperature(0.7)
                .maxTokens(2000)
                .logRequests(true) // 开启请求日志,调试有用
                .logResponses(true)
                .build();
    }
}

为什么需要 @Qualifier 当容器中存在多个同类型( ChatModel OpenAiChatModel )的Bean时,Spring无法自动选择注入哪一个。使用 @Qualifier 注解给Bean起个名字,在注入时指定这个名字,就能精确选择。

4. 基础对话功能实战

我们先实现最简单的功能:通过HTTP API与不同模型进行对话。

4.1 创建统一对话服务

首先,创建一个服务层,封装对不同模型的调用。

// 文件路径:src/main/java/com/example/ai/service/ChatService.java
package com.example.ai.service;

import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;

@Service
public class ChatService {

    private final ChatModel deepSeekChatModel;
    private final ChatModel qwenChatModel;

    // 通过构造器注入,并指定使用哪个Bean
    public ChatService(
            @Qualifier("deepSeekChatModel") ChatModel deepSeekChatModel,
            @Qualifier("qwenChatModel") ChatModel qwenChatModel) {
        this.deepSeekChatModel = deepSeekChatModel;
        this.qwenChatModel = qwenChatModel;
    }

    /**
     * 使用DeepSeek模型进行简单对话
     */
    public String chatWithDeepSeek(String userMessage) {
        // 最简单的调用:将用户输入包装成UserMessage
        UserMessage message = new UserMessage(userMessage);
        // 调用模型的call方法
        return deepSeekChatModel.call(message).getResult().getOutput().getContent();
    }

    /**
     * 使用通义千问模型进行简单对话
     */
    public String chatWithQwen(String userMessage) {
        UserMessage message = new UserMessage(userMessage);
        return qwenChatModel.call(message).getResult().getOutput().getContent();
    }

    /**
     * 带系统提示词(System Prompt)的对话 - 以DeepSeek为例
     * 系统提示词用于设定AI的角色和行为
     */
    public String chatWithSystemPrompt(String userMessage) {
        // 1. 定义系统提示词模板
        String systemText = """
                你是一个专业的Java技术专家,擅长Spring Boot和微服务架构。
                你的回答应该简洁、准确,并且包含代码示例。
                如果用户的问题与Java无关,请礼貌地拒绝回答。
                """;
        SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemText);
        Message systemMessage = systemPromptTemplate.createMessage();

        // 2. 创建用户消息
        UserMessage userMsg = new UserMessage(userMessage);

        // 3. 构建Prompt(包含系统消息和用户消息)
        Prompt prompt = new Prompt(List.of(systemMessage, userMsg));

        // 4. 调用模型
        return deepSeekChatModel.call(prompt).getResult().getOutput().getContent();
    }

    /**
     * 多轮对话示例(简化版,实际需要持久化历史记录)
     */
    public String multiTurnChat(List<Message> conversationHistory, String newUserInput) {
        // 将历史记录和新输入合并为一个新的Prompt
        conversationHistory.add(new UserMessage(newUserInput));
        Prompt prompt = new Prompt(conversationHistory);
        return deepSeekChatModel.call(prompt).getResult().getOutput().getContent();
    }
}

4.2 创建RESTful控制器

暴露HTTP接口供前端或测试调用。

// 文件路径:src/main/java/com/example/ai/controller/ChatController.java
package com.example.ai.controller;

import com.example.ai.service.ChatService;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/chat")
public class ChatController {

    private final ChatService chatService;
    // 简易内存存储,用于演示多轮对话。生产环境请用Redis或数据库。
    private final Map<String, List<org.springframework.ai.chat.messages.Message>> sessionHistory = new HashMap<>();

    public ChatController(ChatService chatService) {
        this.chatService = chatService;
    }

    @PostMapping("/deepseek")
    public Map<String, String> chatWithDeepSeek(@RequestBody Map<String, String> request) {
        String message = request.get("message");
        String response = chatService.chatWithDeepSeek(message);
        return Map.of("model", "DeepSeek", "response", response);
    }

    @PostMapping("/qwen")
    public Map<String, String> chatWithQwen(@RequestBody Map<String, String> request) {
        String message = request.get("message");
        String response = chatService.chatWithQwen(message);
        return Map.of("model", "Qwen", "response", response);
    }

    @PostMapping("/deepseek-with-role")
    public Map<String, String> chatWithRole(@RequestBody Map<String, String> request) {
        String message = request.get("message");
        String response = chatService.chatWithSystemPrompt(message);
        return Map.of("model", "DeepSeek (Java Expert)", "response", response);
    }

    @PostMapping("/multi-turn")
    public Map<String, String> multiTurnChat(@RequestBody Map<String, String> request,
                                             @RequestHeader(value = "Session-Id", defaultValue = "default") String sessionId) {
        String userInput = request.get("message");

        // 获取或创建该会话的历史记录
        List<org.springframework.ai.chat.messages.Message> history = sessionHistory.getOrDefault(sessionId, new ArrayList<>());
        
        // 调用服务(这里简化,实际应将AI回复也加入history)
        String response = chatService.chatWithDeepSeek(userInput); // 简单调用,未传递历史
        
        // 更新历史(生产环境需要更复杂的状态管理)
        // history.add(new UserMessage(userInput));
        // history.add(new AssistantMessage(response));
        // sessionHistory.put(sessionId, history);

        return Map.of("sessionId", sessionId, "response", response);
    }
}

4.3 运行与测试

  1. 启动应用 :运行 JavaAiDemoApplication 的main方法。
  2. 使用工具测试 :使用Postman、curl或任何HTTP客户端测试接口。
    • 请求示例 (POST http://localhost:8080/api/chat/deepseek )
      {
          "message": "用Java写一个Hello World程序"
      }
      
    • 预期响应
      {
          "model": "DeepSeek",
          "response": "以下是Java的Hello World程序...\n```java\npublic class HelloWorld {\n    public static void main(String[] args) {\n        System.out.println(\"Hello, World!\");\n    }\n}\n```"
      }
      

至此,你已经完成了Spring Boot与两个大模型的基础集成。但这只是开始,真正的威力在于RAG和Agent。

5. 进阶实战:构建RAG智能客服Agent

我们将实现一个更复杂的场景:一个智能客服Agent,它能根据我们提供的内部知识库(如产品手册PDF)回答问题,并在无法回答时,自动调用“人工客服”工具。

这个案例将综合运用:

  • LangChain4j :构建Agent工作流,管理工具。
  • Spring AI :用于文档嵌入(Embedding)和向量存储(VectorStore)。
  • 自定义工具 :让Agent能执行特定操作。

5.1 文档加载与向量化(RAG核心)

首先,我们需要将知识库文档(如TXT、PDF)转换为向量并存储。

// 文件路径:src/main/java/com/example/ai/service/RagService.java
package com.example.ai.service;

import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.reader.TextReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils;

import jakarta.annotation.PostConstruct;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.List;

@Service
public class RagService {

    private final EmbeddingModel embeddingModel; // Spring AI会自动注入
    private final ResourceLoader resourceLoader;
    private VectorStore vectorStore;

    public RagService(EmbeddingModel embeddingModel, ResourceLoader resourceLoader) {
        this.embeddingModel = embeddingModel;
        this.resourceLoader = resourceLoader;
    }

    /**
     * 项目启动时,初始化向量存储并加载知识库文档
     */
    @PostConstruct
    public void init() throws IOException {
        // 1. 初始化一个内存向量存储(生产环境可用PgVector、Redis等)
        this.vectorStore = new SimpleVectorStore(embeddingModel);

        // 2. 加载资源文件下的文档
        Resource resource = resourceLoader.getResource("classpath:documents/product_manual.txt");
        String content = FileCopyUtils.copyToString(
                new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8));

        // 3. 创建Document对象
        Document document = new Document(content);
        document.getMetadata().put("source", "product_manual.txt");

        // 4. 文本分割(将长文档切分为小块,便于检索)
        TokenTextSplitter splitter = new TokenTextSplitter(500, 100, 10, 1000); // 参数:块大小、重叠大小等
        List<Document> splitDocuments = splitter.apply(List.of(document));

        // 5. 将分割后的文档添加到向量存储(会自动调用EmbeddingModel生成向量)
        vectorStore.add(splitDocuments);
        System.out.println("知识库文档已加载并向量化,共 " + splitDocuments.size() + " 个片段。");
    }

    /**
     * 相似性检索:根据用户问题,从知识库中找出最相关的文档片段
     */
    public List<Document> searchRelevantDocuments(String query) {
        // 检索最相关的4个文档片段
        return vectorStore.similaritySearch(query, 4);
    }

    /**
     * 构建RAG提示词:将检索到的上下文和用户问题组合
     */
    public String buildRagPrompt(String userQuestion) {
        List<Document> relevantDocs = searchRelevantDocuments(userQuestion);
        if (relevantDocs.isEmpty()) {
            return userQuestion; // 没有相关上下文,直接返回原问题
        }

        StringBuilder contextBuilder = new StringBuilder();
        contextBuilder.append("请根据以下上下文信息回答问题。如果上下文信息不足以回答问题,请如实说明。\n\n");
        contextBuilder.append("【上下文开始】\n");
        for (Document doc : relevantDocs) {
            contextBuilder.append(doc.getContent()).append("\n---\n");
        }
        contextBuilder.append("【上下文结束】\n\n");
        contextBuilder.append("问题:").append(userQuestion);

        return contextBuilder.toString();
    }
}

5.2 定义Agent工具

Agent的强大之处在于可以使用工具。我们定义一个“转接人工客服”的工具。

// 文件路径:src/main/java/com/example/ai/tool/ManualCustomerServiceTool.java
package com.example.ai.tool;

import dev.langchain4j.agent.tool.Tool;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Component // 注册为Spring Bean
public class ManualCustomerServiceTool {

    /**
     * 当AI无法回答用户问题时,调用此工具转接人工客服。
     * @Tool 注解是LangChain4j的标记,表示这是一个可供Agent使用的工具。
     * @param userId 用户ID
     * @param question 用户的问题
     * @return 转接结果描述
     */
    @Tool("当问题超出知识范围或用户明确要求时,将对话转接给人工客服。请提供用户ID和问题摘要。")
    public String transferToManualService(String userId, String question) {
        // 这里模拟转接逻辑,实际项目中可能是发送消息到工单系统、通知客服人员等。
        String ticketId = "TICKET-" + System.currentTimeMillis();
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        
        // 记录日志或持久化到数据库
        System.out.printf("[人工客服转接] 工单ID: %s, 用户: %s, 时间: %s, 问题: %s%n",
                ticketId, userId, timestamp, question);
        
        return String.format("已为您创建工单【%s】并转接至人工客服。客服人员将在15分钟内通过系统消息与您联系。", ticketId);
    }
}

5.3 构建智能体(Agent)服务

这是最核心的部分,我们将使用LangChain4j的 AiServices 来创建一个集成了工具、记忆和RAG上下文的智能体。

// 文件路径:src/main/java/com/example/ai/service/agent/CustomerServiceAgent.java
package com.example.ai.service.agent;

import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import com.example.ai.tool.ManualCustomerServiceTool;
import com.example.ai.service.RagService;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

/**
 * 定义智能体的交互接口。
 * LangChain4j的AiServices会根据这个接口动态生成实现类。
 */
interface Assistant {
    // @SystemMessage 定义系统提示词,设定Agent的角色和行为准则
    @SystemMessage("""
            你是一个专业的智能客服助手,负责解答关于公司产品的疑问。
            你的知识来源于提供的产品手册。请严格根据已知信息回答。
            如果用户的问题超出你的知识范围,或者用户明确要求人工服务,请果断调用`transferToManualService`工具。
            回答请保持友好、专业、简洁。
            """)
    // @UserMessage 中的 {{context}} 和 {{question}} 是模板变量,由调用者传入
    String chat(@V("context") String context, @V("question") String question);
}

@Service
public class CustomerServiceAgent {

    private final Assistant assistant;
    private final RagService ragService;

    public CustomerServiceAgent(
            @Qualifier("langChainOpenAiModel") ChatLanguageModel chatLanguageModel, // 注入LangChain4j的模型
            ManualCustomerServiceTool manualTool,
            RagService ragService) {
        this.ragService = ragService;

        // 1. 创建聊天记忆,保留最近10轮对话
        ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);

        // 2. 使用AiServices构建智能体
        // 它会将接口、模型、工具、记忆绑定在一起
        this.assistant = AiServices.builder(Assistant.class)
                .chatLanguageModel(chatLanguageModel)
                .tools(manualTool) // 注入工具
                .chatMemory(chatMemory) // 注入记忆
                .build();
    }

    /**
     * 主服务方法:处理用户问题,集成RAG和Agent
     */
    public String answerQuestion(String userId, String userQuestion) {
        // 步骤1:通过RAG检索相关知识
        String ragContext = ragService.buildRagPrompt(userQuestion);
        
        // 步骤2:让Agent基于上下文和工具进行回答
        // 这里将RAG构建的完整提示词(包含上下文)作为`context`变量传入
        // 将原始用户问题作为`question`变量传入,供Agent在需要时传递给工具
        String answer = assistant.chat(ragContext, userQuestion);
        
        return answer;
    }
}

5.4 创建Agent控制器

// 文件路径:src/main/java/com/example/ai/controller/AgentController.java
package com.example.ai.controller;

import com.example.ai.service.agent.CustomerServiceAgent;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
@RequestMapping("/api/agent")
public class AgentController {

    private final CustomerServiceAgent customerServiceAgent;

    public AgentController(CustomerServiceAgent customerServiceAgent) {
        this.customerServiceAgent = customerServiceAgent;
    }

    @PostMapping("/customer-service")
    public Map<String, String> handleCustomerQuery(@RequestBody Map<String, String> request) {
        String userId = request.getOrDefault("userId", "anonymous");
        String question = request.get("question");
        
        String answer = customerServiceAgent.answerQuestion(userId, question);
        
        return Map.of(
            "userId", userId,
            "question", question,
            "answer", answer
        );
    }
}

5.5 准备知识库文档并测试

  1. src/main/resources/documents/ 目录下创建 product_manual.txt ,内容例如:
    产品名称:AI助手API
    版本:v2.1
    主要功能:提供自然语言对话、代码生成、文本总结能力。
    计费方式:按调用次数计费,每月前1000次免费。
    技术支持:如需人工帮助,请在对话中明确说“转人工客服”。
    故障处理:如遇API超时,请检查网络并重试,或联系技术支持。
    
  2. 重启应用,观察日志,确认文档加载成功。
  3. 测试场景1:知识库内问题
    • 请求 POST /api/agent/customer-service
    • Body {"userId": "user123", "question": "你们的AI助手API怎么收费?"}
    • 预期 :Agent从知识库检索到“计费方式”片段,并生成回答。
  4. 测试场景2:知识库外问题/要求转人工
    • 请求Body {"userId": "user123", "question": "我想咨询一下企业定制方案,转人工客服。"}
    • 预期 :Agent识别到“转人工客服”关键词或判断超出知识范围,调用 transferToManualService 工具,返回工单创建成功的消息。观察控制台输出的转接日志。

6. 常见问题与排查思路

在集成过程中,你可能会遇到以下典型问题:

问题现象 可能原因 排查步骤与解决方案
启动报错: No qualifying bean of type 'EmbeddingModel' Spring AI 的自动配置未生效或依赖冲突。 1. 检查 pom.xml spring-ai-openai-spring-boot-starter 依赖是否正确。
2. 确保 spring-ai-bom 的依赖管理已正确引入。
3. 尝试在启动类添加 @EnableAutoConfiguration
调用API时报错: 401 Unauthorized API密钥错误、过期或未设置。 1. 检查 application.yml 中的 api-key ,或对应的环境变量 OPENAI_API_KEY DASHSCOPE_API_KEY 是否已设置且正确。
2. 在代码中打印密钥前几位(勿泄露完整密钥)确认已注入。
3. 前往对应平台检查密钥状态和余额。
调用DeepSeek报错: 404 Not Found base-url 配置错误或模型名称 model 不对。 1. 确认 base-url https://api.deepseek.com
2. 确认 model deepseek-chat deepseek-coder
3. 查阅DeepSeek官方文档确认最新API地址。
LangChain4j的Agent不调用工具 工具方法签名不符合要求,或提示词未引导。 1. 确保工具方法使用 @Tool 注解,且描述清晰。
2. 确保工具方法是 public 的。
3. 在 @SystemMessage 中明确指示Agent在何种条件下调用工具。
RAG检索结果不相关 文档分割策略不合理或嵌入模型不匹配。 1. 调整 TokenTextSplitter 的块大小( chunkSize )和重叠大小( chunkOverlap )。
2. 尝试不同的嵌入模型(如OpenAI的 text-embedding-3-small )。
3. 检查检索时返回的文档数量( similaritySearch 的第二个参数)。
内存溢出 ( OutOfMemoryError ) 加载的文档过大,或向量存储未使用外部数据库。 1. 对于大文档,务必进行有效的文本分割。
2. 生产环境务必使用外部向量数据库(如PgVector, Redis, Milvus),避免使用内存存储 SimpleVectorStore
中文回答质量差 系统提示词未指定语言,或模型对中文优化不足。 1. 在 @SystemMessage 或系统提示词中明确要求“请使用中文回答”。
2. 对于中文场景,可优先考虑 qwen-turbo glm-4 等国内模型。

7. 最佳实践与工程建议

将AI能力集成到生产级Java项目,除了功能实现,还需关注以下方面:

  1. 配置与密钥管理

    • 永远不要 将API密钥提交到代码仓库。使用环境变量、配置中心(如Apollo、Nacos)或云厂商的密钥管理服务。
    • 为不同环境(开发、测试、生产)配置不同的模型和参数,生产环境建议使用更稳定的模型版本。
  2. 性能与稳定性

    • 设置超时与重试 :在 RestClient 或HTTP客户端配置合理的连接超时、读取超时,并实现重试机制(如使用Spring Retry)。
    • 实现熔断降级 :使用Resilience4j或Sentinel为AI服务调用添加熔断器,防止因模型服务不稳定导致整个应用雪崩。
    • 异步化处理 :对于耗时的AI生成任务,使用 @Async 或消息队列(如RabbitMQ、Kafka)异步处理,避免阻塞HTTP请求线程。
  3. 可观测性与监控

    • 全链路日志 :记录每次AI调用的请求、响应、耗时和Token使用量。LangChain4j和Spring AI都支持开启日志。
    • 指标监控 :通过Micrometer将调用次数、成功率、延迟等指标暴露给Prometheus,并配置Grafana看板。
    • 成本监控 :由于AI API按Token计费,必须监控Token消耗,设置预算告警。
  4. RAG优化

    • 高质量数据预处理 :清洗、去重、格式化你的知识库文档。垃圾输入会导致垃圾输出。
    • 多路召回与重排序 :不要只依赖向量相似度。可结合关键词检索(如Elasticsearch),并对召回结果进行重排序,提升准确率。
    • 引用溯源 :在回答中注明引用的文档片段来源,增加可信度。
  5. Agent设计

    • 工具设计要精确 :工具的功能应单一、明确,输入输出定义清晰。避免让Agent去猜测如何调用一个模糊的工具。
    • 限制Agent权限 :赋予Agent的工具应是安全的、无副作用的。特别是涉及写数据库、调用外部API、发送消息等操作,必须有严格的权限和审计。
    • 人工审核闭环 :对于关键业务(如客服、审核),设计“人工审核”环节。Agent可生成草稿,由人最终确认后发出。
  6. 版本与依赖管理

    • Spring AI和LangChain4j迭代较快,锁定小版本号,升级前在测试环境充分验证。
    • 注意Spring Boot主版本与Spring AI版本的兼容性,参考官方文档的版本矩阵。

掌握Spring AI、Spring AI Alibaba和LangChain4j这三大框架,你就能在熟悉的Java和Spring生态中,自如地构建各类AI增强型应用。从简单的对话接口到复杂的RAG智能体,其核心模式都是相通的: 配置模型 -> 处理数据 -> 设计流程 -> 集成工具 。建议你从本文的示例项目出发,尝试替换不同的模型、接入真实的业务数据、设计更有趣的工具,逐步搭建起符合自己业务需求的AI应用。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐