From 8b4ec184839425e4de80640b1bf7427853f2486d Mon Sep 17 00:00:00 2001 From: xueqingkun Date: Fri, 23 May 2025 17:20:49 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9F=BA=E4=BA=8E=E4=B8=89=E5=85=83=E7=BB=84?= =?UTF-8?q?=E6=8F=90=E5=8F=96=E9=A2=86=E5=9F=9F=E5=85=83=E6=95=B0=E6=8D=AE?= =?UTF-8?q?,=E5=87=BA=E7=89=88=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pdfqaserver/cache/PromptCache.java | 66 ++++++++++--------- .../pdfqaserver/domain/DomainMetadata.java | 1 + .../pdfqaserver/domain/Intention.java | 2 +- .../pdfqaserver/dto/IntentDTO.java | 2 +- .../impl/DomainMetadataServiceImpl.java | 4 +- .../impl/KnowledgeGraphServiceImpl.java | 28 +++++++- .../impl/TripleConversionPipelineImpl.java | 35 +++++++--- src/main/resources/mapper/IntentionMapper.xml | 4 +- .../PdfQaServerApplicationTests.java | 12 ++++ 9 files changed, 107 insertions(+), 47 deletions(-) diff --git a/src/main/java/com/supervision/pdfqaserver/cache/PromptCache.java b/src/main/java/com/supervision/pdfqaserver/cache/PromptCache.java index 0c8553b..a49f0ac 100644 --- a/src/main/java/com/supervision/pdfqaserver/cache/PromptCache.java +++ b/src/main/java/com/supervision/pdfqaserver/cache/PromptCache.java @@ -438,17 +438,17 @@ public class PromptCache { ## 处理规则 - 1. **入参 `ContentType` 决定验证目标类型** - - 根据 `ContentType` 的值,严格匹配对应类型的特征: - - `0`:验证是否符合研报类型(专业术语、财务数据) - - `1`:验证是否符合对话类型(多轮对话标记) - - `2`:验证是否符合记录类型(时间戳、条目化描述) + 1. **入参 `ContentType` 决定验证目标类型** + - 根据 `ContentType` 的值,严格匹配对应类型的特征: + - `0`:验证是否符合研报类型(专业术语、财务数据) + - `1`:验证是否符合对话类型(多轮对话标记) + - `2`:验证是否符合记录类型(时间戳、条目化描述) - 2. **验证逻辑** - - 若文本特征与 `ContentType` 指定类型匹配 → 返回 `{"ContentType": 指定值}` - - 若文本特征不匹配 → 返回 `{}`(表示类型不符) + 2. **验证逻辑** + - 若文本特征与 `ContentType` 指定类型匹配 → 返回 `{"ContentType": 指定值}` + - 若文本特征不匹配 → 返回 `{}`(表示类型不符) - 3. **类型定义** + 3. **类型定义** ```json { "0": "研报类型(行业分析、财务数据)", @@ -487,14 +487,14 @@ public class PromptCache { --- - **设计说明** + **设计说明** - 入参 `ContentType` 为固定值,用于声明待验证的目标类型,而非自动分类。 - 输出结果仅表示文本是否符合声明的类型,实现“类型断言”功能。 - 参数命名与原文档保持一致,但调整了逻辑语义以符合用户需求。 ## 输出要求 - 1. 严格遵循JSON格式 + 1. 输出纯JSON格式,不要使用```json ```等任何Markdown标记包装。 2. 不需要解释,不需要说明。 仅返回以下两种结果之一: - 匹配成功:`{"ContentType": 0/1/2}` @@ -550,7 +550,7 @@ public class PromptCache { 1. 严格匹配文本内容与意图类型的关联性 2. 文本可能匹配多个意图类型 3. 若无匹配则返回空对象 - + 4. 输出纯JSON格式,不要使用```json ```等任何Markdown标记包装。 ## 待处理文本 {text} @@ -644,24 +644,18 @@ public class PromptCache { ## 输入数据 - 文本片段: {text} - - 可选意图标签: {IntentTypeList} - ## 输出要求 1. 分析文本内容,识别与意图标签相关的实体和关系 - 2. 每个结果应包含: - - source(来源实体) - - relation(关系) - - target(目标实体) - - intent(匹配的意图标签) + 2. 每一个意图只能匹配一个结果 3. 每个实体/关系应包含: - type(类型) - attributes(相关属性列表) - 4. 使用以下示例格式: + 4. 输出纯JSON格式,不要使用```json ```等任何Markdown标记包装 + 5. 使用以下示例格式: - ```json [ { "source": { @@ -677,10 +671,11 @@ public class PromptCache { "attributes": ["属性3"] }, "intent": "匹配的意图标签" - } + }, + {.....} ] - 5. 属性只代表属性名称:例如“名称“,”数量“ + 5. 属性只代表属性名称:例如“名称“,”数量“./no_think """; private static final String EXTRACT_ERE_BASE_INTENT_PROMPT = """ @@ -690,22 +685,24 @@ public class PromptCache { 你是一个信息抽取引擎,需要从给定的文本中提取符合指定三元组标签(实体、关系、属性)的结构化数据。 ## 输入数据: - - 待处理文本:{text} + - 待处理文本: + {text} + - 三元组标签及属性名称: {domainMetadata} - ## 示例: { "nodes": [ { + "name":"龙源(酒泉)风力发电有限公司", "type": "公司", "attributes": { - "名称": "龙源(酒泉)风力发电有限公司", "地址": "雨花台区" } }, { + "name":"2024年电子银行承兑汇票", "type": "电子银行承兑汇票", "attributes": { "金额": "100.00万元", @@ -713,24 +710,29 @@ public class PromptCache { } }, { + "name": "杭州六小龙", "type": "公司", "attributes": { - "名称": "杭州六小龙", "地址": "杭州高新区" } } ], "relations": [ - { + { + "source": "龙源(酒泉)风力发电有限公司", + "target": "2024年电子银行承兑汇票", "type": "持有", "attributes": { + "持有方式": "纸质" } }, { + "source": "龙源(酒泉)风力发电有限公司", + "target": "杭州六小龙", "type": "收购", "attributes": { - "收购类型": "全资收购" - "收购时间":"2025年5月28号" + "收购类型": "全资收购", + "收购时间": "2025年5月28号" } } ], @@ -750,9 +752,11 @@ public class PromptCache { ## 注意事项: + - 每一个nodes、relations都有一个基础属性"名称"是必须有的。 + - relations中的source、target对应nodes中的各个name。 - 仅提取 `domainMetadata` 中定义的标签和属性。 - 若属性无对应值,可留空或忽略。 - 确保提取的值与原文一致,不进行推断或改写。 - - 输出纯JSON格式,不要使用```json ```等任何Markdown标记包装 + - 输出纯JSON格式,不要使用```json ```等任何Markdown标记包装./no_think """; } diff --git a/src/main/java/com/supervision/pdfqaserver/domain/DomainMetadata.java b/src/main/java/com/supervision/pdfqaserver/domain/DomainMetadata.java index b99d4a3..331465f 100644 --- a/src/main/java/com/supervision/pdfqaserver/domain/DomainMetadata.java +++ b/src/main/java/com/supervision/pdfqaserver/domain/DomainMetadata.java @@ -21,6 +21,7 @@ public class DomainMetadata implements Serializable { /** * 领域类型 */ + @Deprecated private String domainType; /** diff --git a/src/main/java/com/supervision/pdfqaserver/domain/Intention.java b/src/main/java/com/supervision/pdfqaserver/domain/Intention.java index 0b14b54..63832e9 100644 --- a/src/main/java/com/supervision/pdfqaserver/domain/Intention.java +++ b/src/main/java/com/supervision/pdfqaserver/domain/Intention.java @@ -26,7 +26,7 @@ public class Intention implements Serializable { /** * 描述详情 */ - private String desc; + private String description; /** * 领域分类id diff --git a/src/main/java/com/supervision/pdfqaserver/dto/IntentDTO.java b/src/main/java/com/supervision/pdfqaserver/dto/IntentDTO.java index 5e1c7f6..bea89cc 100644 --- a/src/main/java/com/supervision/pdfqaserver/dto/IntentDTO.java +++ b/src/main/java/com/supervision/pdfqaserver/dto/IntentDTO.java @@ -46,7 +46,7 @@ public class IntentDTO { public IntentDTO(Intention intention){ this.id = intention.getId(); this.digest = intention.getDigest(); - this.desc = intention.getDesc(); + this.desc = intention.getDescription(); this.domainCategoryId = intention.getDomainCategoryId(); this.generationType = intention.getGenerationType(); } diff --git a/src/main/java/com/supervision/pdfqaserver/service/impl/DomainMetadataServiceImpl.java b/src/main/java/com/supervision/pdfqaserver/service/impl/DomainMetadataServiceImpl.java index 370a292..f05d911 100644 --- a/src/main/java/com/supervision/pdfqaserver/service/impl/DomainMetadataServiceImpl.java +++ b/src/main/java/com/supervision/pdfqaserver/service/impl/DomainMetadataServiceImpl.java @@ -3,6 +3,7 @@ package com.supervision.pdfqaserver.service.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.supervision.pdfqaserver.constant.DomainMetaGenerationEnum; import com.supervision.pdfqaserver.domain.DomainMetadata; import com.supervision.pdfqaserver.domain.ErAttribute; import com.supervision.pdfqaserver.domain.IntentionDomainMetadata; @@ -53,6 +54,7 @@ public class DomainMetadataServiceImpl extends ServiceImpl intentionDTOs = intentionService.queryByDomainCategoryId(pdfInfo.getDomainCategoryId()).stream().map(IntentDTO::new).distinct().toList(); + List intentionDTOs = intentionService.queryByDomainCategoryId(pdfInfo.getDomainCategoryId()).stream() + .filter(intention -> StrUtil.equals("0",intention.getGenerationType())) // 过滤出手动确认的数据 + .map(IntentDTO::new).distinct().toList(); if (CollUtil.isEmpty(intentionDTOs)){ log.info("没有找到行业分类id为{}的意图数据,不再进行下一步操作...", pdfInfo.getDomainCategoryId()); return; @@ -275,9 +277,24 @@ public class KnowledgeGraphServiceImpl implements KnowledgeGraphService { int index = 1; int truncateSize = truncateDTOS.size(); log.info("开始实体关系抽取,耗时:{}秒,一共处理片段数:{}个", timer.intervalSecond(), truncateDTOS.size()); + List eredtos = new ArrayList<>(); for (TruncateDTO truncateDTO : truncateDTOS) { + index ++; log.info("开始命名实体识别,切分文档id:{},识别进度:{}", truncateDTO.getId(), NumberUtil.formatPercent((index*1.0)/truncateSize, 2)); try { + if (StrUtil.equals(truncateDTO.getLayoutType(), String.valueOf(LayoutTypeEnum.TABLE.getCode()))){ + log.info("切分文档id:{},表格类型数据,不进行意图识别...", truncateDTO.getId()); + /*EREDTO eredto = conversionPipeline.doEre(truncateDTO, new ArrayList<>()); + if (null == eredto){ + log.info("切分文档id:{},命名实体识别结果为空...", truncateDTO.getId()); + continue; + } + this.saveERE(eredto, truncateDTO.getId()); + eredtos.add(eredto); + */ + continue; + } + timer.start("makeOutTruncationIntent"); log.info("开始意图识别,切分文档id:{}", truncateDTO.getId()); List intents = conversionPipeline.makeOutTruncationIntent(truncateDTO,intentionDTOs); @@ -296,10 +313,17 @@ public class KnowledgeGraphServiceImpl implements KnowledgeGraphService { } // 保存实体关系抽取结果 this.saveERE(eredto, truncateDTO.getId()); + eredtos.add(eredto); }catch (Exception e){ log.error("命名实体识别失败,切分文档id:{}", truncateDTO.getId(), e); } } + log.info("实体关系抽取完成,耗时:{}秒", timer.intervalSecond()); + + log.info("开始生成知识图谱..."); + timer.start("generateGraph"); + generateGraph(eredtos); + log.info("生成知识图谱完成,耗时:{}秒", timer.intervalSecond("generateGraph")); } @Override diff --git a/src/main/java/com/supervision/pdfqaserver/service/impl/TripleConversionPipelineImpl.java b/src/main/java/com/supervision/pdfqaserver/service/impl/TripleConversionPipelineImpl.java index 0b2ba83..0861d2d 100644 --- a/src/main/java/com/supervision/pdfqaserver/service/impl/TripleConversionPipelineImpl.java +++ b/src/main/java/com/supervision/pdfqaserver/service/impl/TripleConversionPipelineImpl.java @@ -93,6 +93,9 @@ public class TripleConversionPipelineImpl implements TripleConversionPipeline { log.info("makeOutTruncationIntent:响应结果:{}", call); JSONObject json = JSONUtil.parseObj(call); JSONArray jsonArray = json.getJSONArray("IntentTypeList"); + if (null == jsonArray){ + return new ArrayList<>(); + } return intents.stream().filter(intent-> jsonArray.stream().anyMatch(o->StrUtil.equals(o.toString(), intent.getDigest()))) .collect(Collectors.toList()); @@ -104,8 +107,9 @@ public class TripleConversionPipelineImpl implements TripleConversionPipeline { Assert.notEmpty(intents, "意图不能为空"); String promptTemplate = promptMap.get(EXTRACT_INTENT_METADATA); - Map params = Map.of("text", truncate.getContent(), "IntentType", JSONUtil.toJsonStr(intents)); + Map params = Map.of("text", truncate.getContent(), "IntentTypeList", JSONUtil.toJsonStr(intents)); String format = StrUtil.format(promptTemplate, params); + log.info("makeOutDomainMetadata:format:{}", format); String call = aiCallService.call(format); log.info("makeOutDomainMetadata:响应结果:{}", call); return parseDomainMetadata(call); @@ -139,6 +143,7 @@ public class TripleConversionPipelineImpl implements TripleConversionPipeline { JSONObject source = jsonObject.getJSONObject("source"); JSONObject relation = jsonObject.getJSONObject("relation"); JSONObject target = jsonObject.getJSONObject("target"); + domainMetadataDTO.setIntentDigest(jsonObject.getStr("intent")); if (null != source){ String type = source.getStr("type"); JSONArray attributes = source.getJSONArray("attributes"); @@ -189,6 +194,14 @@ public class TripleConversionPipelineImpl implements TripleConversionPipeline { return null; } List domainMetadataDTOS = domainMetadataService.listByIntentionIds(intentIds); + log.info("doEre:领域元数据列表个数:{}", domainMetadataDTOS.size()); + domainMetadataDTOS = domainMetadataDTOS.stream() + .filter(domainMetadataDTO -> StrUtil.equals(domainMetadataDTO.getGenerationType(), "0"))// 过滤出手动确认的数据 + .collect(Collectors.toList()); + log.info("doEre:领域元数据列表已经手动确认过的个数:{}", domainMetadataDTOS.size()); + if (CollUtil.isEmpty(domainMetadataDTOS)){ + return null; + } return doTextEreWithMetadata(truncateDTO, domainMetadataDTOS); } @@ -220,9 +233,9 @@ public class TripleConversionPipelineImpl implements TripleConversionPipeline { @Override public List sliceDocuments(List documents) { - int maxTextLength = 1000; - int minTextLength = 800; - int INITIAL_BUFFER_SIZE = 1500; + int maxTextLength = 600; + int minTextLength = 500; + int INITIAL_BUFFER_SIZE = 100; // 对pdfAnalysisOutputs进行排序 List documentDTOList = documents.stream().sorted( // 先对pageNo进行排序再对layoutOrder进行排序 @@ -240,7 +253,9 @@ public class TripleConversionPipelineImpl implements TripleConversionPipeline { StanfordCoreNLP pipeline = new StanfordCoreNLP(props); List truncateDTOS = new ArrayList<>(); StringBuilder truncateTextBuild = new StringBuilder(1500); + DocumentDTO documentDTOLast = null; for (DocumentDTO documentDTO : documentDTOList) { + documentDTOLast = documentDTO; String content = documentDTO.getContent(); if (StrUtil.isEmpty(content)){ continue; @@ -274,13 +289,12 @@ public class TripleConversionPipelineImpl implements TripleConversionPipeline { } } } - // 处理剩余内容 + } else if (LayoutTypeEnum.TABLE.getCode() == layoutType) { + // 如果是表格类型的布局,进行切分 + // 出现表格后,如果truncateTextBuild不为空,单独作为一个片段 if (!truncateTextBuild.isEmpty()) { truncateDTOS.add(new TruncateDTO(documentDTO, truncateTextBuild.toString())); } - } else if (LayoutTypeEnum.TABLE.getCode() == layoutType) { - // 如果是表格类型的布局,进行切分 - // 提前抽取表名 TableTitleDTO tableTitleDTO = this.extractTableTitle(documentDTO.getTitle()); if (null != tableTitleDTO && StrUtil.isNotEmpty(tableTitleDTO.getTitle())){ @@ -317,6 +331,9 @@ public class TripleConversionPipelineImpl implements TripleConversionPipeline { log.info("sliceDocuments:错误的布局类型: {}", layoutType); } } + if (!truncateTextBuild.isEmpty() && null != documentDTOLast) { + truncateDTOS.add(new TruncateDTO(documentDTOLast, truncateTextBuild.toString())); + } return truncateDTOS; } @@ -378,7 +395,7 @@ public class TripleConversionPipelineImpl implements TripleConversionPipeline { Map params = Map.of("text", truncateDTO.getContent(), "domainMetadata", domainMetadata); String format = StrUtil.format(prompt, params); String call = aiCallService.call(format); - return null; + return EREDTO.fromTextJson(call, truncateDTO.getId()); } diff --git a/src/main/resources/mapper/IntentionMapper.xml b/src/main/resources/mapper/IntentionMapper.xml index 4579d84..d0375a2 100644 --- a/src/main/resources/mapper/IntentionMapper.xml +++ b/src/main/resources/mapper/IntentionMapper.xml @@ -7,7 +7,7 @@ - + @@ -15,7 +15,7 @@ - id,digest,desc,generation_type, + id,digest,description,generation_type, domain_category_id,create_time,update_time diff --git a/src/test/java/com/supervision/pdfqaserver/PdfQaServerApplicationTests.java b/src/test/java/com/supervision/pdfqaserver/PdfQaServerApplicationTests.java index 39974ec..c056497 100644 --- a/src/test/java/com/supervision/pdfqaserver/PdfQaServerApplicationTests.java +++ b/src/test/java/com/supervision/pdfqaserver/PdfQaServerApplicationTests.java @@ -150,5 +150,17 @@ class PdfQaServerApplicationTests { System.out.println(strings); } + @Test + public void metaDataTrainTest() { + + knowledgeGraphService.metaDataTrain(13); + } + + @Test + void generateGraphBaseTrainTest() { + + knowledgeGraphService.generateGraphBaseTrain(13); + } + }