VibeCoding后端项目生成-AI生成应用

1 阅读20分钟

AI代码生成器项目实现详解

摘要: 本文详解Spring Boot+DeepSeek+LangChain4j实现AI代码生成器,含大模型接入、基础服务构建、AI核心服务开发,附完整可落地代码,后端开发者可直接参考搭建带上下文记忆的智能代码生成系统。

一、大模型接入

要实现AI代码生成功能,首先需要接入第三方大模型的API能力。本文选用DeepSeek大模型,它性价比高且适配代码生成场景,结合LangChain4j框架可实现快速集成,具体操作步骤如下:

第一步,进入DeepSeek开放平台,完成注册、登录后创建应用并获取API Key。这是后续调用大模型接口的核心凭证,需妥善保管,避免泄露。平台官方地址:api-docs.deepseek.com/zh-cn/

第二步,引入必要的依赖包。项目基于Spring Boot开发,我们借助LangChain4j提供的Spring Boot Starter简化大模型接入的配置流程。可参考LangChain4j官方文档,将以下依赖内容写入项目的pom.xml文件,实现LangChain4j框架与DeepSeek大模型的集成。官方文档地址:docs.langchain4j.dev/tutorials/s…

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-spring-boot-starter</artifactId>
    <version>1.12.2-beta22</version>
</dependency>
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
    <version>1.12.2-beta22</version>
</dependency>

第三步,配置大模型连接信息。创建application-local.yml配置文件,写入DeepSeek的API地址、API Key、模型名称等核心参数,同时启用local环境配置,便于本地开发调试,区分生产环境。API Key需替换为自己在DeepSeek开放平台获取的真实密钥,模型名称固定为deepseek-chat,这是DeepSeek用于对话生成的核心模型。开启请求和响应日志,方便后续调试排查问题。

# AI大模型配置(DeepSeek)
langchain4j:
  open-ai:
    chat-model:
      base-url: https://api.deepseek.com  # DeepSeek API请求地址
      api-key: <Your API Key>             # 替换为自己的DeepSeek API Key
      model-name: deepseek-chat           # 固定使用deepseek-chat模型
      log-requests: true                  # 开启请求日志,便于调试
      log-responses: true                 # 开启响应日志,便于调试

第四步,启用local配置文件。在spring配置中指定激活local环境,确保上述大模型配置生效,避免与生产环境配置冲突。

spring:
  profiles:
    active: local  # 激活local环境,使用application-local.yml中的配置

二、构建基础服务

基础服务主要包含两个核心模块:应用模块和聊天历史记录模块。应用模块负责管理AI生成相关的应用信息,聊天历史记录模块用于存储用户与AI的对话上下文,为AI提供上下文感知能力,支撑更贴合需求的代码生成。两个模块均遵循MyBatis-Plus开发规范,实现实体、服务接口及具体实现,确保数据操作的规范性和可扩展性。

2.1 应用模块

应用模块用于管理每个AI代码生成任务的核心信息,包括应用名称、初始化提示词、代码生成类型、部署标识等。每个应用对应一个独立的代码生成任务,便于后续关联对话历史、缓存AI实例等操作。

首先定义应用实体类App,对应数据库中的app表。使用Lombok注解简化getter/setter、构造方法等冗余代码,同时通过MyBatis-Plus注解指定表名、主键策略、逻辑删除字段等,确保实体与数据库表的映射一致。

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table("app")  // 指定对应数据库表名
public class App implements Serializable {

    @Serial
    private static final long serialVersionUID = 1L;

    /**
     * 主键id,自增策略
     */
    @Id(keyType = KeyType.Auto)
    private Long id;

    /**
     * 应用名称,用于标识不同的代码生成任务
     */
    @Column("appName")
    private String appName;

    /**
     * 应用封面,可选字段,用于前端展示
     */
    private String cover;

    /**
     * 应用初始化的prompt,核心字段,用于指定AI代码生成的基础要求
     */
    @Column("initPrompt")
    private String initPrompt;

    /**
     * 代码生成类型(枚举),如SpringBoot项目、Vue项目等,用于区分生成场景
     */
    @Column("codeGenType")
    private String codeGenType;

    /**
     * 部署标识,用于后续应用部署时的唯一标识
     */
    @Column("deployKey")
    private String deployKey;

    /**
     * 部署时间,记录应用部署的时间戳
     */
    @Column("deployedTime")
    private LocalDateTime deployedTime;

    /**
     * 优先级,用于排序展示应用列表
     */
    private Integer priority;

    /**
     * 创建用户id,关联用户表,记录该应用的创建者
     */
    @Column("userId")
    private Long userId;

    /**
     * 编辑时间,记录应用最后一次编辑的时间
     */
    @Column("editTime")
    private LocalDateTime editTime;

    /**
     * 创建时间,自动填充(需配置MyBatis-Plus自动填充策略)
     */
    @Column("createTime")
    private LocalDateTime createTime;

    /**
     * 更新时间,自动填充(需配置MyBatis-Plus自动填充策略)
     */
    @Column("updateTime")
    private LocalDateTime updateTime;

    /**
     * 是否删除,逻辑删除字段(1=删除,0=未删除)
     */
    @Column(value = "isDelete", isLogicDelete = true)
    private Integer isDelete;

}

接着定义应用服务接口AppService,继承MyBatis-Plus的IService接口,封装应用相关的业务方法。包括应用VO转换、查询条件构造、应用创建、应用删除等,实现业务逻辑与数据访问层的解耦。

public interface AppService extends IService<App> {

    /**
     * 将App实体转换为AppVO(视图对象),用于前端展示,隐藏敏感字段
     * @param app 应用实体
     * @return AppVO 视图对象
     */
    AppVO getAppVO(App app);

    /**
     * 根据查询请求参数,构造MyBatis-Plus的QueryWrapper,用于分页查询应用列表
     * @param appQueryRequest 应用查询请求参数(如应用名称、创建者id等)
     * @return QueryWrapper 查询条件封装对象
     */
    QueryWrapper getQueryWrapper(AppQueryRequest appQueryRequest);

    /**
     * 批量将App实体列表转换为AppVO列表,提升查询效率
     * @param appList 应用实体列表
     * @return List<AppVO> 应用视图对象列表
     */
    List<AppVO> getAppVOList(List<App> appList);

    /**
     * 创建应用,关联当前登录用户,校验必填参数
     * @param appAddRequest 应用新增请求参数
     * @param loginUser 当前登录用户信息
     * @return Long 新增应用的主键id
     */
    Long createApp(AppAddRequest appAddRequest, User loginUser);
}

最后实现AppService接口,即AppServiceImpl类。通过@Service注解将其注入Spring容器,实现接口中定义的所有业务方法,同时处理关联数据查询、参数校验、日志记录等细节,确保业务逻辑的完整性和健壮性。

@Slf4j
@Service  // 注入Spring容器,作为业务层实现
public class AppServiceImpl extends ServiceImpl<AppMapper, App>  implements AppService{

    @Autowired
    private UserService userService;  // 注入用户服务,用于关联查询用户信息

    @Resource
    private ChatHistoryService chatHistoryService;  // 注入聊天历史服务,用于删除应用时关联删除对话

    @Override
    public AppVO getAppVO(App app) {
        if (app == null) {
            return null;
        }
        AppVO appVO = new AppVO();
        BeanUtil.copyProperties(app, appVO);
        Long userId = app.getUserId();
        if (userId != null) {
            User user = userService.getById(userId);
            UserVO userVO = userService.getUserVO(user);
            appVO.setUser(userVO);
        }
        return appVO;
    }

    @Override
    public QueryWrapper getQueryWrapper(AppQueryRequest appQueryRequest) {
        if (appQueryRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空");
        }
        Long id = appQueryRequest.getId();
        String appName = appQueryRequest.getAppName();
        String cover = appQueryRequest.getCover();
        String initPrompt = appQueryRequest.getInitPrompt();
        String codeGenType = appQueryRequest.getCodeGenType();
        String deployKey = appQueryRequest.getDeployKey();
        Integer priority = appQueryRequest.getPriority();
        Long userId = appQueryRequest.getUserId();
        String sortField = appQueryRequest.getSortField();
        String sortOrder = appQueryRequest.getSortOrder();
        return QueryWrapper.create()
                .eq("id", id)
                .like("appName", appName)
                .like("cover", cover)
                .like("initPrompt", initPrompt)
                .eq("codeGenType", codeGenType)
                .eq("deployKey", deployKey)
                .eq("priority", priority)
                .eq("userId", userId)
                .orderBy(sortField, "ascend".equals(sortOrder));
    }

    @Override
    public List<AppVO> getAppVOList(List<App> appList) {
        if (CollUtil.isEmpty(appList)) {
            return new ArrayList<>();
        }
        Set<Long> userIds = appList.stream()
                .map(App::getUserId)
                .collect(Collectors.toSet());
        Map<Long, UserVO> userVOMap = userService.listByIds(userIds).stream()
                .collect(Collectors.toMap(User::getId, userService::getUserVO));
        return appList.stream().map(app -> {
            AppVO appVO = getAppVO(app);
            UserVO userVO = userVOMap.get(app.getUserId());
            appVO.setUser(userVO);
            return appVO;
        }).collect(Collectors.toList());
    }

    /**
     * 重写删除方法,实现删除应用时关联删除该应用的所有对话历史
     * 避免数据冗余,确保数据一致性
     * @param id 应用ID
     * @return 是否删除成功
     */
    @Override
    public boolean removeById(Serializable id) {
        if (id == null) {
            return false;
        }
        Long appId = Long.valueOf(id.toString());
        if (appId <= 0) {
            return false;
        }
        try {
            chatHistoryService.deleteByAppId(appId);
        } catch (Exception e) {
            log.error("删除应用关联对话历史失败: {}", e.getMessage());
        }
        return super.removeById(id);
    }

    @Override
    public Long createApp(AppAddRequest appAddRequest, User loginUser) {
        String initPrompt = appAddRequest.getInitPrompt();
        ThrowUtils.throwIf(StrUtil.isBlank(initPrompt), ErrorCode.PARAMS_ERROR, "初始化 prompt 不能为空");
        App app = new App();
        BeanUtil.copyProperties(appAddRequest, app);
        app.setUserId(loginUser.getId());
        app.setAppName(initPrompt.substring(0, Math.min(initPrompt.length(), 12)));
        app.setCodeGenType(CodeGenTypeEnum.SPRINGBOOT.getValue());
        boolean result = this.save(app);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        log.info("应用创建成功,ID: {}, 类型: {}", app.getId(), CodeGenTypeEnum.SPRINGBOOT.getValue());
        return app.getId();
    }
}

2.2 聊天历史记录模块

聊天历史记录模块用于存储用户与AI的每一次对话信息,包括用户提问和AI响应,同时关联对应的应用id和用户id。其核心作用是为AI提供上下文感知能力,让AI能够记住当前应用的历史对话,生成更贴合需求的代码。比如用户先要求生成SpringBoot接口,再要求修改接口参数,AI可通过历史对话感知上下文,做出准确响应。

首先定义聊天历史实体类ChatHistory,对应数据库中的chat_history表。同样使用Lombok注解简化代码,通过MyBatis-Plus注解指定表结构映射,核心字段包括消息内容、消息类型、应用id等。

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table("chat_history")  // 指定对应数据库表名
public class ChatHistory implements Serializable {

    @Serial
    private static final long serialVersionUID = 1L;

    /**
     * 主键id,自增策略
     */
    @Id(keyType = KeyType.Auto)
    private Long id;

    /**
     * 消息内容,存储用户提问或AI响应的具体文本
     */
    private String message;

    /**
     * 消息类型(user/ai),用于区分是用户发送的消息还是AI返回的消息
     */
    @Column("messageType")
    private String messageType;

    /**
     * 应用id,关联app表,标识该对话属于哪个应用
     */
    @Column("appId")
    private Long appId;

    /**
     * 创建用户id,关联用户表,标识该对话的创建者
     */
    @Column("userId")
    private Long userId;

    /**
     * 创建时间,自动填充,记录对话产生的时间
     */
    @Column("createTime")
    private LocalDateTime createTime;

    /**
     * 更新时间,自动填充,记录对话最后一次修改的时间(一般无需修改)
     */
    @Column("updateTime")
    private LocalDateTime updateTime;

    /**
     * 是否删除,逻辑删除字段(1=删除,0=未删除)
     */
    @Column(value = "isDelete", isLogicDelete = true)
    private Integer isDelete;

}

接着定义聊天历史服务接口ChatHistoryService,继承MyBatis-Plus的IService接口,封装聊天历史相关的业务方法。核心包括添加对话消息、根据应用id删除对话、加载历史对话到AI记忆中等,以此支撑AI的上下文感知功能。

public interface ChatHistoryService extends IService<ChatHistory> {

    /**
     * 添加一条对话消息(用户或AI),自动关联应用id、用户id和创建时间
     * @param appId 应用id,关联对应的应用
     * @param message 消息内容
     * @param messageType 消息类型(user/ai)
     * @param userId 用户id,对话的创建者
     * @return 是否添加成功
     */
    boolean addChatMessage(Long appId, String message, String messageType, Long userId);

    /**
     * 根据应用id,删除该应用的所有对话历史(用于删除应用时关联删除)
     * @param appId 应用id
     * @return 是否删除成功
     */
    boolean deleteByAppId(Long appId);

    /**
     * 根据查询请求参数,构造QueryWrapper,用于分页查询对话历史
     * @param chatHistoryQueryRequest 对话历史查询请求参数
     * @return QueryWrapper 查询条件封装对象
     */
    QueryWrapper getQueryWrapper(ChatHistoryQueryRequest chatHistoryQueryRequest);

    /**
     * 分页查询某个应用的对话历史,支持按创建时间分页(下拉加载更多)
     * @param appId 应用id
     * @param pageSize 每页条数
     * @param lastCreateTime 上一页最后一条对话的创建时间(用于分页加载)
     * @param loginUser 当前登录用户(用于权限校验,确保只能查询自己的对话)
     * @return Page<ChatHistory> 分页对话历史列表
     */
    Page<ChatHistory> listAppChatHistoryByPage(Long appId, int pageSize,
                                               LocalDateTime lastCreateTime,
                                               User loginUser);

    /**
     * 将某个应用的历史对话加载到AI的对话记忆中,支撑上下文感知
     * @param appId 应用id
     * @param chatMemory AI的对话记忆对象(LangChain4j提供)
     * @param maxCount 最大加载条数,避免加载过多对话导致性能问题
     * @return int 实际加载的对话条数
     */
    int loadChatHistoryToMemory(Long appId, MessageWindowChatMemory chatMemory, int maxCount);

}

loadChatHistoryToMemory方法是核心方法,负责将数据库中存储的历史对话加载到AI的对话记忆中,让AI能够获取历史上下文。以下是该方法的具体实现,包含查询逻辑、记忆填充、异常处理等细节。

@Override
public int loadChatHistoryToMemory(Long appId, MessageWindowChatMemory chatMemory, int maxCount) {
    try {
        // 按应用id查询,按创建时间倒序排列,跳过第一条最新消息避免重复,限制最大加载条数
        QueryWrapper queryWrapper = QueryWrapper.create()
                .eq(ChatHistory::getAppId, appId)
                .orderBy(ChatHistory::getCreateTime, false)
                .limit(1, maxCount);
        List<ChatHistory> historyList = this.list(queryWrapper);
        if (CollUtil.isEmpty(historyList)) {
            return 0;
        }
        // 反转列表,确保对话按时间正序排列,符合AI记忆的上下文顺序
        historyList = historyList.reversed();
        int loadedCount = 0;
        chatMemory.clear();
        for (ChatHistory history : historyList) {
            if (ChatHistoryMessageTypeEnum.USER.getValue().equals(history.getMessageType())) {
                chatMemory.add(UserMessage.from(history.getMessage()));
                loadedCount++;
            } else if (ChatHistoryMessageTypeEnum.AI.getValue().equals(history.getMessageType())) {
                chatMemory.add(AiMessage.from(history.getMessage()));
                loadedCount++;
            }
        }
        log.info("成功为 appId: {} 加载了 {} 条历史对话", appId, loadedCount);
        return loadedCount;
    } catch (Exception e) {
        log.error("加载历史对话失败,appId: {}, error: {}", appId, e.getMessage(), e);
        return 0;
    }
}

三、开发AI服务

AI服务是整个项目的核心,负责对接大模型、处理用户请求、生成代码、调用工具、管理对话记忆等。本模块通过LangChain4j的Agent机制封装AI的核心能力,结合Redis缓存对话记忆,采用外观模式简化调用,确保功能的可扩展性和易用性。

3.1 支持AI会话

首先在项目中新建ai包,用于存放AI相关的接口和实现类。创建AiCodeGeneratorAgent接口,定义AI代码生成的核心方法,通过LangChain4j的注解指定系统提示词、对话记忆和用户消息,实现AI会话的基础能力。

public interface AiCodeGeneratorAgent {

    /**
     * 生成SpringBoot项目代码(流式响应),支持上下文感知
     * @param appId 应用id,用于关联对话记忆(通过MemoryId注解绑定)
     * @param userMessage 用户提示词,指定代码生成的具体需求
     * @return TokenStream 流式响应对象,用于实时返回AI生成的内容(避免等待完整响应)
     * @SystemMessage 系统提示词,从指定文件中读取,定义AI的角色和生成规则
     */
    @SystemMessage(fromResource = "prompt/codegen-springboot-project-system-prompt.txt")
    TokenStream generateSpringbootProjectStream(@MemoryId long appId, @UserMessage String userMessage);

}

系统提示词是AI生成符合要求代码的关键,用于定义AI的角色、核心技术栈、项目结构和开发约束,确保AI生成的代码规范可运行。以下是核心提示词设计,存放于resources/prompt/codegen-springboot-project-system-prompt.txt文件中:

你是一位资深的 Java 后端架构师,精通 Spring Boot 生态、微服务架构、数据库设计和企业级应用开发。

你的任务是根据用户提供的项目描述,创建一个完整的、可运行的 Spring Boot 工程项目

## 核心技术栈(固定,无需修改)
- Spring Boot 3
- Java 17
- Maven 构建工具
- MyBatis-Plus
- MySQL
- Lombok(简化代码)

## 项目结构(必须严格遵循,确保代码可直接运行)
项目根目录/
├── pom.xml                          # Maven 依赖配置(需包含所有核心依赖)
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/
│   │   │       └── example/
│   │   │           └── demo/
│   │   │               ├── DemoApplication.java          # 启动类(必须包含main方法)
│   │   │               ├── config/                        # 配置类(如数据库配置、MyBatis配置)
│   │   │               ├── controller/                    # 控制器层(处理HTTP请求)
│   │   │               ├── service/                       # 服务层(业务逻辑处理)
│   │   │               │   └── impl/                      # 服务实现类
│   │   │               ├── mapper/                        # 数据访问层(MyBatis接口)
│   │   │               ├── entity/                        # 实体类(与数据库表映射)
│   │   │               ├── dto/                           # 数据传输对象(接口请求/响应)
│   │   │               ├── vo/                            # 视图对象(前端展示)
│   │   │               ├── common/                        # 通用工具类(如响应结果、异常枚举)
│   │   │               └── utils/                         # 工具类(如日期工具、字符串工具)
│   │   └── resources/
│   │       ├── application.yml          # 主配置文件(核心配置)
│   │       ├── application-dev.yml      # 开发环境配置(数据库地址、端口等)
│   │       ├── application-prod.yml     # 生产环境配置(线上环境参数)
│   │       └── mapper/                  # MyBatis XML 映射文件(如使用XML方式编写SQL)
│   └── test/                            # 测试代码(可选,建议添加简单的单元测试)
└── README.md                            # 项目说明(可选,包含启动步骤、功能介绍)

## 开发约束(必须严格遵守,否则生成的代码视为无效)
1)架构设计:严格遵循分层架构,Controller -> Service -> Mapper,各层职责清晰,不跨层调用
2)代码规范:使用 Lombok 简化 Getter/Setter、构造方法,使用构造函数注入依赖(避免@Autowired)
3)RESTful API:遵循 RESTful 设计规范,使用统一的响应格式(如包含code、message、data)
4)异常处理:实现全局异常处理,统一返回错误信息,避免直接抛出异常到前端
5)数据库设计:合理设计表结构,包含必要的索引和约束(如主键、非空约束),字段命名规范
6)可运行优先:将可运行作为项目生成的第一要义,尽量用最简单的方式满足需求,避免使用复杂的技术或代码逻辑
7)组件限制:禁止使用过于复杂的分布式组件(如 Redis、MQ、ES 等),除非用户明确要求

3.2 Tool工具制作

为让AI实现更复杂的功能,比如生成代码后自动创建文件、保存代码到本地,需要实现文件操作相关的Tool工具。LangChain4j的Agent会根据用户需求,自动调用对应的Tool工具,扩展AI的能力边界。

Tool工具的核心是实现LangChain4j的Tool接口,封装具体业务逻辑,比如创建文件、写入文件、删除文件等,同时通过注解指定工具名称和描述,让AI能够识别工具用途并主动调用。常用的文件操作工具包括:

  • 创建文件工具(CreateFileTool):根据文件路径和内容创建新文件

  • 写入文件工具(WriteFileTool):向已存在的文件中写入内容,支持覆盖或追加

  • 删除文件工具(DeleteFileTool):根据文件路径删除指定文件

  • 读取文件工具(ReadFileTool):读取指定文件内容,供AI参考

这些工具的实现需结合Java的IO流操作,处理文件路径、权限、异常等问题,确保工具调用稳定。工具实现完成后,通过ToolManager进行统一管理,供Agent调用。

3.3 对话信息存储与加载

AI的对话记忆默认采用内存存储,重启项目后记忆会丢失,且无法支持分布式部署。因此引入Redis作为对话记忆的存储介质,实现对话记忆的持久化和分布式共享。

第一步,引入Redis相关依赖包,使用LangChain4j提供的Redis集成Starter,简化Redis与LangChain4j的整合过程。

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-community-redis-spring-boot-starter</artifactId>
    &lt;version&gt;1.1.0-beta7&lt;/version&gt;&#xA;&lt;/dependency&gt;

第二步,在application-local.yml中配置Redis连接信息,包括Redis主机地址、端口、密码、数据库索引和缓存过期时间,确保能够正常连接Redis服务。

spring:
    data:
      redis:
        host: localhost  # Redis主机地址(本地开发默认localhost)
        port: 6379       # Redis端口(默认6379)
        ttl: 3600        # 缓存过期时间(单位:秒),1小时后自动过期,避免缓存冗余
        password:        # Redis密码(若未设置密码,留空即可)
        database: 0      # Redis数据库索引(默认0)

第三步,创建RedisChatMemoryStore配置类,注册RedisChatMemoryStore Bean,将Redis连接信息注入,供LangChain4j的对话记忆使用,实现对话记忆的Redis存储。

@Configuration  // 标识为配置类,注入Spring容器
@ConfigurationProperties(prefix = "spring.data.redis")  // 读取spring.data.redis前缀的配置
@Data  // Lombok注解,简化getter/setter
public class RedisChatMemoryStoreConfig {

    private String host;  // Redis主机地址
    private int port;     // Redis端口
    private String password;  // Redis密码
    private long ttl;     // 缓存过期时间(秒)

    /**
     * 注册RedisChatMemoryStore Bean,供LangChain4j使用
     * @return RedisChatMemoryStore 对话记忆的Redis存储实现
     */
    @Bean
    public RedisChatMemoryStore redisChatMemoryStore() {
        return RedisChatMemoryStore.builder()
                .host(host)
                .port(port)
                .password(password)
                .ttl(ttl)
                .build();
    }
}

3.4 创建Agent工厂

每个应用对应一个独立的AI Agent实例,需关联该应用的对话记忆和代码生成类型。若每次请求都创建新的Agent实例,会造成性能损耗。因此创建Agent工厂类,负责Agent实例的创建、缓存和管理,提升系统性能。

Agent工厂使用Caffeine缓存,这是一种高性能本地缓存,可缓存Agent实例并设置过期时间,避免缓存冗余,同时支持根据appId和代码生成类型快速获取对应的Agent实例。

@Slf4j
@Configuration  // 标识为配置类,注入Spring容器
public class AiCodeGeneratorAgentFactory {

    @Resource
    private ChatModel chatModel;  // 基础聊天模型(非流式)

    @Resource(name = "reasoningStreamingChatModelPrototype")
    private StreamingChatModel reasoningStreamingChatModel;  // 流式聊天模型(用于实时返回生成结果)

    @Resource
    private RedisChatMemoryStore redisChatMemoryStore;  // Redis对话记忆存储

    @Resource
    private ChatHistoryService chatHistoryService;  // 聊天历史服务,用于加载历史对话

    @Resource
    private ToolManager toolManager;  // Tool工具管理器,用于获取所有可用工具

    /**
     * AI 服务实例缓存(Caffeine),缓存Agent实例,提升性能
     * 配置缓存最大容量、过期时间,避免内存溢出
     */
    private final Cache<String, AiCodeGeneratorAgent> serviceCache = Caffeine.newBuilder()
            .maximumSize(1000)  // 缓存最大容量(最多缓存1000个Agent实例)
            .expireAfterWrite(Duration.ofMinutes(30))  // 写入后30分钟过期
            .expireAfterAccess(Duration.ofMinutes(10))  // 访问后10分钟过期
            .removalListener((key, value, cause) -> {
                log.debug("AI 服务实例被移除,缓存键: {}, 原因: {}", key, cause);
            })
            .build();

    /**
     * 对外提供的核心方法:根据appId和代码生成类型获取Agent实例(优先从缓存获取,无则创建)
     * @param appId 应用id
     * @param codeGenType 代码生成类型(如SpringBoot项目)
     * @return AiCodeGeneratorAgent AI代码生成Agent实例
     */
    public AiCodeGeneratorAgent getAiCodeGeneratorAgent(long appId, CodeGenTypeEnum codeGenType) {
        String cacheKey = buildCacheKey(appId, codeGenType);
        return serviceCache.get(cacheKey, key -> createAiCodeGeneratorAgent(appId, codeGenType));
    }

    /**
     * 构建缓存键,格式为"appId_代码生成类型",确保唯一
     * @param appId 应用id
     * @param codeGenType 代码生成类型
     * @return String 缓存键
     */
    private String buildCacheKey(long appId, CodeGenTypeEnum codeGenType) {
        return appId + "_" + codeGenType.getValue();
    }

    /**
     * 核心方法:创建新的AI Agent实例,关联对话记忆、工具等
     * @param appId 应用id
     * @param codeGenType 代码生成类型
     * @return AiCodeGeneratorAgent 新创建的Agent实例
     */
    private AiCodeGeneratorAgent createAiCodeGeneratorAgent(long appId, CodeGenTypeEnum codeGenType) {
        // 为当前应用构建独立的对话记忆,关联Redis存储,最大缓存60条对话
        MessageWindowChatMemory chatMemory = MessageWindowChatMemory
                .builder()
                .id(appId)
                .chatMemoryStore(redisChatMemoryStore)
                .maxMessages(60)
                .build();
        // 从数据库加载该应用的历史对话到AI记忆中,最多加载20条
        chatHistoryService.loadChatHistoryToMemory(appId, chatMemory, 20);
        // 根据代码生成类型,创建对应的Agent实例
        return switch (codeGenType) {
            case SPRINGBOOT_PROJECT ->
                    AiServices.builder(AiCodeGeneratorAgent.class)
                            .streamingChatModel(reasoningStreamingChatModel)
                            .chatMemoryProvider(memoryId -> chatMemory)
                            .tools(toolManager.getAllTools())
                            .hallucinatedToolNameStrategy(toolExecutionRequest -> ToolExecutionResultMessage.from(
                                    toolExecutionRequest, "Error: there is no tool called " + toolExecutionRequest.name()
                            ))
                            .build();
            default -> throw new BusinessException(ErrorCode.SYSTEM_ERROR,
                    "不支持的代码生成类型: " + codeGenType.getValue());
        };

    }
    
}

3.5 AI外观类

为简化系统其他模块对AI服务的调用,采用外观模式创建AiCodeGeneratorFacade类,将AI代码生成、流式响应转换等功能封装组合,对外提供统一调用入口,隐藏内部复杂实现细节。

该类的核心功能的是接收用户请求,包括提示词、代码生成类型和appId,获取对应的Agent实例,调用AI生成代码,将AI返回的TokenStream流式响应转换为前端可接收的Flux格式,同时处理工具调用相关的消息转换。

@Slf4j
@Service  // 注入Spring容器,作为AI服务的统一入口
public class AiCodeGeneratorFacade {

    @Resource
    private AiCodeGeneratorAgentFactory aiCodeGeneratorServiceFactory;  // 注入Agent工厂,获取Agent实例

    /**
     * 统一入口:根据代码生成类型,生成并保存代码(流式响应)
     * 对外提供统一调用方法,无需关心内部Agent创建、记忆加载等细节
     * @param userMessage 用户提示词(代码生成需求)
     * @param codeGenTypeEnum 代码生成类型(如SpringBoot项目)
     * @param appId 应用ID,关联对应的对话记忆和Agent实例
     * @return Flux<String> 流式响应,实时返回AI生成的内容(JSON格式)
     */
    public Flux<String> generateAndSaveCodeStream(String userMessage, CodeGenTypeEnum codeGenTypeEnum, Long appId) {
        if (codeGenTypeEnum == null) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "生成类型为空");
        }
        AiCodeGeneratorAgent aiCodeGeneratorService = aiCodeGeneratorServiceFactory.getAiCodeGeneratorAgent(appId, codeGenTypeEnum);
        return switch (codeGenTypeEnum) {
            case SPRINGBOOT_PROJECT -> {
                TokenStream tokenStream = aiCodeGeneratorService.generateSpringbootProjectStream(appId, userMessage);
                yield processTokenStream(tokenStream, appId);
            }
            default -> {
                String errorMessage = "不支持的生成类型:" + codeGenTypeEnum.getValue();
                throw new BusinessException(ErrorCode.SYSTEM_ERROR, errorMessage);
            }
        };

    }

    /**
     * 辅助方法:将LangChain4j的TokenStream转换为Spring WebFlux的Flux<String>
     * 同时处理AI的部分响应、工具调用请求、工具执行结果等消息,转换为统一的JSON格式
     * @param tokenStream AI返回的流式响应对象
     * @param appId 应用ID(用于日志记录)
     * @return Flux<String> 转换后的流式响应,供前端接收
     */
    private Flux<String> processTokenStream(TokenStream tokenStream, Long appId) {
        return Flux.create(sink -> {
            tokenStream
                    .onPartialResponse((String partialResponse) -> {
                        AiResponseMessage aiResponseMessage = new AiResponseMessage(partialResponse);
                        sink.next(JSONUtil.toJsonStr(aiResponseMessage));
                    })
                    .onPartialToolExecutionRequest((index, toolExecutionRequest) -> {
                        ToolRequestMessage toolRequestMessage = new ToolRequestMessage(toolExecutionRequest);
                        sink.next(JSONUtil.toJsonStr(toolRequestMessage));
                    })
                    .onToolExecuted((ToolExecution toolExecution) -> {
                        ToolExecutedMessage toolExecutedMessage = new ToolExecutedMessage(toolExecution);
                        sink.next(JSONUtil.toJsonStr(toolExecutedMessage));
                    })
                    .onCompleteResponse((ChatResponse response) -> {
                        sink.complete();
                    })
                    .onError((Throwable error) -> {
                        error.printStackTrace();
                        sink.error(error);
                    })
                    .start();
        });
    }

}

至此,AI代码生成服务的核心功能已全部实现。系统其他模块只需调用AiCodeGeneratorFacade的generateAndSaveCodeStream方法,即可实现AI代码生成功能,无需关心内部的Agent管理、对话记忆、工具调用等复杂细节,极大提升了代码的可维护性和易用性。

(注:文档部分内容可能由 AI 生成)