refactor: 重构项目架构,优化向量服务

- 移除 Graph 知识图谱相关模块(Neo4j、GraphRAG等)
- 移除 demo、job、wechat 示例模块,简化项目结构
- 修复向量维度获取方式,改为从数据库配置读取
- 添加 gRPC BOM 依赖管理,解决 Milvus SDK 版本冲突
- 新增 PPIO 服务和 Embedding 提供者支持
- 清理冗余代码和未使用的依赖

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ageerle
2026-03-15 23:39:30 +08:00
parent accac603cf
commit a5e7c59fd4
156 changed files with 132 additions and 11639 deletions

View File

@@ -148,7 +148,6 @@ public class AgentChatHandler implements ChatHandler {
messageBO.setContent(content);
messageBO.setRole(role);
messageBO.setModelName(chatRequest.getModel());
messageBO.setBillingType(chatModelVo.getModelType());
messageBO.setRemark(null);
chatMessageService.insertByBo(messageBO);

View File

@@ -79,10 +79,8 @@ public class ChatMessageServiceImpl implements IChatMessageService {
lqw.eq(bo.getUserId() != null, ChatMessage::getUserId, bo.getUserId());
lqw.eq(StringUtils.isNotBlank(bo.getContent()), ChatMessage::getContent, bo.getContent());
lqw.eq(StringUtils.isNotBlank(bo.getRole()), ChatMessage::getRole, bo.getRole());
lqw.eq(bo.getDeductCost() != null, ChatMessage::getDeductCost, bo.getDeductCost());
lqw.eq(bo.getTotalTokens() != null, ChatMessage::getTotalTokens, bo.getTotalTokens());
lqw.like(StringUtils.isNotBlank(bo.getModelName()), ChatMessage::getModelName, bo.getModelName());
lqw.eq(StringUtils.isNotBlank(bo.getBillingType()), ChatMessage::getBillingType, bo.getBillingType());
return lqw;
}

View File

@@ -92,11 +92,7 @@ public class ChatModelServiceImpl implements IChatModelService {
lqw.like(StringUtils.isNotBlank(bo.getModelName()), ChatModel::getModelName, bo.getModelName());
lqw.like(StringUtils.isNotBlank(bo.getProviderCode()), ChatModel::getProviderCode, bo.getProviderCode());
lqw.eq(StringUtils.isNotBlank(bo.getModelDescribe()), ChatModel::getModelDescribe, bo.getModelDescribe());
lqw.eq(bo.getModelPrice() != null, ChatModel::getModelPrice, bo.getModelPrice());
lqw.eq(StringUtils.isNotBlank(bo.getModelType()), ChatModel::getModelType, bo.getModelType());
lqw.eq(StringUtils.isNotBlank(bo.getModelShow()), ChatModel::getModelShow, bo.getModelShow());
lqw.eq(StringUtils.isNotBlank(bo.getModelFree()), ChatModel::getModelFree, bo.getModelFree());
lqw.eq(bo.getPriority() != null, ChatModel::getPriority, bo.getPriority());
lqw.eq(StringUtils.isNotBlank(bo.getApiHost()), ChatModel::getApiHost, bo.getApiHost());
lqw.eq(StringUtils.isNotBlank(bo.getApiKey()), ChatModel::getApiKey, bo.getApiKey());
return lqw;

View File

@@ -0,0 +1,50 @@
package org.ruoyi.service.chat.impl.provider;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.model.chat.StreamingChatModel;
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.enums.ChatModeType;
import org.ruoyi.service.chat.impl.AbstractStreamingChatService;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* OPENAI服务调用
*
* @author ageerle@163.com
* @date 2025/12/13
*/
@Service
@Slf4j
public class PPIOServiceImpl extends AbstractStreamingChatService {
@Override
public StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest) {
return OpenAiStreamingChatModel.builder()
.baseUrl(chatModelVo.getApiHost())
.apiKey(chatModelVo.getApiKey())
.modelName(chatModelVo.getModelName())
.returnThinking(chatRequest.getEnableThinking())
.build();
}
@Override
public void doChat(ChatModelVo chatModelVo, ChatRequest chatRequest, List<ChatMessage> messagesWithMemory, StreamingChatResponseHandler handler) {
StreamingChatModel streamingChatModel = buildStreamingChatModel(chatModelVo, chatRequest);
streamingChatModel.chat(messagesWithMemory, handler);
}
@Override
public String getProviderName() {
return ChatModeType.PPIO.getCode();
}
}

View File

@@ -38,7 +38,7 @@ public class AliBaiLianBaseEmbedProvider extends OpenAiEmbeddingProvider {
return QwenEmbeddingModel.builder()
.apiKey(chatModelVo.getApiKey())
.modelName(chatModelVo.getModelName())
.dimension(1024)
.dimension(chatModelVo.getModelDimension())
.build()
.embedAll(textSegments);
}

View File

@@ -37,7 +37,7 @@ public class OpenAiEmbeddingProvider implements BaseEmbedModelService {
.baseUrl(chatModelVo.getApiHost())
.apiKey(chatModelVo.getApiKey())
.modelName(chatModelVo.getModelName())
.dimensions(chatModelVo.getDimension())
.dimensions(chatModelVo.getModelDimension())
.build()
.embedAll(textSegments);
}

View File

@@ -0,0 +1,14 @@
package org.ruoyi.service.embed.impl;
import org.springframework.stereotype.Component;
/**
* @Author: Robust_H
* @Date: 2025-09-30-下午3:59
* @Description: 硅基流动(兼容 OpenAi
*/
@Component("ppio")
public class PPIOEmbeddingProvider extends OpenAiEmbeddingProvider {
}

View File

@@ -1,44 +0,0 @@
//package org.ruoyi.service.embed.impl;
//
//import dev.langchain4j.community.model.zhipu.ZhipuAiEmbeddingModel;
//import dev.langchain4j.data.embedding.Embedding;
//import dev.langchain4j.data.segment.TextSegment;
//import dev.langchain4j.model.output.Response;
//import org.ruoyi.domain.vo.chat.ChatModelVo;
//import org.ruoyi.enums.ModalityType;
//import org.ruoyi.service.embed.BaseEmbedModelService;
//import org.springframework.stereotype.Component;
//
//import java.util.List;
//import java.util.Set;
//
///**
// * @Author: Robust_H
// * @Date: 2025-09-30-下午4:02
// * @Description: 智谱AI
// */
//@Component("zhipu")
//public class ZhiPuAiEmbeddingProvider implements BaseEmbedModelService {
// private ChatModelVo chatModelVo;
//
// @Override
// public void configure(ChatModelVo config) {
// this.chatModelVo = config;
// }
//
// @Override
// public Set<ModalityType> getSupportedModalities() {
// return Set.of();
// }
//
// @Override
// public Response<List<Embedding>> embedAll(List<TextSegment> textSegments) {
// return ZhipuAiEmbeddingModel.builder()
// .baseUrl(chatModelVo.getApiHost())
// .apiKey(chatModelVo.getApiKey())
// .model(chatModelVo.getModelName())
// .dimensions(chatModelVo.getDimension())
// .build()
// .embedAll(textSegments);
// }
//}

View File

@@ -1,136 +0,0 @@
package org.ruoyi.service.graph;
import org.ruoyi.domain.bo.graph.GraphBuildTask;
import java.util.List;
/**
* 图谱构建任务服务接口
*
* @author ruoyi
* @date 2025-09-30
*/
public interface IGraphBuildTaskService {
/**
* 创建构建任务
*
* @param graphUuid 图谱UUID
* @param knowledgeId 知识库ID
* @param docId 文档ID可选
* @param taskType 任务类型
* @return 任务信息
*/
GraphBuildTask createTask(String graphUuid, String knowledgeId, String docId, Integer taskType);
/**
* 启动构建任务(异步)
*
* @param taskUuid 任务UUID
* @return 异步结果
*/
void startTask(String taskUuid);
/**
* 根据UUID获取任务
*
* @param taskUuid 任务UUID
* @return 任务信息
*/
GraphBuildTask getByUuid(String taskUuid);
/**
* 根据图谱UUID获取任务列表
*
* @param graphUuid 图谱UUID
* @return 任务列表
*/
List<GraphBuildTask> listByGraphUuid(String graphUuid);
/**
* 获取图谱的最新构建任务
*
* @param graphUuid 图谱UUID
* @return 最新任务
*/
GraphBuildTask getLatestTask(String graphUuid);
/**
* 根据知识库ID获取任务列表
*
* @param knowledgeId 知识库ID
* @return 任务列表
*/
List<GraphBuildTask> listByKnowledgeId(String knowledgeId);
/**
* 获取待执行和执行中的任务
*
* @return 任务列表
*/
List<GraphBuildTask> getPendingAndRunningTasks();
/**
* 更新任务进度
*
* @param taskUuid 任务UUID
* @param progress 进度百分比
* @param processedDocs 已处理文档数
* @return 是否成功
*/
boolean updateProgress(String taskUuid, Integer progress, Integer processedDocs);
/**
* 更新任务状态
*
* @param taskUuid 任务UUID
* @param status 状态
* @return 是否成功
*/
boolean updateStatus(String taskUuid, Integer status);
/**
* 更新提取统计信息
*
* @param taskUuid 任务UUID
* @param extractedEntities 提取的实体数
* @param extractedRelations 提取的关系数
* @return 是否成功
*/
boolean updateExtractionStats(String taskUuid, Integer extractedEntities, Integer extractedRelations);
/**
* 标记任务为成功
*
* @param taskUuid 任务UUID
* @param resultSummary 结果摘要
* @return 是否成功
*/
boolean markSuccess(String taskUuid, String resultSummary);
/**
* 标记任务为失败
*
* @param taskUuid 任务UUID
* @param errorMessage 错误信息
* @return 是否成功
*/
boolean markFailed(String taskUuid, String errorMessage);
/**
* 取消任务
*
* @param taskUuid 任务UUID
* @return 是否成功
*/
boolean cancelTask(String taskUuid);
/**
* 重试失败的任务
*
* @param taskUuid 任务UUID
* @return 新任务UUID
*/
String retryTask(String taskUuid);
}

View File

@@ -1,47 +0,0 @@
package org.ruoyi.service.graph;
import org.ruoyi.domain.dto.GraphExtractionResult;
/**
* 图谱实体关系抽取服务接口
*
* @author ruoyi
* @date 2025-09-30
*/
public interface IGraphExtractionService {
/**
* 从文本中抽取实体和关系
*
* @param text 输入文本
* @return 抽取结果
*/
GraphExtractionResult extractFromText(String text);
/**
* 从文本中抽取实体和关系(自定义实体类型)
*
* @param text 输入文本
* @param entityTypes 实体类型列表
* @return 抽取结果
*/
GraphExtractionResult extractFromText(String text, String[] entityTypes);
/**
* 从文本中抽取实体和关系使用指定的LLM模型
*
* @param text 输入文本
* @param modelName LLM模型名称
* @return 抽取结果
*/
GraphExtractionResult extractFromTextWithModel(String text, String modelName);
/**
* 解析LLM响应为实体和关系
*
* @param response LLM响应文本
* @return 抽取结果
*/
GraphExtractionResult parseGraphResponse(String response);
}

View File

@@ -1,120 +0,0 @@
package org.ruoyi.service.graph;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.ruoyi.domain.bo.graph.GraphInstance;
import java.util.List;
/**
* 图谱实例服务接口
*
* @author ruoyi
* @date 2025-09-30
*/
public interface IGraphInstanceService {
/**
* 创建图谱实例
*
* @param knowledgeId 知识库ID
* @param graphName 图谱名称
* @param config 配置信息
* @return 图谱实例
*/
GraphInstance createInstance(String knowledgeId, String graphName, String config);
/**
* 根据主键ID获取图谱实例
*
* @param id 主键ID
* @return 图谱实例
*/
GraphInstance getById(Long id);
/**
* 根据UUID获取图谱实例
*
* @param graphUuid 图谱UUID
* @return 图谱实例
*/
GraphInstance getByUuid(String graphUuid);
/**
* 更新图谱实例
*
* @param instance 图谱实例
* @return 是否成功
*/
boolean updateInstance(GraphInstance instance);
/**
* 根据知识库ID获取图谱列表
*
* @param knowledgeId 知识库ID
* @return 图谱实例列表
*/
List<GraphInstance> listByKnowledgeId(String knowledgeId);
/**
* 条件查询图谱实例列表(分页)
*
* @param page 分页对象
* @param instanceName 图谱名称(模糊查询)
* @param knowledgeId 知识库ID
* @param graphStatus 图谱状态码
* @return 分页结果
*/
Page<GraphInstance> queryPage(Page<GraphInstance> page, String instanceName, String knowledgeId, Integer graphStatus);
/**
* 更新图谱状态
*
* @param graphUuid 图谱UUID
* @param status 状态
* @return 是否成功
*/
boolean updateStatus(String graphUuid, Integer status);
/**
* 更新图谱统计信息
*
* @param graphUuid 图谱UUID
* @param nodeCount 节点数量
* @param relationshipCount 关系数量
* @return 是否成功
*/
boolean updateCounts(String graphUuid, Integer nodeCount, Integer relationshipCount);
/**
* 更新图谱配置
*
* @param graphUuid 图谱UUID
* @param config 配置信息
* @return 是否成功
*/
boolean updateConfig(String graphUuid, String config);
/**
* 删除图谱实例(软删除)
*
* @param graphUuid 图谱UUID
* @return 是否成功
*/
boolean deleteInstance(String graphUuid);
/**
* 物理删除图谱实例及其数据
*
* @param graphUuid 图谱UUID
* @return 是否成功
*/
boolean deleteInstanceAndData(String graphUuid);
/**
* 获取图谱统计信息
*
* @param graphUuid 图谱UUID
* @return 统计信息
*/
java.util.Map<String, Object> getStatistics(String graphUuid);
}

View File

@@ -1,33 +0,0 @@
package org.ruoyi.service.graph;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
/**
* 图谱LLM服务接口
* 参考 ruoyi-chat 的 IChatService 设计
* 支持多种LLM模型OpenAI、Qwen、Zhipu等
*
* @author ruoyi
* @date 2025-10-11
*/
public interface IGraphLLMService {
/**
* 调用LLM进行图谱实体关系抽取
*
* @param prompt 提示词(包含文本和抽取指令)
* @param chatModel 模型配置
* @return LLM响应文本
*/
String extractGraph(String prompt, ChatModelVo chatModel);
/**
* 获取此服务支持的模型类别
* 例如: "openai", "qwen", "zhipu", "ollama"
*
* @return 模型类别标识
*/
String getCategory();
}

View File

@@ -1,75 +0,0 @@
package org.ruoyi.service.graph;
import org.ruoyi.domain.dto.GraphExtractionResult;
import java.util.Map;
/**
* GraphRAG服务接口
* 负责文档的图谱化处理和基于图谱的检索
*
* @author ruoyi
* @date 2025-09-30
*/
public interface IGraphRAGService {
/**
* 将文本入库到图谱
*
* @param text 文本内容
* @param knowledgeId 知识库ID
* @param metadata 元数据
* @return 抽取结果
*/
GraphExtractionResult ingestText(String text, String knowledgeId, Map<String, Object> metadata);
/**
* 将文本入库到图谱(指定模型)
*
* @param text 文本内容
* @param knowledgeId 知识库ID
* @param metadata 元数据
* @param modelName LLM模型名称
* @return 抽取结果
*/
GraphExtractionResult ingestTextWithModel(String text, String knowledgeId, Map<String, Object> metadata, String modelName);
/**
* 将文档入库到图谱(自动分片)
*
* @param documentText 文档内容
* @param knowledgeId 知识库ID
* @param metadata 元数据
* @return 总抽取结果(合并所有分片)
*/
GraphExtractionResult ingestDocument(String documentText, String knowledgeId, Map<String, Object> metadata);
/**
* 将文档入库到图谱(自动分片,指定模型)
*
* @param documentText 文档内容
* @param knowledgeId 知识库ID
* @param metadata 元数据
* @param modelName LLM模型名称
* @return 总抽取结果(合并所有分片)
*/
GraphExtractionResult ingestDocumentWithModel(String documentText, String knowledgeId, Map<String, Object> metadata, String modelName);
/**
* 基于图谱检索相关内容
*
* @param query 查询文本
* @param knowledgeId 知识库ID
* @param maxResults 最大结果数
* @return 检索到的相关实体和关系
*/
String retrieveFromGraph(String query, String knowledgeId, int maxResults);
/**
* 删除知识库的图谱数据
*
* @param knowledgeId 知识库ID
* @return 是否成功
*/
boolean deleteGraphData(String knowledgeId);
}

View File

@@ -1,268 +0,0 @@
package org.ruoyi.service.graph;
import org.ruoyi.domain.bo.graph.GraphEdge;
import org.ruoyi.domain.bo.graph.GraphVertex;
import java.util.List;
/**
* 图存储服务接口
* 核心服务负责与Neo4j图数据库交互
*
* @author ruoyi
* @date 2025-09-30
*/
public interface IGraphStoreService {
// ==================== 节点操作 ====================
/**
* 添加单个节点
*
* @param vertex 节点信息
* @return 是否成功
*/
boolean addVertex(GraphVertex vertex);
/**
* 批量添加节点
*
* @param vertices 节点列表
* @return 成功添加的节点数
*/
int addVertices(List<GraphVertex> vertices);
/**
* 获取节点信息
*
* @param nodeId 节点ID
* @param graphUuid 图谱UUID
* @return 节点信息
*/
GraphVertex getVertex(String nodeId, String graphUuid);
/**
* 根据条件搜索节点
*
* @param graphUuid 图谱UUID
* @param label 节点标签(可选)
* @param limit 返回数量限制
* @return 节点列表
*/
List<GraphVertex> searchVertices(String graphUuid, String label, Integer limit);
/**
* 根据名称搜索节点
*
* @param graphUuid 图谱UUID
* @param name 节点名称
* @return 节点列表
*/
List<GraphVertex> searchVerticesByName(String graphUuid, String name);
/**
* 根据关键词和知识库ID搜索节点
*
* @param keyword 关键词
* @param knowledgeId 知识库ID可选
* @param limit 限制数量
* @return 节点列表
*/
List<GraphVertex> searchVerticesByName(String keyword, String knowledgeId, Integer limit);
/**
* 根据知识库ID查询节点
*
* @param knowledgeId 知识库ID
* @param limit 限制数量
* @return 节点列表
*/
List<GraphVertex> queryVerticesByKnowledgeId(String knowledgeId, Integer limit);
/**
* 更新节点信息
*
* @param vertex 节点信息
* @return 是否成功
*/
boolean updateVertex(GraphVertex vertex);
/**
* 删除节点
*
* @param nodeId 节点ID
* @param graphUuid 图谱UUID
* @param includeEdges 是否同时删除相关关系
* @return 是否成功
*/
boolean deleteVertex(String nodeId, String graphUuid, boolean includeEdges);
// ==================== 关系操作 ====================
/**
* 添加关系
*
* @param edge 关系信息
* @return 是否成功
*/
boolean addEdge(GraphEdge edge);
/**
* 批量添加关系
*
* @param edges 关系列表
* @return 成功添加的关系数
*/
int addEdges(List<GraphEdge> edges);
/**
* 获取关系信息
*
* @param edgeId 关系ID
* @param graphUuid 图谱UUID
* @return 关系信息
*/
GraphEdge getEdge(String edgeId, String graphUuid);
/**
* 搜索关系
*
* @param graphUuid 图谱UUID
* @param sourceNodeId 源节点ID可选
* @param targetNodeId 目标节点ID可选
* @param limit 返回数量限制
* @return 关系列表
*/
List<GraphEdge> searchEdges(String graphUuid, String sourceNodeId, String targetNodeId, Integer limit);
/**
* 根据知识库ID查询关系
*
* @param knowledgeId 知识库ID
* @param limit 限制数量
* @return 关系列表
*/
List<GraphEdge> queryEdgesByKnowledgeId(String knowledgeId, Integer limit);
/**
* 获取节点的所有关系
*
* @param nodeId 节点ID
* @param graphUuid 图谱UUID
* @param direction 方向: IN(入边), OUT(出边), BOTH(双向)
* @return 关系列表
*/
List<GraphEdge> getNodeEdges(String nodeId, String graphUuid, String direction);
/**
* 更新关系信息
*
* @param edge 关系信息
* @return 是否成功
*/
boolean updateEdge(GraphEdge edge);
/**
* 删除关系
*
* @param edgeId 关系ID
* @param graphUuid 图谱UUID
* @return 是否成功
*/
boolean deleteEdge(String edgeId, String graphUuid);
// ==================== 图谱管理 ====================
/**
* 创建图谱Schema
*
* @param graphUuid 图谱UUID
* @return 是否成功
*/
boolean createGraphSchema(String graphUuid);
/**
* 删除整个图谱数据
*
* @param graphUuid 图谱UUID
* @return 是否成功
*/
boolean deleteGraph(String graphUuid);
/**
* 根据知识库ID删除图谱数据
*
* @param knowledgeId 知识库ID
* @return 是否成功
*/
boolean deleteByKnowledgeId(String knowledgeId);
/**
* 获取图谱统计信息
*
* @param graphUuid 图谱UUID
* @return 统计信息 {nodeCount, relationshipCount}
*/
java.util.Map<String, Object> getGraphStatistics(String graphUuid);
/**
* 根据知识库ID获取统计信息
*
* @param knowledgeId 知识库ID
* @return 统计信息
*/
java.util.Map<String, Object> getStatistics(String knowledgeId);
// ==================== 高级查询 ====================
/**
* 查找两个节点之间的路径
*
* @param sourceNodeId 源节点ID
* @param targetNodeId 目标节点ID
* @param graphUuid 图谱UUID
* @param maxDepth 最大深度
* @return 路径列表
*/
List<List<GraphVertex>> findPaths(String sourceNodeId, String targetNodeId, String graphUuid, Integer maxDepth);
/**
* 查找路径(简化版)
*
* @param startNodeId 起始节点ID
* @param endNodeId 结束节点ID
* @param maxDepth 最大深度
* @return 路径列表
*/
List<List<GraphVertex>> findPaths(String startNodeId, String endNodeId, Integer maxDepth);
/**
* 查找节点的邻居节点
*
* @param nodeId 节点ID
* @param graphUuid 图谱UUID
* @param depth 深度(几度关系)
* @return 邻居节点列表
*/
List<GraphVertex> findNeighbors(String nodeId, String graphUuid, Integer depth);
/**
* 获取节点的邻居(简化版)
*
* @param nodeId 节点ID
* @param knowledgeId 知识库ID可选
* @param limit 限制数量
* @return 邻居节点列表
*/
List<GraphVertex> getNeighbors(String nodeId, String knowledgeId, Integer limit);
/**
* 执行自定义Cypher查询
*
* @param cypher Cypher查询语句
* @param params 参数
* @return 查询结果
*/
List<java.util.Map<String, Object>> executeCypher(String cypher, java.util.Map<String, Object> params);
}

View File

@@ -1,85 +0,0 @@
package org.ruoyi.service.graph.impl;
import dev.langchain4j.model.chat.StreamingChatModel;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.service.graph.IGraphLLMService;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
* DeepSeek 图谱LLM服务实现
* 支持 DeepSeek 系列模型
* <p>
* 注意:使用 langchain4j 的 OpenAiStreamingChatModel通过 CompletableFuture 转换为同步调用
* 参考 DeepSeekChatImpl 的实现,但改为同步模式
*
* @author ruoyi
* @date 2025-10-13
*/
@Slf4j
@Service
public class DeepSeekGraphLLMServiceImpl implements IGraphLLMService {
@Override
public String extractGraph(String prompt, ChatModelVo chatModel) {
log.info("DeepSeek模型调用: model={}, apiHost={}, 提示词长度={}",
chatModel.getModelName(), chatModel.getApiHost(), prompt.length());
try {
// 使用 langchain4j 的 OpenAiStreamingChatModel参考 DeepSeekChatImpl
StreamingChatModel streamingModel = OpenAiStreamingChatModel.builder()
.baseUrl(chatModel.getApiHost())
.apiKey(chatModel.getApiKey())
.modelName(chatModel.getModelName())
.temperature(0.8)
.logRequests(false)
.logResponses(false)
.build();
// 用于收集完整响应
StringBuilder fullResponse = new StringBuilder();
CompletableFuture<String> responseFuture = new CompletableFuture<>();
// 发送流式消息,但通过 CompletableFuture 转换为同步
long startTime = System.currentTimeMillis();
streamingModel.chat(prompt, new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(String partialResponse) {
fullResponse.append(partialResponse);
}
@Override
public void onCompleteResponse(ChatResponse completeResponse) {
long duration = System.currentTimeMillis() - startTime;
String responseText = fullResponse.toString();
log.info("DeepSeek模型响应成功: 耗时={}ms, 响应长度={}", duration, responseText.length());
responseFuture.complete(responseText);
}
@Override
public void onError(Throwable error) {
log.error("DeepSeek模型调用错误: {}", error.getMessage());
responseFuture.completeExceptionally(error);
}
});
// 同步等待结果最多2分钟
return responseFuture.get(2, TimeUnit.MINUTES);
} catch (Exception e) {
log.error("DeepSeek模型调用失败: {}", e.getMessage(), e);
throw new RuntimeException("DeepSeek模型调用失败: " + e.getMessage(), e);
}
}
@Override
public String getCategory() {
return "deepseek"; // 对应 ChatModel 表中的 category 字段
}
}

View File

@@ -1,107 +0,0 @@
package org.ruoyi.service.graph.impl;
import io.github.imfangs.dify.client.DifyClient;
import io.github.imfangs.dify.client.DifyClientFactory;
import io.github.imfangs.dify.client.callback.ChatStreamCallback;
import io.github.imfangs.dify.client.enums.ResponseMode;
import io.github.imfangs.dify.client.event.ErrorEvent;
import io.github.imfangs.dify.client.event.MessageEndEvent;
import io.github.imfangs.dify.client.event.MessageEvent;
import io.github.imfangs.dify.client.model.DifyConfig;
import io.github.imfangs.dify.client.model.chat.ChatMessage;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.service.graph.IGraphLLMService;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
* Dify 图谱LLM服务实现
* 支持 Dify 平台的对话模型
* <p>
* 注意Dify 使用流式调用,通过 CompletableFuture 实现同步等待
*
* @author ruoyi
* @date 2025-10-11
*/
@Slf4j
@Service
public class DifyGraphLLMServiceImpl implements IGraphLLMService {
@Override
public String extractGraph(String prompt, ChatModelVo chatModel) {
log.info("Dify模型调用: model={}, apiHost={}, 提示词长度={}",
chatModel.getModelName(), chatModel.getApiHost(), prompt.length());
try {
// 创建 Dify 客户端配置
DifyConfig config = DifyConfig.builder()
.baseUrl(chatModel.getApiHost())
.apiKey(chatModel.getApiKey())
.connectTimeout(5000)
.readTimeout(120000) // 2分钟超时
.writeTimeout(30000)
.build();
DifyClient chatClient = DifyClientFactory.createClient(config);
// 创建聊天消息(使用流式模式)
ChatMessage message = ChatMessage.builder()
.query(prompt)
.user("graph-system") // 图谱系统用户
.responseMode(ResponseMode.STREAMING) // 流式模式
.build();
// 用于收集完整响应
StringBuilder fullResponse = new StringBuilder();
CompletableFuture<String> responseFuture = new CompletableFuture<>();
// 发送流式消息
long startTime = System.currentTimeMillis();
chatClient.sendChatMessageStream(message, new ChatStreamCallback() {
@Override
public void onMessage(MessageEvent event) {
fullResponse.append(event.getAnswer());
}
@Override
public void onMessageEnd(MessageEndEvent event) {
long duration = System.currentTimeMillis() - startTime;
String responseText = fullResponse.toString();
log.info("Dify模型响应成功: 耗时={}ms, 响应长度={}, messageId={}",
duration, responseText.length(), event.getMessageId());
responseFuture.complete(responseText);
}
@Override
public void onError(ErrorEvent event) {
log.error("Dify模型调用错误: {}", event.getMessage());
responseFuture.completeExceptionally(
new RuntimeException("Dify调用错误: " + event.getMessage())
);
}
@Override
public void onException(Throwable throwable) {
log.error("Dify模型调用异常: {}", throwable.getMessage(), throwable);
responseFuture.completeExceptionally(throwable);
}
});
// 同步等待结果最多2分钟
return responseFuture.get(2, TimeUnit.MINUTES);
} catch (Exception e) {
log.error("Dify模型调用失败: {}", e.getMessage(), e);
throw new RuntimeException("Dify模型调用失败: " + e.getMessage(), e);
}
}
@Override
public String getCategory() {
return "dify"; // 对应 ChatModel 表中的 category 字段
}
}

View File

@@ -1,367 +0,0 @@
package org.ruoyi.service.graph.impl;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.chat.service.chat.IChatModelService;
import org.ruoyi.common.chat.domain.bo.chat.ChatModelBo;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.config.GraphExtractPrompt;
import org.ruoyi.constant.GraphConstants;
import org.ruoyi.domain.dto.ExtractedEntity;
import org.ruoyi.domain.dto.ExtractedRelation;
import org.ruoyi.domain.dto.GraphExtractionResult;
import org.ruoyi.factory.GraphLLMServiceFactory;
import org.ruoyi.service.graph.IGraphExtractionService;
import org.ruoyi.service.graph.IGraphLLMService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 图谱实体关系抽取服务实现
* 使用工厂模式支持多种LLM模型参考 ruoyi-chat 设计)
*
* @author ruoyi
* @date 2025-09-30
*/
@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "knowledge.graph", name = "enabled", havingValue = "true")
public class GraphExtractionServiceImpl implements IGraphExtractionService {
private final IChatModelService chatModelService;
private final GraphLLMServiceFactory llmServiceFactory;
/**
* 实体匹配正则表达式
* 格式: ("entity"<|>ENTITY_NAME<|>ENTITY_TYPE<|>ENTITY_DESCRIPTION)
*/
private static final Pattern ENTITY_PATTERN = Pattern.compile(
"\\(\"entity\"" +
Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) +
"([^" + Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) + "]+)" +
Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) +
"([^" + Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) + "]+)" +
Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) +
"([^)]+)\\)"
);
/**
* 关系匹配正则表达式
* 格式: ("relationship"<|>SOURCE<|>TARGET<|>DESCRIPTION<|>STRENGTH)
*/
private static final Pattern RELATION_PATTERN = Pattern.compile(
"\\(\"relationship\"" +
Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) +
"([^" + Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) + "]+)" +
Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) +
"([^" + Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) + "]+)" +
Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) +
"([^" + Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) + "]+)" +
Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) +
"([^)]+)\\)"
);
@Override
public GraphExtractionResult extractFromText(String text) {
return extractFromText(text, GraphConstants.DEFAULT_ENTITY_TYPES);
}
@Override
public GraphExtractionResult extractFromText(String text, String[] entityTypes) {
log.info("开始从文本中抽取实体和关系,文本长度: {}", text.length());
try {
// 1. 构建提示词
String prompt = GraphExtractPrompt.buildExtractionPrompt(text, entityTypes);
// 2. 调用LLM使用默认模型
String llmResponse = callLLM(prompt);
// 3. 解析响应
GraphExtractionResult result = parseGraphResponse(llmResponse);
result.setRawResponse(llmResponse);
result.setSuccess(true);
log.info("抽取完成,实体数: {}, 关系数: {}",
result.getEntities().size(), result.getRelations().size());
return result;
} catch (Exception e) {
log.error("实体关系抽取失败", e);
return GraphExtractionResult.builder()
.entities(new ArrayList<>())
.relations(new ArrayList<>())
.success(false)
.errorMessage(e.getMessage())
.build();
}
}
@Override
public GraphExtractionResult extractFromTextWithModel(String text, String modelName) {
log.info("开始从文本中抽取实体和关系,使用模型: {}, 文本长度: {}", modelName, text.length());
try {
// 1. 获取模型配置
ChatModelVo chatModel = chatModelService.selectModelByName(modelName);
if (chatModel == null) {
log.warn("未找到模型: {}, 使用默认模型", modelName);
return extractFromText(text);
}
// 2. 构建提示词
String prompt = GraphExtractPrompt.buildExtractionPrompt(text, GraphConstants.DEFAULT_ENTITY_TYPES);
// 3. 调用LLM使用指定模型
String llmResponse = callLLMWithModel(prompt, chatModel);
// 4. 解析响应
GraphExtractionResult result = parseGraphResponse(llmResponse);
result.setRawResponse(llmResponse);
result.setSuccess(true);
log.info("抽取完成,实体数: {}, 关系数: {}, 使用模型: {}",
result.getEntities().size(), result.getRelations().size(), modelName);
// ⭐ 调试:如果没有关系,记录原始响应(便于诊断)
if (result.getRelations().isEmpty() && !result.getEntities().isEmpty()) {
log.warn("⚠️ LLM 提取到 {} 个实体,但没有提取到任何关系!", result.getEntities().size());
log.warn("LLM 原始响应预览前500字符: {}",
llmResponse.length() > 500 ? llmResponse.substring(0, 500) + "..." : llmResponse);
}
return result;
} catch (Exception e) {
log.error("实体关系抽取失败,模型: {}", modelName, e);
return GraphExtractionResult.builder()
.entities(new ArrayList<>())
.relations(new ArrayList<>())
.success(false)
.errorMessage(e.getMessage())
.build();
}
}
@Override
public GraphExtractionResult parseGraphResponse(String response) {
log.debug("开始解析图谱响应,响应长度: {}", response != null ? response.length() : 0);
List<ExtractedEntity> entities = new ArrayList<>();
List<ExtractedRelation> relations = new ArrayList<>();
if (StrUtil.isBlank(response)) {
log.warn("响应为空,无法解析");
return GraphExtractionResult.builder()
.entities(entities)
.relations(relations)
.success(false)
.errorMessage("LLM响应为空")
.build();
}
try {
// 1. 解析实体
Matcher entityMatcher = ENTITY_PATTERN.matcher(response);
while (entityMatcher.find()) {
String name = entityMatcher.group(1).trim();
String type = entityMatcher.group(2).trim();
String description = entityMatcher.group(3).trim();
// ⭐ 过滤无效实体N/A 或包含特殊字符)
if (isInvalidEntity(name, type)) {
log.debug("跳过无效实体: name={}, type={}", name, type);
continue;
}
ExtractedEntity entity = ExtractedEntity.builder()
.name(name)
.type(type)
.description(description)
.build();
entities.add(entity);
log.debug("解析到实体: name={}, type={}", name, type);
}
// 2. 解析关系
Matcher relationMatcher = RELATION_PATTERN.matcher(response);
while (relationMatcher.find()) {
String sourceEntity = relationMatcher.group(1).trim();
String targetEntity = relationMatcher.group(2).trim();
String description = relationMatcher.group(3).trim();
String strengthStr = relationMatcher.group(4).trim();
Integer strength = parseStrength(strengthStr);
Double confidence = calculateConfidence(strength);
ExtractedRelation relation = ExtractedRelation.builder()
.sourceEntity(sourceEntity)
.targetEntity(targetEntity)
.description(description)
.strength(strength)
.confidence(confidence)
.build();
relations.add(relation);
log.debug("解析到关系: sourceEntity={}, targetEntity={}, strength={}",
sourceEntity, targetEntity, strength);
}
log.info("解析完成,实体数: {}, 关系数: {}", entities.size(), relations.size());
return GraphExtractionResult.builder()
.entities(entities)
.relations(relations)
.success(true)
.build();
} catch (Exception e) {
log.error("解析图谱响应失败", e);
return GraphExtractionResult.builder()
.entities(entities)
.relations(relations)
.success(false)
.errorMessage("解析失败: " + e.getMessage())
.build();
}
}
/**
* 调用LLM获取响应使用默认模型
*
* @param prompt 提示词
* @return LLM响应
*/
private String callLLM(String prompt) {
// 获取聊天分类的最高优先级模型作为默认模型
// 如果没有chat分类的模型尝试查询任意可用模型
ChatModelVo defaultModel = chatModelService.queryList(new ChatModelBo()).get(0);
if (defaultModel == null) {
log.error("未找到可用的LLM模型");
throw new RuntimeException("未找到可用的LLM模型请先配置聊天模型");
}
log.info("使用默认模型: {}", defaultModel.getModelName());
return callLLMWithModel(prompt, defaultModel);
}
/**
* 使用指定模型调用LLM获取响应使用工厂模式支持多种LLM
*
* @param prompt 提示词
* @param chatModel 模型配置
* @return LLM响应
*/
private String callLLMWithModel(String prompt, ChatModelVo chatModel) {
log.info("调用LLM模型: model={}, category={}, 提示词长度={}",
chatModel.getModelName(), chatModel.getCategory(), prompt.length());
try {
// 根据模型类别获取对应的LLM服务实现
IGraphLLMService llmService = llmServiceFactory.getLLMService(chatModel.getCategory());
// 调用LLM进行图谱抽取
String responseText = llmService.extractGraph(prompt, chatModel);
log.info("LLM调用成功: model={}, category={}, 响应长度={}",
chatModel.getModelName(), chatModel.getCategory(), responseText.length());
return responseText;
} catch (IllegalArgumentException e) {
// 不支持的模型类别,降级到默认实现
log.warn("不支持的模型类别: {}, 尝试使用OpenAI兼容模式", chatModel.getCategory());
try {
IGraphLLMService openAiService = llmServiceFactory.getLLMService("openai");
return openAiService.extractGraph(prompt, chatModel);
} catch (Exception fallbackEx) {
log.error("降级调用也失败: {}", fallbackEx.getMessage(), fallbackEx);
throw new RuntimeException("LLM调用失败: " + fallbackEx.getMessage(), fallbackEx);
}
} catch (Exception e) {
log.error("LLM调用失败: {}", e.getMessage(), e);
throw new RuntimeException("LLM调用失败: " + e.getMessage(), e);
}
}
/**
* 解析关系强度
*
* @param strengthStr 强度字符串
* @return 强度值0-10
*/
private Integer parseStrength(String strengthStr) {
try {
// 尝试解析为整数
int strength = Integer.parseInt(strengthStr);
// 限制在0-10范围内
return Math.max(0, Math.min(10, strength));
} catch (NumberFormatException e) {
log.debug("无法解析关系强度: {}, 使用默认值5", strengthStr);
return 5; // 默认中等强度
}
}
/**
* 验证实体是否有效
* 过滤 N/A 以及包含 Neo4j 不支持的特殊字符的实体
*
* @param name 实体名称
* @param type 实体类型
* @return true=无效false=有效
*/
private boolean isInvalidEntity(String name, String type) {
// 1. 检查是否为 N/A
if ("N/A".equalsIgnoreCase(name) || "N/A".equalsIgnoreCase(type)) {
return true;
}
// 2. 检查是否为空或纯空格
if (StrUtil.isBlank(name) || StrUtil.isBlank(type)) {
return true;
}
// 3. 检查类型是否包含 Neo4j Label 不支持的字符
// Neo4j Label 规则:不能包含 / : & | 等特殊字符
if (type.matches(".*[/:&|\\\\].*")) {
log.warn("⚠️ 实体类型包含非法字符,将被过滤: type={}", type);
return true;
}
// 4. 检查名称是否过长Neo4j 建议 < 256
if (name.length() > 255 || type.length() > 64) {
log.warn("⚠️ 实体名称或类型过长,将被过滤: name.length={}, type.length={}",
name.length(), type.length());
return true;
}
return false;
}
/**
* 根据关系强度计算置信度
*
* @param strength 关系强度0-10
* @return 置信度0.0-1.0
*/
private Double calculateConfidence(Integer strength) {
if (strength == null) {
return 0.5;
}
// 将0-10的强度映射到0.3-1.0的置信度
return 0.3 + (strength / 10.0) * 0.7;
}
}

View File

@@ -1,284 +0,0 @@
package org.ruoyi.service.graph.impl;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.domain.bo.graph.GraphInstance;
import org.ruoyi.mapper.graph.GraphInstanceMapper;
import org.ruoyi.service.graph.IGraphInstanceService;
import org.ruoyi.service.graph.IGraphStoreService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 图谱实例服务实现
*
* @author ruoyi
* @date 2025-09-30
*/
@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "knowledge.graph", name = "enabled", havingValue = "true")
public class GraphInstanceServiceImpl implements IGraphInstanceService {
private final GraphInstanceMapper graphInstanceMapper;
private final IGraphStoreService graphStoreService;
@Override
@Transactional(rollbackFor = Exception.class)
public GraphInstance createInstance(String knowledgeId, String graphName, String config) {
// 检查是否已存在
LambdaQueryWrapper<GraphInstance> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(GraphInstance::getKnowledgeId, knowledgeId);
GraphInstance existing = graphInstanceMapper.selectOne(wrapper);
if (existing != null) {
log.warn("知识库 {} 已存在图谱实例", knowledgeId);
return existing;
}
// 创建新实例
GraphInstance instance = new GraphInstance();
instance.setGraphUuid(String.valueOf(IdUtil.getSnowflake().nextId())); // UUID
instance.setKnowledgeId(knowledgeId);
instance.setGraphName(StringUtils.isNotBlank(graphName) ? graphName : "知识图谱-" + knowledgeId);
instance.setGraphStatus(0); // 0-未构建(新建时状态为未构建,需手动点击"构建"按钮)
instance.setNodeCount(0);
instance.setRelationshipCount(0);
// 解析配置
if (StringUtils.isNotBlank(config)) {
instance.setConfig(config);
}
graphInstanceMapper.insert(instance);
// 创建 Neo4j Schema
graphStoreService.createGraphSchema(knowledgeId);
log.info("创建图谱实例成功: knowledgeId={}, instanceId={}", knowledgeId, instance.getId());
return instance;
}
@Override
public GraphInstance getById(Long id) {
return graphInstanceMapper.selectById(id);
}
@Override
public GraphInstance getByUuid(String graphUuid) {
LambdaQueryWrapper<GraphInstance> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(GraphInstance::getGraphUuid, graphUuid);
return graphInstanceMapper.selectOne(wrapper);
}
@Override
public boolean updateInstance(GraphInstance instance) {
try {
int rows = graphInstanceMapper.updateById(instance);
log.info("更新图谱实例: id={}, rows={}", instance.getId(), rows);
return rows > 0;
} catch (Exception e) {
log.error("更新图谱实例失败: id={}", instance.getId(), e);
return false;
}
}
@Override
public List<GraphInstance> listByKnowledgeId(String knowledgeId) {
LambdaQueryWrapper<GraphInstance> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(GraphInstance::getKnowledgeId, knowledgeId);
wrapper.orderByDesc(GraphInstance::getCreateTime);
return graphInstanceMapper.selectList(wrapper);
}
@Override
public Page<GraphInstance> queryPage(Page<GraphInstance> page, String instanceName, String knowledgeId, Integer graphStatus) {
LambdaQueryWrapper<GraphInstance> wrapper = new LambdaQueryWrapper<>();
// 图谱名称模糊查询
if (StringUtils.isNotBlank(instanceName)) {
wrapper.like(GraphInstance::getGraphName, instanceName.trim());
}
// 知识库ID精确查询
if (StringUtils.isNotBlank(knowledgeId)) {
wrapper.eq(GraphInstance::getKnowledgeId, knowledgeId.trim());
}
// 状态精确查询
if (graphStatus != null) {
wrapper.eq(GraphInstance::getGraphStatus, graphStatus);
}
// 只查询未删除的记录
wrapper.eq(GraphInstance::getDelFlag, "0");
// 按创建时间倒序
wrapper.orderByDesc(GraphInstance::getCreateTime);
return graphInstanceMapper.selectPage(page, wrapper);
}
@Override
public boolean updateStatus(String graphUuid, Integer status) {
try {
LambdaUpdateWrapper<GraphInstance> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(GraphInstance::getGraphUuid, graphUuid);
wrapper.set(GraphInstance::getGraphStatus, status);
int rows = graphInstanceMapper.update(null, wrapper);
log.info("更新图谱状态: graphUuid={}, status={}, rows={}", graphUuid, status, rows);
return rows > 0;
} catch (Exception e) {
log.error("更新图谱状态失败: graphUuid={}, status={}", graphUuid, status, e);
return false;
}
}
@Override
public boolean updateCounts(String graphUuid, Integer nodeCount, Integer relationshipCount) {
try {
LambdaUpdateWrapper<GraphInstance> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(GraphInstance::getGraphUuid, graphUuid);
if (nodeCount != null) {
wrapper.set(GraphInstance::getNodeCount, nodeCount);
}
if (relationshipCount != null) {
wrapper.set(GraphInstance::getRelationshipCount, relationshipCount);
}
int rows = graphInstanceMapper.update(null, wrapper);
log.info("更新图谱统计: graphUuid={}, nodeCount={}, relationshipCount={}, rows={}",
graphUuid, nodeCount, relationshipCount, rows);
return rows > 0;
} catch (Exception e) {
log.error("更新图谱统计失败: graphUuid={}", graphUuid, e);
return false;
}
}
@Override
public boolean updateConfig(String graphUuid, String config) {
try {
LambdaUpdateWrapper<GraphInstance> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(GraphInstance::getGraphUuid, graphUuid);
wrapper.set(GraphInstance::getConfig, config);
int rows = graphInstanceMapper.update(null, wrapper);
log.info("更新图谱配置: graphUuid={}, rows={}", graphUuid, rows);
return rows > 0;
} catch (Exception e) {
log.error("更新图谱配置失败: graphUuid={}", graphUuid, e);
return false;
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteInstance(String graphUuid) {
try {
log.info("🗑️ 开始删除图谱实例及数据graphUuid: {}", graphUuid);
// ⭐ 1. 先获取实例信息获取knowledgeId
GraphInstance instance = getByUuid(graphUuid);
if (instance == null) {
log.warn("⚠️ 图谱实例不存在: graphUuid={}", graphUuid);
return false;
}
String knowledgeId = instance.getKnowledgeId();
// ⭐ 2. 删除Neo4j中的图数据通过knowledgeId
if (StrUtil.isNotBlank(knowledgeId)) {
log.info("删除Neo4j图数据knowledgeId: {}", knowledgeId);
boolean neo4jDeleted = graphStoreService.deleteByKnowledgeId(knowledgeId);
if (neo4jDeleted) {
log.info("✅ Neo4j图数据删除成功");
} else {
log.warn("⚠️ Neo4j图数据删除失败可能是没有数据");
}
} else {
log.warn("⚠️ 实例没有关联知识库ID跳过Neo4j数据删除");
}
// 3. 删除MySQL中的实例记录
LambdaQueryWrapper<GraphInstance> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(GraphInstance::getGraphUuid, graphUuid);
int rows = graphInstanceMapper.delete(wrapper);
log.info("✅ 删除图谱实例成功: graphUuid={}, knowledgeId={}, rows={}",
graphUuid, knowledgeId, rows);
return rows > 0;
} catch (Exception e) {
log.error("❌ 删除图谱实例失败: graphUuid={}", graphUuid, e);
return false;
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteInstanceAndData(String graphUuid) {
try {
// 1. 删除 Neo4j 中的图谱数据
boolean graphDeleted = graphStoreService.deleteGraph(graphUuid);
// 2. 删除 MySQL 中的实例记录
boolean instanceDeleted = deleteInstance(graphUuid);
log.info("删除图谱实例及数据: graphUuid={}, graphDeleted={}, instanceDeleted={}",
graphUuid, graphDeleted, instanceDeleted);
return graphDeleted && instanceDeleted;
} catch (Exception e) {
log.error("删除图谱实例及数据失败: graphUuid={}", graphUuid, e);
return false;
}
}
@Override
public Map<String, Object> getStatistics(String graphUuid) {
try {
// 从 Neo4j 获取实时统计
Map<String, Object> stats = graphStoreService.getGraphStatistics(graphUuid);
// 更新到 MySQL异步
if (stats.containsKey("nodeCount") && stats.containsKey("relationshipCount")) {
updateCounts(
graphUuid,
(Integer) stats.get("nodeCount"),
(Integer) stats.get("relationshipCount")
);
}
// 添加实例信息
GraphInstance instance = getByUuid(graphUuid);
if (instance != null) {
stats.put("graphName", instance.getGraphName());
stats.put("status", instance.getGraphStatus());
stats.put("createTime", instance.getCreateTime());
stats.put("updateTime", instance.getUpdateTime());
}
return stats;
} catch (Exception e) {
log.error("获取图谱统计信息失败: graphUuid={}", graphUuid, e);
return new HashMap<>();
}
}
}

View File

@@ -1,462 +0,0 @@
package org.ruoyi.service.graph.impl;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.constant.GraphConstants;
import org.ruoyi.domain.bo.graph.GraphEdge;
import org.ruoyi.domain.bo.graph.GraphVertex;
import org.ruoyi.domain.dto.ExtractedEntity;
import org.ruoyi.domain.dto.ExtractedRelation;
import org.ruoyi.domain.dto.GraphExtractionResult;
import org.ruoyi.service.graph.IGraphExtractionService;
import org.ruoyi.service.graph.IGraphRAGService;
import org.ruoyi.service.graph.IGraphStoreService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import java.util.*;
/**
* GraphRAG服务实现
*
* @author ruoyi
* @date 2025-09-30
*/
@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "knowledge.graph", name = "enabled", havingValue = "true")
public class GraphRAGServiceImpl implements IGraphRAGService {
private final IGraphExtractionService graphExtractionService;
private final IGraphStoreService graphStoreService;
@Override
public GraphExtractionResult ingestText(String text, String knowledgeId, Map<String, Object> metadata) {
return ingestTextWithModel(text, knowledgeId, metadata, null);
}
@Override
public GraphExtractionResult ingestTextWithModel(String text, String knowledgeId, Map<String, Object> metadata, String modelName) {
log.info("开始将文本入库到图谱知识库ID: {}, 模型: {}, 文本长度: {}",
knowledgeId, modelName != null ? modelName : "默认", text.length());
try {
// 1. 从文本中抽取实体和关系
GraphExtractionResult extractionResult;
if (StrUtil.isNotBlank(modelName)) {
extractionResult = graphExtractionService.extractFromTextWithModel(text, modelName);
} else {
extractionResult = graphExtractionService.extractFromText(text);
}
if (!extractionResult.getSuccess()) {
log.error("实体抽取失败: {}", extractionResult.getErrorMessage());
return extractionResult;
}
// 2. 将抽取的实体转换为图节点
List<GraphVertex> vertices = convertEntitiesToVertices(
extractionResult.getEntities(),
knowledgeId,
metadata
);
// 3. 批量添加节点到Neo4j并建立实体名称→nodeId的映射
Map<String, String> entityNameToNodeIdMap = new HashMap<>();
if (!vertices.isEmpty()) {
int addedCount = graphStoreService.addVertices(vertices);
log.info("成功添加 {} 个节点到图谱", addedCount);
// ⭐ 建立映射:实体名称 → nodeId
for (GraphVertex vertex : vertices) {
entityNameToNodeIdMap.put(vertex.getName(), vertex.getNodeId());
}
log.debug("建立实体名称映射: {} 个实体", entityNameToNodeIdMap.size());
}
// 4. 将抽取的关系转换为图边使用映射填充nodeId
List<GraphEdge> edges = convertRelationsToEdges(
extractionResult.getRelations(),
knowledgeId,
metadata,
entityNameToNodeIdMap // ⭐ 传入映射
);
// 5. 批量添加关系到Neo4j
if (!edges.isEmpty()) {
int addedCount = graphStoreService.addEdges(edges);
log.info("成功添加 {} 个关系到图谱", addedCount);
}
return extractionResult;
} catch (Exception e) {
log.error("文本入库失败", e);
return GraphExtractionResult.builder()
.entities(new ArrayList<>())
.relations(new ArrayList<>())
.success(false)
.errorMessage(e.getMessage())
.build();
}
}
@Override
public GraphExtractionResult ingestDocument(String documentText, String knowledgeId, Map<String, Object> metadata) {
return ingestDocumentWithModel(documentText, knowledgeId, metadata, null);
}
@Override
public GraphExtractionResult ingestDocumentWithModel(String documentText, String knowledgeId, Map<String, Object> metadata, String modelName) {
log.info("开始将文档入库到图谱知识库ID: {}, 模型: {}, 文档长度: {}",
knowledgeId, modelName != null ? modelName : "默认", documentText.length());
// 如果文档较短,直接处理
if (documentText.length() < GraphConstants.RAG_MAX_SEGMENT_SIZE_IN_TOKENS * 4) {
return ingestTextWithModel(documentText, knowledgeId, metadata, modelName);
}
// 文档较长,需要分片处理
List<String> chunks = splitDocument(documentText);
log.info("文档已分割为 {} 个片段", chunks.size());
// 合并结果
List<ExtractedEntity> allEntities = new ArrayList<>();
List<ExtractedRelation> allRelations = new ArrayList<>();
int totalTokenUsed = 0;
for (int i = 0; i < chunks.size(); i++) {
String chunk = chunks.get(i);
log.debug("处理第 {}/{} 个片段", i + 1, chunks.size());
// 为每个片段添加序号元数据
Map<String, Object> chunkMetadata = new HashMap<>(metadata);
chunkMetadata.put("chunk_index", i);
chunkMetadata.put("total_chunks", chunks.size());
GraphExtractionResult result = ingestTextWithModel(chunk, knowledgeId, chunkMetadata, modelName);
if (result.getSuccess()) {
allEntities.addAll(result.getEntities());
allRelations.addAll(result.getRelations());
if (result.getTokenUsed() != null) {
totalTokenUsed += result.getTokenUsed();
}
}
}
// 去重实体(基于名称和类型)
List<ExtractedEntity> uniqueEntities = deduplicateEntities(allEntities);
log.info("去重后实体数: {} -> {}", allEntities.size(), uniqueEntities.size());
return GraphExtractionResult.builder()
.entities(uniqueEntities)
.relations(allRelations)
.tokenUsed(totalTokenUsed)
.success(true)
.build();
}
@Override
public String retrieveFromGraph(String query, String knowledgeId, int maxResults) {
log.info("从图谱检索相关内容,查询: {}, 知识库ID: {}", query, knowledgeId);
try {
// 1. 从查询中抽取关键词(简单分词)
List<String> keywords = extractKeywords(query);
log.debug("提取的关键词: {}", keywords);
if (keywords.isEmpty()) {
return "未能从查询中提取关键信息";
}
// 2. 在图谱中搜索相关实体节点
List<GraphVertex> matchedNodes = new ArrayList<>();
for (String keyword : keywords) {
List<GraphVertex> nodes = graphStoreService.searchVerticesByName(
keyword, knowledgeId, Math.min(5, maxResults)
);
matchedNodes.addAll(nodes);
}
if (matchedNodes.isEmpty()) {
return "图谱中未找到相关实体";
}
log.info("找到 {} 个匹配的实体节点", matchedNodes.size());
// 3. 去重按nodeId
Map<String, GraphVertex> uniqueNodes = new HashMap<>();
for (GraphVertex node : matchedNodes) {
uniqueNodes.putIfAbsent(node.getNodeId(), node);
}
matchedNodes = new ArrayList<>(uniqueNodes.values());
// 限制结果数量
if (matchedNodes.size() > maxResults) {
matchedNodes = matchedNodes.subList(0, maxResults);
}
// 4. 为每个匹配节点获取邻居,构建子图上下文
StringBuilder result = new StringBuilder();
result.append("### 图谱检索结果\n\n");
result.append(String.format("查询: %s\n", query));
result.append(String.format("找到 %d 个相关实体:\n\n", matchedNodes.size()));
for (int i = 0; i < matchedNodes.size(); i++) {
GraphVertex node = matchedNodes.get(i);
result.append(String.format("**%d. %s** (%s)\n", i + 1, node.getName(), node.getLabel()));
if (StrUtil.isNotBlank(node.getDescription())) {
result.append(String.format(" 描述: %s\n", node.getDescription()));
}
// 获取邻居节点1跳
List<GraphVertex> neighbors = graphStoreService.getNeighbors(
node.getNodeId(), knowledgeId, 5
);
if (!neighbors.isEmpty()) {
result.append(" 关联实体: ");
List<String> neighborNames = neighbors.stream()
.map(GraphVertex::getName)
.limit(5)
.collect(java.util.stream.Collectors.toList());
result.append(String.join(", ", neighborNames));
result.append("\n");
}
result.append("\n");
}
// 5. 添加统计信息
result.append("---\n");
result.append(String.format("总计: %d 个实体节点\n", matchedNodes.size()));
return result.toString();
} catch (Exception e) {
log.error("图谱检索失败", e);
return "检索失败: " + e.getMessage();
}
}
/**
* 从查询中提取关键词
*
* @param query 查询文本
* @return 关键词列表
*/
private List<String> extractKeywords(String query) {
List<String> keywords = new ArrayList<>();
// 简单的中文分词策略
// 1. 去除标点符号
String cleaned = query.replaceAll("[\\p{Punct}\\s]+", " ");
// 2. 按空格分割
String[] words = cleaned.split("\\s+");
// 3. 过滤停用词和短词
Set<String> stopWords = new HashSet<>(Arrays.asList(
"", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", ""
));
for (String word : words) {
word = word.trim();
if (word.length() >= 2 && !stopWords.contains(word)) {
keywords.add(word);
}
}
// 4. 如果没有提取到关键词尝试按2-3字切分
if (keywords.isEmpty() && query.length() >= 2) {
for (int i = 0; i <= query.length() - 2; i++) {
String chunk = query.substring(i, Math.min(i + 3, query.length()));
if (chunk.length() >= 2 && !stopWords.contains(chunk)) {
keywords.add(chunk);
}
}
}
// 去重并限制数量
return keywords.stream()
.distinct()
.limit(5)
.collect(java.util.stream.Collectors.toList());
}
@Override
public boolean deleteGraphData(String knowledgeId) {
log.info("删除知识库图谱数据知识库ID: {}", knowledgeId);
try {
// 删除该知识库的所有节点和关系
return graphStoreService.deleteByKnowledgeId(knowledgeId);
} catch (Exception e) {
log.error("删除图谱数据失败", e);
return false;
}
}
/**
* 将抽取的实体转换为图节点
*/
private List<GraphVertex> convertEntitiesToVertices(
List<ExtractedEntity> entities,
String knowledgeId,
Map<String, Object> metadata) {
List<GraphVertex> vertices = new ArrayList<>();
for (ExtractedEntity entity : entities) {
GraphVertex vertex = new GraphVertex();
vertex.setNodeId(IdUtil.simpleUUID()); // 生成唯一ID
vertex.setName(entity.getName());
vertex.setLabel(entity.getType());
vertex.setDescription(entity.getDescription());
vertex.setKnowledgeId(knowledgeId);
vertex.setConfidence(entity.getConfidence() != null ? entity.getConfidence() : 1.0);
// 添加元数据
if (metadata != null && !metadata.isEmpty()) {
vertex.setMetadata(metadata);
}
vertices.add(vertex);
}
return vertices;
}
/**
* 将抽取的关系转换为图边
*
* @param relations 抽取的关系列表
* @param knowledgeId 知识库ID
* @param metadata 元数据
* @param entityNameToNodeIdMap 实体名称到节点ID的映射
* @return 图边列表
*/
private List<GraphEdge> convertRelationsToEdges(
List<ExtractedRelation> relations,
String knowledgeId,
Map<String, Object> metadata,
Map<String, String> entityNameToNodeIdMap) {
List<GraphEdge> edges = new ArrayList<>();
int skippedCount = 0;
for (ExtractedRelation relation : relations) {
// ⭐ 通过实体名称查找对应的nodeId
String sourceNodeId = entityNameToNodeIdMap.get(relation.getSourceEntity());
String targetNodeId = entityNameToNodeIdMap.get(relation.getTargetEntity());
// 如果找不到对应的节点ID跳过这个关系
if (sourceNodeId == null || targetNodeId == null) {
log.warn("⚠️ 跳过关系(节点未找到): {} -> {}",
relation.getSourceEntity(), relation.getTargetEntity());
skippedCount++;
continue;
}
GraphEdge edge = new GraphEdge();
edge.setEdgeId(IdUtil.simpleUUID());
edge.setSourceNodeId(sourceNodeId); // ⭐ 设置源节点ID
edge.setTargetNodeId(targetNodeId); // ⭐ 设置目标节点ID
edge.setSourceName(relation.getSourceEntity());
edge.setTargetName(relation.getTargetEntity());
edge.setLabel("RELATED_TO"); // 默认关系类型
edge.setDescription(relation.getDescription());
edge.setWeight(relation.getStrength() / 10.0); // 转换为0-1的权重
edge.setKnowledgeId(knowledgeId);
edge.setConfidence(relation.getConfidence() != null ? relation.getConfidence() : 1.0);
// 添加元数据
if (metadata != null && !metadata.isEmpty()) {
edge.setMetadata(metadata);
}
edges.add(edge);
}
if (skippedCount > 0) {
log.warn("⚠️ 共跳过 {} 个关系(对应的实体节点未找到)", skippedCount);
}
return edges;
}
/**
* 分割文档为多个片段
*/
private List<String> splitDocument(String text) {
List<String> chunks = new ArrayList<>();
int chunkSize = GraphConstants.RAG_MAX_SEGMENT_SIZE_IN_TOKENS * 4; // 简单估算字符数
int overlap = GraphConstants.RAG_SEGMENT_OVERLAP_IN_TOKENS * 4;
int start = 0;
while (start < text.length()) {
int end = Math.min(start + chunkSize, text.length());
// 尝试在句子边界分割
if (end < text.length()) {
int lastPeriod = text.lastIndexOf('。', end);
int lastNewline = text.lastIndexOf('\n', end);
int boundary = Math.max(lastPeriod, lastNewline);
if (boundary > start) {
end = boundary + 1;
}
}
chunks.add(text.substring(start, end));
// ⭐ 修复死循环:确保 start 一定会增加
// 如果已经到达文本末尾,直接退出
if (end >= text.length()) {
break;
}
// 计算下一个起始位置确保至少前进1个字符
int nextStart = end - overlap;
if (nextStart <= start) {
// 如果 overlap 太大导致无法前进,强制前进到 end
start = end;
} else {
start = nextStart;
}
}
return chunks;
}
/**
* 去重实体
*/
private List<ExtractedEntity> deduplicateEntities(List<ExtractedEntity> entities) {
Map<String, ExtractedEntity> entityMap = new HashMap<>();
for (ExtractedEntity entity : entities) {
String key = entity.getName() + "|" + entity.getType();
if (!entityMap.containsKey(key)) {
entityMap.put(key, entity);
} else {
// 如果已存在,保留置信度更高的
ExtractedEntity existing = entityMap.get(key);
double entityConf = entity.getConfidence() != null ? entity.getConfidence() : 1.0;
double existingConf = existing.getConfidence() != null ? existing.getConfidence() : 1.0;
if (entityConf > existingConf) {
entityMap.put(key, entity);
}
}
}
return new ArrayList<>(entityMap.values());
}
}

View File

@@ -1,46 +0,0 @@
package org.ruoyi.service.graph.impl;
import dev.langchain4j.model.openai.OpenAiChatModel;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.service.graph.IGraphLLMService;
import org.springframework.stereotype.Service;
/**
* OpenAI 图谱LLM服务实现
* 支持 OpenAI 兼容的模型GPT-3.5, GPT-4, 自定义OpenAI兼容接口等
*
* @author ruoyi
* @date 2025-10-11
*/
@Slf4j
@Service
public class OpenAIGraphLLMServiceImpl implements IGraphLLMService {
@Override
public String extractGraph(String prompt, ChatModelVo chatModel) {
log.info("OpenAI模型调用: model={}, apiHost={}, 提示词长度={}",
chatModel.getModelName(), chatModel.getApiHost(), prompt.length());
try {
OpenAiChatModel model = OpenAiChatModel.builder()
.baseUrl(chatModel.getApiHost())
.apiKey(chatModel.getApiKey())
.modelName(chatModel.getModelName())
.build();
String content = model.chat(prompt);
String responseText = content != null ? content : "";
log.info("OpenAI模型响应成功:, 响应长度={}", responseText.length());
return responseText;
} catch (Exception e) {
log.error("OpenAI模型调用失败: {}", e.getMessage(), e);
throw new RuntimeException("OpenAI模型调用失败: " + e.getMessage(), e);
}
}
@Override
public String getCategory() {
return "openai"; // 对应 ChatModel 表中的 category 字段
}
}

View File

@@ -4,6 +4,7 @@ import dev.langchain4j.model.embedding.EmbeddingModel;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.chat.service.chat.IChatModelService;
import org.ruoyi.config.VectorStoreProperties;
import org.ruoyi.factory.EmbeddingModelFactory;
import org.ruoyi.service.vector.VectorStoreService;
@@ -22,6 +23,9 @@ public abstract class AbstractVectorStoreStrategy implements VectorStoreService
private final EmbeddingModelFactory embeddingModelFactory;
protected final IChatModelService chatModelService;
/**
* 将float数组转换为Float对象数组
*/
@@ -37,8 +41,8 @@ public abstract class AbstractVectorStoreStrategy implements VectorStoreService
* 获取向量模型
*/
@SneakyThrows
protected EmbeddingModel getEmbeddingModel(String modelName, Integer dimension) {
return embeddingModelFactory.createModel(modelName, dimension);
protected EmbeddingModel getEmbeddingModel(String modelName) {
return embeddingModelFactory.createModel(modelName);
}
/**

View File

@@ -14,6 +14,8 @@ import io.milvus.param.IndexType;
import io.milvus.param.MetricType;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.common.chat.service.chat.IChatModelService;
import org.ruoyi.config.VectorStoreProperties;
import org.ruoyi.domain.bo.vector.QueryVectorBo;
import org.ruoyi.domain.bo.vector.StoreEmbeddingBo;
@@ -31,8 +33,9 @@ import java.util.stream.IntStream;
public class MilvusVectorStoreStrategy extends AbstractVectorStoreStrategy {
public MilvusVectorStoreStrategy(VectorStoreProperties vectorStoreProperties,
IChatModelService chatModelService,
EmbeddingModelFactory embeddingModelFactory) {
super(vectorStoreProperties, embeddingModelFactory);
super(vectorStoreProperties, embeddingModelFactory, chatModelService);
}
// 缓存不同集合与 autoFlush 配置的 Milvus 连接
@@ -42,38 +45,27 @@ public class MilvusVectorStoreStrategy extends AbstractVectorStoreStrategy {
* 获取 Milvus Store支持动态维度
*/
private EmbeddingStore<TextSegment> getMilvusStore(String collectionName, int dimension, boolean autoFlushOnInsert) {
String key = collectionName + "|" + dimension + "|" + autoFlushOnInsert;
return storeCache.computeIfAbsent(key, k ->
MilvusEmbeddingStore.builder()
.uri(vectorStoreProperties.getMilvus().getUrl())
.collectionName(collectionName)
.dimension(dimension)
.indexType(IndexType.IVF_FLAT)
.metricType(MetricType.L2)
.autoFlushOnInsert(autoFlushOnInsert)
.idFieldName("id")
.textFieldName("text")
.metadataFieldName("metadata")
.vectorFieldName("vector")
.build()
);
return MilvusEmbeddingStore.builder()
.uri(vectorStoreProperties.getMilvus().getUrl())
.collectionName(collectionName)
.dimension(dimension)
.indexType(IndexType.IVF_FLAT)
.metricType(MetricType.L2)
.autoFlushOnInsert(autoFlushOnInsert)
.idFieldName("id")
.textFieldName("text")
.metadataFieldName("metadata")
.vectorFieldName("vector")
.build();
}
/**
* 获取 embedding 模型的实际维度
*/
private int getModelDimension(String modelName) {
try {
EmbeddingModel model = getEmbeddingModel(modelName, null);
// 使用一个测试文本获取向量维度
Embedding testEmbedding = model.embed("test").content();
int dimension = testEmbedding.dimension();
log.info("Detected embedding model dimension: {} for model: {}", dimension, modelName);
return dimension;
} catch (Exception e) {
log.warn("Failed to detect model dimension for: {}, using default 1024", modelName, e);
return 1024; // 默认使用 1024 (bge-m3 的维度)
}
ChatModelVo modelConfig = chatModelService.selectModelByName(modelName);
return modelConfig.getModelDimension();
}
@Override
@@ -88,7 +80,7 @@ public class MilvusVectorStoreStrategy extends AbstractVectorStoreStrategy {
@Override
public void storeEmbeddings(StoreEmbeddingBo storeEmbeddingBo) {
int dimension = getModelDimension(storeEmbeddingBo.getEmbeddingModelName());
EmbeddingModel embeddingModel = getEmbeddingModel(storeEmbeddingBo.getEmbeddingModelName(), dimension);
EmbeddingModel embeddingModel = getEmbeddingModel(storeEmbeddingBo.getEmbeddingModelName());
List<String> chunkList = storeEmbeddingBo.getChunkList();
List<String> fidList = storeEmbeddingBo.getFids();
@@ -121,7 +113,7 @@ public class MilvusVectorStoreStrategy extends AbstractVectorStoreStrategy {
@Override
public List<String> getQueryVector(QueryVectorBo queryVectorBo) {
int dimension = getModelDimension(queryVectorBo.getEmbeddingModelName());
EmbeddingModel embeddingModel = getEmbeddingModel(queryVectorBo.getEmbeddingModelName(), dimension);
EmbeddingModel embeddingModel = getEmbeddingModel(queryVectorBo.getEmbeddingModelName());
Embedding queryEmbedding = embeddingModel.embed(queryVectorBo.getQuery()).content();
String collectionName = vectorStoreProperties.getMilvus().getCollectionname() + queryVectorBo.getKid();

View File

@@ -7,6 +7,7 @@ import dev.langchain4j.model.embedding.EmbeddingModel;
import io.weaviate.client.WeaviateClient;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.chat.service.chat.IChatModelService;
import org.ruoyi.common.core.exception.ServiceException;
import org.ruoyi.config.VectorStoreProperties;
import org.ruoyi.domain.bo.vector.QueryVectorBo;
@@ -41,8 +42,9 @@ public class WeaviateVectorStoreStrategy extends AbstractVectorStoreStrategy {
private WeaviateClient client;
public WeaviateVectorStoreStrategy(VectorStoreProperties vectorStoreProperties,
IChatModelService chatModelService,
EmbeddingModelFactory embeddingModelFactory) {
super(vectorStoreProperties, embeddingModelFactory);
super(vectorStoreProperties, embeddingModelFactory,chatModelService);
}
@Override
@@ -91,12 +93,12 @@ public class WeaviateVectorStoreStrategy extends AbstractVectorStoreStrategy {
@Override
public void storeEmbeddings(StoreEmbeddingBo storeEmbeddingBo) {
createSchema(storeEmbeddingBo.getKid(), storeEmbeddingBo.getEmbeddingModelName());
EmbeddingModel embeddingModel = getEmbeddingModel(storeEmbeddingBo.getEmbeddingModelName(), null);
EmbeddingModel embeddingModel = getEmbeddingModel(storeEmbeddingBo.getEmbeddingModelName());
List<String> chunkList = storeEmbeddingBo.getChunkList();
List<String> fidList = storeEmbeddingBo.getFids();
String kid = storeEmbeddingBo.getKid();
String docId = storeEmbeddingBo.getDocId();
log.info("向量存储条数记录: " + chunkList.size());
log.info("向量存储条数记录: {}", chunkList.size());
long startTime = System.currentTimeMillis();
for (int i = 0; i < chunkList.size(); i++) {
String text = chunkList.get(i);
@@ -123,7 +125,7 @@ public class WeaviateVectorStoreStrategy extends AbstractVectorStoreStrategy {
@Override
public List<String> getQueryVector(QueryVectorBo queryVectorBo) {
createSchema(queryVectorBo.getKid(), queryVectorBo.getEmbeddingModelName());
EmbeddingModel embeddingModel = getEmbeddingModel(queryVectorBo.getEmbeddingModelName(), null);
EmbeddingModel embeddingModel = getEmbeddingModel(queryVectorBo.getEmbeddingModelName());
Embedding queryEmbedding = embeddingModel.embed(queryVectorBo.getQuery()).content();
float[] vector = queryEmbedding.vector();
List<String> vectorStrings = new ArrayList<>();