汇报人:者亚杰

日期:2026-05-23

本周重点

一、跳过构建-条件边

对于 HTML 和 MULTI_FILE 网站生成类型,网站生成工作节点中已经会自动保存网站文件,不需要进入项目构建节点。

除了在项目构建工作节点中写 if-else 外,还可以使用 LangGraph4j 的条件边特性:

1.工作流新增路由函数和条件边配置
.addEdge("router", "code_generator")
// 使用条件边:根据代码生成类型决定是否需要构建
.addConditionalEdges("code_generator",
        edge_async(this::routeBuildOrSkip),
        Map.of(
                "build", "project_builder",  // 需要构建的情况
                "skip_build", END             // 跳过构建直接结束
        ))
.addEdge("project_builder", END)
2.路由函数决定代码生成后是否需要项目构建

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";
}
3.项目构建工作节点中移除 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. 关注点分离:节点专注业务逻辑,边专注流程控制

二、质量检查 - 循环边

新增代码质检 Agent 工作节点,在生成代码后,检验代码是否有异常、能否正常打包,如果有异常则返回到网站生成 Agent 重新生成。

该节点所需的状态:generatedCodeDir+generationType

该节点返回的状态:qualityResult质检结果

  • isValid是否通过
  • errors错误列表
  • suggestions改进建议
1.方案选择

选择使用:工作节点 + 条件边的组合

原因如下:

1)代码质检本身是一个复杂的工作任务:

  • 需要读取并拼接代码文件
  • 调用 AI 进行代码分析
  • 生成质检结果(错误、建议等)
  • 这是实际的业务逻辑,应该用工作节点

2)根据质检结果的路由决策:

  • 质检通过 → 继续到项目构建
  • 质检失败 → 回到代码生成节点
  • 这是流程控制逻辑,应该用条件边
2.定义数据模型

langgraph4j.model下增加QualityResult

@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;
}

WorkflowContext状态补充字段

/**
 * 质量检查结果
 */
private QualityResult qualityResult;
3.质量检查 AI 服务
1)编写质量检查 AI 提示词 code-quality-check-system-prompt.txt,提示词重点突出检查语法错误,并且强调结构化输出的 JSON 格式。
你是一个专业的代码质量检查专家。你的任务是分析用户提供的网站代码,检查语法错误等方面的问题,确保项目可以正常运行和打包。

## 检查重点

### 1. 语法和结构错误
- HTML 标签是否正确闭合
- CSS 语法是否正确
- JavaScript 语法错误
- 文件引用路径是否正确
- 缺失的依赖或资源

### 2. 代码质量
- 代码结构是否合理
- 命名规范是否一致
- 代码重复性检查

### 3. 功能完整性
- 页面功能是否完整
- 交互逻辑是否正确
- 响应式设计检查

## 输出格式

请严格按照以下 JSON 格式返回检查结果:
json
{
  "isValid": true/false,
  "errors": [
    "具体的错误描述1",
    "具体的错误描述2"
  ],
  "suggestions": [
    "改进建议1",
    "改进建议2"
  ]
}

 ## 判断标准

- isValid = true: 代码无严重语法错误,能够正常运行和打包
- isValid = false: 存在语法错误、结构问题或其他会导致无法正常运行的问题
- errors: 必须修复的问题,如语法错误、缺失文件等
- suggestions: 关于如何修复错误和改进代码的建议

请仔细分析代码,提供专业的质量检查结果。
2)开发 AI 服务,利用结构化输出
public interface CodeQualityCheckService {
    /**
     * 检查代码质量
     * AI 会分析代码并返回质量检查结果
     */
    @SystemMessage(fromResource = "prompt/code-quality-check-system-prompt.txt")
    QualityResult checkCodeQuality(@UserMessage String codeContent);
}
3)开发创建 AI 服务的工厂
@Slf4j
@Configuration
public class CodeQualityCheckServiceFactory {
    @Resource
    private ChatModel chatModel;
    /**
     * 创建代码质量检查 AI 服务
     */
    @Bean
    public CodeQualityCheckService createCodeQualityCheckService() {
        return AiServices.builder(CodeQualityCheckService.class)
                .chatModel(chatModel)
                .build();
    }
}
4.开发质量检查工作节点
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);
}
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);
        });
    }
}
5.修改代码生成节点

代码生成节点要判断状态中有没有错误信息,如果有的话,需要根据错误信息构造提示词,引导 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();
}
2)节点方法中调用构造用户消息
// 构造用户消息(包含原始提示词和可能的错误修复信息)
String userMessage = buildUserMessage(context);
6.修改工作流
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);
}

思考

  1. 通过将原有的代码逻辑判断替换为LangGraph4j条件边配置,实现了业务逻辑与流程控制的解耦。不仅简化了节点冗余的if-else判断,让工作流链路可视化、逻辑更清晰,同时规避了无效Bean加载,优化了整体执行性能,也让代码结构更规范、可维护性更强。
  2. 完成AI代码质检节点与循环路由能力的落地,搭建了完整的代码生成-质检-修复闭环流程。通过自定义质检数据模型、精细化AI检测提示词、文件批量解析工具方法,实现了代码语法、结构、规范性的自动化校验,同时利用条件边实现质检失败自动重试生成,大幅提升了代码生成的准确率与可用性。
  3. 本次开发让我深刻意识到,工作流开发中应遵循单一职责原则,节点聚焦核心业务,条件边专注流程路由。同时,自动化质检+迭代修复的模式,有效解决了代码生成出错、打包失败的问题,为后续项目稳定运行提供了保障。后续将持续优化AI质检精准度,精简代码逻辑,进一步提升工作流的稳定性和运行效率。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐