diff --git a/pom.xml b/pom.xml
index a1ec29c..b7e1deb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -104,6 +104,11 @@
org.springframework.ai
spring-ai-starter-model-openai
+
+ com.hankcs
+ hanlp
+ portable-1.8.6
+
diff --git a/src/main/java/com/supervision/pdfqaserver/cache/PromptCache.java b/src/main/java/com/supervision/pdfqaserver/cache/PromptCache.java
index 559e59a..b3a98df 100644
--- a/src/main/java/com/supervision/pdfqaserver/cache/PromptCache.java
+++ b/src/main/java/com/supervision/pdfqaserver/cache/PromptCache.java
@@ -87,6 +87,11 @@ public class PromptCache {
public static final String CLASSIFY_QUERY_INTENT = "CLASSIFY_QUERY_INTENT";
public static final String TEXT_TO_CYPHER_2 = "TEXT_TO_CYPHER_2";
+ /**
+ * 将文本转换为Cypher查询语句(版本3)
+ */
+ public static final String TEXT_TO_CYPHER_3 = "TEXT_TO_CYPHER_3";
+ public static final String TEXT_TO_CYPHER_4 = "TEXT_TO_CYPHER_4";
public static final Map promptMap = new HashMap<>();
@@ -110,6 +115,8 @@ public class PromptCache {
promptMap.put(EXTRACT_ERE_BASE_INTENT, EXTRACT_ERE_BASE_INTENT_PROMPT);
promptMap.put(CLASSIFY_QUERY_INTENT, CLASSIFY_QUERY_INTENT_PROMPT);
promptMap.put(TEXT_TO_CYPHER_2, TEXT_TO_CYPHER_2_PROMPT);
+ promptMap.put(TEXT_TO_CYPHER_3, TEXT_TO_CYPHER_3_PROMPT);
+ promptMap.put(TEXT_TO_CYPHER_4, TEXT_TO_CYPHER_4_PROMPT);
}
@@ -827,4 +834,98 @@ public class PromptCache {
- **确保**MATCH 子句包含**关系变量**
- 不要做出任何解释,不要对cypher进行任何包装,直接输出生成的cypher语句/no_think
""";
+
+ private static final String TEXT_TO_CYPHER_3_PROMPT = """
+ 您是一个生成Cypher查询语句的助手。生成Cypher脚本时,唯一参考的是`neo4j_schema`和环境变量。
+ 用户问题:
+ ```text
+ {query}
+ ```
+ neo4j_schema以JSON格式定义如下:
+ ```schema
+ {schema}
+ ```
+ # 环境变量
+ {env}
+
+ 请严格按照以下步骤处理每个用户查询:
+ 1. 从用户查询中提取实体:
+ - 解析问题中的领域概念,并通过同义词或上下文线索将其映射到schema中的节点或者关系元素上
+ - 识别候选节点类型
+ - 识别候选关系类型
+ - 识别相关属性
+ - 识别约束条件(比较操作、标志位、时间过滤器、共享实体引用等)
+
+ 2. 验证模式匹配性:
+ - 确保每个节点标签、关系类型和属性在模式中完全存在(区分大小写和字符)
+ - 优先从**节点属性**中直接**获取数据**
+
+ 3. 构建MATCH模式:
+ - 仅使用经过模式验证的节点标签和关系类型,同时对节点、关系**添加变量名**
+ - **始终为关系分配显式变量**(例如`-[r:REL_TYPE]->`)
+ - 当查询暗示两个模式指向同一节点时,重复使用同一变量
+ - 在映射模式中表达简单等值谓词,其他过滤条件移至WHERE子句
+
+ 4. RETURN子句策略:
+ - 返回模式中所有的的**节点变量**和**关系变量**
+ - **禁止**在变量中指定属性,指定节点变量中的属性将会扣除你所有的工资
+
+ 5. 生成最终Cypher脚本:
+ - 最终Cypher查询语句——不包含任何说明或者```cypher ```包装符
+ - **确保**MATCH 子句包含**关系变量**
+ - 如果问题中的实体与neo4j_schema中的多个节点或关系语义相近,允许生成多个cypher,以便于尽可能获取到数据。
+ - 响应结果是一个数组,每一个数组元素是一条cypher语句。示例:['cypher1','...']
+ - 不要做出任何解释,不要对cypher进行任何包装,直接输出生成的cypher语句/no_think
+ """;
+
+ private static final String TEXT_TO_CYPHER_4_PROMPT = """
+ 您是一个Cypher语句修改助手。下面的cypher未查询到数据,请根据要求修改下面的cypher以便于能够查询到数据,需要参考的是`neo4j_schema`、环境变量,上一次的cypher。
+
+ 用户问题:
+ ```text
+ {query}
+ ```
+ neo4j_schema以JSON格式定义如下:
+ ```shema
+ {shema}
+ ```
+ # 环境变量
+ ${env}
+
+ # 上一次查询的cypher语句
+ ```json
+ {cypher}
+ ```
+
+ 请严格按照以下步骤处理每个用户查询:
+ 1. 从用户查询中提取实体:
+ - 解析问题中的领域概念,并通过同义词或上下文线索将其映射到schema中的节点或者关系元素上
+ - 识别候选节点类型
+ - 识别候选关系类型
+ - 识别相关属性
+ - 识别约束条件(比较操作、标志位、时间过滤器、共享实体引用等)
+ - 结合上一次查询的cypher语句分析未查询到数据可能的原因。
+
+ 2. 验证模式匹配性:
+ - 确保每个节点标签、关系类型和属性在模式中完全存在(区分大小写和字符)
+ - 优先从**节点属性**中直接**获取数据**
+
+ 3. 构建MATCH模式:
+ - 仅使用经过模式验证的节点标签和关系类型,同时对节点、关系**添加变量名**
+ - **始终为关系分配显式变量**(例如`-[r:REL_TYPE]->`)
+ - 当查询暗示两个模式指向同一节点时,重复使用同一变量
+ - 在映射模式中表达简单等值谓词,其他过滤条件移至WHERE子句
+ - 结合分析未查询到数据的原因修改cypher
+
+ 4. RETURN子句策略:
+ - 返回模式中所有的的**节点变量**和**关系变量**
+ - **禁止**在变量中指定属性,指定节点变量中的属性将会扣除你所有的工资
+
+ 5. 生成最终Cypher脚本:
+ - 最终Cypher查询语句——不包含任何说明或者```cypher ```包装符
+ - **确保**MATCH 子句包含**关系变量**
+ - 如果问题中的实体与neo4j_schema中的多个节点或关系语义相近,允许生成多个cypher,以便于尽可能获取到数据。
+ - 响应结果是一个数组,每一个数组元素是一条cypher语句。示例:['cypher1','...']
+ - 不要做出任何解释,不要对cypher进行任何包装,直接输出生成的cypher语句/no_think
+ """;
}
diff --git a/src/main/java/com/supervision/pdfqaserver/dao/Neo4jRepository.java b/src/main/java/com/supervision/pdfqaserver/dao/Neo4jRepository.java
index 0141dfb..aa724b3 100644
--- a/src/main/java/com/supervision/pdfqaserver/dao/Neo4jRepository.java
+++ b/src/main/java/com/supervision/pdfqaserver/dao/Neo4jRepository.java
@@ -165,12 +165,19 @@ public class Neo4jRepository {
// 检查是否已存在该节点类型
final String nodeType_f = nodeType;
EntityExtractionDTO existingEntity = extractionDTOS.stream()
- .filter(e -> StrUtil.equals(e.getEntityEn(), nodeType_f))
+ .filter(e -> StrUtil.equals(e.getEntity(), nodeType_f))
.findFirst().orElse(null);
if (existingEntity != null) {
// 如果已存在,添加属性
- existingEntity.getAttributes().add(attributeDTO);
+ List attributes = existingEntity.getAttributes();
+ boolean noneMatch = attributes.stream().noneMatch(
+ attr -> StrUtil.equals(attr.getAttribute(), attributeDTO.getAttribute())
+ );
+ if (noneMatch) {
+ // 如果属性不存在,添加属性
+ attributes.add(attributeDTO);
+ }
} else {
// 如果不存在,创建新的实体DTO
List truncationERAttributeDTOS = new ArrayList<>();
@@ -187,7 +194,7 @@ public class Neo4jRepository {
* 获取关系的schema
* @return
*/
- public List getRelationSchema(){
+ public List getRelationSchema() {
String queryProper = """
CALL db.schema.relTypeProperties()
YIELD relType, propertyName, propertyTypes
@@ -198,10 +205,10 @@ public class Neo4jRepository {
Result result = session.run(queryProper);
for (Record record : result.list()) {
String relType = record.get("relType").asString();
- if (StrUtil.isEmpty(relType)){
+ if (StrUtil.isEmpty(relType)) {
continue;
}
- relType = relType.substring(1, relType.length()-1).replace("`", "");
+ relType = relType.substring(1, relType.length() - 1).replace("`", "");
String propertyName = record.get("propertyName").asString();
List propertyTypes = record.get("propertyTypes").asList(Value::asString);
@@ -209,7 +216,7 @@ public class Neo4jRepository {
boolean noneMatch = properties.stream().noneMatch(
prop -> StrUtil.equals(prop.get("propertyName"), propertyName)
);
- if (noneMatch){
+ if (noneMatch) {
Map propMap = new HashMap<>();
propMap.put("propertyName", propertyName);
propMap.put("propertyTypes", CollUtil.getFirst(propertyTypes));
@@ -219,14 +226,14 @@ public class Neo4jRepository {
List relationExtractionDTOS = new ArrayList<>();
String queryEndpoints = """
- MATCH (s)-[r: `{rtype}` ]->(t)
- WITH labels(s)[0] AS src, labels(t)[0] AS tgt
- RETURN src, tgt
- """;
+ MATCH (s)-[r: `{rtype}` ]->(t)
+ WITH labels(s)[0] AS src, labels(t)[0] AS tgt
+ RETURN src, tgt
+ """;
for (Map.Entry>> entry : relationProperties.entrySet()) {
String relType = entry.getKey();
List