AI工作流
AI工作流
这里使用了langgraph4j来完成工作流:
在我们的执行文件中:
这个类中我们提供了两个方法:
一个是创建工作流和执行工作流的方法:
创建:
我们在这个方法里创建节点并用边连接,返回的是MessagesStateGraph编译后的工作流
执行:
这里先创建了一个工作流,然后配置工作流的上下文,然后去生成一个我们这个工作流的工作流图,然后for循环内是给工作流一个初始状态(提示词和上下文),然后再每一个节点输出后,我们记录状态,以及它的上下文信息。
@Slf4j
public class CodeGenWorkflow {
/**
* 创建完整的工作流
*/
public CompiledGraph<MessagesState<String>> createWorkflow() {
try {
return new MessagesStateGraph<String>()
// 添加节点 - 使用完整实现的节点
.addNode("image_collector", ImageCollectorNode.create())
.addNode("prompt_enhancer", PromptEnhancerNode.create())
.addNode("router", RouterNode.create())
.addNode("code_generator", CodeGeneratorNode.create())
.addNode("project_builder", ProjectBuilderNode.create())
// 添加边
.addEdge(START, "image_collector")
.addEdge("image_collector", "prompt_enhancer")
.addEdge("prompt_enhancer", "router")
.addEdge("router", "code_generator")
.addEdge("code_generator", "project_builder")
.addEdge("project_builder", END)
// 编译工作流
.compile();
} catch (GraphStateException e) {
throw new BusinessException(ErrorCode.OPERATION_ERROR, "工作流创建失败");
}
}
/**
* 执行工作流
*/
public WorkflowContext executeWorkflow(String originalPrompt) {
CompiledGraph<MessagesState<String>> workflow = createWorkflow();
// 初始化 WorkflowContext
WorkflowContext initialContext = WorkflowContext.builder()
.originalPrompt(originalPrompt)
.currentStep("初始化")
.build();
GraphRepresentation graph = workflow.getGraph(GraphRepresentation.Type.MERMAID);
log.info("工作流图:\n{}", graph.content());
log.info("开始执行代码生成工作流");
WorkflowContext finalContext = null;
int stepCounter = 1;
for (NodeOutput<MessagesState<String>> step : workflow.stream(
Map.of(WorkflowContext.WORKFLOW_CONTEXT_KEY, initialContext))) {
log.info("--- 第 {} 步完成 ---", stepCounter);
// 显示当前状态
WorkflowContext currentContext = WorkflowContext.getContext(step.state());
if (currentContext != null) {
finalContext = currentContext;
log.info("当前步骤上下文: {}", currentContext);
}
stepCounter++;
}
log.info("代码生成工作流执行完成!");
return finalContext;
}
}
然后我们看一下节点:
代码生成节点:
调用AI获得我们之前的AI代码服务,这里的是我们在最开始的时候传入的上下文,所以我们可以通过节点来获取上下文
@Slf4j
public class CodeGeneratorNode {
public static AsyncNodeAction<MessagesState<String>> create() {
return node_async(state -> {
WorkflowContext context = WorkflowContext.getContext(state);
log.info("执行节点: 代码生成");
// 使用增强提示词作为发给 AI 的用户消息
String userMessage = context.getEnhancedPrompt();
CodeGenTypeEnum generationType = context.getGenerationType();
// 获取 AI 代码生成外观服务
AiCodeGeneratorFacade codeGeneratorFacade = SpringContextUtil.getBean(AiCodeGeneratorFacade.class);
log.info("开始生成代码,类型: {} ({})", generationType.getValue(), generationType.getText());
// 先使用固定的 appId (后续再整合到业务中)
Long appId = 0L;
// 调用流式代码生成
Flux<String> codeStream = codeGeneratorFacade.generateAndSaveCodeStream(userMessage, generationType, appId);
// 同步等待流式输出完成
codeStream.blockLast(Duration.ofMinutes(10)); // 最多等待 10 分钟
// 根据类型设置生成目录
String generatedCodeDir = String.format("%s/%s_%s", AppConstant.CODE_OUTPUT_ROOT_DIR, generationType.getValue(), appId);
log.info("AI 代码生成完成,生成目录: {}", generatedCodeDir);
// 更新状态
context.setCurrentStep("代码生成");
context.setGeneratedCodeDir(generatedCodeDir);
return WorkflowContext.saveContext(context);
});
}
}
这个是用到的代码增强:
@Slf4j
public class PromptEnhancerNode {
public static AsyncNodeAction<MessagesState<String>> create() {
return node_async(state -> {
WorkflowContext context = WorkflowContext.getContext(state);
log.info("执行节点: 提示词增强");
// 获取原始提示词和图片列表
String originalPrompt = context.getOriginalPrompt();
String imageListStr = context.getImageListStr();
List<ImageResource> imageList = context.getImageList();
// 构建增强后的提示词
StringBuilder enhancedPromptBuilder = new StringBuilder();
enhancedPromptBuilder.append(originalPrompt);
// 如果有图片资源,则添加图片信息
if (CollUtil.isNotEmpty(imageList) || StrUtil.isNotBlank(imageListStr)) {
enhancedPromptBuilder.append("\n\n## 可用素材资源\n");
enhancedPromptBuilder.append("请在生成网站使用以下图片资源,将这些图片合理地嵌入到网站的相应位置中。\n");
if (CollUtil.isNotEmpty(imageList)) {
for (ImageResource image : imageList) {
enhancedPromptBuilder.append("- ")
.append(image.getCategory().getText())
.append(":")
.append(image.getDescription())
.append("(")
.append(image.getUrl())
.append(")\n");
}
} else {
enhancedPromptBuilder.append(imageListStr);
}
}
String enhancedPrompt = enhancedPromptBuilder.toString();
// 更新状态
context.setCurrentStep("提示词增强");
context.setEnhancedPrompt(enhancedPrompt);
log.info("提示词增强完成,增强后长度: {} 字符", enhancedPrompt.length());
return WorkflowContext.saveContext(context);
});
}
}
在代码生成节点前还要有一个智能路由节点:
@Slf4j
public class RouterNode {
public static AsyncNodeAction<MessagesState<String>> create() {
return node_async(state -> {
WorkflowContext context = WorkflowContext.getContext(state);
log.info("执行节点: 智能路由");
CodeGenTypeEnum generationType;
try {
// 获取AI路由服务
AiCodeGenTypeRoutingService routingService = SpringContextUtil.getBean(AiCodeGenTypeRoutingService.class);
// 根据原始提示词进行智能路由
generationType = routingService.routeCodeGenType(context.getOriginalPrompt());
log.info("AI智能路由完成,选择类型: {} ({})", generationType.getValue(), generationType.getText());
} catch (Exception e) {
log.error("AI智能路由失败,使用默认HTML类型: {}", e.getMessage());
generationType = CodeGenTypeEnum.HTML;
}
// 更新状态
context.setCurrentStep("智能路由");
context.setGenerationType(generationType);
return WorkflowContext.saveContext(context);
});
}
}
项目构建节点:
@Slf4j
public class ProjectBuilderNode {
public static AsyncNodeAction<MessagesState<String>> create() {
return node_async(state -> {
WorkflowContext context = WorkflowContext.getContext(state);
log.info("执行节点: 项目构建");
// 获取必要的参数
String generatedCodeDir = context.getGeneratedCodeDir();
CodeGenTypeEnum generationType = context.getGenerationType();
String buildResultDir;
// Vue 项目类型:使用 VueProjectBuilder 进行构建
if (generationType == CodeGenTypeEnum.VUE_PROJECT) {
try {
VueProjectBuilder vueBuilder = SpringContextUtil.getBean(VueProjectBuilder.class);
// 执行 Vue 项目构建(npm install + npm run build)
boolean buildSuccess = vueBuilder.buildProject(generatedCodeDir);
if (buildSuccess) {
// 构建成功,返回 dist 目录路径
buildResultDir = generatedCodeDir + File.separator + "dist";
log.info("Vue 项目构建成功,dist 目录: {}", buildResultDir);
} else {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "Vue 项目构建失败");
}
} catch (Exception e) {
log.error("Vue 项目构建异常: {}", e.getMessage(), e);
buildResultDir = generatedCodeDir; // 异常时返回原路径
}
} else {
// HTML 和 MULTI_FILE 代码生成时已经保存了,直接使用生成的代码目录
buildResultDir = generatedCodeDir;
}
// 更新状态
context.setCurrentStep("项目构建");
context.setBuildResultDir(buildResultDir);
log.info("项目构建节点完成,最终目录: {}", buildResultDir);
return WorkflowContext.saveContext(context);
});
}
}
工具收集节点:
1)内容图片收集工具
@Slf4j
@Component
public class ImageSearchTool {
private static final String PEXELS_API_URL = "https://api.pexels.com/v1/search";
@Value("${pexels.api-key}")
private String pexelsApiKey;
@Tool("搜索内容相关的图片,用于网站内容展示")
public List<ImageResource> searchContentImages(@P("搜索关键词") String query) {
List<ImageResource> imageList = new ArrayList<>();
int searchCount = 12;
// 调用 API,注意释放资源
try (HttpResponse response = HttpRequest.get(PEXELS_API_URL)
.header("Authorization", pexelsApiKey)
.form("query", query)
.form("per_page", searchCount)
.form("page", 1)
.execute()) {
if (response.isOk()) {
JSONObject result = JSONUtil.parseObj(response.body());
JSONArray photos = result.getJSONArray("photos");
for (int i = 0; i < photos.size(); i++) {
JSONObject photo = photos.getJSONObject(i);
JSONObject src = photo.getJSONObject("src");
imageList.add(ImageResource.builder()
.category(ImageCategoryEnum.CONTENT)
.description(photo.getStr("alt", query))
.url(src.getStr("medium"))
.build());
}
}
} catch (Exception e) {
log.error("Pexels API 调用失败: {}", e.getMessage(), e);
}
return imageList;
}
}
插画图片收集工具:
@Slf4j
@Component
public class UndrawIllustrationTool {
private static final String UNDRAW_API_URL = "https://undraw.co/_next/data/mMWmJSt23qpgo8cLTD_pB/search/%s.json?term=%s";
@Tool("搜索插画图片,用于网站美化和装饰")
public List<ImageResource> searchIllustrations(@P("搜索关键词") String query) {
List<ImageResource> imageList = new ArrayList<>();
int searchCount = 12;
String apiUrl = String.format(UNDRAW_API_URL, query, query);
// 使用 try-with-resources 自动释放 HTTP 资源
try (HttpResponse response = HttpRequest.get(apiUrl).timeout(10000).execute()) {
if (!response.isOk()) {
return imageList;
}
JSONObject result = JSONUtil.parseObj(response.body());
JSONObject pageProps = result.getJSONObject("pageProps");
if (pageProps == null) {
return imageList;
}
JSONArray initialResults = pageProps.getJSONArray("initialResults");
if (initialResults == null || initialResults.isEmpty()) {
return imageList;
}
int actualCount = Math.min(searchCount, initialResults.size());
for (int i = 0; i < actualCount; i++) {
JSONObject illustration = initialResults.getJSONObject(i);
String title = illustration.getStr("title", "插画");
String media = illustration.getStr("media", "");
if (StrUtil.isNotBlank(media)) {
imageList.add(ImageResource.builder()
.category(ImageCategoryEnum.ILLUSTRATION)
.description(title)
.url(media)
.build());
}
}
} catch (Exception e) {
log.error("搜索插画失败:{}", e.getMessage(), e);
}
return imageList;
}
}
架构图绘制工具:
@Slf4j
@Component
public class MermaidDiagramTool {
@Resource
private CosManager cosManager;
@Tool("将 Mermaid 代码转换为架构图图片,用于展示系统结构和技术关系")
public List<ImageResource> generateMermaidDiagram(@P("Mermaid 图表代码") String mermaidCode,
@P("架构图描述") String description) {
if (StrUtil.isBlank(mermaidCode)) {
return new ArrayList<>();
}
try {
// 转换为SVG图片
File diagramFile = convertMermaidToSvg(mermaidCode);
// 上传到COS
String keyName = String.format("/mermaid/%s/%s",
RandomUtil.randomString(5), diagramFile.getName());
String cosUrl = cosManager.uploadFile(keyName, diagramFile);
// 清理临时文件
FileUtil.del(diagramFile);
if (StrUtil.isNotBlank(cosUrl)) {
return Collections.singletonList(ImageResource.builder()
.category(ImageCategoryEnum.ARCHITECTURE)
.description(description)
.url(cosUrl)
.build());
}
} catch (Exception e) {
log.error("生成架构图失败: {}", e.getMessage(), e);
}
return new ArrayList<>();
}
/**
* 将Mermaid代码转换为SVG图片
*/
private File convertMermaidToSvg(String mermaidCode) {
// 创建临时输入文件
File tempInputFile = FileUtil.createTempFile("mermaid_input_", ".mmd", true);
FileUtil.writeUtf8String(mermaidCode, tempInputFile);
// 创建临时输出文件
File tempOutputFile = FileUtil.createTempFile("mermaid_output_", ".svg", true);
// 根据操作系统选择命令
String command = SystemUtil.getOsInfo().isWindows() ? "mmdc.cmd" : "mmdc";
// 构建命令
String cmdLine = String.format("%s -i %s -o %s -b transparent",
command,
tempInputFile.getAbsolutePath(),
tempOutputFile.getAbsolutePath()
);
// 执行命令
RuntimeUtil.execForStr(cmdLine);
// 检查输出文件
if (!tempOutputFile.exists() || tempOutputFile.length() == 0) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "Mermaid CLI 执行失败");
}
// 清理输入文件,保留输出文件供上传使用
FileUtil.del(tempInputFile);
return tempOutputFile;
}
}
LOGO图片生成工具:
@Slf4j
@Component
public class LogoGeneratorTool {
@Value("${dashscope.api-key:}")
private String dashScopeApiKey;
@Value("${dashscope.image-model:wan2.2-t2i-flash}")
private String imageModel;
@Tool("根据描述生成 Logo 设计图片,用于网站品牌标识")
public List<ImageResource> generateLogos(@P("Logo 设计描述,如名称、行业、风格等,尽量详细") String description) {
List<ImageResource> logoList = new ArrayList<>();
try {
// 构建 Logo 设计提示词
String logoPrompt = String.format("生成 Logo,Logo 中禁止包含任何文字!Logo 介绍:%s", description);
ImageSynthesisParam param = ImageSynthesisParam.builder()
.apiKey(dashScopeApiKey)
.model(imageModel)
.prompt(logoPrompt)
.size("512*512")
.n(1) // 生成 1 张足够,因为 AI 不知道哪张最好
.build();
ImageSynthesis imageSynthesis = new ImageSynthesis();
ImageSynthesisResult result = imageSynthesis.call(param);
if (result != null && result.getOutput() != null && result.getOutput().getResults() != null) {
List<Map<String, String>> results = result.getOutput().getResults();
for (Map<String, String> imageResult : results) {
String imageUrl = imageResult.get("url");
if (StrUtil.isNotBlank(imageUrl)) {
logoList.add(ImageResource.builder()
.category(ImageCategoryEnum.LOGO)
.description(description)
.url(imageUrl)
.build());
}
}
}
} catch (Exception e) {
log.error("生成 Logo 失败: {}", e.getMessage(), e);
}
return logoList;
}
}
图片收集AI服务:
/**
* 图片收集 AI 服务接口
* 使用 AI 调用工具收集不同类型的图片资源
*/
public interface ImageCollectionService {
/**
* 根据用户提示词收集所需的图片资源
* AI 会根据需求自主选择调用相应的工具
*/
@SystemMessage(fromResource = "prompt/image-collection-system-prompt.txt")
List<ImageResource> collectImages(@UserMessage String userPrompt);
}
Spring上下文工具:
/**
* Spring上下文工具类
* 用于在静态方法中获取Spring Bean
*/
@Component
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtil.applicationContext = applicationContext;
}
/**
* 获取Spring Bean
*/
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
/**
* 获取Spring Bean
*/
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
/**
* 根据名称和类型获取Spring Bean
*/
public static <T> T getBean(String name, Class<T> clazz) {
return applicationContext.getBean(name, clazz);
}
}
对于我们的代码,先输入原始的prompt,然后我们会收集的节点调用工具去收集图片Agent,然后到提示词增强节点去强化我们的提示词再到智能路由节点去选择我们的生成模式,最后让AI根据我们的要求和素材去生成我们的代码,最后如果是判断项目构建,如果是VUE文件保存就去保存构建,如果是普通的我们就不用构建了。
LangGraph4j工作流特性实战:
跳过构建-条件边(工作流新增路由函数和条件边配置)
.addEdge("router", "code_generator")
// 使用条件边:根据代码生成类型决定是否需要构建
.addConditionalEdges("code_generator",
edge_async(this::routeBuildOrSkip),
Map.of(
"build", "project_builder", // 需要构建的情况
"skip_build", END // 跳过构建直接结束
))
.addEdge("project_builder", END)
路由函数决定代码生成后是否需要项目构建
private String routeBuildOrSkip(MessagesState<String> state) {
WorkflowContext context = WorkflowContext.getContext(state);
CodeGenTypeEnum generationType = context.getGenerationType();
// HTML 和 MULTI_FILE 类型不需要构建,直接结束
if (generationType == CodeGenTypeEnum.HTML || generationType == CodeGenTypeEnum.MULTI_FILE) {
return "skip_build";
}
// VUE_PROJECT 需要构建
return "build";
}
项目构建工作节点中移除if-else逻辑:
String buildResultDir;
// 一定是 Vue 项目类型:使用 VueProjectBuilder 进行构建
try {
VueProjectBuilder vueBuilder = SpringContextUtil.getBean(VueProjectBuilder.class);
// 执行 Vue 项目构建(npm install + npm run build)
boolean buildSuccess = vueBuilder.buildProject(generatedCodeDir);
if (buildSuccess) {
// 构建成功,返回 dist 目录路径
buildResultDir = generatedCodeDir + File.separator + "dist";
log.info("Vue 项目构建成功,dist 目录: {}", buildResultDir);
} else {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "Vue 项目构建失败");
}
} catch (Exception e) {
log.error("Vue 项目构建异常: {}", e.getMessage(), e);
buildResultDir = generatedCodeDir; // 异常时返回原路径
}
用条件边的优势:
1.可视化更清晰:工作流图能直观显示不同路径
2.性能更好:直接跳过不需要加载的节点,避免无用的Bean加载
3.关注点分离:节点专注业务逻辑,边专注流程控制
质量检查-循环边
1.定义我们的数据模型:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class QualityResult implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 是否通过质检
*/
private Boolean isValid;
/**
* 错误列表
*/
private List<String> errors;
/**
* 改进建议
*/
private List<String> suggestions;
}
2.质量检查AI服务
给予AI质量检查服务的提示词(重点突出检查语法错误,并且强调结构化输出的JSON格式)
你是一个专业的代码质量检查专家。你的任务是分析用户提供的网站代码,检查语法错误等方面的问题,确保项目可以正常运行和打包。
## 检查重点
### 1. 语法和结构错误
- HTML 标签是否正确闭合
- CSS 语法是否正确
- JavaScript 语法错误
- 文件引用路径是否正确
- 缺失的依赖或资源
### 2. 代码质量
- 代码结构是否合理
- 命名规范是否一致
- 代码重复性检查
### 3. 功能完整性
- 页面功能是否完整
- 交互逻辑是否正确
- 响应式设计检查
## 输出格式
请严格按照以下 JSON 格式返回检查结果:
json
{
"isValid": true/false,
"errors": [
"具体的错误描述1",
"具体的错误描述2"
],
"suggestions": [
"改进建议1",
"改进建议2"
]
}
## 判断标准
- isValid = true: 代码无严重语法错误,能够正常运行和打包
- isValid = false: 存在语法错误、结构问题或其他会导致无法正常运行的问题
- errors: 必须修复的问题,如语法错误、缺失文件等
- suggestions: 关于如何修复错误和改进代码的建议
请仔细分析代码,提供专业的质量检查结果。
开发AI服务,利用结构化输出:
public interface CodeQualityCheckService {
/**
* 检查代码质量
* AI 会分析代码并返回质量检查结果
*/
@SystemMessage(fromResource = "prompt/code-quality-check-system-prompt.txt")
QualityResult checkCodeQuality(@UserMessage String codeContent);
}
开发创建AI服务的工厂:
@Slf4j
@Configuration
public class CodeQualityCheckServiceFactory {
@Resource
private ChatModel chatModel;
/**
* 创建代码质量检查 AI 服务
*/
@Bean
public CodeQualityCheckService createCodeQualityCheckService() {
return AiServices.builder(CodeQualityCheckService.class)
.chatModel(chatModel)
.build();
}
}
3.开发质量检查工作节点
1)我们开发一个方法来读取生成代码的文件结构和具体内容,拼接好后交给AI检查,利用Hutool工具类完成。
/**
* 需要检查的文件扩展名
*/
private static final List<String> CODE_EXTENSIONS = Arrays.asList(
".html", ".htm", ".css", ".js", ".json", ".vue", ".ts", ".jsx", ".tsx"
);
/**
* 读取并拼接代码目录下的所有代码文件
*/
private static String readAndConcatenateCodeFiles(String codeDir) {
if (StrUtil.isBlank(codeDir)) {
return "";
}
File directory = new File(codeDir);
if (!directory.exists() || !directory.isDirectory()) {
log.error("代码目录不存在或不是目录: {}", codeDir);
return "";
}
StringBuilder codeContent = new StringBuilder();
codeContent.append("# 项目文件结构和代码内容\n\n");
// 使用 Hutool 的 walkFiles 方法遍历所有文件
FileUtil.walkFiles(directory, file -> {
// 过滤条件:跳过隐藏文件、特定目录下的文件、非代码文件
if (shouldSkipFile(file, directory)) {
return;
}
if (isCodeFile(file)) {
String relativePath = FileUtil.subPath(directory.getAbsolutePath(), file.getAbsolutePath());
codeContent.append("## 文件: ").append(relativePath).append("\n\n");
String fileContent = FileUtil.readUtf8String(file);
codeContent.append(fileContent).append("\n\n");
}
});
return codeContent.toString();
}
/**
* 判断是否应该跳过此文件
*/
private static boolean shouldSkipFile(File file, File rootDir) {
String relativePath = FileUtil.subPath(rootDir.getAbsolutePath(), file.getAbsolutePath());
// 跳过隐藏文件
if (file.getName().startsWith(".")) {
return true;
}
// 跳过特定目录下的文件
return relativePath.contains("node_modules" + File.separator) ||
relativePath.contains("dist" + File.separator) ||
relativePath.contains("target" + File.separator) ||
relativePath.contains(".git" + File.separator);
}
/**
* 判断是否是需要检查的代码文件
*/
private static boolean isCodeFile(File file) {
String fileName = file.getName().toLowerCase();
return CODE_EXTENSIONS.stream().anyMatch(fileName::endsWith);
}
上述代码中,用到了Hutool的walkFiles方法来遍历文件,这里用到了设计模式 ---------访问者模式
访问者模式允许你再不修改对象结构的情况加,定义作用于这些对象的新操作。在这个例子中,walkFiles方法遍历文件树结构,而我们传入的的lambda就是访问者,它定义了对每个文件要执行的具体操作。这样的设计文件遍历逻辑和文件处理逻辑完全分离,可以灵活地定义不同的文件处理策略,而不需要修改遍历文件的核心代码。
2)开发具体的代码质量检查逻辑,调用AI完成检查,并更新质量检查结果:
/**
* 代码质量检查节点
*/
@Slf4j
public class CodeQualityCheckNode {
public static AsyncNodeAction<MessagesState<String>> create() {
return node_async(state -> {
WorkflowContext context = WorkflowContext.getContext(state);
log.info("执行节点: 代码质量检查");
String generatedCodeDir = context.getGeneratedCodeDir();
QualityResult qualityResult;
try {
// 1. 读取并拼接代码文件内容
String codeContent = readAndConcatenateCodeFiles(generatedCodeDir);
if (StrUtil.isBlank(codeContent)) {
log.warn("未找到可检查的代码文件");
qualityResult = QualityResult.builder()
.isValid(false)
.errors(List.of("未找到可检查的代码文件"))
.suggestions(List.of("请确保代码生成成功"))
.build();
} else {
// 2. 调用 AI 进行代码质量检查
CodeQualityCheckService qualityCheckService = SpringContextUtil.getBean(CodeQualityCheckService.class);
qualityResult = qualityCheckService.checkCodeQuality(codeContent);
log.info("代码质量检查完成 - 是否通过: {}", qualityResult.getIsValid());
}
} catch (Exception e) {
log.error("代码质量检查异常: {}", e.getMessage(), e);
qualityResult = QualityResult.builder()
.isValid(true) // 异常直接跳到下一个步骤
.build();
}
// 3. 更新状态
context.setCurrentStep("代码质量检查");
context.setQualityResult(qualityResult);
return WorkflowContext.saveContext(context);
});
}
}
4.修改代码生成节点
代码生成节点要判断状态中有没有错误信息,如果有的话,根据错误信息构造提示词,引导AI修复错误。
1)编写构造用户消息的方法,如果存在质检失败结果则添加错误修复信息:
/**
* 构造用户消息,如果存在质检失败结果则添加错误修复信息
*/
private static String buildUserMessage(WorkflowContext context) {
String userMessage = context.getEnhancedPrompt();
// 检查是否存在质检失败结果
QualityResult qualityResult = context.getQualityResult();
if (isQualityCheckFailed(qualityResult)) {
// 直接将错误修复信息作为新的提示词(起到了修改的作用)
userMessage = buildErrorFixPrompt(qualityResult);
}
return userMessage;
}
/**
* 判断质检是否失败
*/
private static boolean isQualityCheckFailed(QualityResult qualityResult) {
return qualityResult != null &&
!qualityResult.getIsValid() &&
qualityResult.getErrors() != null &&
!qualityResult.getErrors().isEmpty();
}
/**
* 构造错误修复提示词
*/
private static String buildErrorFixPrompt(QualityResult qualityResult) {
StringBuilder errorInfo = new StringBuilder();
errorInfo.append("\n\n## 上次生成的代码存在以下问题,请修复:\n");
// 添加错误列表
qualityResult.getErrors().forEach(error ->
errorInfo.append("- ").append(error).append("\n"));
// 添加修复建议(如果有)
if (qualityResult.getSuggestions() != null && !qualityResult.getSuggestions().isEmpty()) {
errorInfo.append("\n## 修复建议:\n");
qualityResult.getSuggestions().forEach(suggestion ->
errorInfo.append("- ").append(suggestion).append("\n"));
}
errorInfo.append("\n请根据上述问题和建议重新生成代码,确保修复所有提到的问题。");
return errorInfo.toString();
}
5.修改工作流
1)新增节点和边,质检条件边可以和之前的构建条件边合并:
.addNode("code_quality_check", CodeQualityCheckNode.create())
// ...
.addEdge("code_generator", "code_quality_check")
// 新增质检条件边:根据质检结果决定下一步
.addConditionalEdges("code_quality_check",
edge_async(this::routeAfterQualityCheck),
Map.of(
"build", "project_builder", // 质检通过且需要构建
"skip_build", END, // 质检通过但跳过构建
"fail", "code_generator" // 质检失败,重新生成
))
2)新写路由函数,根据质检结果决定下一步,直接复用之前的routeBuildOrSkip方法:
private String routeAfterQualityCheck(MessagesState<String> state) {
WorkflowContext context = WorkflowContext.getContext(state);
QualityResult qualityResult = context.getQualityResult();
// 如果质检失败,重新生成代码
if (qualityResult == null || !qualityResult.getIsValid()) {
log.error("代码质检失败,需要重新生成代码");
return "fail";
}
// 质检通过,使用原有的构建路由逻辑
log.info("代码质检通过,继续后续流程");
return routeBuildOrSkip(state);
}
现在我们的是利用工具调用来获取图片同时和AI 进行多轮会话,耗时性能低,还有消耗大量token。
可以进行优化:先通过调用AI获取要收集的图片类别和参数(输出图片收集任务),利用结构化输出返回这些信息,然后并发调用对应的图片收集工具执行。
图片收集规划实现:
你是一个专业的图片收集规划师。你的任务是分析用户的网站需求,制定合理的图片收集计划。
## 图片类型说明
### 1. 内容图片 (contentImageTasks)
- 用途:网站的主要内容配图
- 来源:通过关键词搜索获取
- 示例:产品图片、场景图片、人物图片等
### 2. 插画图片 (illustrationTasks)
- 用途:装饰性插画,提升页面美观度
- 来源:Undraw 插画库
- 示例:抽象插画、概念图解等
### 3. 架构图 (diagramTasks)
- 用途:展示系统架构、流程图等技术图表
- 来源:通过 Mermaid 代码生成
- 示例:系统架构图、流程图、组织结构图等
### 4. Logo图片 (logoTasks)
- 用途:品牌标识、图标等
- 来源:AI 生成
- 示例:公司Logo、产品图标等
## 规划原则
1. 需求导向:根据用户描述的网站类型和用途来规划图片
2. 适量原则:每种类型的图片数量要合理,避免过多或过少
3. 关键词精准:选择最能体现需求的关键词
4. 描述清晰:为任务提供清晰的描述说明
## 输出要求
请严格按照以下 JSON 格式返回图片收集计划:
json
{
"contentImageTasks": [
{
"query": "搜索关键词"
}
],
"illustrationTasks": [
{
"query": "插画关键词"
}
],
"diagramTasks": [
{
"mermaidCode": "mermaid图表代码",
"description": "图表用途描述"
}
],
"logoTasks": [
{
"description": "Logo设计描述,如名称、行业、风格等"
}
]
}
注意:
- 如果某种类型的图片不需要,对应数组可以为空
- 每个任务的 description 要说明图片的具体用途和位置
- mermaidCode 要是有效的 Mermaid 语法代码
- 关键词要使用中文或英文,选择搜索效果最好的语言
2)在model包下定义ImageCollectionPlan数据模型,用于保存图片收集任务。
@Data
public class ImageCollectionPlan implements Serializable {
/**
* 内容图片搜索任务列表
*/
private List<ImageSearchTask> contentImageTasks;
/**
* 插画图片搜索任务列表
*/
private List<IllustrationTask> illustrationTasks;
/**
* 架构图生成任务列表
*/
private List<DiagramTask> diagramTasks;
/**
* Logo生成任务列表
*/
private List<LogoTask> logoTasks;
/**
* 内容图片搜索任务
* 对应 ImageSearchTool.searchContentImages(String query)
*/
public record ImageSearchTask(String query) implements Serializable {}
/**
* 插画图片搜索任务
* 对应 UndrawIllustrationTool.searchIllustrations(String query)
*/
public record IllustrationTask(String query) implements Serializable {}
/**
* 架构图生成任务
* 对应 MermaidDiagramTool.generateMermaidDiagram(String mermaidCode, String description)
*/
public record DiagramTask(String mermaidCode, String description) implements Serializable {}
/**
* Logo生成任务
* 对应 LogoGeneratorTool.generateLogos(String description)
*/
public record LogoTask(String description) implements Serializable {}
}
我们这里用到了record来简化每种图片搜集任务的定义。
3)编写图片收集AI服务和工厂
public interface ImageCollectionPlanService {
/**
* 根据用户提示词分析需要收集的图片类型和参数
*/
@SystemMessage(fromResource = "prompt/image-collection-plan-system-prompt.txt")
ImageCollectionPlan planImageCollection(@UserMessage String userPrompt);
}
@Configuration
public class ImageCollectionPlanServiceFactory {
@Resource
private ChatModel chatModel;
@Bean
public ImageCollectionPlanService createImageCollectionPlanService() {
return AiServices.builder(ImageCollectionPlanService.class)
.chatModel(chatModel)
.build();
}
}
第一个方法:工具节点内部实现并发(推荐)
在图片收集节点内部通过CompletableFuture并发调用工具进行收集,并更新结果。
直接修改图片收集工作节点。先调用AI进行规划,然后并发收集图片进行汇总,最后设置imageList状态。
@Slf4j
public class ImageCollectorNode {
public static AsyncNodeAction<MessagesState<String>> create() {
return node_async(state -> {
WorkflowContext context = WorkflowContext.getContext(state);
String originalPrompt = context.getOriginalPrompt();
List<ImageResource> collectedImages = new ArrayList<>();
try {
// 第一步:获取图片收集计划
ImageCollectionPlanService planService = SpringContextUtil.getBean(ImageCollectionPlanService.class);
ImageCollectionPlan plan = planService.planImageCollection(originalPrompt);
log.info("获取到图片收集计划,开始并发执行");
// 第二步:并发执行各种图片收集任务
List<CompletableFuture<List<ImageResource>>> futures = new ArrayList<>();
// 并发执行内容图片搜索
if (plan.getContentImageTasks() != null) {
ImageSearchTool imageSearchTool = SpringContextUtil.getBean(ImageSearchTool.class);
for (ImageCollectionPlan.ImageSearchTask task : plan.getContentImageTasks()) {
futures.add(CompletableFuture.supplyAsync(() ->
imageSearchTool.searchContentImages(task.query())));
}
}
// 并发执行插画图片搜索
if (plan.getIllustrationTasks() != null) {
UndrawIllustrationTool illustrationTool = SpringContextUtil.getBean(UndrawIllustrationTool.class);
for (ImageCollectionPlan.IllustrationTask task : plan.getIllustrationTasks()) {
futures.add(CompletableFuture.supplyAsync(() ->
illustrationTool.searchIllustrations(task.query())));
}
}
// 并发执行架构图生成
if (plan.getDiagramTasks() != null) {
MermaidDiagramTool diagramTool = SpringContextUtil.getBean(MermaidDiagramTool.class);
for (ImageCollectionPlan.DiagramTask task : plan.getDiagramTasks()) {
futures.add(CompletableFuture.supplyAsync(() ->
diagramTool.generateMermaidDiagram(task.mermaidCode(), task.description())));
}
}
// 并发执行Logo生成
if (plan.getLogoTasks() != null) {
LogoGeneratorTool logoTool = SpringContextUtil.getBean(LogoGeneratorTool.class);
for (ImageCollectionPlan.LogoTask task : plan.getLogoTasks()) {
futures.add(CompletableFuture.supplyAsync(() ->
logoTool.generateLogos(task.description())));
}
}
// 等待所有任务完成并收集结果
CompletableFuture<Void> allTasks = CompletableFuture.allOf(
futures.toArray(new CompletableFuture[0]));
allTasks.join();
// 收集所有结果
for (CompletableFuture<List<ImageResource>> future : futures) {
List<ImageResource> images = future.get();
if (images != null) {
collectedImages.addAll(images);
}
}
log.info("并发图片收集完成,共收集到 {} 张图片", collectedImages.size());
} catch (Exception e) {
log.error("图片收集失败: {}", e.getMessage(), e);
}
// 更新状态
context.setCurrentStep("图片收集");
context.setImageList(collectedImages);
return WorkflowContext.saveContext(context);
});
}
}
2)LangGraph4j并发实现
利用LangGraph4j的Parallel Branch特性,将每个图片收集工具都定义为一个工作节点,这些工作节点可以并发执行。
工作流程:图片规划=>并发收集=>图片聚合
/**
* 图片收集计划
*/
private ImageCollectionPlan imageCollectionPlan;
/**
* 并发图片收集的中间结果字段
*/
private List<ImageResource> contentImages;
private List<ImageResource> illustrations;
private List<ImageResource> diagrams;
private List<ImageResource> logos;
节点:
1)图片计划节点:分析用户需求,生成图片收集计划,为并发执行做准备
@Slf4j
public class ImagePlanNode {
public static AsyncNodeAction<MessagesState<String>> create() {
return node_async(state -> {
WorkflowContext context = WorkflowContext.getContext(state);
String originalPrompt = context.getOriginalPrompt();
try {
// 获取图片收集计划服务
ImageCollectionPlanService planService = SpringContextUtil.getBean(ImageCollectionPlanService.class);
ImageCollectionPlan plan = planService.planImageCollection(originalPrompt);
log.info("生成图片收集计划,准备启动并发分支");
// 将计划存储到上下文中
context.setImageCollectionPlan(plan);
context.setCurrentStep("图片计划");
} catch (Exception e) {
log.error("图片计划生成失败: {}", e.getMessage(), e);
}
return WorkflowContext.saveContext(context);
});
}
}
2)内容图片收集节点:并发执行内容图片搜索任务
@Slf4j
public class ContentImageCollectorNode {
public static AsyncNodeAction<MessagesState<String>> create() {
return node_async(state -> {
WorkflowContext context = WorkflowContext.getContext(state);
List<ImageResource> contentImages = new ArrayList<>();
try {
ImageCollectionPlan plan = context.getImageCollectionPlan();
if (plan != null && plan.getContentImageTasks() != null) {
ImageSearchTool imageSearchTool = SpringContextUtil.getBean(ImageSearchTool.class);
log.info("开始并发收集内容图片,任务数: {}", plan.getContentImageTasks().size());
for (ImageCollectionPlan.ImageSearchTask task : plan.getContentImageTasks()) {
List<ImageResource> images = imageSearchTool.searchContentImages(task.query());
if (images != null) {
contentImages.addAll(images);
}
}
log.info("内容图片收集完成,共收集到 {} 张图片", contentImages.size());
}
} catch (Exception e) {
log.error("内容图片收集失败: {}", e.getMessage(), e);
}
// 将收集到的图片存储到上下文的中间字段中
context.setContentImages(contentImages);
context.setCurrentStep("内容图片收集");
return WorkflowContext.saveContext(context);
});
}
}
3)插画节点
@Slf4j
public class IllustrationCollectorNode {
public static AsyncNodeAction<MessagesState<String>> create() {
return node_async(state -> {
WorkflowContext context = WorkflowContext.getContext(state);
List<ImageResource> illustrations = new ArrayList<>();
try {
ImageCollectionPlan plan = context.getImageCollectionPlan();
if (plan != null && plan.getIllustrationTasks() != null) {
UndrawIllustrationTool illustrationTool = SpringContextUtil.getBean(UndrawIllustrationTool.class);
log.info("开始并发收集插画图片,任务数: {}", plan.getIllustrationTasks().size());
for (ImageCollectionPlan.IllustrationTask task : plan.getIllustrationTasks()) {
List<ImageResource> images = illustrationTool.searchIllustrations(task.query());
if (images != null) {
illustrations.addAll(images);
}
}
log.info("插画图片收集完成,共收集到 {} 张图片", illustrations.size());
}
} catch (Exception e) {
log.error("插画图片收集失败: {}", e.getMessage(), e);
}
context.setIllustrations(illustrations);
context.setCurrentStep("插画图片收集");
return WorkflowContext.saveContext(context);
});
}
}
4.架构图绘制节点
@Slf4j
public class DiagramCollectorNode {
public static AsyncNodeAction<MessagesState<String>> create() {
return node_async(state -> {
WorkflowContext context = WorkflowContext.getContext(state);
List<ImageResource> diagrams = new ArrayList<>();
try {
ImageCollectionPlan plan = context.getImageCollectionPlan();
if (plan != null && plan.getDiagramTasks() != null) {
MermaidDiagramTool diagramTool = SpringContextUtil.getBean(MermaidDiagramTool.class);
log.info("开始并发生成架构图,任务数: {}", plan.getDiagramTasks().size());
for (ImageCollectionPlan.DiagramTask task : plan.getDiagramTasks()) {
List<ImageResource> images = diagramTool.generateMermaidDiagram(
task.mermaidCode(), task.description());
if (images != null) {
diagrams.addAll(images);
}
}
log.info("架构图生成完成,共生成 {} 张图片", diagrams.size());
}
} catch (Exception e) {
log.error("架构图生成失败: {}", e.getMessage(), e);
}
context.setDiagrams(diagrams);
context.setCurrentStep("架构图生成");
return WorkflowContext.saveContext(context);
});
}
}
5.Logo生成节点
@Slf4j
public class LogoCollectorNode {
public static AsyncNodeAction<MessagesState<String>> create() {
return node_async(state -> {
WorkflowContext context = WorkflowContext.getContext(state);
List<ImageResource> logos = new ArrayList<>();
try {
ImageCollectionPlan plan = context.getImageCollectionPlan();
if (plan != null && plan.getLogoTasks() != null) {
LogoGeneratorTool logoTool = SpringContextUtil.getBean(LogoGeneratorTool.class);
log.info("开始并发生成Logo,任务数: {}", plan.getLogoTasks().size());
for (ImageCollectionPlan.LogoTask task : plan.getLogoTasks()) {
List<ImageResource> images = logoTool.generateLogos(task.description());
if (images != null) {
logos.addAll(images);
}
}
log.info("Logo生成完成,共生成 {} 张图片", logos.size());
}
} catch (Exception e) {
log.error("Logo生成失败: {}", e.getMessage(), e);
}
context.setLogos(logos);
context.setCurrentStep("Logo生成");
return WorkflowContext.saveContext(context);
});
}
}
6)图片聚合
@Slf4j
public class ImageAggregatorNode {
public static AsyncNodeAction<MessagesState<String>> create() {
return node_async(state -> {
WorkflowContext context = WorkflowContext.getContext(state);
List<ImageResource> allImages = new ArrayList<>();
log.info("开始聚合并发收集的图片");
// 从各个中间字段聚合图片
if (context.getContentImages() != null) {
allImages.addAll(context.getContentImages());
}
if (context.getIllustrations() != null) {
allImages.addAll(context.getIllustrations());
}
if (context.getDiagrams() != null) {
allImages.addAll(context.getDiagrams());
}
if (context.getLogos() != null) {
allImages.addAll(context.getLogos());
}
log.info("图片聚合完成,总共 {} 张图片", allImages.size());
// 更新最终的图片列表
context.setImageList(allImages);
context.setCurrentStep("图片聚合");
return WorkflowContext.saveContext(context);
});
}
}
编写新的工作流CodeGenConcurrentWorkflow
@Slf4j
public class CodeGenConcurrentWorkflow {
/**
* 创建并发工作流
*/
public CompiledGraph<MessagesState<String>> createWorkflow() {
try {
return new MessagesStateGraph<String>()
// 添加节点
.addNode("image_plan", ImagePlanNode.create())
.addNode("prompt_enhancer", PromptEnhancerNode.create())
.addNode("router", RouterNode.create())
.addNode("code_generator", CodeGeneratorNode.create())
.addNode("code_quality_check", CodeQualityCheckNode.create())
.addNode("project_builder", ProjectBuilderNode.create())
// 添加并发图片收集节点
.addNode("content_image_collector", ContentImageCollectorNode.create())
.addNode("illustration_collector", IllustrationCollectorNode.create())
.addNode("diagram_collector", DiagramCollectorNode.create())
.addNode("logo_collector", LogoCollectorNode.create())
.addNode("image_aggregator", ImageAggregatorNode.create())
// 添加边
.addEdge(START, "image_plan")
// 并发分支:从计划节点分发到各个收集节点
.addEdge("image_plan", "content_image_collector")
.addEdge("image_plan", "illustration_collector")
.addEdge("image_plan", "diagram_collector")
.addEdge("image_plan", "logo_collector")
// 汇聚:所有收集节点都汇聚到聚合器
.addEdge("content_image_collector", "image_aggregator")
.addEdge("illustration_collector", "image_aggregator")
.addEdge("diagram_collector", "image_aggregator")
.addEdge("logo_collector", "image_aggregator")
// 继续串行流程
.addEdge("image_aggregator", "prompt_enhancer")
.addEdge("prompt_enhancer", "router")
.addEdge("router", "code_generator")
.addEdge("code_generator", "code_quality_check")
// 质检条件边
.addConditionalEdges("code_quality_check",
edge_async(this::routeAfterQualityCheck),
Map.of(
"build", "project_builder",
"skip_build", END,
"fail", "code_generator"
))
.addEdge("project_builder", END)
.compile();
} catch (GraphStateException e) {
throw new BusinessException(ErrorCode.OPERATION_ERROR, "并发工作流创建失败");
}
}
/**
* 执行并发工作流
*/
public WorkflowContext executeWorkflow(String originalPrompt) {
CompiledGraph<MessagesState<String>> workflow = createWorkflow();
WorkflowContext initialContext = WorkflowContext.builder()
.originalPrompt(originalPrompt)
.currentStep("初始化")
.build();
GraphRepresentation graph = workflow.getGraph(GraphRepresentation.Type.MERMAID);
log.info("并发工作流图:\n{}", graph.content());
log.info("开始执行并发代码生成工作流");
WorkflowContext finalContext = null;
int stepCounter = 1;
for (NodeOutput<MessagesState<String>> step : workflow.stream(
Map.of(WorkflowContext.WORKFLOW_CONTEXT_KEY, initialContext)
)) {
log.info("--- 第 {} 步完成 ---", stepCounter);
WorkflowContext currentContext = WorkflowContext.getContext(step.state());
if (currentContext != null) {
finalContext = currentContext;
log.info("当前步骤上下文: {}", currentContext);
}
stepCounter++;
}
log.info("并发代码生成工作流执行完成!");
return finalContext;
}
/**
* 路由函数:根据质检结果决定下一步
*/
private String routeAfterQualityCheck(MessagesState<String> state) {
WorkflowContext context = WorkflowContext.getContext(state);
QualityResult qualityResult = context.getQualityResult();
if (qualityResult == null || !qualityResult.getIsValid()) {
log.error("代码质检失败,需要重新生成代码");
return "fail";
}
log.info("代码质检通过,继续后续流程");
CodeGenTypeEnum generationType = context.getGenerationType();
if (generationType == CodeGenTypeEnum.VUE_PROJECT) {
return "build";
} else {
return "skip_build";
}
}
}
这样的并发需要我们配置线程池:
// 配置并发执行
ExecutorService pool = ExecutorBuilder.create()
.setCorePoolSize(10)
.setMaxPoolSize(20)
.setWorkQueue(new LinkedBlockingQueue<>(100))
.setThreadFactory(ThreadFactoryBuilder.create().setNamePrefix("Parallel-Image-Collect").build())
.build();
RunnableConfig runnableConfig = RunnableConfig.builder()
.addParallelNodeExecutor("image_plan", pool)
.build();
for (NodeOutput<MessagesState<String>> step : workflow.stream(
Map.of(WorkflowContext.WORKFLOW_CONTEXT_KEY, initialContext),
runnableConfig)) {}
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)