NLP与知识图谱在招投标文档智能生成中的应用与工程实践
简介:面向招投标文档智能化生成场景的完整工程包,适合NLP与知识图谱方向开发者、招投标系统建设者学习参考。系统集成多线程文件预处理、微调NLP模型、多模型融合、知识图谱构建与查询、模板自动化填充等能力,可高效生成招标文件、评标报告与答疑函。压缩包共20个文件,含6个Python脚本(涵盖主程序、NLP模型集成、知识图谱构建、实体识别、模板管理等)、6个CSV数据(投标数据、专家、案例、法规等)、4个JSON模板、说明文档与依赖文件,整体仅49KB,结构清晰、轻量易读。已有64人学习下载。资料提供从数据处理到文档生成的完整代码与配置,并附赠说明与配套文档,便于快速跑通流程并在此基础上扩展。
1. 招投标文档智能生成:为什么 NLP 与知识图谱要绑在一起出手
要赶一份评标报告,如果还靠人工从几十份投标文件里抠技术参数、翻法规条文、核对专家信息,一晚上基本交代给复制粘贴了。把自然语言处理与知识图谱用在招投标文档生成上,解决的正是这个场景:通过多线程文件预处理完成数据清洗,用微调后的 NLP 模型读懂标书里的专业术语,再靠知识图谱把案例、法规、供应商、专家串成一张可查询的网,最后由模板自动化填充直接落成招标文件、评标报告和答疑函。这套资源适合两类人:天天跟标书打交道的业务人员,以及想把 NLP 和知识图谱真正落到具体业务里的开发者——后者能直接拿走代码改场景。
2. 多线程文件预处理:并行清洗五类数据源与格式归一化
2.1 文件预处理的三个目标:清洗、归一化、错误检测
招投标文档生成的第一道坎从来不是模型,而是数据。原始输入里混杂着 PDF 转出的文本、Excel 导出的表格、扫描件 OCR 结果,字段名一会儿是“投标单位”,一会儿是“投标人名称”,日期格式更是五花八门。文件预处理模块要做的就是把这一切拉齐。
我把预处理拆成三个明确目标:第一,清洗,去掉页眉页脚、水印残留、乱码字符和多余空白;第二,归一化,把“中建三局”“中国建筑第三工程局有限公司”“中建三局集团有限公司”这类指代拉通到统一实体名;第三,错误检测,识别编码损坏、字段缺失、类型不匹配的数据行,宁可标出来跳过,也不能让脏数据流到后面的 NLP 模型里。
在这个系统里,multi_threaded_file_preprocessor.py 承担的就是这条流水线。它读入 experts.csv、project_cases.csv、laws.csv、suppliers.csv、standards.csv、bidding_data.csv 六个数据文件,每个文件都有可能来自不同来源、编码不同、字段错位。如果串行处理,光清洗这六张表就要等半天,所以这个模块用多线程来并行跑。
2.2 线程池与进程池的选择:文件 IO 场景下为什么用线程池
多线程在 Python 里有个绕不开的话题:GIL。很多人一看到多线程就担心 CPU 密集型任务跑不满,但在文件预处理这个场景里,瓶颈根本不在 CPU,而在磁盘读写和字符串解码。清洗一份 PDF 文本要做的正则替换、编码转换都是轻量操作,真正耗时的是读文件、写文件、IO 等待。这种场景下线程池比进程池更合适——省去进程间通信和序列化开销,代码也更容易维护。
我一般会这样组织预处理逻辑:
import pandas as pd from concurrent.futures import ThreadPoolExecutor, as_completed from pathlib import Path DATA_FILES = { "experts": "data/experts.csv", "project_cases": "data/project_cases.csv", "laws": "data/laws.csv", "suppliers": "data/suppliers.csv", "standards": "data/standards.csv", "bidding_data": "data/bidding_data.csv", } def clean_text(text: str) -> str: """清洗单条文本:去控制字符、合并空白、统一常见全角符号""" import re text = re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", text) text = text.replace("\u3000", " ").replace("\uf0fc", "") text = re.sub(r"\s+", " ", text).strip() return text def preprocess_one_file(file_key: str, file_path: str) -> pd.DataFrame: """读取并清洗单个 CSV,返回标准化后的 DataFrame""" df = pd.read_csv(file_path, encoding="utf-8-sig", dtype=str) df = df.dropna(how="all") # 整行为空直接丢弃 for col in df.select_dtypes(include=["object"]).columns: df[col] = df[col].map(clean_text) # 错误检测:关键字段缺失时记录日志,不直接抛异常中断整个流程 if "name" in df.columns: df = df[df["name"].notna() & (df["name"] != "")] return df def parallel_preprocess(max_workers: int = 4) -> dict[str, pd.DataFrame]: """用线程池并行处理全部数据文件""" results = {} with ThreadPoolExecutor(max_workers=max_workers) as executor: future_to_key = { executor.submit(preprocess_one_file, key, path): key for key, path in DATA_FILES.items() } for future in as_completed(future_to_key): key = future_to_key[future] try: results[key] = future.result() print(f"[OK] {key} 清洗完成,{len(results[key])} 行") except Exception as e: print(f"[FAIL] {key} 处理失败: {e}") results[key] = pd.DataFrame() # 失败时给空表,后续流程可以跳过 return results参数说明:max_workers 我通常取 4 到 8,不需要盲目调到很大。因为文件预处理的耗时主要卡在磁盘 IO,线程数超过磁盘并发上限之后,增加线程只会带来上下文切换开销。这里每个文件用 pandas 读取后逐列做 clean_text,注意 dtype=str 这个参数——CSV 里电话号码、统一社会信用代码如果按默认类型读,会被自动转成数值,前导零直接丢,后面做实体对齐时必翻车。编码统一用 utf-8-sig,能自动处理带 BOM 的 Excel 导出文件,没有 BOM 时也能正常读。
这个并行方式还有个好处:as_completed 是拿到结果就处理,不会等最慢的那个文件全部跑完。如果你的机器内存足够,也可以换成 ProcessPoolExecutor,但那需要对 DataFrame 做序列化,小数据集反而更慢。
2.3 数据文件设计:六张表如何对接后续流程
预处理做完,下一步是喂给 NLP 模型和知识图谱模块。这个系统把数据分成六张表,各有明确用途:
| 文件 | 典型字段 | 在系统中的用途 |
|---|---|---|
| experts.csv | 姓名、职称、专业领域、所在单位、回避项目 | 评标报告专家名单生成、回避关系核查 |
| project_cases.csv | 项目名称、招标编号、中标人、中标金额、评分明细 | 历史案例查询、评标数据参考 |
| laws.csv | 法规名称、条款号、条文内容、生效日期 | 答疑函引用依据、招标文件合规性参照 |
| suppliers.csv | 供应商名称、统一社会信用代码、资质等级、业绩 | 资格预审、投标人筛选 |
| standards.csv | 标准编号、标准名称、适用范围、强制/推荐 | 技术参数校验、招标文件技术要求 |
| bidding_data.csv | 项目编号、标段、投标人、报价、技术分、商务分 | 评标报告核心数据来源 |
这六张表在知识图谱构建时会变成不同类型的节点和属性。比如 suppliers.csv 里的公司名称会归一化后作为 Supplier 节点,standards.csv 里的标准编号会作为 Standard 节点,project_cases.csv 则同时关联 Supplier、Expert、Standard 三类节点。所以预处理阶段做没做实体归一化,直接决定后面图谱里的节点会不会重复。
一个需要提前想清楚的地方:六张表的更新频率完全不同。laws.csv 和 standards.csv 几乎不更新,project_cases.csv 和 bidding_data.csv 每个项目都要追加,experts.csv 可能有年度调整。我一般建议把不常更新的表预处理一次后缓存成 parquet,每次跑主流程只重新处理增量数据,不然每次生成文档都全量清洗一遍,时间全浪费在重复劳动上。
3. NLP 模型微调与多模型融合:让模型听懂标书语言
3.1 为什么通用 NLP 模型在招投标语料上会翻车
拿通用的中文预训练模型直接抽招标文件里的实体,十有八九会闹笑话。“投标保证金”可能被识别成金额实体,“废标条款”里的“废”字被断错词,“评标基准价”里的“基准价”被拆得支离破碎。原因不复杂:招投标文本是强领域文本,术语的边界和通用语料差别太大,通用模型见的新闻、评论、百科文本里根本没有足够的“招标编号 / 资质等级 / 技术偏离表”这类表达。
这个系统的做法是先用通用模型做底座,再用招投标领域的语料做微调。nlp_model_integration.py 里封装的流程就是:加载预训练模型,在标注好的招投标实体数据上继续训练几轮,然后再接上多模型融合。微调的目的不是让模型重新学语言,而是让它在领域内的分词、实体边界、语义理解上更贴合实际场景。
3.2 微调的关键配置:小数据集上的策略
招投标领域的标注数据通常不会很大,几千条已经是很好的家底。这种规模下微调,最怕的是过拟合——训练集上实体识别 F1 值能到 0.97,一跑真实数据就崩。我一般会控制训练轮数、用较小的学习率、并且冻结大部分底层参数只微调顶层,这样既保留了通用语言能力,又能吸收领域知识。
from transformers import AutoTokenizer, AutoModelForTokenClassification, Trainer, TrainingArguments model_name = "bert-base-chinese" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForTokenClassification.from_pretrained( model_name, num_labels=len(ENTITY_LABELS), # 你的实体标签集合大小 id2label={i: label for i, label in enumerate(ENTITY_LABELS)}, label2id={label: i for i, label in enumerate(ENTITY_LABELS)}, ) # 冻结前 10 层 BERT 参数,只微调后两层和分类头 for i, layer in enumerate(model.bert.encoder.layer): if i < 10: for param in layer.parameters(): param.requires_grad = False training_args = TrainingArguments( output_dir="./finetuned_bidder", learning_rate=2e-5, # 小学习率,防微调灾难性遗忘 per_device_train_batch_size=16, num_train_epochs=3, weight_decay=0.01, warmup_ratio=0.1, logging_steps=50, save_strategy="epoch", ) trainer = Trainer( model=model, args=training_args, train_dataset=train_dataset, eval_dataset=eval_dataset, tokenizer=tokenizer, ) trainer.train()逻辑说明:这段代码先加载中文 BERT 作为底座,分词器保持不变,模型头的 num_labels 改成你的实体标签数量。冻结前 10 层是把 BERT 的通用语义能力锁住,只允许后 2 层和分类头去适配招投标实体边界。
参数说明:learning_rate 设成 2e-5 而不是常见的 5e-5,是因为领域数据量小,学习率太大容易一步跨过最优区域。num_train_epochs=3 在这个规模下是合理起点,如果 eval loss 到第 2 轮就开始回升,那基本就是过拟合信号,可以改成 2 或者加 dropout。warmup_ratio=0.1 表示前 10% 的训练步数学习率从 0 线性上升到目标值,避免模型刚开始训练就收到剧烈梯度冲击。冻结层数也可以调——如果数据量特别少,冻结前 11 层只微调最后一层和分类头,效果往往更稳。
微调完之后,保存模型权重,后面所有文档生成流程都复用这份微调过的模型来做命名实体识别和关系抽取。注意保存的时候要把 tokenizer 一起保存,因为 label 映射关系是跟着 tokenizer 配置走的,只拿权重文件到别处推理,很容易在标签索引上错位。
3.3 多模型融合:召回与精度的折中方案
招投标文档生成里,单模型的输出往往不够信。同一个文本片段,BERT-based NER 可能识别出“三证合一”,但漏掉“安许证已过期”里隐含的证件状态;规则引擎可能把“投标有效期 90 天”里的“90”和“天”分开,但能精准抓到固定的法规编号格式。多模型融合的价值就是把深度学习模型的泛化能力和规则模型的确定性拼在一起。
这个系统在 nlp_model_integration.py 里实现了三种融合策略:简单投票、加权求和、串联兜底。实际跑下来,招投标场景最适合的是“分层决策”:先用规则模型处理格式强约束的实体(法规编号、统一社会信用代码、金额),再用微调后的 BERT 模型处理语义类实体(废标理由、技术偏离描述),最后把两路结果合并、去重、优先级排序。
def fuse_entities(bert_entities: list[dict], rule_entities: list[dict]) -> list[dict]: """多模型融合:规则结果优先,BERT 结果兜底,冲突时按置信度裁决""" merged = {} # 先放规则结果:格式类实体可信度高 for ent in rule_entities: key = (ent["entity"], ent["start"], ent["end"]) merged[key] = ent | {"source": "rule", "confidence": 0.95} # 再放 BERT 结果:与规则结果重叠时,规则优先 for ent in bert_entities: key = (ent["entity"], ent["start"], ent["end"]) if key not in merged: ent["source"] = "bert" merged[key] = ent else: # 同位置冲突,保留规则结果并记录日志便于复盘 print(f"[CONFLICT] {merged[key]['entity']} vs {ent['entity']},规则优先") return sorted(merged.values(), key=lambda x: x["start"])这段融合逻辑的核心是“冲突时规则优先”。规则模型不会把“845 万元”识别成日期,但 BERT 模型在复杂上下文里可能出错;反过来,规则模型面对“投标人未按招标文件要求提供社保证明”这种语义判断无能为力,此时 BERT 的输出就是有效补充。当你调试系统发现某类实体识别率上不去时,先看这类实体是不是格式强约束的——如果是,优先补规则;如果是语义松散的,优先找标注数据做微调。
多模型融合另一个容易被忽略的价值是召回率兜底。招投标文档里同一个实体在不同位置写法不同,比如“项目经理”可能在技术标里写全名、在商务标里写“项目负责人王某”。规则模型很难覆盖所有写法变体,BERT 模型在这一层的泛化能力能捡回不少漏掉的实体,让后续知识图谱构建时实体链接的输入更完整。
4. 知识图谱构建与查询:把法规、案例、供应商织成可查的图
4.1 本体设计与实体关系抽取
知识图谱在招投标系统里的定位不是炫技,而是解决一个实际问题:文档生成过程中需要大量跨表查询,而这些查询天然是多跳的。比如生成评标报告时,要查“某供应商是否满足某标准的强制条款”,对应到数据库就是 suppliers、standards、laws 三张表的多表关联;如果数据平铺在关系表里,每次都要写复杂的 SQL,而把数据织成图之后,一次路径查询就能给出答案。
这个系统的 knowledge_graph_construction.py 定义了一个精简本体:实体类型包括招标人、投标人、评标专家、法规、标准、历史项目;关系类型包括“违反法规”“满足标准”“参与项目”“负责评标”“引用条款”“属于行业”。实体识别和关系抽取由 entity_recognition_and_relation_extraction.py 完成,它把预处理后的六张表结构化为三元组,再写入图数据库。
4.2 从非结构化文本到三元组
结构化表格转三元组相对直接,但从 laws.csv 的条文文本里抽关系就麻烦一些。比如法规条文“投标人不得以低于成本的报价竞标”,要抽出来的是投标人 --[禁止行为]--> 低于成本报价这样的二元结构。这个系统采用规则+模型混合的方式:先跑微调后的 NER 模型找出候选实体,再用触发词词典(如“不得”“应当”“禁止”)和依存句法分析确定关系方向。
import re TRIGGER_WORDS = { "不得": "negative_obligation", "应当": "positive_obligation", "禁止": "prohibition", "必须": "mandatory", } def extract_relations_from_text(text: str, entities: list[dict]) -> list[dict]: """从法规条文中抽取关系三元组""" relations = [] for match in re.finditer(r"(不得|应当|禁止|必须)", text): trigger = match.group() start, end = match.span() # 从实体列表中找距离触发词最近的左右实体 left_entity = None right_entity = None for ent in entities: if ent["end"] < start: left_entity = ent # 最后一个结束位置在触发词前的实体 elif ent["start"] > end and right_entity is None: right_entity = ent # 第一个开始位置在触发词后的实体 break if left_entity and right_entity: relations.append({ "head": left_entity["entity"], "head_type": left_entity["type"], "relation": TRIGGER_WORDS[trigger], "tail": right_entity["entity"], "tail_type": right_entity["type"], "text": text[start:end + 40], }) return relations逻辑说明:这段代码先用正则定位触发词位置,然后从已识别的实体列表里找触发词左边最近的实体作为关系头、右边最近的实体作为关系尾。不得映射成 negative_obligation,应当映射成 positive_obligation,这样知识图谱里每种关系都带明确语义类型,后面查询时能精确过滤。
这个方法的局限很明显:只适用于“触发词在中间、左右各有一个实体”的简单句式。遇到“投标人不得将中标项目转让给他人,也不得将中标项目肢解后分别转让”这种并列长句,正则就抓不全了。我处理这类情况时会额外加一条规则:如果触发词右侧连续出现多个候选实体,都作为该关系尾追加进去,而不是只取第一个。文本里的远近关系是相对可靠的启发式线索,尤其在法规条文这种书面语规范、句式稳定的文本里,能解决大部分关系抽取需求。
三元组建好之后,导入图数据库。如果用的是 Neo4j,每条三元组对应一条CREATE (a)-[:RELATION]->(b)语句;如果是自研轻量图谱,也可以直接用 Python dict 存邻接表。这个资源里的源码走的是 Neo4j 接口,但核心三元组数据结构是通用的。
4.3 图数据库查询与决策支撑
图谱建好之后真正的价值才出现。生成评标报告时,系统要回答的问题包括:该供应商过去有没有废标记录、该项目的评标专家是否与某投标人存在利益关联、招标文件里的技术参数是否符合现行强制标准。这些问题在图谱里都是路径查询:
// 查询某供应商是否存在违反法规的历史记录 MATCH (s:Supplier {name: $supplier_name})-[r:negative_obligation]->(l:Law) RETURN l.name AS law_name, r.text AS violation_detail ORDER BY l.publish_date DESC // 查询专家与投标人之间的潜在利益关联(两跳以内) MATCH path = (e:Expert {name: $expert_name})-[:participates_in*1..2]-(b:Supplier {name: $bidder_name}) RETURN path第一条查询直接监控合规风险:评标报告里如果自动检测到某投标人和某条法规条文之间存在 negative_obligation 关系,系统会把这个风险点单独标红提醒人工复核。第二条查询是回避关系核查:评标专家如果和投标人在历史项目里有过交集,报告生成时会自动提示是否需要回避。
这两个查询写起来简单,但能不能查到,完全取决于前面的实体归一化做得干不干净。suppliers.csv 里写的“中建三局”和图谱里的节点“中国建筑第三工程局有限公司”如果没有拉通,这条路径永远查不出来。所以我在知识图谱构建流程里专门加了一步实体对齐:对公司名做后缀归一化(去掉“有限公司”“股份有限公司”等后缀)、对专家名做同名消歧(结合所属单位字段区分同名专家)。这一步属于脏活累活,但没它图谱就只是一堆孤岛节点。
5. 避坑与常见问题排查:从预处理到模板填充的血泪经验
5.1 多线程与数据格式类陷阱
坑 1:CSV 编码不对,NLP 模型全部输出乱码
现象:预处理跑完,日志全是[OK],但知识图谱构建时报实体识别结果全是乱码,中文变成了“鏄犲悕”“浠锋牸”这类不明字符。
原因:Windows 下 Excel 导出的 CSV 默认是 GBK 或带 BOM 的 UTF-8,pandas 用默认编码读取时,字段内容被错误解码,但 pandas 不报错,脏数据一路流到后面的 NLP 模块才暴露。
解决:读取 CSV 时统一用encoding="utf-8-sig",它对带 BOM 和无 BOM 的 UTF-8 都能正确处理。如果已经是乱码数据,不要试图在清洗阶段修复——直接用二进制方式重读原始文件,加一个encoding="gbk"的兜底尝试。从那以后我每次写文件读取逻辑都会先打印前两行的 repr,确认编码没有隐性翻车再往下走。
坑 2:多线程里共享同一个 DataFrame,字段互相覆盖
现象:六个文件并行处理后,experts.csv 的结果里出现 suppliers.csv 的行,数据串台。
原因:在多线程回调函数里,如果直接给全局变量df_result赋值,各线程之间没有隔离,后完成的线程会覆盖先完成的结果。
解决:线程处理函数只返回结果值,不修改任何全局状态;用future_to_key字典保存每个 future 对应的文件 key,在as_completed时按 key 写入独立位置。如果你手头代码里的多线程写法是“每个 worker 操作同一个 dict 的不同 key”,在 CPython 下恰好能跑,但换个解释器或换个调度时机就出问题,最好还是改成无共享状态的结构。
坑 3:日期和金额格式不统一,模板填充时校验失败
现象:bidding_data.csv 里的中标金额有的写“8450000 元”,有的写“845 万”,有的写“¥8.45M”,模板自动化填充后生成的评标报告金额一栏风格混乱,人工审查时被打回。
原因:预处理只做了文本清洗,没做字段级格式归一化。金额和日期这类字段,文本清洗去不掉格式差异,必须做类型感知的转换。
解决:在preprocess_one_file里增加字段类型映射参数,对金额字段统一转成数值后按模板要求的格式重新渲染,对日期字段统一成YYYY-MM-DD。格式转换逻辑不要写在模板填充模块里——模板只负责展示,数据归一化是预处理的责任,职责混在一起后面改一处动全身。
5.2 模型与图谱联调类陷阱
坑 4:微调后模型在训练集上 F1 值 0.97,真实文本上召回率暴跌
现象:微调阶段的评估指标非常漂亮,但跑到真实招标文件上做实体抽取,之前能识别出来的实体大量消失,甚至把整句话切成碎块。
原因:标注语料和真实语料的分布差异。标注数据通常是从招标文件里精挑细选的规范段落,真实数据里有表格转文本、扫描件噪声、编号和正文混排,模型没见过这类输入格式。
解决:微调时不要只喂干净文本,把预处理阶段的输出也混一部分进训练集,让模型看到“带噪声的真实输入长什么样”。另外把 dropout 调高到 0.2 左右,强迫模型不要把注意力全押在个别特征上。如果你手头已经存了历史项目的真实预处理结果,把这些未标注文本加进训练集做半监督,哪怕不标标签,也能显著提升模型的鲁棒性。
坑 5:知识图谱里同一家公司出现两个节点,查询结果对不上
现象:Cypher 查询MATCH (s:Supplier {name: "中建三局"})查不到任何结果,但图谱里明明有“中国建筑第三工程局有限公司”这个节点。
原因:实体归一化没有覆盖到别名映射。预处理阶段做了全称和简称的双向映射,但漏了“中建三局”这种口语化简称,图谱构建时把它当成了新实体。
解决:维护一个公司别名映射表,把所有历史数据里出现过的写法收敛到一个标准名称上。常见做法是建立一个name_alias_map.json,每次遇到新写法就手动归并一次,前几次麻烦,后面新数据就少了。另外在 Cypher 查询时不要只查精确匹配,加一层apoc.text.phonetic或者用CONTAINS模糊匹配,配合图谱里的同义词关系兜底。从那以后我每次构建图谱前都会跑一遍实体名称去重统计,专门看有没有高频疑似重复节点。
坑 6:模板嵌套层级深,自动化填充满了漏网之鱼
现象:生成的答疑函基本正确,但某个条款号是空的,或者某个供应商资质信息没填进去,文档已发出才发现。
原因:模板文件里用${field}占位符做填充,但嵌套对象(比如条款列表里的每个条目又包含子字段)遍历时少走了一层,导致深层字段没匹配上。
解决:模板填充前先跑一遍占位符扫描,提取所有${...}里的字段路径,跟数据源字段做一次交叉校验,缺失时直接报错而不是默默跳过。校验代码就几句话,但它能避免“生成之后才发现字段缺失”这种返工成本最高的错误。
6. 模板自动化填充与全流程串联:四个 JSON 模板如何落到最终成文
模板自动化填充是这套系统的收口环节。bidding_template.json、evaluation_report_template.json、answer_letter_template.json、winning_notice_template.json 四个模板覆盖了招标文件、评标报告、答疑函、中标通知四类高频文档。每个模板内部是字段占位符和数据结构的混合体:
{ "doc_type": "评标报告", "project_name": "${bidding_data.project_name}", "bidder_list": [ { "supplier_name": "${bidding_data.supplier_name}", "bid_price": "${bidding_data.bid_price}", "score": "${bidding_data.score}" } ], "expert_opinion": "${expert_opinion}" }template_management.py 的填充逻辑分三步:先解析模板中所有占位符,生成字段清单;再从知识图谱和预处理结果里查询对应值;最后回填并用校验函数确认没有遗漏占位符。main.py 把整条链路串起来:预处理 → NLP 实体识别 → 知识图谱构建查询 → 模板选择与填充 → 输出最终文档。我第一次跑通全流程时的验证方法是:准备一个字段对照表,左边列模板字段,右边列数据源字段,跑完自动比对,缺一个就亮红灯——从那以后我每次跑系统都强制走一遍这个对照校验,再没出现过文档发出后才发现字段缺失的事故。
这套资源的代码路径很清楚,顺着 main.py 的调用链往下读就能把每个模块串起来。遇到实体识别不准,先去查预处理阶段的数据质量;遇到图谱查询为空,先查实体归一化;遇到模板漏填,先跑占位符交叉校验。希望帮到你。
本文还有配套的精品资源,点击获取