feat(复习):回忆对比优先走 AI 语义对比,失败时降级为内置算法
- CompareResult.java:新增 evaluation 字段(AI 对比时写入评价文本) - AiServiceClient.java:新增 compareRecall(),调用 lpt-ai /ai/compare-recall - StandardMindMapServiceImpl.java:recallCompare() 中注入 AiServiceClient, 优先 try AI 语义对比,含层级上下文和同义表达识别; AI 不可用或失败时自动 fallback 到内置标题匹配算法 - StandardMindMapServiceImplTest.java:适配新增的 AiServiceClient 依赖
This commit is contained in:
@@ -2,6 +2,7 @@ package com.guo.learningprogresstracker.service.impl;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.guo.learningprogresstracker.utils.CompareResult;
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -129,4 +130,40 @@ public class AiServiceClient {
|
|||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用 lpt-ai 进行语义化回忆对比。
|
||||||
|
*
|
||||||
|
* @return 带 MATCHED/MISSED 标注和评价文本的对比结果;服务未配置或调用失败时返回 empty
|
||||||
|
*/
|
||||||
|
public Optional<CompareResult> compareRecall(String taskName, String standardOutline, String recallOutline) {
|
||||||
|
if (!isConfigured()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Map<String, String> body = Map.of(
|
||||||
|
"taskName", taskName,
|
||||||
|
"standardOutline", standardOutline,
|
||||||
|
"recallOutline", recallOutline
|
||||||
|
);
|
||||||
|
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(url + "/ai/compare-recall"))
|
||||||
|
.timeout(Duration.ofSeconds(timeoutSeconds))
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
log.warn("lpt-ai 回忆对比失败: status={}, body={}", response.statusCode(),
|
||||||
|
response.body() != null ? response.body().substring(0, Math.min(200, response.body().length())) : "");
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.of(objectMapper.readValue(response.body(), CompareResult.class));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("lpt-ai 回忆对比调用异常,将降级为内置算法: {}", e.getMessage());
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-2
@@ -39,6 +39,7 @@ public class StandardMindMapServiceImpl implements StandardMindMapService {
|
|||||||
|
|
||||||
private final List<MindMapAiClient> aiClients;
|
private final List<MindMapAiClient> aiClients;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
private final AiServiceClient aiServiceClient;
|
||||||
|
|
||||||
private static final String GENERATOR_BUILTIN = "BUILTIN";
|
private static final String GENERATOR_BUILTIN = "BUILTIN";
|
||||||
private static final String GENERATOR_USER = "USER";
|
private static final String GENERATOR_USER = "USER";
|
||||||
@@ -105,8 +106,28 @@ public class StandardMindMapServiceImpl implements StandardMindMapService {
|
|||||||
MindMapNode standardRoot = MindMapTreeTool.fromJson(standard.getContent(), objectMapper);
|
MindMapNode standardRoot = MindMapTreeTool.fromJson(standard.getContent(), objectMapper);
|
||||||
MindMapNode recallRoot = MindMapTreeTool.parseOutline(recallOutline);
|
MindMapNode recallRoot = MindMapTreeTool.parseOutline(recallOutline);
|
||||||
|
|
||||||
// 3. 执行对比
|
// 3. 执行对比(优先 AI 语义对比,失败时降级为内置算法)
|
||||||
CompareResult result = compareTrees(standardRoot, recallRoot);
|
CompareResult result;
|
||||||
|
if (aiServiceClient.isConfigured()) {
|
||||||
|
String taskName = tasksMapper.selectOne(
|
||||||
|
Wrappers.<TaskEntity>lambdaQuery().eq(TaskEntity::getTaskNum, taskNum).last("LIMIT 1"))
|
||||||
|
.getTaskName();
|
||||||
|
var optResult = aiServiceClient.compareRecall(
|
||||||
|
taskName,
|
||||||
|
MindMapTreeTool.toFullOutline(standardRoot),
|
||||||
|
recallOutline
|
||||||
|
);
|
||||||
|
if (optResult.isPresent()) {
|
||||||
|
result = optResult.get();
|
||||||
|
log.info("AI 回忆对比: taskNum={}, recallRatio={}, evaluation={}",
|
||||||
|
taskNum, result.getRecallRatio(),
|
||||||
|
result.getEvaluation() != null ? result.getEvaluation().substring(0, Math.min(50, result.getEvaluation().length())) : "");
|
||||||
|
} else {
|
||||||
|
result = compareTrees(standardRoot, recallRoot);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = compareTrees(standardRoot, recallRoot);
|
||||||
|
}
|
||||||
|
|
||||||
// 4. 序列化对比结果
|
// 4. 序列化对比结果
|
||||||
String resultJson;
|
String resultJson;
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ public class CompareResult {
|
|||||||
/** 额外节点数 */
|
/** 额外节点数 */
|
||||||
private int extraCount;
|
private int extraCount;
|
||||||
|
|
||||||
|
/** AI 评价文本(仅 AI 对比时有值,内置对比为 null) */
|
||||||
|
private String evaluation;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
|
|||||||
+3
-1
@@ -45,6 +45,8 @@ class StandardMindMapServiceImplTest {
|
|||||||
private List<MindMapAiClient> aiClients;
|
private List<MindMapAiClient> aiClients;
|
||||||
@Mock
|
@Mock
|
||||||
private MindMapAiClient mockAiClient;
|
private MindMapAiClient mockAiClient;
|
||||||
|
@Mock
|
||||||
|
private AiServiceClient aiServiceClient;
|
||||||
|
|
||||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
@@ -189,7 +191,7 @@ class StandardMindMapServiceImplTest {
|
|||||||
standardMindMapMapper, recallRecordMapper,
|
standardMindMapMapper, recallRecordMapper,
|
||||||
tasksMapper, studyReportsMapper, studyReportFragmentsMapper,
|
tasksMapper, studyReportsMapper, studyReportFragmentsMapper,
|
||||||
taskApplicationMapper, studySessionsMapper,
|
taskApplicationMapper, studySessionsMapper,
|
||||||
List.of(mockAiClient), objectMapper);
|
List.of(mockAiClient), objectMapper, aiServiceClient);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user