feat: 收敛知识库检索调度与评分语义

- 固定 rag.engine 与 Milvus 配置,补齐启动期检索基础设施校验

- 支持调用方配置 retrievalMode,并统一知识库检索入口与结果来源展示

- 修正关键词检索 knowledgeId 过滤、混合检索评分归一化与本地 ES 默认配置
This commit is contained in:
2026-04-05 20:23:05 +08:00
parent 2592a1f09d
commit b5dd427920
41 changed files with 1260 additions and 600 deletions

View File

@@ -1,6 +1,7 @@
package tech.easyflow.admin.controller.ai;
import org.springframework.web.bind.annotation.PostMapping;
import tech.easyflow.ai.dto.BotKnowledgeBindingRequest;
import tech.easyflow.ai.entity.BotDocumentCollection;
import tech.easyflow.ai.entity.DocumentCollection;
import tech.easyflow.ai.permission.KnowledgeReadAccessSnapshot;
@@ -63,20 +64,21 @@ public class BotDocumentCollectionController extends BaseCurdController<BotDocum
}
@PostMapping("updateBotKnowledgeIds")
public Result<?> save(@JsonBody("botId") BigInteger botId, @JsonBody("knowledgeIds") BigInteger [] knowledgeIds) {
if (knowledgeIds != null) {
for (BigInteger knowledgeId : knowledgeIds) {
if (knowledgeId == null) {
public Result<?> save(@JsonBody("botId") BigInteger botId,
@JsonBody("knowledgeBindings") List<BotKnowledgeBindingRequest> knowledgeBindings) {
if (knowledgeBindings != null) {
for (BotKnowledgeBindingRequest binding : knowledgeBindings) {
if (binding == null || binding.getKnowledgeId() == null) {
continue;
}
DocumentCollection collection = documentCollectionService.getById(knowledgeId);
DocumentCollection collection = documentCollectionService.getById(binding.getKnowledgeId());
if (collection == null) {
continue;
}
resourceAccessService.assertAccess(CategoryResourceType.KNOWLEDGE, collection, ResourceAction.READ, "无权限绑定知识库");
}
}
service.saveBotAndKnowledge(botId, knowledgeIds);
service.saveBotAndKnowledge(botId, knowledgeBindings);
return Result.ok();
}
}

View File

@@ -2,6 +2,7 @@ package tech.easyflow.admin.controller.ai;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.easyagents.core.document.Document;
import com.easyagents.rag.retrieval.RagRetrievalMetadataKeys;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.util.StringUtils;
@@ -12,9 +13,12 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.ai.permission.KnowledgeVisibilityQueryHelper;
import tech.easyflow.ai.documentimport.DocumentImportDtos;
import tech.easyflow.ai.dto.KnowledgeSearchResultItem;
import tech.easyflow.ai.entity.BotDocumentCollection;
import tech.easyflow.ai.entity.DocumentCollection;
import tech.easyflow.ai.entity.Model;
import tech.easyflow.ai.rag.KnowledgeRetrievalRequest;
import tech.easyflow.ai.rag.KnowledgeRetrievalModes;
import tech.easyflow.ai.service.BotDocumentCollectionService;
import tech.easyflow.ai.service.DocumentChunkService;
import tech.easyflow.ai.service.DocumentCollectionService;
@@ -32,11 +36,15 @@ import tech.easyflow.system.service.ResourceAccessService;
import javax.annotation.Resource;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* 控制层。
@@ -105,13 +113,11 @@ public class DocumentCollectionController extends BaseCurdController<DocumentCol
Map<String, Object> options = entity.getOptions() == null
? new HashMap<>()
: new HashMap<>(entity.getOptions());
if (entity.getSearchEngineEnable() == null){
entity.setSearchEngineEnable(false);
}
options.putIfAbsent(DocumentCollection.KEY_CAN_UPDATE_EMBEDDING_MODEL, true);
options.putIfAbsent(DocumentCollection.KEY_RERANK_ENABLE, entity.getRerankModelId() != null);
entity.setOptions(options);
}
normalizeInfrastructureFields(entity, isSave);
return super.onSaveOrUpdateBefore(entity, isSave);
}
@@ -124,8 +130,16 @@ public class DocumentCollectionController extends BaseCurdController<DocumentCol
idExpr = "#knowledgeId",
denyMessage = "无权限访问知识库"
)
public Result<List<Document>> search(@RequestParam BigInteger knowledgeId, @RequestParam String keyword) {
return Result.ok(service.search(knowledgeId, keyword));
public Result<List<KnowledgeSearchResultItem>> search(@RequestParam BigInteger knowledgeId,
@RequestParam String keyword,
@RequestParam(required = false) String retrievalMode) {
KnowledgeRetrievalRequest request = new KnowledgeRetrievalRequest();
request.setKnowledgeId(knowledgeId);
request.setQuery(keyword);
request.setRetrievalMode(KnowledgeRetrievalModes.parse(retrievalMode));
request.setCallerType("API");
request.setCallerId(String.valueOf(knowledgeId));
return Result.ok(toKnowledgeSearchResult(service.search(request)));
}
@@ -234,4 +248,85 @@ public class DocumentCollectionController extends BaseCurdController<DocumentCol
}
return collection;
}
private void normalizeInfrastructureFields(DocumentCollection entity, boolean isSave) {
if (entity == null) {
return;
}
if (isSave) {
entity.setVectorStoreEnable(true);
entity.setVectorStoreType("milvus");
entity.setSearchEngineEnable(true);
entity.setVectorStoreCollection(generateVectorCollectionName());
return;
}
if (entity.getVectorStoreEnable() != null) {
entity.setVectorStoreEnable(true);
}
if (entity.getVectorStoreType() != null) {
entity.setVectorStoreType("milvus");
}
if (entity.getSearchEngineEnable() != null) {
entity.setSearchEngineEnable(true);
}
}
private String generateVectorCollectionName() {
return "kb_" + UUID.randomUUID().toString().replace("-", "").substring(0, 28);
}
private List<KnowledgeSearchResultItem> toKnowledgeSearchResult(List<Document> documents) {
List<KnowledgeSearchResultItem> results = new ArrayList<>();
if (documents == null) {
return results;
}
for (int index = 0; index < documents.size(); index++) {
Document document = documents.get(index);
if (document == null) {
continue;
}
KnowledgeSearchResultItem item = new KnowledgeSearchResultItem();
item.setSorting(index + 1);
item.setContent(document.getContent());
item.setScore(roundScore(document.getScore()));
item.setHitSource(readMetadataAsString(document, RagRetrievalMetadataKeys.HIT_SOURCE));
item.setVectorScore(roundScore(readMetadataAsDouble(document, RagRetrievalMetadataKeys.VECTOR_SCORE)));
item.setKeywordScore(roundScore(readMetadataAsDouble(document, RagRetrievalMetadataKeys.KEYWORD_SCORE)));
results.add(item);
}
return results;
}
private String readMetadataAsString(Document document, String key) {
Object value = document == null ? null : document.getMetadata(key);
if (value == null) {
return null;
}
String text = String.valueOf(value);
return StringUtils.hasText(text) ? text : null;
}
private Double readMetadataAsDouble(Document document, String key) {
Object value = document == null ? null : document.getMetadata(key);
if (value instanceof Number) {
return ((Number) value).doubleValue();
}
if (value instanceof String && StringUtils.hasText((String) value)) {
try {
return Double.valueOf((String) value);
} catch (NumberFormatException ignore) {
return null;
}
}
return null;
}
private Double roundScore(Double value) {
if (value == null) {
return null;
}
return new BigDecimal(String.valueOf(value))
.setScale(4, RoundingMode.HALF_UP)
.doubleValue();
}
}