银行存储过程迁移智能体平台:后端执行流程与深挖讲解
1. 后端项目定位
这个项目的后端不是一个简单的“调用大模型生成代码”接口,而是一个面向企业 SQL/存储过程迁移场景的任务编排系统。
它的核心目标是:让用户把一个业务模块涉及的表结构、SQL 或存储过程提交进来,后端自动完成 SQL 解析、模块上下文组装、RAG 知识检索、迁移规则匹配、结构化迁移计划生成、Java/MyBatis 分层代码生成、AI 代码复审、动态编译校验、自动修复、日志追踪和迁移报告生成。
一句话讲法:
我做的是一个银行存储过程迁移 ReAct Agent 后端平台。它把迁移过程拆成 SQL 解析、RAG 检索、规则匹配、迁移计划生成、模型主导代码生成、AI 代码复审、Java Compiler 校验、修复、报告、完成验收和人工复核几个迁移阶段级工具。模型通过原生 Function Calling 自主选择工具,并在代码生成阶段按后端契约生成完整 Java/MyBatis 文件集;后端用工具白名单、taskId 校验、前置条件、契约门禁、编译和终态验收守住安全边界,最终输出可追踪、可复核、可编译的代码。
后端最重要的设计思想:
- 模块上下文优先:单条 SQL 信息不足,必须结合模块表结构才能生成高质量 Java 对象和 Mapper XML。
- 模型主导代码生成,后端负责契约和门禁:模型根据迁移计划、模块上下文、RAG 规范和工具观察生成完整源码文件集;后端负责校验 SQL 覆盖、命名质量、危险 DML、参数绑定、编译、复审和终态验收。
- Agent 决策 + 后端 Guardrails:模型决定下一步工具,后端只做白名单、参数、前置条件和终态验收。
- RAG 是企业知识增强层:Chroma 中存放代码模板、迁移规范、正反例和风险规则,提升生成质量。
- 双重质量门禁:AI 复审负责检查 SQL 语义遗漏、敏感字段暴露和危险 DML,Java Compiler API 负责检查语法、类型和引用关系。
2. 后端整体架构
|
|
后端按职责可以拆成 8 层:
| 层级 | 解决的问题 | 核心类 |
|---|---|---|
| API 层 | 暴露模块、任务、知识库、规则接口 | BusinessModuleController、MigrationTaskController、KnowledgeBaseController、MappingRuleController |
| 应用服务层 | 编排业务流程,控制任务生命周期 | BusinessModuleService、MigrationTaskService |
| Agent 调度层 | 原生 Function Calling 工具循环,顺序执行 tool_calls 并校验前置条件 | ReActAgentOrchestrator、ToolRegistry |
| Tool 层 | 把每一步能力封装成白名单工具 | SqlParseAgentTool、RagSearchAgentTool、CodeGenerateAgentTool、CodeReviewAgentTool 等 |
| SQL/规则层 | 解析 SQL 特征,匹配迁移规则 | SqlStatementSplitter、SqlParserService、MappingRuleService |
| RAG/模型层 | 接入 Chroma、Embedding、大模型 | ChromaRagService、ChromaKnowledgeService、DeepSeekModelClient |
| 代码与校验层 | 模型主导生成代码、契约门禁、AI 复审、保存产物、动态编译 | ModelCodeGenerationService、CodeGenerationService、CodeReviewService、ArtifactService、JavaCompilerService |
| 持久化层 | 保存任务、模块、表结构、日志、规则 | MigrationTaskRepository、ModuleTableRepository、AgentLogRepository |
3. 后端主执行流程
3.1 模块级迁移推荐链路
|
|
3.2 一次任务从创建到结束的后端调用链
|
|
这个链路的关键点是:HTTP 请求只负责创建任务和启动任务,真正耗时的 Agent 流程走异步线程池。任务状态是审计投影,不再驱动固定流程;下一步由模型基于工具描述和 Observation 决定。日志、产物和报告都落库或落盘,调用方通过任务详情接口查询执行进度。
4. Controller 层类职责
4.1 BusinessModuleController
职责:业务模块相关 API 入口。
它解决的是“单 SQL 无上下文”的问题。用户先创建一个业务模块,再维护这个模块涉及的表结构,最后在模块内创建 SQL 迁移任务。
核心接口:
| 接口 | 后端动作 | 深挖点 |
|---|---|---|
POST /api/modules |
创建业务模块 | 生成模块 ID、模块编码、默认基础包名 |
GET /api/modules |
查询模块列表 | 让业务按模块沉淀迁移上下文 |
GET /api/modules/{moduleId} |
查询模块详情 | 校验模块是否存在 |
POST /api/modules/{moduleId}/tables |
新增表结构 | 调用 DdlParseService 解析 DDL |
PUT /api/modules/{moduleId}/tables/{tableId} |
修改表结构 | 支持用户修正自动解析结果 |
DELETE /api/modules/{moduleId}/tables/{tableId} |
删除表结构 | 同步更新时间 |
POST /api/modules/{moduleId}/migrations |
创建模块内迁移任务 | 组装 ModuleContext,这是核心入口 |
可被追问:
-
为什么不直接让用户传 SQL?
因为单条 SQL 缺少业务和表结构上下文。比如SELECT * FROM user只能看出查了 user 表,但不知道 user 表有哪些字段、哪些字段是密码或逻辑删除字段、主键是什么、Java 包名应该是什么、返回对象应该暴露哪些属性。如果只让模型基于 SQL 猜,很容易生成字段不全、字段类型错误、VO 暴露敏感字段或者方法名没有业务语义。模块级迁移先维护表结构,再提交 SQL,本质上是先建立领域上下文,再做代码生成。 -
表结构上下文如何提升生成质量?
表结构提供了生成代码所需的确定性信息,包括表名、字段名、字段注释、数据库类型、Java 类型、主键、是否可空、默认值和索引约束。代码生成器可以基于这些信息生成 Entity、QueryDTO、CommandDTO、VO、Mapper XML,并且能把SELECT *展开成明确字段列表,还能过滤 password、token、isDelete、createTime、updateTime 等不适合直接返回的字段。也就是说,表结构让生成过程从“猜代码”变成“按真实 schema 生成代码”。 -
用户 DDL 不规范怎么办?
当前设计是先用DdlParseService做规则解析,把能识别的表名、字段、注释、类型提取成TableSchema,同时保留用户原始rawDdl方便追溯。如果解析结果不准确,前端允许用户修改表结构后再保存,保存时可以标记为人工修正。后续也可以扩展为“规则解析失败后调用模型补充解析”,并通过parseSource=RULE/MODEL/MANUAL区分来源。这样不会因为 DDL 不完全标准就阻塞整个迁移流程。 -
多条 SQL 为什么要在后端二次拆分?
因为前端传来的 SQL 类型和 SQL 单元不一定可靠。用户可能在一个输入框里粘贴多条 SQL,而且 SELECT、UPDATE、DELETE 可能混在一起,甚至没有分号,只靠前端下拉框会把整段脚本误判成一种类型。后端SqlStatementSplitter会根据 SQL 正文、行首关键字、分号和-- 功能注释重新拆分,再为每个SqlUnit独立推断类型。这样可以避免多 SQL 场景下漏生成 UPDATE/DELETE 方法。
4.2 MigrationTaskController
职责:迁移任务相关 API 入口。
它不负责具体迁移逻辑,只把任务操作转给 MigrationTaskService。
核心接口:
| 接口 | 后端动作 | 深挖点 |
|---|---|---|
POST /api/migration-tasks |
创建独立迁移任务 | 兼容单 SQL 快速模式 |
GET /api/migration-tasks |
查询任务列表 | 支持状态和关键词筛选 |
GET /api/migration-tasks/{taskId} |
查询任务详情 | 返回完整任务聚合 |
POST /api/migration-tasks/{taskId}/start |
启动 Agent | 异步执行,避免阻塞 HTTP |
POST /api/migration-tasks/{taskId}/retry |
重试任务 | 清空修复次数,复用源 SQL |
POST /api/migration-tasks/{taskId}/cancel |
取消任务 | 软中断,避免强杀线程 |
POST /api/migration-tasks/{taskId}/validate |
重新编译校验 | 用于人工改完产物后复验 |
GET /api/migration-tasks/{taskId}/artifacts |
查询产物列表 | 返回生成文件元数据和内容 |
GET /api/migration-tasks/{taskId}/agent-logs |
查询 Agent 日志 | 展示 Thought/Action/Observation |
GET /api/migration-tasks/{taskId}/report |
查询迁移报告 | 返回 Markdown 报告 |
可被追问:
-
为什么启动任务要异步?
SQL 解析、RAG 检索、大模型调用、代码生成、Java 编译和自动修复都可能比较耗时。如果在 HTTP 请求线程里同步执行,前端容易超时,用户体验也不好。现在的设计是接口只负责把任务状态改为RUNNING并提交到线程池,前端通过任务详情和 Agent 日志轮询进度。这样既不会阻塞 HTTP 线程,也方便以后做任务队列、重试和并发控制。 -
怎么避免重复启动同一个任务?
MigrationTaskService里维护了RUNNING_STATUSES集合,包含RUNNING、SQL_PARSING、RAG_RETRIEVING、CODE_GENERATING、COMPILING、FIXING等运行中状态。启动任务时先调用markRunning检查当前状态,如果任务已经处于运行态,就直接抛错,避免同一个任务被多个线程同时执行,导致产物文件、编译结果和报告互相覆盖。 -
取消任务为什么用软中断而不是强制停止线程?
因为任务执行过程中可能正在写文件、保存数据库记录、调用 Chroma、执行编译或生成报告。如果强制停止线程,容易留下半写入产物、状态不一致或者临时目录未清理。当前做法是设置cancelRequested=true并把状态改成CANCELLED,Agent 调度器在每轮工具执行前后调用isStopped检查,等工具执行到安全边界再退出。这种方式更稳,也更符合企业任务处理习惯。 -
重新校验和重新生成的区别是什么?
重新校验只针对当前已经生成的 Java 产物重新执行 Java Compiler API,不重新调用模型,也不重新生成代码,适合人工修改产物后做复验。重新生成则会重新跑 Agent 链路,可能重新执行 SQL 解析、RAG 检索、规则匹配和代码生成,会覆盖或刷新当前产物。简单说,validate是“验现有代码”,retry是“重新跑一遍迁移流程”。
4.3 KnowledgeBaseController
职责:Chroma 知识库维护入口。
它让项目不再依赖手动执行 Python 脚本灌库,而是通过后端 API 维护企业迁移知识。
核心接口:
| 接口 | 后端动作 | 深挖点 |
|---|---|---|
GET /api/knowledge-documents |
查询知识片段 | 从 Chroma collection 拉取文档 |
POST /api/knowledge-documents |
新增知识片段 | 生成 embedding 后 upsert |
PUT /api/knowledge-documents/{id} |
更新知识片段 | 覆盖文档、metadata、embedding |
DELETE /api/knowledge-documents/{id} |
删除知识片段 | 按 ID 删除 Chroma 文档 |
POST /api/knowledge-documents/search |
语义检索 | 使用 query_embeddings 查询 |
POST /api/knowledge-documents/seed-defaults |
写入默认企业知识 | 初始化 SELECT/UPDATE/INSERT/DELETE 等规则 |
POST /api/knowledge-documents/import |
上传 txt/md 文件入库 | 调用 KnowledgeDocumentChunker 切分 |
可被追问:
-
为什么 Chroma 查询要传
query_embeddings?
当前项目选择由后端自己调用 DashScope Embedding 生成查询向量,再把向量传给 Chroma。这样不依赖 Chroma 服务端内置 embedding function,也能保证和写入知识库时使用的模型一致。Chroma 的 query 接口在这种模式下需要query_embeddings,如果只传文本而服务端又没有配置 embedding function,就会出现missing field query_embeddings这类错误。 -
为什么写入和查询必须使用同一个 embedding 模型?
向量检索要求写入向量和查询向量处于同一个语义空间,并且维度一致。比如一个 collection 之前用 384 维模型写入,后来查询时用 DashScopetext-embedding-v4生成 1024 维向量,Chroma 会直接报维度不一致。即使维度碰巧一样,不同模型的向量空间也不一定可比,召回质量会变差。所以写入脚本、后端配置和 Chroma collection 必须绑定同一套 embedding 模型。 -
文档切分为什么不能简单按固定长度切?
RAG 的召回质量很依赖 chunk 质量。固定长度切分可能把一个完整规则切断,也可能把代码块、SQL 示例、标题和解释拆散,导致检索时拿到的片段语义不完整。项目里的KnowledgeDocumentChunker会识别 Markdown 标题、段落和代码块,保留标题路径,超长内容再用滑动窗口和 overlap 切分。这样每个 chunk 既不会太长稀释语义,也不会太碎丢失上下文。
4.4 MappingRuleController
职责:SQL 特征到 Java 实现模式的规则维护入口。
它支撑 rule_match 工具,让存储过程特征可以被明确映射为 Java 侧实现策略。
典型规则:
CURSOR-> Mapper 查询 List 后 Service 层循环。TRANSACTION-> Spring@Transactional。EXCEPTION->try-catch+ 业务异常。DYNAMIC_SQL-> 高风险,人工复核。
5. 应用服务层深挖
5.1 BusinessModuleService
这个类是 ToB 场景的核心。它把项目从“单 SQL 翻译器”升级为“模块级迁移平台”。
核心方法:
| 方法 | 作用 | 关键逻辑 |
|---|---|---|
create |
创建业务模块 | 默认补齐模块编码、基础包名、源库、目标技术栈 |
saveTable |
保存模块表结构 | 没有结构化 tableSchema 时调用 DdlParseService.parse(rawDdl) |
migrationTasks |
查询模块历史任务 | 根据模块名和基础包名检索任务 |
createMigrationTask |
创建模块级迁移任务 | 查询表结构、拆分 SQL、组装 ModuleContext、委托任务服务保存 |
buildSqlUnits |
构建 SQL 单元列表 | 支持显式 sqlUnits,也支持从整段 SQL 自动拆分 |
resolveSqlType |
推断 SQL 类型 | SQL 正文优先,调用方传入类型只作为辅助 |
这里最重要的设计:
-
必须先有表结构再创建模块迁移任务
如果模块没有表结构,直接抛出“当前模块还没有表结构”。这是为了防止生成器凭空猜字段。 -
多 SQL 后端二次拆分
即使调用方传了sqlUnits,如果某个单元里实际包含多条 SQL,后端还会继续拆开。这样可以避免SELECT; UPDATE被整体误判为 SELECT。 -
功能注释会保留到 sourceSql
例如:1 2-- 功能:根据用户名查询用户 SELECT * FROM user WHERE userName = '用户名'这段注释会进入任务源 SQL,后续 SQL 解析和 RAG 检索都能读到业务意图。
-
SQL 类型以正文为准
如果外部传SELECT,但 SQL 正文是UPDATE user SET ...,最终按 UPDATE 处理。这个是多 SQL 混合任务不漏生成写方法的关键。
面试可以这样讲:
这个类解决的是上下文建模问题。我没有让用户直接丢一段 SQL 给模型,而是先创建业务模块、维护表结构,再在模块里创建迁移任务。这样生成代码时有真实字段、主键、表注释、模块包名和 SQL 功能说明,代码质量会比单 SQL 猜测稳定很多。
5.2 MigrationTaskService
这个类负责任务生命周期管理,是 Controller 和 Agent 之间的桥。
核心方法:
| 方法 | 作用 | 深挖点 |
|---|---|---|
create |
创建任务聚合 | 初始状态为 CREATED |
start |
启动迁移 | 标记 RUNNING 后异步执行 Agent |
retry |
重试迁移 | 清空 retryCount,复用上下文 |
cancel |
取消任务 | 设置 cancelRequested 和 CANCELLED |
delete |
软删除任务 | 标记 DELETED,不物理删文件 |
validate |
重新校验产物 | 不调用模型,只重新编译当前 Java 产物 |
runAgent |
后台执行 Agent | 捕获异常并转 FAILED_NEED_MANUAL_REVIEW |
markRunning |
标记运行态 | 拦截重复启动和已删除任务 |
任务运行态集合:
|
|
设计重点:
- 防重复启动:如果任务处于运行中状态,再次 start 会抛错,避免多个线程同时写产物。
- 异步执行:HTTP 线程只提交任务,Agent 在线程池中跑。
- 异常可见:后台异常不会被吞掉,会写入任务风险列表并转人工复核。
- 软取消:取消任务只是标记状态,Agent 在每轮工具执行前后检查
isStopped,避免粗暴中断正在写文件或编译的线程。
可被追问:
-
如果 Agent 执行到一半服务重启怎么办?
当前 MySQL 会保存任务状态、模块上下文、解析结果、RAG 结果、命中规则、产物快照、编译结果和报告,Redis 保存运行态上下文和 Agent 记忆。如果服务中途重启,至少不会丢失已经落库的执行快照。严格生产环境可以再加一个任务恢复扫描器:应用启动时扫描长时间停留在RUNNING/COMPILING/FIXING等中间状态的任务,根据最后日志判断是转人工复核,还是从某个工具步骤继续执行。这个项目当前更偏面试和演示闭环,所以先采用“异常可见 + 人工复核”策略。 -
为什么删除是软删除?
因为迁移任务、Agent 日志、生成产物和报告都有审计价值。银行系统迁移不是普通草稿删除,后续可能需要追溯某段代码为什么生成、哪一步失败、谁做了人工复核。如果直接物理删除,会影响排查和复盘。当前先把任务标记为DELETED,不立即删除文件和历史记录;如果后续要做物理清理,也应该加审计日志和清理策略,比如只清理超过保留期的已删除任务。
5.3 DdlParseService
职责:把用户粘贴的建表语句解析成结构化 TableSchema。
输入可以是类似:
|
|
输出结构:
- 表名。
- 表注释。
- 字段列表。
- 字段数据库类型。
- Java 字段名。
- Java 类型。
- 是否主键。
- 是否可空。
- 字段注释。
深挖点:
-
为什么 DDL 解析要独立成服务?
因为后续代码生成、VO 字段过滤、Mapper XML 字段展开都依赖表结构。单独封装后可以替换成更强的解析器或模型解析。 -
用户输入不规范怎么办?
当前先用规则解析,保存parseSource=RULE。如果后续接模型解析或人工修正,可以用parseSource=MODEL/MANUAL区分来源。
5.4 ModelCodeGenerationService 与 CodeGenerationService
这是代码质量最核心的链路。新的主路径是 ModelCodeGenerationService,旧的 CodeGenerationService 保留为模型不可用时的兜底或测试夹具。
主路径的分工是:
- 模型负责源码设计和生成:按迁移计划、模块上下文、SQL 单元、表结构、RAG 规范生成完整文件集。
- 后端负责契约门禁:校验文件路径、package、类名、SQL 覆盖、机械命名、高风险 SQL、动态排序和空文件。
- 旧模板生成器负责兜底:当模型调用异常时允许生成保守代码,并在风险中标记“已降级到模板生成器”。
模型主导生成的核心流程:
|
|
关键设计点:
-
结构化文件集返回
模型第一步必须返回
GeneratedCodeBlueprintJSON,包含summary、files、risks。每个文件只包含fileName、type、purpose、relatedSqlIds,不包含源码正文。 -
单文件源码生成
模型第二步按蓝图一次只生成一个文件的原始 Java/XML 正文,不返回 JSON,不返回 Markdown。这样避免 50 条 SQL 对应的大量源码被塞进一个 JSON
content字符串后截断。 -
SQL 覆盖门禁
每个
SqlUnit.sqlId必须被生成文件的relatedSqlIds覆盖;如果不能自动生成,必须在risks中明确写出该 SQL 需要人工复核。这样能避免多 SQL 场景只生成第一张表或第一条查询。 -
命名质量门禁
后端会拒绝
TestQueryBankUser2VO、count1AsTotal、dateFinishTimeAsStatDate这类机械命名。模型必须按业务语义设计 DTO、VO、Mapper 和 Service,而不是把 SQL 表达式硬转成字段名。 -
安全门禁
无 WHERE 的 UPDATE/DELETE、无法白名单校验的动态排序、危险
${}、宽泛 DML 必须声明人工复核风险,不能生成可直接执行的方法。 -
类型和结构质量
金额字段应使用
BigDecimal,时间字段使用LocalDateTime/LocalDate,ID 使用Long;返回对象按业务场景拆分,避免一个大而全的TestVO承载所有 SQL 字段。 -
兜底生成器边界
CodeGenerationService仍会保留 SQL 类型推断、SELECT 字段过滤、固定条件处理等保守能力,但它不再是正常代码质量的主路径。
可被追问:
-
为什么现在改成模型主导生成?
因为模块级银行场景包含多表、多 SQL、联表、聚合、分页、动态排序和风险控制,模板生成器很难靠堆规则持续提升命名、对象边界和业务语义。模型更适合根据上下文设计文件集,后端则用契约门禁约束它不能乱来。 -
如何处理
SELECT *?
SELECT *不会原样照搬到 VO。生成器会先根据 SQL 涉及的表找到模块里的TableSchema,把*展开成明确字段列表,再过滤明显不适合返回的字段,比如 password、token、secret、salt、isDelete、createTime、updateTime、version 等。这样生成的 VO 更接近接口返回对象,而不是把数据库整行直接暴露出去。固定条件例如isDelete = 0会保留在 XML 里,不会变成 QueryDTO 字段。 -
如何避免生成多余代码?
第一层是在 SQL 拆分阶段把多条 SQL 拆成多个SqlUnit;第二层是迁移计划要求每条 SQL 有明确用途和风险;第三层是模型生成文件必须声明relatedSqlIds;第四层是后端契约校验,发现漏覆盖、机械命名或高风险 SQL 未声明复核就拒绝本次代码生成。
5.5 MigrationPlanService
职责:在代码生成前调用大模型生成结构化迁移计划。
这是代码生成前的语义规划位置。它让模型基于源 SQL、模块表结构、SQL 解析结果、RAG 企业规范和映射规则,输出一个可被后端和代码生成模型共同消费的 MigrationPlan。
迁移计划通常包含:
- 每条 SQL 对应的业务意图。
- 操作类型,例如 QUERY、UPDATE、INSERT、DELETE。
- 推荐方法名。
- 输入对象和返回对象建议。
- 需要保留的 WHERE、ORDER BY、LIMIT、逻辑删除条件。
- 是否需要事务、批处理、分页、人工复核。
- 风险说明。
为什么要加这一层:
RAG 和规则本身不会自动改变代码生成结果,必须有一个“结构化计划”把企业规范、SQL 意图和源码生成连接起来。模型先理解“这条 SQL 是做什么”,再在代码生成阶段根据契约生成文件集;后端负责验证这个文件集是否可交付。
可被追问:
-
为什么不让代码生成阶段直接吃 RAG 原文?
因为 RAG 返回的是自然语言片段和案例,如果直接塞给生成阶段,模型容易照抄局部示例而忽略当前 SQL 和表结构。MigrationPlanService会先把自然语言知识转成结构化计划,例如 operation、methodName、riskLevel、manualReviewRequired;ModelCodeGenerationService再同时读取计划、表结构、SQL 单元和 RAG 规范生成文件集,最后由后端契约门禁兜底。 -
如果模型计划生成失败怎么办?
服务会走规则兜底,根据 SQL 类型、表结构和解析特征生成保守计划,并把来源标记为 fallback。这样模型不可用时不会让整条链路直接断掉,但会在风险和报告中提示人工复核。
5.6 CodeReviewService
职责:代码生成后的大模型复审。
它位于 generate_code 和 compile_java 之间,解决“代码能编译不代表忠实实现 SQL”的问题。编译器只能发现语法、类型和引用错误,无法判断 UPDATE 是否漏了 isDelete = 0,也无法判断 VO 是否暴露了 password。
复审输入包括:
- 源 SQL。
- 迁移计划 JSON。
- SQL 解析结果。
- RAG 企业规范与案例。
- 命中的映射规则。
- 已生成的 Java / XML 代码产物。
复审输出是结构化 CodeReviewResult:
|
|
重点检查:
- 是否遗漏源 SQL 中的 SELECT、INSERT、UPDATE、DELETE。
- Mapper XML 是否保留 WHERE、ORDER BY、LIMIT 和固定条件。
- DTO 是否暴露了不该由调用方传入的审计字段。
- VO 是否泄露 password、token、secret、isDelete 等字段。
- UPDATE/DELETE 是否存在无 WHERE、宽泛 WHERE 或物理删除风险。
- 是否生成了 SQL 无关的多余 CRUD、Controller 或伪造业务流程。
可被追问:
-
AI 复审和编译校验有什么区别?
AI 复审看业务语义和工程规范,例如 SQL 条件是否丢失、字段是否越权暴露、方法是否多生成;编译器看语法、类型、import 和类引用。两者是互补关系:AI 复审像代码评审,编译器像确定性的语法门禁。 -
AI 复审发现问题后为什么还继续编译和生成报告?
因为复审问题本身也是证据链的一部分。即使复审失败,继续编译能告诉用户“代码语法是否可接手”,最终报告会同时展示复审问题和编译结果。最终状态会综合判断:编译通过但复审要求人工复核时,任务仍进入FAILED_NEED_MANUAL_REVIEW。 -
模型复审失败怎么办?
CodeReviewService会生成RULE_FALLBACK结果,标记manualReviewRequired=true。这样不会因为模型返回非 JSON 或调用异常而误判为通过。
5.7 KnowledgeDocumentChunker
职责:上传文档入 Chroma 前的结构化切分。
它不是简单按固定长度截断,而是做了 Markdown 结构感知:
- 识别一级到六级标题。
- 保护代码块,不在代码块中间硬切。
- 保留标题路径
headingPath。 - 普通段落按目标长度合并。
- 超长块使用滑动窗口切分,保留 overlap。
- 记录原文 offset,方便后续溯源。
核心参数:
|
|
为什么这是面试加分点:
RAG 的效果不只取决于向量库,还取决于文档切分。如果切得太碎,语义不完整;切得太长,相似度会被稀释。这里按标题、段落、代码块做结构化切分,能让每个 chunk 既有完整语义,又适合向量检索。
5.8 ReportService
职责:生成迁移报告。
报告通常包含:
- 任务基本信息。
- 源 SQL 摘要。
- SQL 特征。
- 命中规则。
- RAG 检索结果。
- 生成产物列表。
- 编译结果。
- 风险点。
- 人工复核建议。
它的意义是让一次 Agent 执行从“黑盒输出代码”变成“可交付、可复核的迁移记录”。
5.7 RedisStateService
职责:保存任务运行态信息和 Agent 记忆。
保存内容:
- 任务快照。
- Agent 日志。
- 短期记忆:按
taskId保存当前 Agent 的messages,用于同一次任务的上下文恢复和排障。 - 长期记忆:按模块名、包名和表名保存最近若干次迁移经验摘要,用于后续迁移计划生成和 AI 代码复审。
为什么同时用 MySQL 和 Redis:
- MySQL 保存最终事实数据:任务、报告、产物索引、日志。
- Redis 保存运行态和记忆热缓存:便于长流程任务中快速读取、过期清理和跨任务复用。
短期记忆和长期记忆的区别:
| 类型 | Redis Key 维度 | 生命周期 | 应用场景 |
|---|---|---|---|
| 短期记忆 | bank-migration:agent-memory:{taskId} |
默认 168 小时 | 当前任务多轮 ReAct messages、工具 observation、断点续迁排障 |
| 长期记忆 | bank-migration:long-memory:module:{module} / bank-migration:long-memory:table:{table} |
默认 90 天 | 同模块历史生成策略、复审高风险、人工复核经验 |
短期记忆由 ReActAgentOrchestrator 每轮保存,启动时如果 Redis 中已有同版本 system prompt 的 messages,会恢复为当前任务上下文。长期记忆由 FinishMigrationAgentTool 在任务通过验收后沉淀,MigrationPlanService 和 CodeReviewService 在构造模型提示词时召回。这样 Redis 不只是“缓存日志”,而是真正参与模型计划生成和复审质量提升。
5.8 AgentMemoryService
职责:把迁移任务结果压缩成长期记忆,并把历史记忆格式化为模型可读上下文。
它不会保存完整 SQL 或生成源码,而是保存:
- 模块名、基础包名、涉及表名。
- SQL 类型集合。
- 迁移计划摘要。
- AI 复审高风险问题。
- 编译结果摘要。
- 最终状态和是否需要人工复核。
为什么不把长期记忆直接写进 Chroma:
Chroma 更适合保存企业规范、模板、案例文档;Redis 长期记忆更像“近期热经验”,适合同一模块连续迁移时快速复用。这个项目里 Redis 长期记忆默认有 TTL 和条数上限,避免历史错误无限累积。生产环境可以再把人工确认后的高质量长期记忆同步到 Chroma,形成正式知识库。
6. Agent 调度层深挖
6.1 ReActAgentOrchestrator
这是整个 ReAct 工具循环的控制核心。它不再固定基础工具流,而是把 ToolRegistry 导出的 OpenAI tools JSON Schema 发给模型,让模型基于当前任务上下文和历史 Observation 自主选择下一步工具。
典型路径可能是:
|
|
但这不是硬编码流程。编译失败时,Agent 可以选择 repair_code;风险过高时,Agent 可以选择 request_human_review;报告生成后,Agent 需要显式调用 finish_migration 才能完成任务。
关键字段:
| 字段 | 含义 |
|---|---|
MAX_ROUNDS = 18 |
最大 ReAct 决策轮次,防止工具调用和修复循环失控 |
modelClient |
调用 DeepSeek 或内部模型 |
toolRegistry |
工具白名单,并导出 OpenAI tools JSON Schema |
agentLogRepository |
保存每轮日志 |
redisStateService |
保存模型上下文记忆 |
toolInputReader |
解析工具 JSON 参数并统一处理非法参数 |
执行逻辑:
- 清空当前任务旧日志。
- 构建 system prompt。
- 每轮把当前任务上下文和工具 JSON Schema 发给模型。
- 模型通过原生 Function Calling 返回一个或多个
tool_calls。 - 调度器按顺序校验工具白名单、
taskId和工具前置条件。 - 被允许的工具才会执行;被拒绝的调用会变成 Observation 回填给模型。
- 如果同一批次中某个工具失败,后续
tool_call会补充“未执行”Observation,保证 OpenAI tools 协议完整。 - 写入
AgentLog。 - 把 Observation 回填到模型上下文。
- Agent 通过
finish_migration或request_human_review显式结束任务。
最值得讲的点:
- 模型真正决定下一步工具。后端不替模型改工具,只做白名单、参数和前置条件校验。
- 如果模型调用错工具,会收到拒绝 Observation。例如未注册工具、缺少
taskId、串任务、没有 SQL 解析结果、没有代码产物、编译未通过等。 - 每轮都有日志,可以解释 Agent 为什么失败、卡在哪一步。
- 生成后复审,代码生成后先进入
review_code,让模型检查 SQL 语义、字段暴露和危险 DML,再进入编译。 - 修复后重新复审,
repair_code可能改动源码,所以修复成功后会回到review_code -> compile_java,避免只审查修复前的代码。 - 最大轮次保护,防止编译失败、复审和修复形成死循环。
6.2 原生 Function Calling 工具循环
职责:让模型基于真实工具描述和 JSON Schema 自主选择工具,调度器顺序执行并回填 Observation。
协议形态:
|
|
为什么这样做:
- 工具调用是模型原生结构化输出,不再依赖正则解析
<|call_tool|>文本。 - 每个工具都有真实
name、description和parametersJSON Schema。 - 多个
tool_calls可以在同一轮返回,但后端按顺序执行,不并行。 - 失败、拒绝或终态会阻断本批次后续工具,同时补齐未执行工具的 Observation,保证协议完整。
面试讲法:
我使用 OpenAI tools 风格的原生 Function Calling。模型不是输出伪协议文本,而是返回结构化
tool_calls;后端负责白名单、taskId、参数和前置条件校验,既保留 Agent 决策权,也守住企业迁移的安全边界。
6.3 ToolRegistry
职责:工具白名单注册表。
作用:
- 启动时收集所有
AgentToolBean。 - 按工具名建立 Map。
- 把工具导出为 OpenAI tools JSON Schema。
- Agent 执行时只能通过工具名获取已注册工具。
- 如果模型输出未注册工具,拒绝执行并返回 Observation。
安全意义:
- 模型不能执行系统命令。
- 模型不能访问未暴露能力。
- 所有工具入参、出参都由 Java 后端控制。
7. Tool 层执行细节
| 工具类 | 工具名 | 状态推进 | 作用 |
|---|---|---|---|
SqlParseAgentTool |
sql_parse |
SQL_PARSING -> SQL_PARSED |
调用 SQL 解析,写回表、字段、特征、风险 |
RagSearchAgentTool |
rag_search |
RAG_RETRIEVING |
调用 Chroma 检索迁移知识 |
RuleMatchAgentTool |
rule_match |
RULE_MATCHING |
根据 SQL 特征匹配 Java 映射规则 |
MigrationPlanAgentTool |
generate_plan |
PLAN_GENERATING |
调用模型生成结构化迁移计划,把 RAG/规则转成生成器可消费的计划 |
CodeGenerateAgentTool |
generate_code |
CODE_GENERATING |
调用模型主导代码生成服务,执行契约门禁并保存产物 |
CodeReviewAgentTool |
review_code |
CODE_REVIEWING |
生成后 AI 复审,检查 SQL 语义遗漏、危险 DML、敏感字段暴露和多余代码 |
CompileJavaAgentTool |
compile_java |
COMPILING |
编译 Java 产物,写回结果 |
RepairCodeAgentTool |
repair_code |
FIXING |
根据编译错误修复代码,控制修复次数 |
JunitGenerateAgentTool |
generate_junit |
TEST_GENERATING |
生成 JUnit 骨架 |
GenerateReportAgentTool |
generate_report |
RUNNING |
汇总报告,但不决定最终终态 |
FinishMigrationAgentTool |
finish_migration |
PASSED |
验收报告、编译和 AI 复审结果后完成任务 |
RequestHumanReviewAgentTool |
request_human_review |
FAILED_NEED_MANUAL_REVIEW |
Agent 主动请求人工复核并记录原因 |
为什么要拆成这么多工具:
- 每一步职责单一,失败点清晰。
- 每一步都能记录日志和耗时。
- 每一步都可以独立测试。
- 模型真正决定下一步工具,后端只做白名单、参数和前置条件守门。
- 后续可以替换某个工具实现,比如把规则 SQL 解析换成更强 Parser。
8. SQL 解析层深挖
8.1 SqlStatementSplitter
职责:把用户提交的一整段 SQL 拆成多条结构化语句。
它需要处理:
- 多条 SQL 有分号。
- 多条 SQL 没有分号。
- SQL 前有
-- 功能:xxx注释。 - 字符串里可能包含分号。
- 一段脚本中同时存在 SELECT、UPDATE、DELETE。
输出:
- SQL 正文。
- SQL 类型。
- 功能注释。
深挖点:
调用方传来的 SQL 类型不可信,尤其是多 SQL 场景。拆分器必须在后端根据 SQL 正文识别类型,否则一个任务里 UPDATE 可能被误当成 SELECT,导致只生成查询方法。
8.2 SqlParserService
职责:识别 SQL 片段、表、字段、特征和风险。
解析结果 SqlParseResult 包含:
segments:SQL 片段。tables:涉及表。columns:涉及字段。features:SQL/存储过程特征。risks:风险点。
典型特征:
SELECT、INSERT、UPDATE、DELETE。CURSOR。LOOP。TRANSACTION。EXCEPTION。DYNAMIC_SQL。
作用:
- 给 RAG 构造检索文本。
- 给规则匹配提供 sourceFeature。
- 给报告生成提供解析依据。
- 给风险复核提供原因。
9. RAG 与 Chroma 深挖
9.1 ChromaRagService
职责:任务执行时检索相似迁移知识。
执行流程:
|
|
为什么使用 query_embeddings:
- Chroma 服务端不一定配置 embedding function。
- 写入和查询必须使用同一个 embedding 模型,才能保证向量维度和语义空间一致。
- 之前报错
missing field query_embeddings的本质就是调用了需要向量的 query 接口,但请求体没有传查询向量。
为什么会有维度不一致:
- 例如旧 collection 用 384 维模型写入,新脚本用 DashScope
text-embedding-v4生成 1024 维。 - Chroma collection 的向量维度一旦确定,就不能混用不同维度。
- 解决方式是删除旧 collection 或新建 collection,并保证写入和查询模型一致。
buildQueryText 为什么包含 features/tables/risks/sql:
- 单纯拿原 SQL 检索,可能召回不到规则文档。
- 加上 features 可以命中“UPDATE 生成规则”“动态 SQL 风险”等知识。
- 加上 tables 可以命中领域表相关模板。
- 加上 risks 可以命中人工复核规范。
9.2 ChromaKnowledgeService
职责:知识片段维护服务。
核心能力:
- 新增知识。
- 更新知识。
- 删除知识。
- 列表查询。
- 语义搜索。
- 默认知识初始化。
- 批量创建。
写入 Chroma 时保存:
ids:文档 ID。documents:正文。metadatas:标题、类别、SQL 类型、场景、来源、标签、创建时间、更新时间。embeddings:由后端 EmbeddingClient 生成的向量。
buildEmbeddingText 为什么不只向量化正文:
|
|
原因是标题、SQL 类型、场景和标签本身就是重要检索信号。比如用户输入 UPDATE SQL,如果正文没写“局部更新”,但 metadata 里有 sqlType=UPDATE、scenario=局部更新,检索会更容易命中相关模板。
默认知识库内容覆盖:
- SELECT 单表查询最小生成规则。
- UPDATE 局部更新生成规则。
- INSERT 新增生成规则。
- DELETE 与逻辑删除生成规则。
- Keyset 分页查询模板。
- 批处理 SQL 迁移策略。
- 动态 SQL 风险处理。
- 生成代码反例约束。
面试讲法:
RAG 不是为了让模型照抄一段代码,而是给迁移计划和代码生成模型提供企业级迁移规范、反例和风险边界。真正产出的源码由模型按契约生成,但必须经过后端契约门禁、AI 复审和 Java Compiler 校验。
10. 映射规则层深挖
10.1 MappingRuleService
职责:根据 SQL 解析出的 features 匹配迁移规则。
规则模型 MappingRule 包含:
sourceFeature:源 SQL/存储过程特征。targetPattern:Java 侧实现模式。confidence:置信度。riskLevel:风险等级。description:说明和人工复核建议。
MySQL 初始化规则示例:
| 特征 | Java 模式 | 风险 |
|---|---|---|
CURSOR |
Mapper 查询 List 后 Service for 循环处理 | MEDIUM |
LOOP |
Java for/while 循环 | MEDIUM |
TRANSACTION |
Spring @Transactional |
HIGH |
EXCEPTION |
try-catch + 业务异常 |
MEDIUM |
DYNAMIC_SQL |
标记高风险并人工复核 | HIGH |
OUT_PARAM |
OUT 参数迁移为返回 DTO | LOW |
为什么规则层必要:
- 模型容易忽略存储过程语义差异。
- 规则能把特征映射成确定的工程策略。
- 高风险规则可以影响
manualReviewRequired。 - 报告里能解释为什么要人工复核。
11. 质量校验与修复闭环
11.1 JavaCompilerService
职责:对生成的 Java 产物做真实编译。
核心价值:
- 不靠肉眼判断代码是否正确。
- 不只检查字符串格式。
- 使用 JDK
JavaCompiler返回真实错误。
编译错误结构:
|
|
可被追问:
-
为什么编译校验重要?
因为代码生成结果“看起来像 Java”不代表真的能编译。尤其是 Mapper、Service、DTO 之间存在类名、包名、import、方法签名、返回类型的联动,一处不一致就会导致后续开发无法接手。Java Compiler API 是最低成本、最确定的一道质量门禁,可以把错误结构化成文件、行号、错误信息,既方便前端展示,也方便自动修复工具消费。 -
编译通过是否等于逻辑正确?
不等于。编译通过只能说明语法、类型、引用关系是正确的,不能证明 SQL 语义和原存储过程完全一致。比如事务边界、异常处理、批处理顺序、金额精度、分页语义、动态 SQL 白名单都需要进一步校验。真实企业场景还要结合 JUnit、原 SQL 与 Java 方法的结果对比、沙箱数据验证和人工复核。所以这个平台定位是“辅助迁移和提高效率”,不是完全替代人工合并。
11.2 RepairCodeAgentTool
职责:编译失败后的自动修复。
设计边界:
- 最多修复配置次数。
- 主要处理保守修复。
- 修不好就转人工复核。
为什么修复不能无限循环:
- 弱模型或模板错误可能导致反复失败。
- 无限修复会浪费资源。
- 企业场景更重视可控性,超过阈值应该交给人工。
12. 持久化模型与数据库设计
12.1 business_module
保存业务模块。
关键字段:
module_id:模块 ID。name:模块名称。code:模块编码。base_package:生成代码基础包名。source_db:源数据库。target_stack:目标技术栈。
设计意义:
- 把 SQL 迁移按业务模块组织。
- 支持一个模块内持续添加表和 SQL。
- 生成代码时可以使用稳定基础包名。
12.2 module_table
保存模块表结构。
关键字段:
raw_ddl:用户原始 DDL。table_schema_json:解析后的结构化表定义。parse_source:解析来源。
设计意义:
- 保留原始 DDL,便于追溯。
- 保存结构化 JSON,便于代码生成。
- 支持后续人工修正或模型解析替换规则解析。
12.3 migration_task
保存迁移任务主数据。
关键字段:
source_sql:原始 SQL 或模块 SQL 汇总。module_context_json:模块上下文。status:任务状态。parse_result_json:SQL 解析结果。rag_documents_json:RAG 检索结果。matched_rules_json:命中规则。migration_plan_json:大模型生成或规则兜底的结构化迁移计划。code_review_result_json:生成后 AI 代码复审结果。artifacts_json:生成产物快照。compile_result_json:编译结果。report_markdown:迁移报告。risks_json:风险点。
设计意义:
- 一个任务就是一次迁移执行的完整快照。
- 即使产物文件丢失,也能从 JSON 快照看到执行过程。
- 支持面试中讲“可观测、可追踪、可复核”。
12.4 agent_log
保存 Agent 每轮执行日志。
关键字段:
round_no:轮次。thought:模型思考摘要。action:工具名。action_input:工具入参。observation:工具结果。success:是否成功。cost_ms:耗时。
设计意义:
- 能定位失败发生在哪一步。
- 能分析每个工具耗时。
- 能解释 Agent 为什么生成某些代码。
12.5 mapping_rule
保存迁移规则。
设计意义:
- 把专家经验数据化。
- 支持后续持续维护规则。
- 高风险规则可以进入报告和人工复核。
13. 领域模型类职责
| 类 | 职责 |
|---|---|
MigrationTask |
任务聚合根,保存一次迁移执行的所有状态、上下文、结果和风险。 |
MigrationStatus |
任务状态机枚举,描述任务当前处于解析、检索、生成、编译、修复还是终态。 |
BusinessModule |
业务模块元数据。 |
ModuleTable |
模块表结构记录,包含原始 DDL 和结构化表模型。 |
ModuleContext |
模块级迁移上下文,包含基础包、表结构和 SQL 单元。 |
TableSchema |
单张表的结构化模型。 |
TableColumn |
单个字段的结构化模型。 |
SqlUnit |
一条待迁移 SQL 的结构化模型。 |
SqlParseResult |
SQL 解析结果。 |
SqlSegment |
SQL 分片。 |
MappingRule |
映射规则。 |
RiskLevel |
风险等级。 |
RagDocument |
RAG 检索结果。 |
KnowledgeDocument |
Chroma 知识库文档。 |
Artifact |
生成产物。 |
ArtifactType |
产物类型。 |
CompileResult |
编译结果。 |
CompileError |
编译错误明细。 |
AgentLog |
Agent 执行日志。 |
14. Repository 层职责
| 类 | 职责 | 深挖点 |
|---|---|---|
MigrationTaskRepository |
保存和查询迁移任务 | 复杂对象通过 JSON 字段持久化 |
BusinessModuleRepository |
保存和查询业务模块 | 支持模块工作台 |
ModuleTableRepository |
保存和查询模块表结构 | 表结构是代码生成上下文 |
MappingRuleRepository |
保存和查询映射规则 | 支持规则持续维护 |
AgentLogRepository |
保存和查询 Agent 日志 | 支撑可观测性 |
JsonColumnMapper |
对象和 JSON 字段互转 | 避免手写重复序列化逻辑 |
为什么不用大量关系表拆得很细:
- 迁移任务中的解析结果、RAG 文档、产物列表、编译结果天然是一次执行快照。
- JSON 字段更适合保存快照型、结构变化较快的数据。
- 核心检索字段,如状态、模块名、创建时间,单独建索引即可。
15. 配置与中间件
后端真实运行依赖:
| 中间件 | 用途 |
|---|---|
| MySQL | 保存模块、任务、表结构、规则、日志 |
| Redis | 保存运行态任务快照、Agent 记忆、临时日志 |
| Chroma | 保存 RAG 知识向量 |
| DeepSeek 或内部模型 | 通过原生 Function Calling 选择工具,并辅助迁移计划、代码复审和修复建议 |
| DashScope Embedding | 生成查询向量和文档向量 |
| 文件系统 | 保存生成的 Java/XML/JUnit/报告产物 |
配置类对应关系:
| 配置类 | 配置内容 |
|---|---|
MigrationProperties |
artifact 目录、最大修复次数、Redis TTL、异步线程池 |
ModelProperties |
模型 provider、base-url、api-key、model、timeout |
EmbeddingProperties |
embedding base-url、api-key、model、timeout |
RagProperties |
Chroma base-url、tenant、database、collection、topK |
15.1 本轮代码生成踩坑与可讲述知识点
这一轮排查的价值不只是“修了几个 bug”,更适合在面试里讲成:模型主导代码生成在真实工程里一定会遇到输出漂移、语义遗漏和任务边界失控,后端必须用契约、门禁和回归测试把它收住。
坑 1:模型 JSON 结构漂移,导致知识库总结反序列化失败
现象:
|
|
根因是 KnowledgeSummary 里一些字段定义为 String,例如 namingConventions 的 value、CodePatternRule.example/applyTo、ReferenceCodeSnippet.purpose/code。但模型很容易把“多个规则”“多个适用场景”“多行代码片段”输出成数组。JSON 本身是合法的,只是和 Java DTO 的严格类型不一致。
工程处理:
- 不能只改 prompt,因为模型仍可能在长上下文或多规则场景下输出数组。
- 不能全局放宽
ObjectMapper,否则会污染迁移计划、工具参数、代码结构规划这些必须严格校验的合同。 - 最终做法是只在知识库总结的自由文本字段上做局部柔性反序列化:字符串正常读取,数组或简单对象折叠成可读文本,其他阶段继续保持严格契约。
面试讲法:
这说明模型输出不是传统后端 DTO,它有“结构漂移”的概率。我的处理不是简单吞异常,而是按字段业务性质分层:迁移计划、工具参数、代码结构规划属于硬合同,必须严格;知识库总结属于提示词上下文里的规则摘要,可以做局部容错。这样既提升稳定性,又不牺牲关键链路的安全边界。
坑 2:ORDER BY 锚点误判,暴露了“字符串匹配”和“语义等价”的差异
现象:
源 SQL 里有:
|
|
但后端门禁报:
|
|
这个问题有两层:
- 第一层是文本归一化不够,
ORDER BY id DESC和内部匹配用的orderby因为空白处理不同可能误判。 - 第二层是生成代码可能不用注解 SQL,而是用 MyBatis-Plus Wrapper,例如
orderBy(true, false, Entity::getId),它和ORDER BY id DESC语义等价,但不是同一段字符串。
工程处理:
- 语义锚点匹配先做统一归一化:大小写、引号、下划线、空白都要处理。
- 对排序方向做等价识别:
DESC可以对应 MyBatis-Plus 的isAsc=false,ASC可以对应isAsc=true。 - 同时保留负例测试:如果生成代码真的没有排序,仍然必须被门禁拦截。
面试讲法:
质量门禁不能只做粗暴字符串包含,否则会误杀合法实现;但也不能因为误杀就放弃门禁。正确做法是把 SQL 的关键语义抽成锚点,再识别几种工程上等价的落地方式,比如注解 SQL 和 MyBatis-Plus Wrapper。这个点体现了后端 Guardrails 的难点:既要严格阻断语义丢失,又要理解框架表达方式。
坑 3:用户只传 7 条 SQL,模型却生成 8 个迁移操作
现象:
用户输入的 SQL 数量是 7 条,但迁移计划阶段出现了 8 个 operation。这类问题本质上是模型把上下文里的案例、旧记忆或自己的推断扩展成了“额外 SQL”。
风险:
- 后续代码生成会多生成 Service/DTO/VO。
- SQL 覆盖关系变乱,
relatedSqlIds可能指向不存在的 SQL。 - 人工看日志会觉得系统“吞了或造了 SQL”,可信度下降。
工程处理:
moduleContext.sqlUnits是当前任务的硬边界,模型不能改变任务 SQL 集合。- 迁移计划归一化时只保留当前任务内的
sqlId。 - 未知
sqlId或重复sqlId的 operation 记录风险并丢弃。 - 如果模型漏掉真实 SQL,则由后端按 SQL 正文补齐兜底 operation。
面试讲法:
模型可以帮助理解 SQL 意图,但不能决定任务边界。任务边界必须来自用户输入和后端拆分出的
SqlUnit。我把模型计划当成建议,而不是事实来源;最终计划必须被收敛到当前任务的 SQL 集合里。这样就避免了“7 条 SQL 变 8 个操作”这种幻觉进入代码生成。
坑 4:只靠 AI 复审不够,必须有确定性契约门禁
这几个问题都说明:AI 复审适合发现语义风险,但不能作为唯一验收者。后端需要确定性门禁拦住这些问题:
- 文件数量异常,避免一条 SQL 固定生成六件套。
- 机械命名,例如
VariantA、queryXxxForSqlHash。 - SQL 覆盖缺失,每个
SqlUnit必须被产物覆盖或声明人工复核。 - 固定条件、
ORDER BY、LIMIT/OFFSET等 SQL 锚点不能丢。 - 高风险 SQL 不能生成可执行产物。
- Mapper/Service/Entity 必须符合 MyBatis-Plus 工程结构。
面试讲法:
我没有把模型当成可信执行器,而是当成候选生成器。候选代码必须经过后端契约门禁、AI 复审和 Java Compiler 三层验收。确定性门禁负责拦截可静态判断的问题,AI 复审负责看业务语义,编译器负责类型和引用关系。这三层互补,才能让代码生成从 Demo 走向可交付。
坑 5:调试这类问题要先建立反馈环,而不是盯着模型猜
这轮修复的方式可以作为项目工程能力来讲:
- 先用单元测试复现
START_ARRAY反序列化失败。 - 再用单元测试复现
ORDER BY锚点误判。 - 再补测试复现“7 条 SQL 被模型扩成 8 个 operation”。
- 每个问题都先看红测,再做局部修复,最后跑完整后端测试。
面试讲法:
大模型链路的问题很容易变成“感觉模型不稳定”。我处理时先把现象变成确定性测试:模型 JSON 漂移、ORDER BY 等价表达、SQL 数量边界分别都有可回放用例。这样修复不是靠调 prompt,而是靠后端合同和回归测试锁住。最终完整后端测试通过,说明修复没有破坏其他链路。
这几个坑可以总结成一句话:
模型主导代码生成的核心难点,不是让模型写出一段看起来像 Java 的代码,而是把模型输出收敛到当前任务、当前 SQL、当前表结构和当前工程规范之内。后端要把“不可信但有创造力的模型输出”变成“可审计、可复核、可编译、边界清晰的工程产物”。
16. 面试深挖题与回答
16.1 为什么要做模块级迁移,而不是单 SQL 迁移?
单 SQL 缺少上下文。比如 SELECT * FROM user 不知道 user 表有哪些字段、哪些字段敏感、主键是什么、Java 包名是什么。模块级迁移让用户先维护表结构,再提交 SQL,代码生成器可以基于真实字段生成 DTO/VO/Mapper/XML。
16.2 为什么现在采用模型主导代码生成?
因为真实模块迁移不是单条 SQL 翻译,而是多 SQL、多表、联表、聚合、分页和风险控制共同决定代码结构。模板生成器容易生成机械类名、大而全 VO、错误字段类型和漏覆盖方法。新的设计让模型根据迁移计划、模块上下文和 RAG 规范生成完整文件集,后端负责契约、门禁、编译和复审。也就是说,模型主导“写什么代码”,后端决定“能不能进入下一步”。
16.3 为什么使用原生 Function Calling?
因为这个项目要实现真正的 ReAct Agent,而不是让模型只配合后端固定流程输出一段工具标记。现在 ToolRegistry 会把每个迁移阶段级工具导出成 OpenAI tools JSON Schema,模型返回结构化 tool_calls,后端按顺序校验白名单、taskId 和前置条件,再执行工具并回填 Observation。
这样有三个好处:
- 工具名和参数是结构化协议,不再依赖正则解析文本。
- 模型真的可以根据 Observation 自主选择下一步工具。
- 后端仍然能拒绝越权、串任务、缺少前置产物或不满足完成条件的调用。
16.4 如果模型调用错工具怎么办?
调度器不会替模型改成预期工具,因为这个项目的目标是真正的 ReAct Agent。模型调用错工具时,后端会拒绝执行,并把原因作为 Observation 回填,例如未注册工具、缺少 taskId、taskId 与当前任务不一致、缺少 SQL 解析结果、没有代码产物、编译未通过等。模型下一轮需要基于这个 Observation 自我修正。
16.5 RAG 在项目里具体起什么作用?
RAG 提供企业迁移知识,包括代码模板、规则、反例、风险处理、字段规范。它会进入 generate_plan、generate_code 和 review_code 的上下文:先帮助模型把 SQL 意图转成结构化迁移计划,再帮助代码生成模型按企业规范生成文件集,最后帮助复审模型判断生成代码是否符合规范。
16.6 Chroma 为什么要传 query_embeddings?
因为后端使用自己的 EmbeddingClient 生成向量,保证查询向量和写入向量使用同一个模型。Chroma query 接口在这种模式下需要 query_embeddings 字段,否则会报缺失字段。
16.7 如何保证多条 SQL 不漏生成方法?
调用方传入类型不可信,后端 SqlStatementSplitter 会重新拆分 SQL,迁移计划会按 SQL 正文补齐或修正操作类型。代码生成模型返回的每个文件还必须声明 relatedSqlIds,后端契约门禁会检查每个 SqlUnit 是否被覆盖;未覆盖且未声明人工复核风险时,generate_code 直接失败。
16.8 如何避免 SELECT * 暴露敏感字段?
生成器会结合模块表结构展开字段,并过滤 password、token、secret、salt、isDelete、createTime、updateTime、version 等敏感或技术字段。
16.9 AI 代码复审解决什么问题?
它解决“代码能编译但业务语义不一定对”的问题。比如多条 SQL 里漏生成 UPDATE 方法、Mapper XML 丢失 isDelete = 0、VO 暴露 password、DELETE 没有 WHERE,这些都不是编译器能发现的。CodeReviewService 会把源 SQL、迁移计划、RAG 规范、映射规则和生成代码一起交给模型复审,输出结构化问题清单;只要复审要求人工确认,最终任务就不会被标记为完全通过。
16.10 编译通过是否代表迁移正确?
不代表。编译通过只说明语法和类型正确。业务逻辑还需要 JUnit、原 SQL 对比测试、人工复核和沙箱数据验证。所以系统会生成报告和风险提示,而不是宣称全自动无风险替代人工。
16.11 任务失败后怎么处理?
后台异常会被 MigrationTaskService.runAgent 捕获,任务状态转 FAILED_NEED_MANUAL_REVIEW,异常信息写入 risks。编译失败会进入自动修复,超过最大次数也会转人工复核。
17. 最终推荐讲法
面试时可以这样完整表达:
这个项目的后端核心是一个可审计的 SQL/存储过程迁移 ReAct Agent 平台。用户先按业务模块维护表结构,后端把表结构和多条 SQL 组装成模块上下文,再把真实迁移阶段级工具以 OpenAI tools JSON Schema 暴露给模型。模型通过原生 Function Calling 自主选择
sql_parse、rag_search、rule_match、generate_plan、generate_code、review_code、compile_java、repair_code、generate_report、finish_migration或request_human_review等工具;后端不替模型固定流程,只校验白名单、taskId和工具前置条件。生成代码时采用模型主导:模型基于迁移计划、表结构、SQL 单元、RAG 规范和工具观察生成完整 Java/MyBatis 文件集;后端负责契约门禁,检查 SQL 覆盖、路径、package、类名、机械命名、危险 DML 和动态 SQL 风险。代码生成后还会交给模型复审 SQL 语义和企业规范,再通过 Java Compiler API 做真实编译,失败后由 Agent 决定修复或转人工复核。整个任务过程会落 MySQL、Redis、文件系统和 Agent 日志,所以它是一个可观测、可追踪、可复核的企业级迁移后端,而不是玩具 Demo。
18. 项目最值得强调的后端亮点
- 模块上下文设计:解决单 SQL 缺字段、缺语义、缺包名的问题。
- 多 SQL 类型识别:后端拆分并二次推断 SQL 类型,避免漏生成 UPDATE/DELETE。
- 原生 Function Calling:使用 OpenAI tools JSON Schema 暴露真实工具,由模型返回结构化
tool_calls。 - ReAct 决策 + 后端 Guardrails:模型决定下一步工具,后端校验白名单、
taskId、前置条件和终态验收。 - RAG 知识运营能力:支持 Chroma CRUD、文件导入、结构化 chunk、默认企业知识。
- 结构化迁移计划:大模型先把 SQL、RAG 和规则转成可控计划,作为代码生成契约的一部分。
- 模型主导代码生成:模型生成完整源码文件集,后端用契约门禁阻断机械命名、漏 SQL、高风险 DML 和不可编译结果。
- 生成后 AI 复审:检查 SQL 语义遗漏、危险 DML、敏感字段暴露和多余代码。
- 编译校验闭环:Java Compiler API 真实编译,失败修复,超过次数人工复核。
- 完整任务可观测性:状态机、AgentLog、风险列表、产物、报告全部可追踪。