feat(llm): 增加知识库命中率测试功能

- 新增 KnowledgeHitRateTestReqVO 和 KnowledgeHitRateTestResultVO 类用于命中率测试请求和响应- 在 KnowledgeBaseController 中添加 executeHitRateTest 方法处理命中率测试请求
- 在 KnowledgeBaseService接口中定义 executeHitRateTest 方法
- 在 KnowledgeBaseServiceImpl 中实现 executeHitRateTest 方法,包括查询知识库文档、调用 RAG 查询接口和解析结果
- 新增 DocumentInfoVO、MetadataVO、QueryMultipleReqVO 和 QueryResultPairVO 类用于 RAG 查询请求和响应
- 修改 AsyncKnowledgeBase 和 RagHttpService 以支持命中率测试功能
This commit is contained in:
Liuyang 2025-03-13 14:46:22 +08:00
parent 0018c535a7
commit 52f0a6a463
11 changed files with 338 additions and 19 deletions

View File

@ -6,9 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.llm.controller.admin.knowledgebase.vo.KnowledgeBasePageReqVO;
import cn.iocoder.yudao.module.llm.controller.admin.knowledgebase.vo.KnowledgeBaseRespVO;
import cn.iocoder.yudao.module.llm.controller.admin.knowledgebase.vo.KnowledgeBaseSaveReqVO;
import cn.iocoder.yudao.module.llm.controller.admin.knowledgebase.vo.*;
import cn.iocoder.yudao.module.llm.dal.dataobject.knowledgebase.KnowledgeBaseDO;
import cn.iocoder.yudao.module.llm.service.knowledgebase.KnowledgeBaseService;
import io.swagger.v3.oas.annotations.Operation;
@ -43,6 +41,20 @@ public class KnowledgeBaseController {
return success(knowledgeBaseService.createKnowledgeBase(createReqVO));
}
/**
* 执行知识库命中测试
*
* @param testReqVO 命中测试请求参数
* @return 命中测试结果
*/
@PostMapping("/hit-test")
@Operation(summary = "执行知识库命中测试")
public CommonResult<List<KnowledgeHitRateTestResultVO>> executeHitRateTest(
@Valid @RequestBody KnowledgeHitRateTestReqVO testReqVO) {
List<KnowledgeHitRateTestResultVO> result = knowledgeBaseService.executeHitRateTest(testReqVO);
return success(result);
}
@PutMapping("/update")
@Operation(summary = "更新知识库")
// @PreAuthorize("@ss.hasPermission('llm:knowledge-base:update')")

View File

@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.llm.controller.admin.knowledgebase.vo;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @Description 知识库命中率测试请求参数
*/
@Data
public class KnowledgeHitRateTestReqVO {
/**
* 查询内容
*/
@NotNull(message = "查询内容不能为空")
private String query;
/**
* 知识库ID
*/
@NotNull(message = "知识库ID不能为空")
private Long knowledgeId;
/**
* 返回结果的条数k值
*/
// @NotNull(message = "k值不能为空")
private Integer k;
}

View File

@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.llm.controller.admin.knowledgebase.vo;
import lombok.Data;
/**
* @Description 知识库命中率测试返回结果
*/
@Data
public class KnowledgeHitRateTestResultVO {
/**
* 页面内容
*/
private String pageContent;
/**
* 命中率
*/
private Double hitRate;
/**
* 摘要信息
*/
private String digest;
/**
* 文件ID
*/
private Long fileId;
}

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.llm.service.async;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.llm.controller.admin.knowledgebase.vo.KnowledgeHitRateTestResultVO;
import cn.iocoder.yudao.module.llm.dal.dataobject.knowledgedocuments.KnowledgeDocumentsDO;
import cn.iocoder.yudao.module.llm.dal.mysql.knowledgedocuments.KnowledgeDocumentsMapper;
import cn.iocoder.yudao.module.llm.enums.KnowledgeStatusEnum;
@ -9,12 +10,16 @@ import cn.iocoder.yudao.module.llm.framework.backend.config.LLMBackendProperties
import cn.iocoder.yudao.module.llm.service.http.RagHttpService;
import cn.iocoder.yudao.module.llm.service.http.vo.KnowledgeRagEmbedReqVO;
import cn.iocoder.yudao.module.llm.service.http.vo.RegUploadReqVO;
import cn.iocoder.yudao.module.llm.service.http.vo.query.multiple.QueryMultipleReqVO;
import cn.iocoder.yudao.module.llm.service.http.vo.query.multiple.QueryResultPairVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@ -133,4 +138,24 @@ public class AsyncKnowledgeBase {
}
}
public List<KnowledgeHitRateTestResultVO> executeHitRateTest (String query, List<Long> fileIds, Integer k) {
QueryMultipleReqVO vo = new QueryMultipleReqVO();
vo.setQuery(query);
vo.setFileIds(Collections.singletonList(String.valueOf(fileIds)));
vo.setK(k);
List<KnowledgeHitRateTestResultVO> resultList = new ArrayList<>();
List<QueryResultPairVO> result = ragHttpService.executeHitRateTest(vo);
for (QueryResultPairVO pair : result) {
KnowledgeHitRateTestResultVO resultVO = new KnowledgeHitRateTestResultVO();
resultVO.setPageContent(pair.getDocument().getPageContent());
resultVO.setHitRate(pair.getHitRate());
resultVO.setDigest(pair.getDocument().getMetadata().getDigest());
resultVO.setFileId(Long.parseLong(pair.getDocument().getMetadata().getFileId()));
resultList.add(resultVO);
}
return resultList;
}
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.llm.service.http;
import cn.hutool.http.Header;
import cn.hutool.http.HttpRequest;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
@ -11,6 +12,9 @@ import cn.iocoder.yudao.module.llm.dal.mysql.knowledgedocuments.KnowledgeDocumen
import cn.iocoder.yudao.module.llm.enums.KnowledgeStatusEnum;
import cn.iocoder.yudao.module.llm.framework.backend.config.LLMBackendProperties;
import cn.iocoder.yudao.module.llm.service.http.vo.*;
import cn.iocoder.yudao.module.llm.service.http.vo.query.multiple.DocumentInfoVO;
import cn.iocoder.yudao.module.llm.service.http.vo.query.multiple.QueryMultipleReqVO;
import cn.iocoder.yudao.module.llm.service.http.vo.query.multiple.QueryResultPairVO;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONException;
@ -49,6 +53,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -393,7 +398,7 @@ public class RagHttpService {
Integer chunkOverlap = Optional.ofNullable(reqVO.getChunkOverlap()).orElse(300);
String mediaType = getMediaType(fileName);
log.info("文件ID: {}, 文件名: {}, 文件URL: {}, 文件类型: {}, 分块大小:{}, 分块重叠:{}", fileId, fileName, fileUrl, mediaType,chunkSize,chunkOverlap);
log.info("文件ID: {}, 文件名: {}, 文件URL: {}, 文件类型: {}, 分块大小:{}, 分块重叠:{}", fileId, fileName, fileUrl, mediaType, chunkSize, chunkOverlap);
// 获取知识库文档
log.info("开始获取知识库文档知识库ID: {}, 文件ID: {}", id, fileId);
@ -418,15 +423,15 @@ public class RagHttpService {
Path tempFilePath = downloadFileToTemp(fileUrl, fileName);
log.info("文件已下载到临时目录: {}", tempFilePath);
// String fileSuffix = getFileSuffix(fileName);
// if ("doc".equals(fileSuffix)) {
// log.info("正在处理 doc 文件");
// try {
// tempFilePath = converterDocToDocx(tempFilePath.toString(), tempFilePath.toString().replace(".doc", ".docx"));
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
// }
// String fileSuffix = getFileSuffix(fileName);
// if ("doc".equals(fileSuffix)) {
// log.info("正在处理 doc 文件");
// try {
// tempFilePath = converterDocToDocx(tempFilePath.toString(), tempFilePath.toString().replace(".doc", ".docx"));
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
// }
// if ("md".equals(fileSuffix)) {
// log.info("正在处理 md 文件");
@ -730,4 +735,79 @@ public class RagHttpService {
private KnowledgeDocumentsDO getKnowledgeDocuments (String fileId) {
return knowledgeDocumentsMapper.selectById(fileId);
}
public List<QueryResultPairVO> executeHitRateTest (QueryMultipleReqVO vo) {
String jsonString = JSON.toJSONString(vo);
String url = llmBackendProperties.getRagQueryMultiple();
//链式构建请求
String result2 = HttpRequest.post(url)
.header(Header.ACCEPT, "application/json")
.header(Header.CONTENT_TYPE, "application/json")
.body(jsonString)
.timeout(20000)
.execute().body();
cn.hutool.core.lang.Console.log(result2);
return parseHitRateTestResults(result2);
}
private static List<QueryResultPairVO> parseHitRateTestResults (String json) {
// JSON 转换为 List<QueryResultPair>
// 解析 JSON 数组
JSONArray jsonArray = JSON.parseArray(json);
// 创建结果列表
List<QueryResultPairVO> results = new ArrayList<>();
// 遍历 JSON 数组
for (int i = 0; i < jsonArray.size(); i++) {
JSONArray pairArray = jsonArray.getJSONArray(i);
// 解析文档信息
JSONObject documentJson = pairArray.getJSONObject(0);
DocumentInfoVO document = JSON.parseObject(documentJson.toJSONString(), DocumentInfoVO.class);
// 解析命中率
double hitRate = pairArray.getDoubleValue(1);
// 创建 QueryResultPair 对象并添加到结果列表
QueryResultPairVO pair = new QueryResultPairVO();
pair.setDocument(document);
pair.setHitRate(hitRate);
results.add(pair);
}
// // 访问数据
// for (QueryResultPairVO pair : results) {
// System.out.println("Page Content: " + pair.getDocument().getPageContent());
// System.out.println("Hit Rate: " + pair.getHitRate());
// System.out.println("File ID: " + pair.getDocument().getMetadata().getFileId());
// System.out.println("----------------------");
// }
return results;
}
public static void main (String[] args) {
List<String> ids = new ArrayList<>();
ids.add("1111");
ids.add("1234");
QueryMultipleReqVO vo = new QueryMultipleReqVO();
vo.setQuery("可乐鸡翅怎么做");
vo.setFileIds(ids);
vo.setK(4);
String jsonString = JSON.toJSONString(vo);
String url = "http://192.168.18.66:8123/query_multiple";
//链式构建请求
String result2 = HttpRequest.post(url)
.header(Header.ACCEPT, "application/json")
.header(Header.CONTENT_TYPE, "application/json")
.body(jsonString)
.timeout(20000)
.execute().body();
cn.hutool.core.lang.Console.log(result2);
// extracted(result2);
}
}

View File

@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.llm.service.http.vo.query.multiple;
import lombok.Data;
/**
* @Description 文档信息类
*/
@Data
public class DocumentInfoVO {
/**
* 文档ID可为空
*/
private String id;
/**
* 元数据
*/
private MetadataVO metadata;
/**
* 页面内容
*/
private String pageContent;
/**
* 文档类型
*/
private String type;
}

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.llm.service.http.vo.query.multiple;
import lombok.Data;
/**
* @Description 文档元数据类
*/
@Data
public class MetadataVO {
/**
* 文件ID
*/
private String fileId;
/**
* 用户ID
*/
private String userId;
/**
* 文件摘要
*/
private String digest;
/**
* 文件来源路径
*/
private String source;
}

View File

@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.llm.service.http.vo.query.multiple;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* @Description 知识库多文件查询
*/
@Data
public class QueryMultipleReqVO {
/**
* 查询内容
*/
@NotNull(message = "查询内容不能为空")
private String query;
/**
* 文件ID列表
*/
@NotNull(message = "文件ID列表不能为空")
@JSONField(name = "file_ids")
private List<String> fileIds;
/**
* 返回结果的条数k值
*/
// @NotNull(message = "k值不能为空")
private Integer k;
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.llm.service.http.vo.query.multiple;
import lombok.Data;
/**
* @Description 查询结果对包含文档信息和命中率
*/
@Data
public class QueryResultPairVO {
/**
* 文档信息
*/
private DocumentInfoVO document;
/**
* 命中率
*/
private Double hitRate;
}

View File

@ -1,9 +1,7 @@
package cn.iocoder.yudao.module.llm.service.knowledgebase;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.llm.controller.admin.knowledgebase.vo.KnowledgeBasePageReqVO;
import cn.iocoder.yudao.module.llm.controller.admin.knowledgebase.vo.KnowledgeBaseRespVO;
import cn.iocoder.yudao.module.llm.controller.admin.knowledgebase.vo.KnowledgeBaseSaveReqVO;
import cn.iocoder.yudao.module.llm.controller.admin.knowledgebase.vo.*;
import cn.iocoder.yudao.module.llm.dal.dataobject.knowledgebase.KnowledgeBaseDO;
import javax.validation.Valid;
@ -70,4 +68,11 @@ public interface KnowledgeBaseService {
* @param updateReqVO 更新信息
*/
void updateKnowledgeBaseInfo (@Valid KnowledgeBaseSaveReqVO updateReqVO);
/**
* 执行知识库命中测试
* @param testReqVO 测试信息
* @return 返回结果
*/
List<KnowledgeHitRateTestResultVO> executeHitRateTest (@Valid KnowledgeHitRateTestReqVO testReqVO);
}

View File

@ -5,9 +5,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.llm.controller.admin.knowledgebase.vo.KnowledgeBasePageReqVO;
import cn.iocoder.yudao.module.llm.controller.admin.knowledgebase.vo.KnowledgeBaseRespVO;
import cn.iocoder.yudao.module.llm.controller.admin.knowledgebase.vo.KnowledgeBaseSaveReqVO;
import cn.iocoder.yudao.module.llm.controller.admin.knowledgebase.vo.*;
import cn.iocoder.yudao.module.llm.controller.admin.knowledgedocuments.vo.KnowledgeDocumentsRespVO;
import cn.iocoder.yudao.module.llm.controller.admin.knowledgedocuments.vo.KnowledgeDocumentsSaveReqVO;
import cn.iocoder.yudao.module.llm.dal.dataobject.knowledgebase.KnowledgeBaseDO;
@ -298,6 +296,37 @@ public class KnowledgeBaseServiceImpl implements KnowledgeBaseService {
knowledgeBaseMapper.updateById(knowledgeBaseDO);
}
/**
* 执行知识库命中测试
*
* @param testReqVO 测试信息
* @return 返回结果
*/
@Override
public List<KnowledgeHitRateTestResultVO> executeHitRateTest (KnowledgeHitRateTestReqVO testReqVO) {
Long knowledgeId = testReqVO.getKnowledgeId();
// 根据知识库ID获取参数信息关联文档
List<KnowledgeDocumentsDO> documentsDOS = knowledgeDocumentsMapper.selectList(new LambdaQueryWrapper<KnowledgeDocumentsDO>()
.eq(KnowledgeDocumentsDO::getKnowledgeBaseId, knowledgeId));
if (com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isEmpty(documentsDOS)) {
throw exception(KNOWLEDGE_DOCUMENTS_NOT_EXISTS);
}
// 获取fileId列表
List<Long> fileIds = documentsDOS.stream()
.map(KnowledgeDocumentsDO::getFileId)
.collect(Collectors.toList());
List<KnowledgeHitRateTestResultVO> result = asyncKnowledgeBase.executeHitRateTest(testReqVO.getQuery(), fileIds, testReqVO.getK());
if (com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isEmpty(result)){
return Collections.emptyList();
}
return result;
}
/**
* 校验知识库是否存在
*