知识库创建

This commit is contained in:
2025-11-06 19:08:20 +08:00
parent 0bb4853d54
commit d9947e273c
23 changed files with 2563 additions and 1018 deletions

View File

@@ -0,0 +1,18 @@
-- ========================================
-- 添加 embedding_model_provider 字段
-- ========================================
-- 用途:在 tb_ai_knowledge 表中添加向量模型提供商字段
-- 执行时间2025-11-06
-- 注意:如果该字段已存在,请忽略此脚本
-- ========================================
USE `school_news`;
-- 检查并添加 embedding_model_provider 字段
ALTER TABLE `tb_ai_knowledge`
ADD COLUMN `embedding_model_provider` VARCHAR(100) DEFAULT NULL COMMENT '向量模型提供商'
AFTER `embedding_model`;
-- 完成
SELECT 'embedding_model_provider 字段添加成功!' AS message;

View File

@@ -27,6 +27,7 @@ DROP TABLE IF EXISTS `tb_ai_knowledge`;
CREATE TABLE `tb_ai_knowledge` (
`id` VARCHAR(50) NOT NULL COMMENT '知识库ID',
`title` VARCHAR(255) NOT NULL COMMENT '知识库标题',
`avatar` VARCHAR(255) DEFAULT NULL COMMENT '知识库头像',
`description` VARCHAR(500) DEFAULT NULL COMMENT '知识库描述',
`content` LONGTEXT COMMENT '知识内容(手动添加时使用)',
`source_type` INT(4) DEFAULT 1 COMMENT '来源类型1手动添加 2文件导入 3资源同步',
@@ -38,6 +39,11 @@ CREATE TABLE `tb_ai_knowledge` (
`dify_dataset_id` VARCHAR(100) DEFAULT NULL COMMENT 'Dify知识库IDDataset ID',
`dify_indexing_technique` VARCHAR(50) DEFAULT 'high_quality' COMMENT 'Dify索引方式high_quality/economy',
`embedding_model` VARCHAR(100) DEFAULT NULL COMMENT '向量模型名称',
`embedding_model_provider` VARCHAR(100) DEFAULT NULL COMMENT '向量模型提供商',
`rerank_model` VARCHAR(100) DEFAULT NULL COMMENT 'Rerank模型名称',
`rerank_model_provider` VARCHAR(100) DEFAULT NULL COMMENT 'Rerank模型提供商',
`retrieval_top_k` INT(11) DEFAULT 2 COMMENT '检索Top K返回前K个结果',
`retrieval_score_threshold` DECIMAL(3,2) DEFAULT 0.00 COMMENT '检索分数阈值0.00-1.00',
`vector_id` VARCHAR(100) DEFAULT NULL COMMENT '向量ID用于向量检索',
`document_count` INT(11) DEFAULT 0 COMMENT '文档数量',
`total_chunks` INT(11) DEFAULT 0 COMMENT '总分段数',
@@ -215,10 +221,10 @@ CREATE TABLE `tb_ai_usage_statistics` (
-- 插入默认智能体配置
INSERT INTO `tb_ai_agent_config`
(`id`, `name`, `avatar`, `description`, `connect_internet`, `status`, `creator`, `create_time`)
(`id`, `name`, `avatar`, `description`, `connect_internet`,`dify_api_key`, `status`, `creator`, `create_time`)
VALUES
('agent_default_001', '校园助手', NULL, '我是您的智能校园助手,可以帮助您解答校园相关问题',
0, 1, '1', NOW());
0, 'app-fwOqGFLTsZtekCQYlOmj9f8x', 1, '1', NOW());
-- 插入示例知识库(需要配合权限表使用)
INSERT INTO `tb_ai_knowledge`

View File

@@ -65,14 +65,14 @@ public class DifyApiClient {
/**
* 创建知识库Dataset
*/
public DatasetCreateResponse createDataset(DatasetCreateRequest request, String apiKey) {
public DatasetCreateResponse createDataset(DatasetCreateRequest request) {
String url = difyConfig.getFullApiUrl("/datasets");
try {
String jsonBody = JSON.toJSONString(request);
Request httpRequest = new Request.Builder()
.url(url)
.header("Authorization", "Bearer " + getApiKey(apiKey))
.header("Authorization", "Bearer " + getKnowledgeApiKey())
.header("Content-Type", "application/json")
.post(RequestBody.create(jsonBody, MediaType.parse("application/json")))
.build();
@@ -96,13 +96,13 @@ public class DifyApiClient {
/**
* 查询知识库列表
*/
public DatasetListResponse listDatasets(int page, int limit, String apiKey) {
public DatasetListResponse listDatasets(int page, int limit) {
String url = difyConfig.getFullApiUrl("/datasets?page=" + page + "&limit=" + limit);
try {
Request httpRequest = new Request.Builder()
.url(url)
.header("Authorization", "Bearer " + getApiKey(apiKey))
.header("Authorization", "Bearer " + getKnowledgeApiKey())
.get()
.build();
@@ -125,13 +125,13 @@ public class DifyApiClient {
/**
* 查询知识库详情
*/
public DatasetDetailResponse getDatasetDetail(String datasetId, String apiKey) {
public DatasetDetailResponse getDatasetDetail(String datasetId) {
String url = difyConfig.getFullApiUrl("/datasets/" + datasetId);
try {
Request httpRequest = new Request.Builder()
.url(url)
.header("Authorization", "Bearer " + getApiKey(apiKey))
.header("Authorization", "Bearer " + getKnowledgeApiKey())
.get()
.build();
@@ -155,14 +155,14 @@ public class DifyApiClient {
/**
* 更新知识库
*/
public void updateDataset(String datasetId, DatasetUpdateRequest request, String apiKey) {
public void updateDataset(String datasetId, DatasetUpdateRequest request) {
String url = difyConfig.getFullApiUrl("/datasets/" + datasetId);
try {
String jsonBody = JSON.toJSONString(request);
Request httpRequest = new Request.Builder()
.url(url)
.header("Authorization", "Bearer " + getApiKey(apiKey))
.header("Authorization", "Bearer " + getKnowledgeApiKey())
.header("Content-Type", "application/json")
.patch(RequestBody.create(jsonBody, MediaType.parse("application/json")))
.build();
@@ -183,13 +183,13 @@ public class DifyApiClient {
/**
* 删除知识库
*/
public void deleteDataset(String datasetId, String apiKey) {
public void deleteDataset(String datasetId) {
String url = difyConfig.getFullApiUrl("/datasets/" + datasetId);
try {
Request httpRequest = new Request.Builder()
.url(url)
.header("Authorization", "Bearer " + getApiKey(apiKey))
.header("Authorization", "Bearer " + getKnowledgeApiKey())
.delete()
.build();
@@ -258,8 +258,7 @@ public class DifyApiClient {
String datasetId,
File file,
String originalFilename,
DocumentUploadRequest uploadRequest,
String apiKey) {
DocumentUploadRequest uploadRequest) {
String url = difyConfig.getFullApiUrl("/datasets/" + datasetId + "/document/create_by_file");
@@ -282,7 +281,7 @@ public class DifyApiClient {
Request httpRequest = new Request.Builder()
.url(url)
.header("Authorization", "Bearer " + getApiKey(apiKey))
.header("Authorization", "Bearer " + getKnowledgeApiKey())
.post(bodyBuilder.build())
.build();
@@ -305,13 +304,13 @@ public class DifyApiClient {
/**
* 查询文档处理状态
*/
public DocumentStatusResponse getDocumentStatus(String datasetId, String batchId, String apiKey) {
public DocumentStatusResponse getDocumentStatus(String datasetId, String batchId) {
String url = difyConfig.getFullApiUrl("/datasets/" + datasetId + "/documents/" + batchId + "/indexing-status");
try {
Request httpRequest = new Request.Builder()
.url(url)
.header("Authorization", "Bearer " + getApiKey(apiKey))
.header("Authorization", "Bearer " + getKnowledgeApiKey())
.get()
.build();
@@ -334,13 +333,13 @@ public class DifyApiClient {
/**
* 查询知识库文档列表
*/
public DocumentListResponse listDocuments(String datasetId, int page, int limit, String apiKey) {
public DocumentListResponse listDocuments(String datasetId, int page, int limit) {
String url = difyConfig.getFullApiUrl("/datasets/" + datasetId + "/documents?page=" + page + "&limit=" + limit);
try {
Request httpRequest = new Request.Builder()
.url(url)
.header("Authorization", "Bearer " + getApiKey(apiKey))
.header("Authorization", "Bearer " + getKnowledgeApiKey())
.get()
.build();
@@ -363,13 +362,13 @@ public class DifyApiClient {
/**
* 删除文档
*/
public void deleteDocument(String datasetId, String documentId, String apiKey) {
public void deleteDocument(String datasetId, String documentId) {
String url = difyConfig.getFullApiUrl("/datasets/" + datasetId + "/documents/" + documentId);
try {
Request httpRequest = new Request.Builder()
.url(url)
.header("Authorization", "Bearer " + getApiKey(apiKey))
.header("Authorization", "Bearer " + getKnowledgeApiKey())
.delete()
.build();
@@ -392,14 +391,14 @@ public class DifyApiClient {
/**
* 从知识库检索相关内容
*/
public RetrievalResponse retrieveFromDataset(String datasetId, RetrievalRequest request, String apiKey) {
public RetrievalResponse retrieveFromDataset(String datasetId, RetrievalRequest request) {
String url = difyConfig.getFullApiUrl("/datasets/" + datasetId + "/retrieve");
try {
String jsonBody = JSON.toJSONString(request);
Request httpRequest = new Request.Builder()
.url(url)
.header("Authorization", "Bearer " + getApiKey(apiKey))
.header("Authorization", "Bearer " + getKnowledgeApiKey())
.header("Content-Type", "application/json")
.post(RequestBody.create(jsonBody, MediaType.parse("application/json")))
.build();
@@ -850,6 +849,7 @@ public class DifyApiClient {
/**
* 获取API密钥优先使用传入的密钥否则使用配置的默认密钥
* 用于智能体相关的API
*/
private String getApiKey(String apiKey) {
if (apiKey != null && !apiKey.trim().isEmpty()) {
@@ -861,6 +861,17 @@ public class DifyApiClient {
throw new DifyException("未配置Dify API密钥");
}
/**
* 获取知识库API密钥统一使用配置中的knowledgeApiKey
* 用于知识库相关的API
*/
private String getKnowledgeApiKey() {
if (difyConfig.getKnowledgeApiKey() != null && !difyConfig.getKnowledgeApiKey().trim().isEmpty()) {
return difyConfig.getKnowledgeApiKey();
}
throw new DifyException("未配置Dify知识库API密钥");
}
/**
* 停止请求的内部类
*/
@@ -919,5 +930,65 @@ public class DifyApiClient {
this.feedback = feedback;
}
}
// ===================== 模型管理 API =====================
/**
* 获取可用的嵌入模型列表
*/
public EmbeddingModelResponse getAvailableEmbeddingModels() {
String url = difyConfig.getFullApiUrl("/workspaces/current/models/model-types/text-embedding");
try {
Request httpRequest = new Request.Builder()
.url(url)
.header("Authorization", "Bearer " + getKnowledgeApiKey())
.get()
.build();
try (Response response = httpClient.newCall(httpRequest).execute()) {
String responseBody = response.body() != null ? response.body().string() : "";
if (!response.isSuccessful()) {
logger.error("获取嵌入模型列表失败: {} - {}", response.code(), responseBody);
throw new DifyException("获取嵌入模型列表失败: " + responseBody);
}
return JSON.parseObject(responseBody, EmbeddingModelResponse.class);
}
} catch (IOException e) {
logger.error("获取嵌入模型列表异常", e);
throw new DifyException("获取嵌入模型列表异常: " + e.getMessage(), e);
}
}
/**
* 获取可用的Rerank模型列表
*/
public RerankModelResponse getAvailableRerankModels() {
String url = difyConfig.getFullApiUrl("/workspaces/current/models/model-types/rerank");
try {
Request httpRequest = new Request.Builder()
.url(url)
.header("Authorization", "Bearer " + getKnowledgeApiKey())
.get()
.build();
try (Response response = httpClient.newCall(httpRequest).execute()) {
String responseBody = response.body() != null ? response.body().string() : "";
if (!response.isSuccessful()) {
logger.error("获取Rerank模型列表失败: {} - {}", response.code(), responseBody);
throw new DifyException("获取Rerank模型列表失败: " + responseBody);
}
return JSON.parseObject(responseBody, RerankModelResponse.class);
}
} catch (IOException e) {
logger.error("获取Rerank模型列表异常", e);
throw new DifyException("获取Rerank模型列表异常: " + e.getMessage(), e);
}
}
}

View File

@@ -35,6 +35,18 @@ public class DatasetCreateRequest {
@JsonProperty("embedding_model")
private String embeddingModel;
/**
* Embedding模型提供商
*/
@JsonProperty("embedding_model_provider")
private String embeddingModelProvider;
/**
* 检索模型配置(包含 Rerank、Top K、Score 阈值等)
*/
@JsonProperty("retrieval_model")
private RetrievalModel retrievalModel;
/**
* 权限only_me仅自己、all_team_members团队所有成员
*/

View File

@@ -1,5 +1,6 @@
package org.xyzh.ai.client.dto;
import com.alibaba.fastjson2.annotation.JSONField;
import lombok.Data;
/**
@@ -21,5 +22,29 @@ public class DatasetUpdateRequest {
* 知识库描述
*/
private String description;
/**
* 索引方式high_quality/economy
*/
@JSONField(name = "indexing_technique")
private String indexingTechnique;
/**
* Embedding模型
*/
@JSONField(name = "embedding_model")
private String embeddingModel;
/**
* Embedding模型提供商
*/
@JSONField(name = "embedding_model_provider")
private String embeddingModelProvider;
/**
* 检索模型配置(包含 Rerank、Top K、Score 阈值等)
*/
@JSONField(name = "retrieval_model")
private RetrievalModel retrievalModel;
}

View File

@@ -0,0 +1,144 @@
package org.xyzh.ai.client.dto;
import com.alibaba.fastjson2.annotation.JSONField;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
* @description Dify嵌入模型响应
* @filename EmbeddingModelResponse.java
* @author AI Assistant
* @since 2025-11-06
*/
@Data
public class EmbeddingModelResponse {
/**
* 模型提供商列表
*/
@JSONField(name = "data")
private List<ModelProvider> data;
/**
* 模型提供商
*/
@Data
public static class ModelProvider {
/**
* 提供商标识
*/
@JSONField(name = "provider")
private String provider;
/**
* 提供商标签
*/
@JSONField(name = "label")
private Map<String, String> label;
/**
* 小图标
*/
@JSONField(name = "icon_small")
private Map<String, String> iconSmall;
/**
* 大图标
*/
@JSONField(name = "icon_large")
private Map<String, String> iconLarge;
/**
* 状态
*/
@JSONField(name = "status")
private String status;
/**
* 模型列表
*/
@JSONField(name = "models")
private List<Model> models;
}
/**
* 模型详情
*/
@Data
public static class Model {
/**
* 模型名称
*/
@JSONField(name = "model")
private String model;
/**
* 模型标签
*/
@JSONField(name = "label")
private Map<String, String> label;
/**
* 模型类型
*/
@JSONField(name = "model_type")
private String modelType;
/**
* 特性列表
*/
@JSONField(name = "features")
private List<Object> features;
/**
* 获取来源
*/
@JSONField(name = "fetch_from")
private String fetchFrom;
/**
* 模型属性
*/
@JSONField(name = "model_properties")
private ModelProperties modelProperties;
/**
* 是否已弃用
*/
@JSONField(name = "deprecated")
private Boolean deprecated;
/**
* 状态
*/
@JSONField(name = "status")
private String status;
/**
* 是否启用负载均衡
*/
@JSONField(name = "load_balancing_enabled")
private Boolean loadBalancingEnabled;
}
/**
* 模型属性
*/
@Data
public static class ModelProperties {
/**
* 上下文大小
*/
@JSONField(name = "context_size")
private Integer contextSize;
/**
* 最大分块数
*/
@JSONField(name = "max_chunks")
private Integer maxChunks;
}
}

View File

@@ -0,0 +1,56 @@
package org.xyzh.ai.client.dto;
import com.alibaba.fastjson2.annotation.JSONField;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
* @description Dify Rerank模型响应
* @filename RerankModelResponse.java
* @author AI Assistant
* @copyright xyzh
* @since 2025-11-06
*/
@Data
public class RerankModelResponse {
private List<ModelProvider> data;
@Data
public static class ModelProvider {
private String provider;
private Map<String, String> label; // e.g., {"en_US": "Cohere", "zh_Hans": "Cohere"}
@JSONField(name = "icon_small")
private String iconSmall;
@JSONField(name = "icon_large")
private String iconLarge;
private String status; // e.g., "active"
private List<Model> models;
}
@Data
public static class Model {
private String model; // e.g., "rerank-multilingual-v3.0"
private Map<String, String> label;
@JSONField(name = "model_type")
private String modelType; // e.g., "rerank"
private List<String> features;
@JSONField(name = "fetch_from")
private String fetchFrom;
@JSONField(name = "model_properties")
private ModelProperties modelProperties;
private Boolean deprecated;
private String status; // e.g., "active"
private String provider; // 模型提供商(可能在 model 数据中)
}
@Data
public static class ModelProperties {
@JSONField(name = "context_size")
private Integer contextSize;
@JSONField(name = "max_chunks")
private Integer maxChunks;
}
}

View File

@@ -0,0 +1,58 @@
package org.xyzh.ai.client.dto;
import com.alibaba.fastjson2.annotation.JSONField;
import lombok.Data;
/**
* @description Dify检索模型配置Retrieval Model
* @filename RetrievalModel.java
* @author AI Assistant
* @copyright xyzh
* @since 2025-11-06
*/
@Data
public class RetrievalModel {
/**
* 搜索方法vector_search向量搜索、full_text_search全文搜索、hybrid_search混合搜索
*/
@JSONField(name = "search_method")
private String searchMethod;
/**
* Rerank模型提供商
*/
@JSONField(name = "reranking_provider_name")
private String rerankingProviderName;
/**
* Rerank模型名称
*/
@JSONField(name = "reranking_model")
private String rerankingModel;
/**
* Rerank是否启用
*/
@JSONField(name = "reranking_enable")
private Boolean rerankingEnable;
/**
* Top K返回前K个结果
*/
@JSONField(name = "top_k")
private Integer topK;
/**
* 分数阈值0.00-1.00
*/
@JSONField(name = "score_threshold")
private Double scoreThreshold;
/**
* 是否启用分数阈值
*/
@JSONField(name = "score_threshold_enabled")
private Boolean scoreThresholdEnabled;
}

View File

@@ -33,6 +33,8 @@ public class DifyConfig {
// private String apiKey="app-PTHzp2DsLPyiUrDYTXBGxL1f";
private String apiKey="app-fwOqGFLTsZtekCQYlOmj9f8x";
private String knowledgeApiKey="dataset-HeDK9gHBqPnI4rBZ2q2Hm7rV";
/**
* 请求超时时间(秒)
*/

View File

@@ -30,21 +30,19 @@ public class AiKnowledgeController {
/**
* @description 创建知识库
* @param requestBody 请求体knowledge, permissionType, deptIds, roleIds
* @param knowledge 知识库对象
* @return ResultDomain<TbAiKnowledge>
* @author AI Assistant
* @since 2025-11-04
*/
@PostMapping
public ResultDomain<TbAiKnowledge> createKnowledge(@RequestBody Map<String, Object> requestBody) {
TbAiKnowledge knowledge = (TbAiKnowledge) requestBody.get("knowledge");
String permissionType = (String) requestBody.get("permissionType");
@SuppressWarnings("unchecked")
List<String> deptIds = (List<String>) requestBody.get("deptIds");
@SuppressWarnings("unchecked")
List<String> roleIds = (List<String>) requestBody.get("roleIds");
public ResultDomain<TbAiKnowledge> createKnowledge(@RequestBody TbAiKnowledge knowledge) {
// 默认权限为PUBLIC公开
String permissionType = "PUBLIC";
List<String> deptIds = null;
List<String> roleIds = null;
log.info("创建知识库: permissionType={}", permissionType);
log.info("创建知识库: name={}", knowledge.getTitle());
return knowledgeService.createKnowledge(knowledge, permissionType, deptIds, roleIds);
}
@@ -109,7 +107,7 @@ public class AiKnowledgeController {
* @since 2025-11-04
*/
@PostMapping("/page")
public PageDomain<TbAiKnowledge> pageKnowledges(@RequestBody PageRequest<TbAiKnowledge> pageRequest) {
public ResultDomain<TbAiKnowledge> pageKnowledges(@RequestBody PageRequest<TbAiKnowledge> pageRequest) {
log.info("分页查询知识库");
return knowledgeService.pageKnowledges(pageRequest.getFilter(), pageRequest.getPageParam());
}
@@ -175,4 +173,28 @@ public class AiKnowledgeController {
log.info("获取知识库统计信息: id={}", id);
return knowledgeService.getKnowledgeStats(id);
}
/**
* @description 获取可用的嵌入模型列表
* @return ResultDomain<Map>
* @author AI Assistant
* @since 2025-11-06
*/
@GetMapping("/embedding-models")
public ResultDomain<Map<String, Object>> getAvailableEmbeddingModels() {
log.info("获取可用的嵌入模型列表");
return knowledgeService.getAvailableEmbeddingModels();
}
/**
* @description 获取可用的Rerank模型列表
* @return ResultDomain<Map>
* @author AI Assistant
* @since 2025-11-06
*/
@GetMapping("/rerank-models")
public ResultDomain<Map<String, Object>> getAvailableRerankModels() {
log.info("获取可用的Rerank模型列表");
return knowledgeService.getAvailableRerankModels();
}
}

View File

@@ -10,6 +10,9 @@ import org.xyzh.ai.client.dto.DatasetCreateRequest;
import org.xyzh.ai.client.dto.DatasetCreateResponse;
import org.xyzh.ai.client.dto.DatasetDetailResponse;
import org.xyzh.ai.client.dto.DatasetUpdateRequest;
import org.xyzh.ai.client.dto.EmbeddingModelResponse;
import org.xyzh.ai.client.dto.RerankModelResponse;
import org.xyzh.ai.client.dto.RetrievalModel;
import org.xyzh.ai.config.DifyConfig;
import org.xyzh.ai.exception.AiKnowledgeException;
import org.xyzh.ai.exception.DifyException;
@@ -26,8 +29,11 @@ import org.xyzh.common.dto.user.TbSysUser;
import org.xyzh.common.vo.UserDeptRoleVO;
import org.xyzh.system.utils.LoginUtil;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
@@ -101,12 +107,50 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
embeddingModel = difyConfig.getDataset().getDefaultEmbeddingModel();
}
difyRequest.setEmbeddingModel(embeddingModel);
// 设置模型提供商(从前端传入或使用配置默认值)
String provider = knowledge.getEmbeddingModelProvider();
// 调用Dify API创建知识库
DatasetCreateResponse difyResponse = difyApiClient.createDataset(
difyRequest,
difyConfig.getApiKey()
);
if (StringUtils.hasText(provider)) {
difyRequest.setEmbeddingModelProvider(provider);
log.info("创建知识库 - 设置Embedding模型: model={}, provider={}", embeddingModel, provider);
}
// 设置检索模型配置Rerank、Top K、Score 阈值)
RetrievalModel retrievalModel = new RetrievalModel();
retrievalModel.setSearchMethod("hybrid_search"); // 默认使用混合搜索
// Top K 配置
if (knowledge.getRetrievalTopK() != null && knowledge.getRetrievalTopK() > 0) {
retrievalModel.setTopK(knowledge.getRetrievalTopK());
} else {
retrievalModel.setTopK(2); // 默认值
}
// Score 阈值配置
if (knowledge.getRetrievalScoreThreshold() != null && knowledge.getRetrievalScoreThreshold() >= 0) {
retrievalModel.setScoreThreshold(knowledge.getRetrievalScoreThreshold());
retrievalModel.setScoreThresholdEnabled(knowledge.getRetrievalScoreThreshold() > 0);
} else {
retrievalModel.setScoreThreshold(0.0);
retrievalModel.setScoreThresholdEnabled(false);
}
// Rerank 模型配置
if (StringUtils.hasText(knowledge.getRerankModel())) {
retrievalModel.setRerankingEnable(true);
retrievalModel.setRerankingModel(knowledge.getRerankModel());
retrievalModel.setRerankingProviderName(knowledge.getRerankModelProvider());
log.info("创建知识库 - 启用Rerank: model={}, provider={}",
knowledge.getRerankModel(), knowledge.getRerankModelProvider());
} else {
retrievalModel.setRerankingEnable(false);
}
difyRequest.setRetrievalModel(retrievalModel);
// 调用Dify API创建知识库使用知识库API Key
DatasetCreateResponse difyResponse = difyApiClient.createDataset(difyRequest);
difyDatasetId = difyResponse.getId();
log.info("Dify知识库创建成功: {} - {}", difyDatasetId, knowledge.getTitle());
@@ -122,6 +166,12 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
knowledge.setDifyDatasetId(difyDatasetId);
knowledge.setDifyIndexingTechnique(indexingTechnique);
knowledge.setEmbeddingModel(embeddingModel);
// 保存模型提供商
if (StringUtils.hasText(knowledge.getEmbeddingModelProvider())) {
knowledge.setEmbeddingModelProvider(knowledge.getEmbeddingModelProvider());
}
// 保存检索配置(从前端传入,已经在 knowledge 对象中)
// retrievalTopK, retrievalScoreThreshold, rerankModel, rerankModelProvider 已设置
knowledge.setCreator(currentUser.getID());
knowledge.setCreatorDept(deptId);
knowledge.setUpdater(currentUser.getID());
@@ -143,7 +193,7 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
if (rows <= 0) {
// 回滚删除Dify中的知识库
try {
difyApiClient.deleteDataset(difyDatasetId, difyConfig.getApiKey());
difyApiClient.deleteDataset(difyDatasetId);
} catch (Exception ex) {
log.error("回滚删除Dify知识库失败", ex);
}
@@ -209,28 +259,54 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
return resultDomain;
}
// 5. 如果修改了title或description同步到Dify
// 5. 检测字段变化同步到Dify
boolean needUpdateDify = false;
DatasetUpdateRequest updateRequest = new DatasetUpdateRequest();
// 标题变化
if (StringUtils.hasText(knowledge.getTitle()) && !knowledge.getTitle().equals(existing.getTitle())) {
updateRequest.setName(knowledge.getTitle());
needUpdateDify = true;
}
// 描述变化
if (knowledge.getDescription() != null && !knowledge.getDescription().equals(existing.getDescription())) {
updateRequest.setDescription(knowledge.getDescription());
needUpdateDify = true;
}
// 索引方式变化economy可以升级为high_quality但不能降级
if (StringUtils.hasText(knowledge.getDifyIndexingTechnique()) &&
!knowledge.getDifyIndexingTechnique().equals(existing.getDifyIndexingTechnique())) {
// 允许从economy升级到high_quality
if ("high_quality".equals(knowledge.getDifyIndexingTechnique()) ||
"economy".equals(existing.getDifyIndexingTechnique())) {
updateRequest.setIndexingTechnique(knowledge.getDifyIndexingTechnique());
needUpdateDify = true;
} else {
log.warn("不允许从high_quality降级为economy: knowledgeId={}", knowledge.getID());
}
}
// Embedding模型变化
if (StringUtils.hasText(knowledge.getEmbeddingModel()) &&
!knowledge.getEmbeddingModel().equals(existing.getEmbeddingModel())) {
updateRequest.setEmbeddingModel(knowledge.getEmbeddingModel());
// 使用前端传入的模型提供商
if (StringUtils.hasText(knowledge.getEmbeddingModelProvider())) {
updateRequest.setEmbeddingModelProvider(knowledge.getEmbeddingModelProvider());
log.info("更新Embedding模型: model={}, provider={}", knowledge.getEmbeddingModel(), knowledge.getEmbeddingModelProvider());
}
needUpdateDify = true;
}
// 同步到Dify
if (needUpdateDify && StringUtils.hasText(existing.getDifyDatasetId())) {
try {
DatasetUpdateRequest updateRequest = new DatasetUpdateRequest();
// 只设置实际改变的字段
if (StringUtils.hasText(knowledge.getTitle())) {
updateRequest.setName(knowledge.getTitle());
}
if (knowledge.getDescription() != null) {
updateRequest.setDescription(knowledge.getDescription());
}
difyApiClient.updateDataset(existing.getDifyDatasetId(), updateRequest, difyConfig.getApiKey());
log.info("Dify知识库更新成功: {} - {}", existing.getDifyDatasetId(), knowledge.getTitle());
difyApiClient.updateDataset(existing.getDifyDatasetId(), updateRequest);
log.info("Dify知识库更新成功: datasetId={}, title={}",
existing.getDifyDatasetId(), knowledge.getTitle());
} catch (DifyException e) {
log.error("更新Dify知识库失败继续更新本地数据", e);
// 不阻塞本地更新流程
@@ -294,7 +370,7 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
// 4. 删除Dify中的知识库
if (StringUtils.hasText(existing.getDifyDatasetId())) {
try {
difyApiClient.deleteDataset(existing.getDifyDatasetId(), difyConfig.getApiKey());
difyApiClient.deleteDataset(existing.getDifyDatasetId());
log.info("Dify知识库删除成功: {}", existing.getDifyDatasetId());
} catch (DifyException e) {
log.error("删除Dify知识库失败继续删除本地记录", e);
@@ -372,7 +448,8 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
}
@Override
public PageDomain<TbAiKnowledge> pageKnowledges(TbAiKnowledge filter, PageParam pageParam) {
public ResultDomain<TbAiKnowledge> pageKnowledges(TbAiKnowledge filter, PageParam pageParam) {
ResultDomain<TbAiKnowledge> resultDomain = new ResultDomain<>();
try {
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
@@ -389,11 +466,13 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
resultPageParam.setTotalElements(total);
resultPageParam.setTotalPages((int) Math.ceil((double) total / pageParam.getPageSize()));
return new PageDomain<>(resultPageParam, knowledges);
resultDomain.success("查询成功", knowledges);
return resultDomain;
} catch (Exception e) {
log.error("分页查询知识库列表异常", e);
return new PageDomain<>();
resultDomain.fail("查询失败: " + e.getMessage());
return resultDomain;
}
}
@@ -417,8 +496,7 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
// 2. 从Dify获取最新信息
try {
DatasetDetailResponse difyDetail = difyApiClient.getDatasetDetail(
knowledge.getDifyDatasetId(),
difyConfig.getApiKey()
knowledge.getDifyDatasetId()
);
// 3. 更新本地信息
@@ -561,5 +639,143 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
throw new AiKnowledgeException("创建权限失败: " + e.getMessage());
}
}
/**
* 获取可用的嵌入模型列表
*/
@Override
public ResultDomain<Map<String, Object>> getAvailableEmbeddingModels() {
ResultDomain<Map<String, Object>> resultDomain = new ResultDomain<>();
try {
// 调用Dify API获取嵌入模型列表
EmbeddingModelResponse response =
difyApiClient.getAvailableEmbeddingModels();
if (response == null || response.getData() == null) {
resultDomain.fail("获取嵌入模型列表失败");
return resultDomain;
}
// 转换为前端需要的格式
Map<String, Object> result = new HashMap<>();
result.put("providers", response.getData());
// 提取所有可用的模型列表(扁平化)
List<Map<String, Object>> modelList = new ArrayList<>();
for (EmbeddingModelResponse.ModelProvider provider : response.getData()) {
if (provider.getModels() != null) {
for (EmbeddingModelResponse.Model model : provider.getModels()) {
// 只返回可用的、非弃用的模型
if (model.getStatus() != null &&
!"deprecated".equals(model.getStatus()) &&
(model.getDeprecated() == null || !model.getDeprecated())) {
Map<String, Object> modelInfo = new HashMap<>();
modelInfo.put("provider", provider.getProvider());
modelInfo.put("model", model.getModel());
modelInfo.put("label", model.getLabel());
modelInfo.put("modelType", model.getModelType());
modelInfo.put("status", model.getStatus());
// 添加模型属性
if (model.getModelProperties() != null) {
modelInfo.put("contextSize", model.getModelProperties().getContextSize());
modelInfo.put("maxChunks", model.getModelProperties().getMaxChunks());
}
modelList.add(modelInfo);
}
}
}
}
result.put("models", modelList);
result.put("total", modelList.size());
log.info("获取嵌入模型列表成功,共{}个提供商,{}个可用模型",
response.getData().size(), modelList.size());
resultDomain.success("获取成功", result);
return resultDomain;
} catch (DifyException e) {
log.error("获取嵌入模型列表失败", e);
resultDomain.fail("获取嵌入模型列表失败: " + e.getMessage());
return resultDomain;
} catch (Exception e) {
log.error("获取嵌入模型列表异常", e);
resultDomain.fail("获取嵌入模型列表异常: " + e.getMessage());
return resultDomain;
}
}
@Override
public ResultDomain<Map<String, Object>> getAvailableRerankModels() {
ResultDomain<Map<String, Object>> resultDomain = new ResultDomain<>();
try {
// 调用Dify API获取Rerank模型列表
RerankModelResponse response =
difyApiClient.getAvailableRerankModels();
if (response == null || response.getData() == null) {
resultDomain.fail("获取Rerank模型列表失败");
return resultDomain;
}
// 转换为前端需要的格式
Map<String, Object> result = new HashMap<>();
result.put("providers", response.getData());
// 提取所有可用的模型列表(扁平化)
List<Map<String, Object>> modelList = new ArrayList<>();
for (RerankModelResponse.ModelProvider provider : response.getData()) {
if (provider.getModels() != null) {
for (RerankModelResponse.Model model : provider.getModels()) {
// 只返回可用的、非弃用的模型
if (model.getStatus() != null &&
!"deprecated".equals(model.getStatus()) &&
(model.getDeprecated() == null || !model.getDeprecated())) {
Map<String, Object> modelInfo = new HashMap<>();
modelInfo.put("provider", provider.getProvider());
modelInfo.put("model", model.getModel());
modelInfo.put("label", model.getLabel());
modelInfo.put("modelType", model.getModelType());
modelInfo.put("status", model.getStatus());
// 添加模型属性
if (model.getModelProperties() != null) {
modelInfo.put("contextSize", model.getModelProperties().getContextSize());
modelInfo.put("maxChunks", model.getModelProperties().getMaxChunks());
}
modelList.add(modelInfo);
}
}
}
}
result.put("models", modelList);
result.put("total", modelList.size());
log.info("获取Rerank模型列表成功共{}个提供商,{}个可用模型",
response.getData().size(), modelList.size());
resultDomain.success("获取成功", result);
return resultDomain;
} catch (DifyException e) {
log.error("获取Rerank模型列表失败", e);
resultDomain.fail("获取Rerank模型列表失败: " + e.getMessage());
return resultDomain;
} catch (Exception e) {
log.error("获取Rerank模型列表异常", e);
resultDomain.fail("获取Rerank模型列表异常: " + e.getMessage());
return resultDomain;
}
}
}

View File

@@ -260,8 +260,7 @@ public class AiUploadFileServiceImpl implements AiUploadFileService {
knowledge.getDifyDatasetId(),
fileToUpload,
originalFilename,
uploadRequest,
difyConfig.getApiKey());
uploadRequest);
// 8. 保存到本地数据库
TbAiUploadFile uploadFile = new TbAiUploadFile();
@@ -378,8 +377,7 @@ public class AiUploadFileServiceImpl implements AiUploadFileService {
try {
difyApiClient.deleteDocument(
knowledge.getDifyDatasetId(),
file.getDifyDocumentId(),
difyConfig.getApiKey());
file.getDifyDocumentId());
log.info("Dify文档删除成功: {}", file.getDifyDocumentId());
} catch (DifyException e) {
log.error("删除Dify文档失败继续删除本地记录", e);
@@ -512,8 +510,7 @@ public class AiUploadFileServiceImpl implements AiUploadFileService {
try {
DocumentStatusResponse statusResponse = difyApiClient.getDocumentStatus(
knowledge.getDifyDatasetId(),
file.getDifyBatchId(),
difyConfig.getApiKey());
file.getDifyBatchId());
// 4. 更新本地状态
TbAiUploadFile update = new TbAiUploadFile();

View File

@@ -6,6 +6,7 @@
<resultMap id="BaseResultMap" type="org.xyzh.common.dto.ai.TbAiKnowledge">
<id column="id" property="id" jdbcType="VARCHAR"/>
<result column="title" property="title" jdbcType="VARCHAR"/>
<result column="avatar" property="avatar" jdbcType="VARCHAR"/>
<result column="description" property="description" jdbcType="VARCHAR"/>
<result column="content" property="content" jdbcType="LONGVARCHAR"/>
<result column="source_type" property="sourceType" jdbcType="INTEGER"/>
@@ -17,6 +18,11 @@
<result column="dify_dataset_id" property="difyDatasetId" jdbcType="VARCHAR"/>
<result column="dify_indexing_technique" property="difyIndexingTechnique" jdbcType="VARCHAR"/>
<result column="embedding_model" property="embeddingModel" jdbcType="VARCHAR"/>
<result column="embedding_model_provider" property="embeddingModelProvider" jdbcType="VARCHAR"/>
<result column="rerank_model" property="rerankModel" jdbcType="VARCHAR"/>
<result column="rerank_model_provider" property="rerankModelProvider" jdbcType="VARCHAR"/>
<result column="retrieval_top_k" property="retrievalTopK" jdbcType="INTEGER"/>
<result column="retrieval_score_threshold" property="retrievalScoreThreshold" jdbcType="DECIMAL"/>
<result column="vector_id" property="vectorID" jdbcType="VARCHAR"/>
<result column="document_count" property="documentCount" jdbcType="INTEGER"/>
<result column="total_chunks" property="totalChunks" jdbcType="INTEGER"/>
@@ -32,8 +38,9 @@
<!-- 基础字段 -->
<sql id="Base_Column_List">
id, title, description, content, source_type, source_id, file_name, file_path,
category, tags, dify_dataset_id, dify_indexing_technique, embedding_model,
id, title, avatar, description, content, source_type, source_id, file_name, file_path,
category, tags, dify_dataset_id, dify_indexing_technique, embedding_model, embedding_model_provider,
rerank_model, rerank_model_provider, retrieval_top_k, retrieval_score_threshold,
vector_id, document_count, total_chunks, status, creator, creator_dept,
updater, create_time, update_time, delete_time, deleted
</sql>
@@ -161,13 +168,15 @@
<!-- insertKnowledge插入知识库 -->
<insert id="insertKnowledge" parameterType="org.xyzh.common.dto.ai.TbAiKnowledge">
INSERT INTO tb_ai_knowledge (
id, title, description, content, source_type, source_id, file_name, file_path,
category, tags, dify_dataset_id, dify_indexing_technique, embedding_model,
id, title, avatar, description, content, source_type, source_id, file_name, file_path,
category, tags, dify_dataset_id, dify_indexing_technique, embedding_model, embedding_model_provider,
rerank_model, rerank_model_provider, retrieval_top_k, retrieval_score_threshold,
vector_id, document_count, total_chunks, status, creator, creator_dept,
updater, create_time, update_time, deleted
) VALUES (
#{ID}, #{title}, #{description}, #{content}, #{sourceType}, #{sourceID}, #{fileName}, #{filePath},
#{category}, #{tags}, #{difyDatasetId}, #{difyIndexingTechnique}, #{embeddingModel},
#{ID}, #{title}, #{avatar}, #{description}, #{content}, #{sourceType}, #{sourceID}, #{fileName}, #{filePath},
#{category}, #{tags}, #{difyDatasetId}, #{difyIndexingTechnique}, #{embeddingModel}, #{embeddingModelProvider},
#{rerankModel}, #{rerankModelProvider}, #{retrievalTopK}, #{retrievalScoreThreshold},
#{vectorID}, #{documentCount}, #{totalChunks}, #{status}, #{creator}, #{creatorDept},
#{updater}, #{createTime}, #{updateTime}, #{deleted}
)
@@ -178,6 +187,7 @@
UPDATE tb_ai_knowledge
<set>
<if test="title != null and title != ''">title = #{title},</if>
<if test="avatar != null">avatar = #{avatar},</if>
<if test="description != null">description = #{description},</if>
<if test="content != null">content = #{content},</if>
<if test="sourceType != null">source_type = #{sourceType},</if>
@@ -189,6 +199,11 @@
<if test="difyDatasetId != null">dify_dataset_id = #{difyDatasetId},</if>
<if test="difyIndexingTechnique != null">dify_indexing_technique = #{difyIndexingTechnique},</if>
<if test="embeddingModel != null">embedding_model = #{embeddingModel},</if>
<if test="embeddingModelProvider != null">embedding_model_provider = #{embeddingModelProvider},</if>
<if test="rerankModel != null">rerank_model = #{rerankModel},</if>
<if test="rerankModelProvider != null">rerank_model_provider = #{rerankModelProvider},</if>
<if test="retrievalTopK != null">retrieval_top_k = #{retrievalTopK},</if>
<if test="retrievalScoreThreshold != null">retrieval_score_threshold = #{retrievalScoreThreshold},</if>
<if test="vectorID != null">vector_id = #{vectorID},</if>
<if test="documentCount != null">document_count = #{documentCount},</if>
<if test="totalChunks != null">total_chunks = #{totalChunks},</if>

View File

@@ -6,6 +6,7 @@ import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.dto.ai.TbAiKnowledge;
import java.util.List;
import java.util.Map;
/**
* @description AI知识库管理服务接口
@@ -65,7 +66,7 @@ public interface AiKnowledgeService {
* @param pageParam 分页参数
* @return 分页结果
*/
PageDomain<TbAiKnowledge> pageKnowledges(TbAiKnowledge filter, PageParam pageParam);
ResultDomain<TbAiKnowledge> pageKnowledges(TbAiKnowledge filter, PageParam pageParam);
/**
* 同步Dify知识库信息到本地
@@ -103,4 +104,16 @@ public interface AiKnowledgeService {
* @return 统计信息
*/
ResultDomain<TbAiKnowledge> getKnowledgeStats(String knowledgeId);
/**
* 获取可用的嵌入模型列表
* @return 嵌入模型列表
*/
ResultDomain<Map<String, Object>> getAvailableEmbeddingModels();
/**
* 获取可用的Rerank模型列表
* @return Rerank模型列表
*/
ResultDomain<Map<String, Object>> getAvailableRerankModels();
}

View File

@@ -18,6 +18,11 @@ public class TbAiKnowledge extends BaseDTO {
*/
private String title;
/**
* @description 知识库头像
*/
private String avatar;
/**
* @description 知识库描述
*/
@@ -73,6 +78,31 @@ public class TbAiKnowledge extends BaseDTO {
*/
private String embeddingModel;
/**
* @description 向量模型提供商
*/
private String embeddingModelProvider;
/**
* @description Rerank模型名称
*/
private String rerankModel;
/**
* @description Rerank模型提供商
*/
private String rerankModelProvider;
/**
* @description 检索Top K返回前K个结果
*/
private Integer retrievalTopK;
/**
* @description 检索分数阈值0.00-1.00
*/
private Double retrievalScoreThreshold;
/**
* @description 向量ID用于向量检索
*/
@@ -116,6 +146,15 @@ public class TbAiKnowledge extends BaseDTO {
this.title = title;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getDescription() {
return description;
}
@@ -260,6 +299,45 @@ public class TbAiKnowledge extends BaseDTO {
this.updater = updater;
}
public String getEmbeddingModelProvider() {
return embeddingModelProvider;
}
public void setEmbeddingModelProvider(String embeddingModelProvider) {
this.embeddingModelProvider = embeddingModelProvider;
}
public String getRerankModel() {
return rerankModel;
}
public void setRerankModel(String rerankModel) {
this.rerankModel = rerankModel;
}
public String getRerankModelProvider() {
return rerankModelProvider;
}
public void setRerankModelProvider(String rerankModelProvider) {
this.rerankModelProvider = rerankModelProvider;
}
public Integer getRetrievalTopK() {
return retrievalTopK;
}
public void setRetrievalTopK(Integer retrievalTopK) {
this.retrievalTopK = retrievalTopK;
}
public Double getRetrievalScoreThreshold() {
return retrievalScoreThreshold;
}
public void setRetrievalScoreThreshold(Double retrievalScoreThreshold) {
this.retrievalScoreThreshold = retrievalScoreThreshold;
}
@Override
public String toString() {
return "TbAiKnowledge{" +