fix(复习):AI 对比结果由 Java 端标注标准树,不再依赖 LLM 输出树结构
根因:LLM 无法可靠输出 CompareResult 所需的完整嵌套树 JSON, 实际返回格式(coverage/matched/missed)与预期不符。 修复: - AiServiceClient.compareRecall() 返回 Optional<JsonNode>(原始 JSON), 不再尝试直接反序列化为 CompareResult - StandardMindMapServiceImpl 新增 buildCompareResultFromAI(): 解析 AI 返回的平铺 matches/missedTitles/extraNodes 列表, 将 MATCHED|/MISSED| 标注写入标准树,确定性构建 CompareResult - AI 只负责语义匹配判断,树结构操作由 Java 端保证可靠性
This commit is contained in:
+72
-3
@@ -1,6 +1,7 @@
|
||||
package com.guo.learningprogresstracker.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.guo.learningprogresstracker.entity.*;
|
||||
import com.guo.learningprogresstracker.exception.NotFindEntitiesException;
|
||||
@@ -112,13 +113,13 @@ public class StandardMindMapServiceImpl implements StandardMindMapService {
|
||||
String taskName = tasksMapper.selectOne(
|
||||
Wrappers.<TaskEntity>lambdaQuery().eq(TaskEntity::getTaskNum, taskNum).last("LIMIT 1"))
|
||||
.getTaskName();
|
||||
var optResult = aiServiceClient.compareRecall(
|
||||
var optJson = aiServiceClient.compareRecall(
|
||||
taskName,
|
||||
MindMapTreeTool.toFullOutline(standardRoot),
|
||||
recallOutline
|
||||
);
|
||||
if (optResult.isPresent()) {
|
||||
result = optResult.get();
|
||||
if (optJson.isPresent()) {
|
||||
result = buildCompareResultFromAI(optJson.get(), standardRoot);
|
||||
log.info("AI 回忆对比: taskNum={}, recallRatio={}, evaluation={}",
|
||||
taskNum, result.getRecallRatio(),
|
||||
result.getEvaluation() != null ? result.getEvaluation().substring(0, Math.min(50, result.getEvaluation().length())) : "");
|
||||
@@ -225,6 +226,74 @@ public class StandardMindMapServiceImpl implements StandardMindMapService {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 AI 返回的平铺匹配列表构建 CompareResult,并将 MATCHED/MISSED 标注回标准树。
|
||||
* <p>AI 只输出哪些节点匹配/遗漏,树结构标注由本方法确定性完成,避免 LLM 输出不可靠的嵌套 JSON。</p>
|
||||
*/
|
||||
private CompareResult buildCompareResultFromAI(JsonNode aiJson, MindMapNode standardRoot) {
|
||||
CompareResult result = new CompareResult();
|
||||
|
||||
// 1. 读取 AI 返回的匹配对 → 构建 standardNorm → matchFlag
|
||||
Set<String> matchedStandardTitles = new HashSet<>();
|
||||
JsonNode matchesArr = aiJson.path("matches");
|
||||
if (matchesArr.isArray()) {
|
||||
for (JsonNode m : matchesArr) {
|
||||
String stdTitle = m.path("standardTitle").asText(null);
|
||||
if (stdTitle != null) {
|
||||
matchedStandardTitles.add(normalize(stdTitle));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 读取遗漏列表
|
||||
Set<String> missedTitles = new HashSet<>();
|
||||
JsonNode missedArr = aiJson.path("missedTitles");
|
||||
if (missedArr.isArray()) {
|
||||
for (JsonNode t : missedArr) {
|
||||
String title = t.asText(null);
|
||||
if (title != null) missedTitles.add(normalize(title));
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 标注标准树的每个非根节点
|
||||
List<MindMapNode> standardFlat = MindMapTreeTool.flatten(standardRoot);
|
||||
int matched = 0, missed = 0;
|
||||
for (MindMapNode node : standardFlat) {
|
||||
if (node == standardRoot) continue;
|
||||
String key = normalize(node.getTitle());
|
||||
if (matchedStandardTitles.contains(key)) {
|
||||
node.setNotes(MATCH_STATUS_MATCHED + "|" + (node.getNotes() != null ? node.getNotes() : ""));
|
||||
matched++;
|
||||
} else {
|
||||
node.setNotes(MATCH_STATUS_MISSED + "|" + (node.getNotes() != null ? node.getNotes() : ""));
|
||||
missed++;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 读取 extraNodes
|
||||
List<CompareResult.FlatNode> extras = new ArrayList<>();
|
||||
JsonNode extrasArr = aiJson.path("extraNodes");
|
||||
if (extrasArr.isArray()) {
|
||||
for (JsonNode e : extrasArr) {
|
||||
CompareResult.FlatNode fn = new CompareResult.FlatNode();
|
||||
fn.setTitle(e.path("title").asText(""));
|
||||
fn.setPath(e.path("path").asText(""));
|
||||
extras.add(fn);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 组装结果
|
||||
result.setMatchedTree(standardRoot);
|
||||
result.setExtraNodes(extras);
|
||||
result.setMatchedCount(matched);
|
||||
result.setMissedCount(missed);
|
||||
result.setExtraCount(extras.size());
|
||||
int total = matched + missed;
|
||||
result.setRecallRatio(total > 0 ? (double) matched / total : 0);
|
||||
result.setEvaluation(aiJson.path("evaluation").asText(null));
|
||||
return result;
|
||||
}
|
||||
|
||||
// ============ 回忆记录查询 ============
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user