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>
<version>1.1.0-beta7</version>
</dependency>
第二步,在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 生成)