diff --git a/pom.xml b/pom.xml index ab3f177..ad3696d 100644 --- a/pom.xml +++ b/pom.xml @@ -111,8 +111,8 @@ maven-compiler-plugin 3.11.0 - ${java.version} - ${java.version} + 21 + 21 org.projectlombok @@ -120,6 +120,7 @@ 1.18.30 + --enable-preview diff --git a/src/main/java/com/labelsys/backend/controller/AnnotationResultArchiveController.java b/src/main/java/com/labelsys/backend/controller/AnnotationResultArchiveController.java index 1fbcb85..232dbc8 100644 --- a/src/main/java/com/labelsys/backend/controller/AnnotationResultArchiveController.java +++ b/src/main/java/com/labelsys/backend/controller/AnnotationResultArchiveController.java @@ -3,8 +3,8 @@ package com.labelsys.backend.controller; import com.labelsys.backend.context.UserContext; import com.labelsys.backend.dto.common.PageResult; import com.labelsys.backend.dto.request.AnnotationResultHistoryPageQuery; +import com.labelsys.backend.dto.response.AnnotationResultHistoryDetailResponse; import com.labelsys.backend.dto.response.AnnotationResultHistoryResponse; -import com.labelsys.backend.dto.response.FileContentResponse; import com.labelsys.backend.service.AnnotationResultArchiveService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -34,17 +34,18 @@ public class AnnotationResultArchiveController { @Operation(summary = "查询归档历史详情") @GetMapping("/{id}") - public ResponseEntity getHistory( + public ResponseEntity getHistory( @Parameter(description = "历史记录ID", example = "901") @PathVariable Long id) { return ResponseEntity.ok(annotationResultArchiveService.getHistory(UserContext.requireUser(), id)); } +} - @Operation(summary = "加载归档文件内容") - @GetMapping("/{id}/content") - public ResponseEntity loadFileContent( - @Parameter(description = "历史记录ID", example = "901") - @PathVariable Long id) { - return ResponseEntity.ok(annotationResultArchiveService.loadFileContent(UserContext.requireUser(), id)); - } -} \ No newline at end of file +// @Operation(summary = "加载归档文件内容") +// @GetMapping("/{id}/content") +// public ResponseEntity loadFileContent( +// @Parameter(description = "历史记录ID", example = "901") +// @PathVariable Long id) { +// return ResponseEntity.ok(annotationResultArchiveService.loadFileContent(UserContext.requireUser(), id)); +// } +//} \ No newline at end of file diff --git a/src/main/java/com/labelsys/backend/dto/request/MergeReviewResultRequest.java b/src/main/java/com/labelsys/backend/dto/request/MergeReviewResultRequest.java index e7930f0..e45c90c 100644 --- a/src/main/java/com/labelsys/backend/dto/request/MergeReviewResultRequest.java +++ b/src/main/java/com/labelsys/backend/dto/request/MergeReviewResultRequest.java @@ -9,7 +9,7 @@ public record MergeReviewResultRequest( @Schema(description = "合并后的答案映射,key为qa记录ID,value为合并后的答案") Map mergedAnswers, - @Schema(description = "审核备注") - String reviewComment + @Schema(description = "每条QA记录的审核评论映射,key为qa记录ID,value为审核评论") + Map reviewComments ) { } \ No newline at end of file diff --git a/src/main/java/com/labelsys/backend/dto/response/AnnotationResultCompareResponse.java b/src/main/java/com/labelsys/backend/dto/response/AnnotationResultCompareResponse.java index b389969..9e8b100 100644 --- a/src/main/java/com/labelsys/backend/dto/response/AnnotationResultCompareResponse.java +++ b/src/main/java/com/labelsys/backend/dto/response/AnnotationResultCompareResponse.java @@ -18,10 +18,25 @@ public record AnnotationResultCompareResponse( @Schema(description = "问答记录") public record QaRecord( @Schema(description = "记录ID", example = "qa_001") String id, + @Schema(description = "批次ID", example = "50") Long batchId, @Schema(description = "问题", example = "运输时效是多久?") String question, @Schema(description = "答案", example = "3天") String answer, - @Schema(description = "是否需要审核", example = "true") Boolean requiresReview - ) {} + @Schema(description = "是否需要审核", example = "true") Boolean requiresReview, + @Schema(description = "源片段信息") SourceSegments sourceSegments, + @Schema(description = "问题分类") String questionCategory, + @Schema(description = "评分") Scores scores, + @Schema(description = "审核评论") String reviewComment + ) { + } + + @Schema(description = "源片段信息") + public record SourceSegments( + @Schema(description = "片段内容") String segment, + @Schema(description = "块索引", example = "0") Integer chunkIndex, + @Schema(description = "块标题") String chunkTitle, + @Schema(description = "块内容") String chunkContent + ) { + } @Schema(description = "差异记录") public record DiffRecord( @@ -30,6 +45,19 @@ public record AnnotationResultCompareResponse( @Schema(description = "提取模型答案", example = "3天") String extractAnswer, @Schema(description = "校验模型答案", example = "72小时") String verifyAnswer, @Schema(description = "差异原因", example = "时间单位不一致") String diffReason, - @Schema(description = "合并后的最终答案", example = "72小时(3天)") String mergedAnswer - ) {} + @Schema(description = "合并后的最终答案", example = "72小时(3天)") String mergedAnswer, + @Schema(description = "问题分类") String questionCategory, + @Schema(description = "评分") Scores scores + ) { + } + + @Schema(description = "评分结构") + public record Scores( + @Schema(description = "相似度", example = "0.7") Double similarity, + @Schema(description = "置信度1", example = "0.9") Double confidence1, + @Schema(description = "置信度2", example = "0.85") Double confidence2, + @Schema(description = "幻觉检测", example = "0.9") Double hallucination, + @Schema(description = "信任度", example = "0.64") Double trust + ) { + } } \ No newline at end of file diff --git a/src/main/java/com/labelsys/backend/dto/response/AnnotationResultDetailResponse.java b/src/main/java/com/labelsys/backend/dto/response/AnnotationResultDetailResponse.java index ff0c62a..9fa3257 100644 --- a/src/main/java/com/labelsys/backend/dto/response/AnnotationResultDetailResponse.java +++ b/src/main/java/com/labelsys/backend/dto/response/AnnotationResultDetailResponse.java @@ -25,8 +25,6 @@ public record AnnotationResultDetailResponse( @Schema(description = "问答内容") QaContentDto qaContent, @Schema(description = "差异摘要(需要审核时有值)") DiffContentDto diffSummary, - @Schema(description = "审核备注", example = "需统一时间字段口径。") String reviewComment, - @Schema(description = "审核时间", example = "2026-04-27T11:00:00") LocalDateTime reviewedAt, @Schema(description = "创建时间", example = "2026-04-27T10:40:00") LocalDateTime createdAt ) { @Schema(description = "问答内容结构") @@ -38,9 +36,23 @@ public record AnnotationResultDetailResponse( @Schema(description = "问答记录") public record QaRecordDto( @Schema(description = "记录ID", example = "q1") String id, + @Schema(description = "批次ID", example = "50") Long batchId, @Schema(description = "问题", example = "产品重量是多少?") String question, @Schema(description = "答案", example = "5kg") String answer, - @Schema(description = "是否需要审核", example = "false") Boolean requiresReview + @Schema(description = "是否需要审核", example = "false") Boolean requiresReview, + @Schema(description = "源片段信息") SourceSegmentsDto sourceSegments, + @Schema(description = "问题分类") String questionCategory, + @Schema(description = "评分") ScoresDto scores, + @Schema(description = "审核评论") String reviewComment + ) { + } + + @Schema(description = "源片段信息") + public record SourceSegmentsDto( + @Schema(description = "片段内容") String segment, + @Schema(description = "块索引", example = "0") Integer chunkIndex, + @Schema(description = "块标题") String chunkTitle, + @Schema(description = "块内容") String chunkContent ) { } @@ -57,7 +69,19 @@ public record AnnotationResultDetailResponse( @Schema(description = "抽取答案", example = "2年") String extractAnswer, @Schema(description = "验证答案", example = "3年") String verifyAnswer, @Schema(description = "差异原因", example = "抽取与验证结果不一致") String diffReason, - @Schema(description = "合并后答案") String mergedAnswer + @Schema(description = "合并后答案") String mergedAnswer, + @Schema(description = "问题分类") String questionCategory, + @Schema(description = "评分") ScoresDto scores + ) { + } + + @Schema(description = "评分结构") + public record ScoresDto( + @Schema(description = "相似度", example = "0.7") Double similarity, + @Schema(description = "置信度1", example = "0.9") Double confidence1, + @Schema(description = "置信度2", example = "0.85") Double confidence2, + @Schema(description = "幻觉检测", example = "0.9") Double hallucination, + @Schema(description = "信任度", example = "0.64") Double trust ) { } } \ No newline at end of file diff --git a/src/main/java/com/labelsys/backend/dto/response/AnnotationResultHistoryDetailResponse.java b/src/main/java/com/labelsys/backend/dto/response/AnnotationResultHistoryDetailResponse.java new file mode 100644 index 0000000..2b2571f --- /dev/null +++ b/src/main/java/com/labelsys/backend/dto/response/AnnotationResultHistoryDetailResponse.java @@ -0,0 +1,63 @@ +package com.labelsys.backend.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "归档历史详情响应") +public record AnnotationResultHistoryDetailResponse( + @Schema(description = "历史记录ID", example = "901") Long id, + @Schema(description = "任务ID", example = "191000000000000301") Long taskId, + @Schema(description = "任务名称", example = "产品说明书标注") String taskName, + @Schema(description = "资源ID", example = "191000000000000101") Long resourceId, + @Schema(description = "资源名称", example = "产品A说明书.pdf") String resourceName, + + @Schema(description = "问答内容文件路径", example = "annotation-results/2/qa/802.json") String qaContentFilePath, + @Schema(description = "问答内容") QaContentDto qaContent, + @Schema(description = "归档原因", example = "审核通过后归档") String archiveReason, + @Schema(description = "归档操作人ID", example = "5") Long archivedBy, + @Schema(description = "归档时间", example = "2026-05-06T10:30:00") LocalDateTime archivedAt, + @Schema(description = "创建时间", example = "2026-05-06T10:30:00") LocalDateTime createdAt, + @Schema(description = "审核人ID,自动归档时为null", example = "5") Long reviewerId, + @Schema(description = "审核人姓名,自动归档时为auto", example = "张三") String reviewerName +) { + @Schema(description = "问答内容结构") + public record QaContentDto( + @Schema(description = "问答记录列表") List records + ) { + } + + @Schema(description = "问答记录") + public record QaRecordDto( + @Schema(description = "记录ID", example = "q1") String id, + @Schema(description = "批次ID", example = "50") Long batchId, + @Schema(description = "问题", example = "产品重量是多少?") String question, + @Schema(description = "答案", example = "5kg") String answer, + @Schema(description = "是否需要审核", example = "false") Boolean requiresReview, + @Schema(description = "源片段信息") SourceSegmentsDto sourceSegments, + @Schema(description = "问题分类") String questionCategory, + @Schema(description = "评分") ScoresDto scores, + @Schema(description = "审核评论") String reviewComment + ) { + } + + @Schema(description = "源片段信息") + public record SourceSegmentsDto( + @Schema(description = "片段内容") String segment, + @Schema(description = "块索引", example = "0") Integer chunkIndex, + @Schema(description = "块标题") String chunkTitle, + @Schema(description = "块内容") String chunkContent + ) { + } + + @Schema(description = "评分结构") + public record ScoresDto( + @Schema(description = "相似度", example = "0.7") Double similarity, + @Schema(description = "置信度1", example = "0.9") Double confidence1, + @Schema(description = "置信度2", example = "0.85") Double confidence2, + @Schema(description = "幻觉检测", example = "0.9") Double hallucination, + @Schema(description = "信任度", example = "0.64") Double trust + ) { + } +} \ No newline at end of file diff --git a/src/main/java/com/labelsys/backend/dto/response/AnnotationResultHistoryResponse.java b/src/main/java/com/labelsys/backend/dto/response/AnnotationResultHistoryResponse.java index 025ec2d..38e90b7 100644 --- a/src/main/java/com/labelsys/backend/dto/response/AnnotationResultHistoryResponse.java +++ b/src/main/java/com/labelsys/backend/dto/response/AnnotationResultHistoryResponse.java @@ -20,7 +20,6 @@ public record AnnotationResultHistoryResponse( @Schema(description = "归档时间", example = "2026-05-06T10:30:00") LocalDateTime archivedAt, @Schema(description = "创建时间", example = "2026-05-06T10:30:00") LocalDateTime createdAt, @Schema(description = "审核人ID,自动归档时为null", example = "5") Long reviewerId, - @Schema(description = "审核人姓名,自动归档时为auto", example = "张三") String reviewerName, - @Schema(description = "审核意见,自动归档时为auto", example = "内容符合要求") String reviewerComment + @Schema(description = "审核人姓名,自动归档时为auto", example = "张三") String reviewerName ) { } \ No newline at end of file diff --git a/src/main/java/com/labelsys/backend/dto/response/AnnotationResultResponse.java b/src/main/java/com/labelsys/backend/dto/response/AnnotationResultResponse.java index 171b692..7f045f7 100644 --- a/src/main/java/com/labelsys/backend/dto/response/AnnotationResultResponse.java +++ b/src/main/java/com/labelsys/backend/dto/response/AnnotationResultResponse.java @@ -17,8 +17,6 @@ public record AnnotationResultResponse( @Schema(description = "是否已删除", example = "false") Boolean isDeleted, @Schema(description = "问答内容文件路径", example = "annotation-results/2/qa/801.json") String qaContentFilePath, @Schema(description = "差异摘要文件路径", example = "annotation-results/2/diff/801.json") String diffSummaryFilePath, - @Schema(description = "审核备注", example = "需统一时间字段口径。") String reviewComment, - @Schema(description = "审核时间", example = "2026-04-27T11:00:00") LocalDateTime reviewedAt, @Schema(description = "创建时间", example = "2026-04-27T10:40:00") LocalDateTime createdAt ) { } \ No newline at end of file diff --git a/src/main/java/com/labelsys/backend/entity/AnnotationResult.java b/src/main/java/com/labelsys/backend/entity/AnnotationResult.java index c25c8a4..884f750 100644 --- a/src/main/java/com/labelsys/backend/entity/AnnotationResult.java +++ b/src/main/java/com/labelsys/backend/entity/AnnotationResult.java @@ -56,12 +56,6 @@ public class AnnotationResult { @TableField("reviewer_id") private Long reviewerId; - @TableField("review_comment") - private String reviewComment; - - @TableField("reviewed_at") - private LocalDateTime reviewedAt; - @TableField("created_at") private LocalDateTime createdAt; diff --git a/src/main/java/com/labelsys/backend/entity/AnnotationResultHistory.java b/src/main/java/com/labelsys/backend/entity/AnnotationResultHistory.java index 06b3f2f..b196baf 100644 --- a/src/main/java/com/labelsys/backend/entity/AnnotationResultHistory.java +++ b/src/main/java/com/labelsys/backend/entity/AnnotationResultHistory.java @@ -37,5 +37,4 @@ public class AnnotationResultHistory { // 新增审核人相关字段 private Long reviewerId; private String reviewerName; - private String reviewerComment; } \ No newline at end of file diff --git a/src/main/java/com/labelsys/backend/mapper/AnnotationResultMapper.java b/src/main/java/com/labelsys/backend/mapper/AnnotationResultMapper.java index bdda150..448ddd4 100644 --- a/src/main/java/com/labelsys/backend/mapper/AnnotationResultMapper.java +++ b/src/main/java/com/labelsys/backend/mapper/AnnotationResultMapper.java @@ -11,7 +11,5 @@ public interface AnnotationResultMapper extends BaseMapper { int markArchived(@Param("id") Long id, @Param("companyId") Long companyId, - @Param("reviewerId") Long reviewerId, - @Param("reviewComment") String reviewComment, - @Param("reviewedAt") LocalDateTime reviewedAt); + @Param("reviewerId") Long reviewerId); } \ No newline at end of file diff --git a/src/main/java/com/labelsys/backend/service/AnnotationResultArchiveService.java b/src/main/java/com/labelsys/backend/service/AnnotationResultArchiveService.java index 4570421..f3ea0bd 100644 --- a/src/main/java/com/labelsys/backend/service/AnnotationResultArchiveService.java +++ b/src/main/java/com/labelsys/backend/service/AnnotationResultArchiveService.java @@ -8,6 +8,7 @@ import com.labelsys.backend.common.exception.BusinessException; import com.labelsys.backend.context.LoginUser; import com.labelsys.backend.dto.common.PageResult; import com.labelsys.backend.dto.request.AnnotationResultHistoryPageQuery; +import com.labelsys.backend.dto.response.AnnotationResultHistoryDetailResponse; import com.labelsys.backend.dto.response.AnnotationResultHistoryResponse; import com.labelsys.backend.dto.response.FileContentResponse; import com.labelsys.backend.dto.response.MergeReviewResultResponse; @@ -29,6 +30,8 @@ import java.time.Duration; import java.time.LocalDateTime; import java.util.List; +import static org.springframework.util.StringUtils.hasText; + @Slf4j @Service @RequiredArgsConstructor @@ -66,6 +69,7 @@ public class AnnotationResultArchiveService { var page = new Page(query.pageNo(), query.pageSize()); var resultPage = annotationResultHistoryMapper.selectPage(page, wrapper); + // 分页查询不加载 qa 内容 var records = resultPage.getRecords().stream() .map(this::toResponse) .toList(); @@ -79,14 +83,17 @@ public class AnnotationResultArchiveService { } } - public AnnotationResultHistoryResponse getHistory(LoginUser currentUser, Long historyId) { + public AnnotationResultHistoryDetailResponse getHistory(LoginUser currentUser, Long historyId) { try { AnnotationResultHistory history = annotationResultHistoryMapper.selectById(historyId); if (history == null || !history.getCompanyId().equals(currentUser.companyId())) { throw new BusinessException(ResultCode.NOT_FOUND, "历史记录不存在"); } assertHistoryPermission(currentUser, history); - return toResponse(history); + + // 详情查询加载 QA 内容 + QaContent qaContent = loadQaContent(history.getQaContentFilePath()); + return toDetailResponse(history, qaContent); } catch (Exception e) { log.error("getHistory failed, companyId={}, userId={}, historyId={}, error={}", currentUser.companyId(), currentUser.userId(), historyId, e.getMessage(), e); @@ -104,19 +111,66 @@ public class AnnotationResultArchiveService { private AnnotationResultHistoryResponse toResponse(AnnotationResultHistory history) { return new AnnotationResultHistoryResponse( history.getId(), - // history.getSourceResultId(), history.getTaskId(), - history.getTaskName(), // 新增 + history.getTaskName(), history.getResourceId(), - history.getResourceName(), // 新增 + history.getResourceName(), history.getQaContentFilePath(), history.getArchiveReason(), history.getArchivedBy(), history.getArchivedAt(), history.getCreatedAt(), history.getReviewerId(), - history.getReviewerName(), - history.getReviewerComment() + history.getReviewerName() + ); + } + + private AnnotationResultHistoryDetailResponse toDetailResponse(AnnotationResultHistory history, + QaContent qaContent) { + // 转换 QA 内容 + AnnotationResultHistoryDetailResponse.QaContentDto qaContentDto = null; + if (qaContent != null && qaContent.records() != null) { + qaContentDto = new AnnotationResultHistoryDetailResponse.QaContentDto( + qaContent.records().stream() + .map(r -> new AnnotationResultHistoryDetailResponse.QaRecordDto( + r.id(), + r.batchId(), + r.question(), + r.answer(), + r.requiresReview(), + r.sourceSegments() != null ? + new AnnotationResultHistoryDetailResponse.SourceSegmentsDto( + r.sourceSegments().segment(), + r.sourceSegments().chunkIndex(), + r.sourceSegments().chunkTitle(), + r.sourceSegments().chunkContent()) : + null, + r.questionCategory(), + r.scores() != null ? new AnnotationResultHistoryDetailResponse.ScoresDto( + r.scores().similarity(), + r.scores().confidence1(), + r.scores().confidence2(), + r.scores().hallucination(), + r.scores().trust()) : null, + r.reviewComment())) + .toList() + ); + } + + return new AnnotationResultHistoryDetailResponse( + history.getId(), + history.getTaskId(), + history.getTaskName(), + history.getResourceId(), + history.getResourceName(), + history.getQaContentFilePath(), + qaContentDto, + history.getArchiveReason(), + history.getArchivedBy(), + history.getArchivedAt(), + history.getCreatedAt(), + history.getReviewerId(), + history.getReviewerName() ); } @@ -124,13 +178,14 @@ public class AnnotationResultArchiveService { public int autoArchiveEligibleResults() { try { LocalDateTime cutoff = LocalDateTime.now().minus(autoArchiveTimeout); - List results = annotationResultMapper.selectList(new LambdaQueryWrapper() - .eq(AnnotationResult::getIsDeleted, false) - .eq(AnnotationResult::getRequiresManualReview, false) - .lt(AnnotationResult::getCreatedAt, cutoff)); + List results = annotationResultMapper.selectList( + new LambdaQueryWrapper() + .eq(AnnotationResult::getIsDeleted, false) + .eq(AnnotationResult::getRequiresManualReview, false) + .lt(AnnotationResult::getCreatedAt, cutoff)); int archivedCount = 0; for (AnnotationResult result : results) { - if (archiveRuntimeResult(result, null, "AUTO_ARCHIVE", null) != null) { + if (archiveRuntimeResult(result, null, "AUTO_ARCHIVE") != null) { archivedCount++; } } @@ -153,8 +208,7 @@ public class AnnotationResultArchiveService { */ private MergeReviewResultResponse archiveRuntimeResult(AnnotationResult result, Long reviewerId, - String archiveReason, - String reviewComment) { + String archiveReason) { LocalDateTime archivedAt = LocalDateTime.now(); // 从对象存储读取 qa.json 内容 @@ -177,22 +231,64 @@ public class AnnotationResultArchiveService { .archivedAt(archivedAt) .reviewerId(null) .reviewerName("auto") - .reviewerComment("auto") .build(); annotationResultHistoryMapper.insert(history); int updated = annotationResultMapper.markArchived( result.getId(), result.getCompanyId(), - reviewerId, - reviewComment, - archivedAt); + reviewerId); if (updated == 0) { return null; } return new MergeReviewResultResponse(result.getId(), history.getId(), archiveReason, archivedAt); } + /** + * 加载 QA 内容 + */ + private QaContent loadQaContent(String filePath) { + try { + if (!hasText(filePath)) { + log.warn("QA content file path is empty"); + return new QaContent(null, null, List.of(), null); + } + String bucketName = extractBucketName(filePath); + String objectKey = extractObjectKey(filePath); + byte[] content = objectStorageService.download(bucketName, objectKey); + String jsonContent = new String(content, StandardCharsets.UTF_8); + return objectMapper.readValue(jsonContent, new com.fasterxml.jackson.core.type.TypeReference() { + }); + } catch (Exception e) { + log.warn("Failed to load QA content, returning empty content. filePath={}, error={}", filePath, + e.getMessage()); + return new QaContent(null, null, List.of(), null); + } + } + + // 内部类:qa.json 结构 + private record QaContent( + Long taskId, + Long resourceId, + List records, + Metadata metadata + ) { + private record QaRecord(String id, Long batchId, String question, String answer, + Boolean requiresReview, SourceSegments sourceSegments, + String questionCategory, Scores scores, String reviewComment) { + } + + private record SourceSegments(String segment, Integer chunkIndex, String chunkTitle, String chunkContent) { + } + + private record Scores(Double similarity, Double confidence1, Double confidence2, + Double hallucination, Double trust) { + } + + private record Metadata(String createdAt, String updatedAt) { + } + } + /** * 从对象存储读取 qa.json 内容 */ @@ -239,8 +335,9 @@ public class AnnotationResultArchiveService { /** * 加载归档记录的文件内容 + * * @param currentUser 当前用户 - * @param historyId 历史记录ID + * @param historyId 历史记录ID * @return 文件内容响应 */ public FileContentResponse loadFileContent(LoginUser currentUser, Long historyId) { @@ -250,17 +347,17 @@ public class AnnotationResultArchiveService { throw new BusinessException(ResultCode.NOT_FOUND, "历史记录不存在"); } //assertHistoryPermission(currentUser, history); - + String filePath = history.getQaContentFilePath(); if (filePath == null || filePath.isEmpty()) { throw new BusinessException(ResultCode.ERROR, "文件路径为空"); } - + String bucketName = extractBucketName(filePath); String objectKey = extractObjectKey(filePath); byte[] content = objectStorageService.download(bucketName, objectKey); String contentStr = new String(content, StandardCharsets.UTF_8); - + return new FileContentResponse(filePath, contentStr, content.length); } catch (BusinessException e) { throw e; @@ -270,4 +367,5 @@ public class AnnotationResultArchiveService { throw new BusinessException(ResultCode.ERROR, "加载文件内容失败"); } } + } \ No newline at end of file diff --git a/src/main/java/com/labelsys/backend/service/AnnotationResultService.java b/src/main/java/com/labelsys/backend/service/AnnotationResultService.java index e8a6eb5..c125a6d 100644 --- a/src/main/java/com/labelsys/backend/service/AnnotationResultService.java +++ b/src/main/java/com/labelsys/backend/service/AnnotationResultService.java @@ -116,7 +116,24 @@ public class AnnotationResultService { AnnotationResultDetailResponse.QaContentDto qaContentDto = new AnnotationResultDetailResponse.QaContentDto( qaContent.records().stream() .map(r -> new AnnotationResultDetailResponse.QaRecordDto( - r.id(), r.question(), r.answer(), r.requiresReview())) + r.id(), + r.batchId(), + r.question(), + r.answer(), + r.requiresReview(), + r.sourceSegments() != null ? new AnnotationResultDetailResponse.SourceSegmentsDto( + r.sourceSegments().segment(), + r.sourceSegments().chunkIndex(), + r.sourceSegments().chunkTitle(), + r.sourceSegments().chunkContent()) : null, + r.questionCategory(), + r.scores() != null ? new AnnotationResultDetailResponse.ScoresDto( + r.scores().similarity(), + r.scores().confidence1(), + r.scores().confidence2(), + r.scores().hallucination(), + r.scores().trust()) : null, + r.reviewComment())) .toList() ); @@ -127,7 +144,14 @@ public class AnnotationResultService { diffContent.records().stream() .map(r -> new AnnotationResultDetailResponse.DiffRecordDto( r.qaId(), r.question(), r.extractAnswer(), - r.verifyAnswer(), r.diffReason(), r.mergedAnswer())) + r.verifyAnswer(), r.diffReason(), r.mergedAnswer(), + r.questionCategory(), + r.scores() != null ? new AnnotationResultDetailResponse.ScoresDto( + r.scores().similarity(), + r.scores().confidence1(), + r.scores().confidence2(), + r.scores().hallucination(), + r.scores().trust()) : null)) .toList() ); } @@ -145,8 +169,6 @@ public class AnnotationResultService { result.getDiffSummaryFilePath(), qaContentDto, diffContentDto, - result.getReviewComment(), - result.getReviewedAt(), result.getCreatedAt() ); } @@ -173,9 +195,23 @@ public class AnnotationResultService { List qaRecords = qaContent.records().stream() .map(qa -> new AnnotationResultCompareResponse.QaRecord( qa.id(), + qa.batchId(), qa.question(), qa.answer(), - qa.requiresReview() + qa.requiresReview(), + qa.sourceSegments() != null ? new AnnotationResultCompareResponse.SourceSegments( + qa.sourceSegments().segment(), + qa.sourceSegments().chunkIndex(), + qa.sourceSegments().chunkTitle(), + qa.sourceSegments().chunkContent()) : null, + qa.questionCategory(), + qa.scores() != null ? new AnnotationResultCompareResponse.Scores( + qa.scores().similarity(), + qa.scores().confidence1(), + qa.scores().confidence2(), + qa.scores().hallucination(), + qa.scores().trust()) : null, + qa.reviewComment() )).toList(); // 转换差异记录 @@ -187,7 +223,14 @@ public class AnnotationResultService { diff.extractAnswer(), diff.verifyAnswer(), diff.diffReason(), - diff.mergedAnswer() + diff.mergedAnswer(), + diff.questionCategory(), + diff.scores() != null ? new AnnotationResultCompareResponse.Scores( + diff.scores().similarity(), + diff.scores().confidence1(), + diff.scores().confidence2(), + diff.scores().hallucination(), + diff.scores().trust()) : null )).toList() : List.of(); return new AnnotationResultCompareResponse( @@ -220,16 +263,22 @@ public class AnnotationResultService { // 读取当前 qa.json QaContent qaContent = loadQaContent(result); - // 更新 qa.json 的 answer 字段 + // 更新 qa.json 的 answer 字段和 reviewComment List updatedQaRecords = qaContent.records().stream() .map(record -> { String mergedAnswer = request.mergedAnswers().get(record.id()); - if (mergedAnswer != null) { + String reviewComment = request.reviewComments() != null ? request.reviewComments().get(record.id()) : null; + if (mergedAnswer != null || reviewComment != null) { return new QaContent.QaRecord( record.id(), + record.batchId(), record.question(), - mergedAnswer, - false + mergedAnswer != null ? mergedAnswer : record.answer(), + false, + record.sourceSegments(), + record.questionCategory(), + record.scores(), + reviewComment != null ? reviewComment : record.reviewComment() ); } return record; @@ -247,11 +296,18 @@ public class AnnotationResultService { ); saveQaContent(result, updatedQaContent); - // 更新数据库记录 - result.setIsDeleted(Boolean.TRUE); - result.setReviewerId(currentUser.userId()); - result.setReviewComment(request.reviewComment()); - result.setReviewedAt(LocalDateTime.now()); + // 更新数据库记录(使用 markArchived 保证幂等性,防止并发重复归档) + int updated = annotationResultMapper.markArchived( + result.getId(), + currentUser.companyId(), + currentUser.userId()); + + if (updated == 0) { + // 记录已被其他进程归档 + throw new BusinessException(ResultCode.CONFLICT, "记录已被归档"); + } + + // 更新 requires_manual_review 字段 result.setRequiresManualReview(false); annotationResultMapper.updateById(result); @@ -281,8 +337,6 @@ public class AnnotationResultService { result.getIsDeleted(), result.getQaContentFilePath(), result.getDiffSummaryFilePath(), - result.getReviewComment(), - result.getReviewedAt(), result.getCreatedAt() ); } @@ -392,17 +446,15 @@ public class AnnotationResultService { // 根据归档类型设置审核人信息 if (isAutoArchive) { - // 自动归档:reviewer_id为NULL,name和comment为"auto" + // 自动归档:reviewer_id为NULL,name为"auto" historyBuilder .reviewerId(null) - .reviewerName("auto") - .reviewerComment("auto"); + .reviewerName("auto"); } else { // 人工审核后归档:使用审核人信息 historyBuilder .reviewerId(result.getReviewerId()) - .reviewerName(currentUser.realName()) - .reviewerComment(result.getReviewComment()); + .reviewerName(currentUser.realName()); } annotationResultHistoryMapper.insert(historyBuilder.build()); @@ -436,7 +488,16 @@ public class AnnotationResultService { List records, Metadata metadata ) { - private record QaRecord(String id, String question, String answer, Boolean requiresReview) { + private record QaRecord(String id, Long batchId, String question, String answer, + Boolean requiresReview, SourceSegments sourceSegments, + String questionCategory, Scores scores, String reviewComment) { + } + + private record SourceSegments(String segment, Integer chunkIndex, String chunkTitle, String chunkContent) { + } + + private record Scores(Double similarity, Double confidence1, Double confidence2, + Double hallucination, Double trust) { } private record Metadata(String createdAt, String updatedAt) { @@ -451,7 +512,12 @@ public class AnnotationResultService { Metadata metadata ) { private record DiffRecord(String qaId, String question, String extractAnswer, - String verifyAnswer, String diffReason, String mergedAnswer) { + String verifyAnswer, String diffReason, String mergedAnswer, + String questionCategory, Scores scores) { + } + + private record Scores(Double similarity, Double confidence1, Double confidence2, + Double hallucination, Double trust) { } private record Metadata(String createdAt) { diff --git a/src/main/resources/mapper/AnnotationResultMapper.xml b/src/main/resources/mapper/AnnotationResultMapper.xml index 76dd7b9..ec18f1a 100644 --- a/src/main/resources/mapper/AnnotationResultMapper.xml +++ b/src/main/resources/mapper/AnnotationResultMapper.xml @@ -13,16 +13,14 @@ - - id, company_id, creator_id, creator_role, task_id, resource_id, qa_content_file_path, - diff_summary_file_path, requires_manual_review, is_deleted, reviewer_id, review_comment, - reviewed_at, created_at, updated_at + diff_summary_file_path, requires_manual_review, is_deleted, reviewer_id, + created_at, updated_at