代码功能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 = """
"You are a Cyphergenerating assistant. "
"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"
"{schema}\\n\\n"
"Follow these exact steps for every user query:\\n\\n"
"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"
"- 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'
"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"
"- Express simple equality predicates in map patterns and move all other filters to a **WHERE** clause.\\n\\n"
"4. Return Clause Strategy:\\n"
"- 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"
"- Use OPTIONAL MATCH only if explicitly required by the user and supported by the schema.\\n"
CypherCypher`neo4j_schema`
{question}
JSON
{schema}
1.
- 线
-
-
-
-
2.
-
-
'\\I could not generate a Cypher script; the required information is not part of the Neo4j schema.\\\\n\\n'
3. MATCH
- 使
- 使
- WHERE
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 org.springframework.stereotype.Service;
@Slf4j
@Service
//@Service
@RequiredArgsConstructor
public class DeepSeekApiImpl implements AiCallService {
private final OpenAiChatModel ollamaChatModel;

@ -5,20 +5,16 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.supervision.pdfqaserver.cache.PromptCache;
import com.supervision.pdfqaserver.dao.Neo4jRepository;
import com.supervision.pdfqaserver.domain.DocumentTruncation;
import com.supervision.pdfqaserver.domain.Intention;
import com.supervision.pdfqaserver.dto.AnswerDetailDTO;
import com.supervision.pdfqaserver.dto.DomainMetadataDTO;
import com.supervision.pdfqaserver.dto.neo4j.RelationObject;
import com.supervision.pdfqaserver.dto.neo4j.NodeDTO;
import com.supervision.pdfqaserver.dto.neo4j.RelationshipValueDTO;
import com.supervision.pdfqaserver.service.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.prompt.Prompt;
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 reactor.core.publisher.Flux;
import java.util.ArrayList;
@ -40,9 +36,6 @@ public class ChatServiceImpl implements ChatService {
private static final String CYPHER_QUERIES = "cypherQueries";
private final OllamaChatModel ollamaChatModel;
private final AiCallService aiCallService;
private final DocumentTruncationService documentTruncationService;
@ -72,27 +65,102 @@ public class ChatServiceImpl implements ChatService {
log.info("生成回答的提示词:{}", generateAnswerMessage);
return aiCallService.stream(new Prompt(generateAnswerMessage))
.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]"));
}
private List<AnswerDetailDTO> convertToAnswerDetails(List<RelationObject> relationObjects) {
if (CollUtil.isEmpty(relationObjects)) {
private List<AnswerDetailDTO> convertToAnswerDetails(List<Map<String, Object>> graphResult) {
if (CollUtil.isEmpty(graphResult)){
return new ArrayList<>();
}
List<AnswerDetailDTO> answerDetailDTOList = relationObjects.stream().map(AnswerDetailDTO::new).collect(Collectors.toList());
if (CollUtil.isNotEmpty(answerDetailDTOList)){
List<String> truncateIds = answerDetailDTOList.stream().map(AnswerDetailDTO::getTruncateId).distinct().toList();
List<AnswerDetailDTO> answerDetailDTOS = new ArrayList<>();
for (Map<String, Object> map : graphResult) {
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)){
return answerDetailDTOList;
return answerDetailDTOS;
}
List<DocumentTruncation> documentTruncations = documentTruncationService.listByIds(truncateIds);
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()));
}
}
return answerDetailDTOList;
return distinct;
}
}

@ -91,14 +91,22 @@ public class DomainMetadataServiceImpl extends ServiceImpl<DomainMetadataMapper,
}
}
// 保存意图和领域元数据的节点属性
List<ERAttributeDTO> nodeAttributes = metadata.getSourceAttributes();
nodeAttributes.addAll(metadata.getTargetAttributes());
if (CollUtil.isNotEmpty(nodeAttributes)){
for (ERAttributeDTO nodeAttribute : nodeAttributes) {
nodeAttribute.setDomainMetadataId(metadata.getId());
nodeAttribute.setErType("1");
nodeAttribute.setErLabel(nodeAttribute.getAttrName());
erAttributeService.saveIfAbsents(nodeAttribute.toErAttribute(), metadata.getId());
List<ERAttributeDTO> sourceAttributes = metadata.getSourceAttributes();
if (CollUtil.isNotEmpty(sourceAttributes)){
for (ERAttributeDTO sourceAttribute : sourceAttributes) {
sourceAttribute.setDomainMetadataId(metadata.getId());
sourceAttribute.setErType("1");
sourceAttribute.setErLabel(metadata.getSourceType());
erAttributeService.saveIfAbsents(sourceAttribute.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);
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 format = StrUtil.format(prompt, Map.of("question", query, "schema", schemaDTO.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);
return null;
}
@ -133,7 +133,7 @@ public class TripleToCypherExecutorImpl implements TripleToCypherExecutor {
continue;
}
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("name", entity.getName());

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

Loading…
Cancel
Save