代码功能bug修复

优化问答功能
v_0.0.2
xueqingkun 2 weeks ago
parent 930fcff1f8
commit ad3135ef90

@ -816,30 +816,39 @@ x {}
private static final String TEXT_TO_CYPHER_2_PROMPT = """ private static final String TEXT_TO_CYPHER_2_PROMPT = """
"You are a Cyphergenerating assistant. " CypherCypher`neo4j_schema`
"Your sole reference for generating Cypher scripts is the `neo4j_schema` variable.\\n\\n"
"User question:\\n{question}\\n\\n"
"The schema is defined below in JSON format:\\n" {question}
"{schema}\\n\\n" JSON
"Follow these exact steps for every user query:\\n\\n" {schema}
"1. Extract Entities from User Query:\\n"
"- Parse the question for domain concepts and use synonyms or contextual cues to map them to schema elements.\\n"
"- Identify candidate **node types**.\\n" 1.
"- Identify candidate **relationship types**.\\n" - 线
"- Identify relevant **properties**.\\n" -
"- Identify **constraints or conditions** (comparisons, flags, temporal filters, sharedentity references, etc.).\\n\\n" -
"2. Validate Against the Schema:\\n" -
"- Ensure every node label, relationship type, and property exists in the schema **exactly** (case and charactersensitive).\\n" -
"- If any required element is missing, respond exactly:\\n"
' \\"I could not generate a Cypher script; the required information is not part of the Neo4j schema.\\"\\n\\n' 2.
"3. Construct the MATCH Pattern:\\n" -
"- Use only schemavalidated node labels and relationship types.\\n" -
"- Reuse a single variable whenever the query implies that two patterns refer to the same node.\\n" '\\I could not generate a Cypher script; the required information is not part of the Neo4j schema.\\\\n\\n'
"- Express simple equality predicates in map patterns and move all other filters to a **WHERE** clause.\\n\\n"
"4. Return Clause Strategy:\\n" 3. MATCH
"- RETURN every node and relationship mentioned, unless the user explicitly requests specific properties.\\n\\n" - 使
"5. Final Cypher Script Generation:\\n" - 使
"- Respond with **only** the final Cypher query—no commentary or extra text.\\n" - WHERE
"- Use OPTIONAL MATCH only if explicitly required by the user and supported by the schema.\\n"
4. RETURN
- 使`RETURN nodes, relationships``RETURN path`
- RETURN
- `RETURN path`
5. Cypher
- Cypher
- 使OPTIONAL MATCH
- RETURN使
"""; """;
} }

@ -8,7 +8,7 @@ import org.springframework.ai.openai.OpenAiChatModel;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Slf4j @Slf4j
@Service //@Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class DeepSeekApiImpl implements AiCallService { public class DeepSeekApiImpl implements AiCallService {
private final OpenAiChatModel ollamaChatModel; private final OpenAiChatModel ollamaChatModel;

@ -5,20 +5,16 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import com.supervision.pdfqaserver.cache.PromptCache; import com.supervision.pdfqaserver.cache.PromptCache;
import com.supervision.pdfqaserver.dao.Neo4jRepository;
import com.supervision.pdfqaserver.domain.DocumentTruncation; import com.supervision.pdfqaserver.domain.DocumentTruncation;
import com.supervision.pdfqaserver.domain.Intention;
import com.supervision.pdfqaserver.dto.AnswerDetailDTO; import com.supervision.pdfqaserver.dto.AnswerDetailDTO;
import com.supervision.pdfqaserver.dto.DomainMetadataDTO; import com.supervision.pdfqaserver.dto.neo4j.NodeDTO;
import com.supervision.pdfqaserver.dto.neo4j.RelationObject; import com.supervision.pdfqaserver.dto.neo4j.RelationshipValueDTO;
import com.supervision.pdfqaserver.service.*; import com.supervision.pdfqaserver.service.*;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate; import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import java.util.ArrayList; import java.util.ArrayList;
@ -40,9 +36,6 @@ public class ChatServiceImpl implements ChatService {
private static final String CYPHER_QUERIES = "cypherQueries"; private static final String CYPHER_QUERIES = "cypherQueries";
private final OllamaChatModel ollamaChatModel;
private final AiCallService aiCallService; private final AiCallService aiCallService;
private final DocumentTruncationService documentTruncationService; private final DocumentTruncationService documentTruncationService;
@ -72,27 +65,102 @@ public class ChatServiceImpl implements ChatService {
log.info("生成回答的提示词:{}", generateAnswerMessage); log.info("生成回答的提示词:{}", generateAnswerMessage);
return aiCallService.stream(new Prompt(generateAnswerMessage)) return aiCallService.stream(new Prompt(generateAnswerMessage))
.map(response -> response.getResult().getOutput().getText()) .map(response -> response.getResult().getOutput().getText())
.concatWith(Flux.just(new JSONObject().set("answerDetails", convertToAnswerDetails(null)).toString())) .concatWith(Flux.just(new JSONObject().set("answerDetails", convertToAnswerDetails(graphResult)).toString()))
.concatWith(Flux.just("[END]")); .concatWith(Flux.just("[END]"));
} }
private List<AnswerDetailDTO> convertToAnswerDetails(List<RelationObject> relationObjects) { private List<AnswerDetailDTO> convertToAnswerDetails(List<Map<String, Object>> graphResult) {
if (CollUtil.isEmpty(relationObjects)) { if (CollUtil.isEmpty(graphResult)){
return new ArrayList<>(); return new ArrayList<>();
} }
List<AnswerDetailDTO> answerDetailDTOList = relationObjects.stream().map(AnswerDetailDTO::new).collect(Collectors.toList()); List<AnswerDetailDTO> answerDetailDTOS = new ArrayList<>();
if (CollUtil.isNotEmpty(answerDetailDTOList)){ for (Map<String, Object> map : graphResult) {
List<String> truncateIds = answerDetailDTOList.stream().map(AnswerDetailDTO::getTruncateId).distinct().toList(); Long start = null;
Long end = null;
for (Map.Entry<String, Object> entry : map.entrySet()) {
// 先找到头节点和尾节点id
if (entry.getValue() instanceof RelationshipValueDTO value){
start = value.getStart();
end = value.getEnd();
break;
}
}
AnswerDetailDTO answerDetailDTO = new AnswerDetailDTO();
if (null == start) {
// 没有关系类型
for (Map.Entry<String, Object> entry : map.entrySet()) {
// 处理头节点
if(entry.getValue() instanceof NodeDTO nodeDTO){
Map<String, Object> properties = nodeDTO.getProperties();
if (StrUtil.isEmpty(answerDetailDTO.getSourceType())){
answerDetailDTO.setSourceName((String) properties.get("name"));
answerDetailDTO.setSourceType(CollUtil.getFirst(nodeDTO.getLabels())); // 假设第一个标签是源类型
// 设置truncationId属性
answerDetailDTO.setTruncateId((String) properties.get("truncationId"));
}else {
answerDetailDTO.setTargetName((String) properties.get("name"));
answerDetailDTO.setTargetType(CollUtil.getFirst(nodeDTO.getLabels())); // 假设第一个标签是目标类型
}
}
}
answerDetailDTOS.add(answerDetailDTO);
}else {
// 有关系节点
for (Map.Entry<String, Object> entry : map.entrySet()) {
// 处理头节点
if(entry.getValue() instanceof NodeDTO nodeDTO){
if (start.equals(nodeDTO.getId())){
Map<String, Object> properties = nodeDTO.getProperties();
answerDetailDTO.setSourceName((String) properties.get("name"));
answerDetailDTO.setSourceType(CollUtil.getFirst(nodeDTO.getLabels())); // 假设第一个标签是源类型
// 设置truncationId属性
answerDetailDTO.setTruncateId((String) properties.get("truncationId"));
}
if (end.equals(nodeDTO.getId())){
Map<String, Object> properties = nodeDTO.getProperties();
answerDetailDTO.setTargetName((String) properties.get("name"));
answerDetailDTO.setTargetType(CollUtil.getFirst(nodeDTO.getLabels())); // 假设第一个标签是目标类型
}
}
if (entry.getValue() instanceof RelationshipValueDTO value) {
// 处理关系
if (start.equals(value.getStart()) || end.equals(value.getEnd())) {
answerDetailDTO.setRelation(value.getType());
}
}
}
answerDetailDTOS.add(answerDetailDTO);
}
}
List<AnswerDetailDTO> distinct = new ArrayList<>();
if (CollUtil.isNotEmpty(answerDetailDTOS)){
//去重answerDetailDTOS
for (AnswerDetailDTO answerDetailDTO : answerDetailDTOS) {
boolean noned = distinct.stream().noneMatch(i ->
StrUtil.equals(i.getSourceName(), answerDetailDTO.getSourceName()) &&
StrUtil.equals(i.getTargetName(), answerDetailDTO.getTargetName()) &&
StrUtil.equals(i.getRelation(), answerDetailDTO.getRelation()) &&
StrUtil.equals(i.getSourceType(), answerDetailDTO.getSourceType()) &&
StrUtil.equals(i.getTargetType(), answerDetailDTO.getTargetType()) &&
StrUtil.equals(i.getTruncateId(), answerDetailDTO.getTruncateId())
);
if (noned){
distinct.add(answerDetailDTO);
}
}
List<String> truncateIds = distinct.stream().map(AnswerDetailDTO::getTruncateId).distinct().toList();
if (CollUtil.isEmpty(truncateIds)){ if (CollUtil.isEmpty(truncateIds)){
return answerDetailDTOList; return answerDetailDTOS;
} }
List<DocumentTruncation> documentTruncations = documentTruncationService.listByIds(truncateIds); List<DocumentTruncation> documentTruncations = documentTruncationService.listByIds(truncateIds);
Map<String, String> contentMap = documentTruncations.stream().collect(Collectors.toMap(DocumentTruncation::getId, DocumentTruncation::getContent)); Map<String, String> contentMap = documentTruncations.stream().collect(Collectors.toMap(DocumentTruncation::getId, DocumentTruncation::getContent));
for (AnswerDetailDTO answerDetailDTO : answerDetailDTOList) { for (AnswerDetailDTO answerDetailDTO : distinct) {
answerDetailDTO.setTruncateContent(contentMap.get(answerDetailDTO.getTruncateId())); answerDetailDTO.setTruncateContent(contentMap.get(answerDetailDTO.getTruncateId()));
} }
} }
return answerDetailDTOList; return distinct;
} }
} }

@ -91,14 +91,22 @@ public class DomainMetadataServiceImpl extends ServiceImpl<DomainMetadataMapper,
} }
} }
// 保存意图和领域元数据的节点属性 // 保存意图和领域元数据的节点属性
List<ERAttributeDTO> nodeAttributes = metadata.getSourceAttributes(); List<ERAttributeDTO> sourceAttributes = metadata.getSourceAttributes();
nodeAttributes.addAll(metadata.getTargetAttributes()); if (CollUtil.isNotEmpty(sourceAttributes)){
if (CollUtil.isNotEmpty(nodeAttributes)){ for (ERAttributeDTO sourceAttribute : sourceAttributes) {
for (ERAttributeDTO nodeAttribute : nodeAttributes) { sourceAttribute.setDomainMetadataId(metadata.getId());
nodeAttribute.setDomainMetadataId(metadata.getId()); sourceAttribute.setErType("1");
nodeAttribute.setErType("1"); sourceAttribute.setErLabel(metadata.getSourceType());
nodeAttribute.setErLabel(nodeAttribute.getAttrName()); erAttributeService.saveIfAbsents(sourceAttribute.toErAttribute(), metadata.getId());
erAttributeService.saveIfAbsents(nodeAttribute.toErAttribute(), metadata.getId()); }
}
List<ERAttributeDTO> targetAttributes = metadata.getTargetAttributes();
if (CollUtil.isNotEmpty(targetAttributes)){
for (ERAttributeDTO targetAttribute : targetAttributes) {
targetAttribute.setDomainMetadataId(metadata.getId());
targetAttribute.setErType("1");
targetAttribute.setErLabel(metadata.getTargetType());
erAttributeService.saveIfAbsents(targetAttribute.toErAttribute(), metadata.getId());
} }
} }
} }

@ -101,7 +101,7 @@ public class KnowledgeGraphServiceImpl implements KnowledgeGraphService {
log.error("pdfId:{}元数据训练失败...", pdfId, e); log.error("pdfId:{}元数据训练失败...", pdfId, e);
pdfInfoService.pdfTrainFail(pdfId); pdfInfoService.pdfTrainFail(pdfId);
} }
log.info("pdfId:{}元数据训练失败,耗时:{}秒", pdfId, timer.intervalSecond()); log.error("pdfId:{}元数据训练失败,耗时:{}秒", pdfId, timer.intervalSecond(),e);
} }
} }

@ -65,7 +65,7 @@ public class TripleToCypherExecutorImpl implements TripleToCypherExecutor {
String prompt = promptMap.get(TEXT_TO_CYPHER_2); String prompt = promptMap.get(TEXT_TO_CYPHER_2);
String format = StrUtil.format(prompt, Map.of("question", query, "schema", schemaDTO.format())); String format = StrUtil.format(prompt, Map.of("question", query, "schema", schemaDTO.format()));
String call = aiCallService.call(format); String call = aiCallService.call(format);
if (StrUtil.equals(call,"I could not generate a Cypher script; the required information is not part of the Neo4j schema.")){ if (StrUtil.contains(call,"I could not generate a Cypher script; the required information is not part of the Neo4j schema.")){
log.info("大模型没能生成cypherquery: {}", query); log.info("大模型没能生成cypherquery: {}", query);
return null; return null;
} }
@ -133,7 +133,7 @@ public class TripleToCypherExecutorImpl implements TripleToCypherExecutor {
continue; continue;
} }
Map<String, Object> attributes = entity.getAttributes().stream().collect(Collectors.toMap( Map<String, Object> attributes = entity.getAttributes().stream().collect(Collectors.toMap(
TruncationERAttributeDTO::getAttributeEn, TruncationERAttributeDTO::getValue TruncationERAttributeDTO::getAttributeEn, TruncationERAttributeDTO::getValue, (v1, v2) -> v1
)); ));
attributes.put("truncationId", entity.getTruncationId()); attributes.put("truncationId", entity.getTruncationId());
attributes.put("name", entity.getName()); attributes.put("name", entity.getName());

@ -33,7 +33,7 @@ class PdfQaServerApplicationTests {
@Test @Test
void testGenerateGraph2() { void testGenerateGraph2() {
List<EREDTO> eredtos = knowledgeGraphService.listPdfEREDTO("16"); List<EREDTO> eredtos = knowledgeGraphService.listPdfEREDTO("15");
knowledgeGraphService.generateGraphSimple(eredtos); knowledgeGraphService.generateGraphSimple(eredtos);
log.info("finish..."); log.info("finish...");

Loading…
Cancel
Save