适配QA问答对数据结构
This commit is contained in:
@@ -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<AnnotationResultHistory>(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<AnnotationResult> results = annotationResultMapper.selectList(new LambdaQueryWrapper<AnnotationResult>()
|
||||
.eq(AnnotationResult::getIsDeleted, false)
|
||||
.eq(AnnotationResult::getRequiresManualReview, false)
|
||||
.lt(AnnotationResult::getCreatedAt, cutoff));
|
||||
List<AnnotationResult> results = annotationResultMapper.selectList(
|
||||
new LambdaQueryWrapper<AnnotationResult>()
|
||||
.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<QaContent>() {
|
||||
});
|
||||
} 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<QaRecord> 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, "加载文件内容失败");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user