文档格式质检Agent
机器查格式,人查内容
你有没有这种经历:一份标书翻来覆去看了三遍,提交后还是被退回——因为正文里有一处忘了脱敏的公司名。我就干过这种事。那次之后我痛定思痛,花一个下午写了个质检脚本。后来这个脚本从50行长到了700多行,已经帮我那几个AI员工审了上百份标书,没放过一个格式错。
一、质检的核心思路:检查和写作分开
很多人觉得"AI质检"就是让AI读完标书全文然后说"没问题/有问题",但这么做有两个硬伤:
第一,贵。让顶流模型读一遍几十页的标书,光token就要烧掉好几块钱。每天审几份,一个月下来比服务器还贵。
第二,AI不擅长度量检查。你让DeepSeek V4检查"编号是否连续""页码是否从1开始",它有50%的概率漏掉。因为它擅长的是理解内容,不是数页码。
我们后来实践下来的最佳方案是两遍制:
两遍下来,覆盖了95%以上的标注问题,token成本比全程用AI省了70-80%。
二、第一遍:零token的规则检查
核心思路:能用一个正则解决的问题,绝不动用一个token。
2.1 脱敏检查(最实用)
标书行业有个铁律:正文里不能出现和本次投标主体不符合的投保人,不能串标,不能废标,这也不能,那也不能。但人眼扫几十页PDF,漏掉一两个是常态。
import re # 已知公司名单(每次做新标书时更新) COMPANY_NAMES = [ "北京华信科技", "上海振华信息", "深圳创达数据", ] def check_desensitization(text, companies): """检查正文中是否残留指定公司名""" hits = [] for name in companies: positions = [ (m.start(), m.end()) for m in re.finditer(re.escape(name), text) ] if positions: hits.append({ "company": name, "occurrences": len(positions), "positions": positions[:5] }) return hits
跑一下,所有残余公司名全部标红——零漏检,零token。
2.2 空白页/占位符检查
def check_empty_pages(text): """检查分页符前后的空白""" issues = [] pages = text.split('\x0c') # 分页符 for i, page in enumerate(pages): clean = re.sub(r'\s+', '', page) if len(clean) < 10: issues.append( f"第{i+1}页疑似空白" f"(仅{len(clean)}个有效字符)" ) return issues
2.3 编号连续性检查
标书的章节目录通常是"1、1.1、1.1.1"这种结构。如果中间跳了号,说明中间删了一段忘改编号了。
def check_numbering(text): """提取所有编号,检查是否连续""" pattern = r'^(\d+(?:\.\d+)*)[.、\s]' numbers = [] for line in text.split('\n'): m = re.match(pattern, line.strip()) if m: numbers.append(m.group(1)) issues = [] for i in range(1, len(numbers)): prev = [int(x) for x in numbers[i-1].split('.')] curr = [int(x) for x in numbers[i].split('.')] if len(prev) == len(curr): if len(prev) == 2 and curr[-1] - prev[-1] > 1: issues.append( f"编号跳号:{numbers[i-1]} -> {numbers[i]}" ) return issues
2.4 表格数字勾稽检查
标书里的报价表,合计行和明细加总必须一致。用正则就能搞定。
def check_table_totals(docx_file): from docx import Document doc = Document(docx_file) issues = [] for table in doc.tables: for row in table.rows: cells = [c.text.strip() for c in row.cells] if any(kw in cells[0] for kw in ["合计", "总计", "小计"]): # 提取数字并加总检查 pass return issues
为什么不用AI做这些:正则100%准确,零成本;AI可能漏检还可能误报,一次调用还要几分钱。
三、第二遍:让AI给疑点做二次判断
第一遍扫完后,你会拿到一堆疑点清单。有些是真问题,有些是误报。比如脱敏检查找到的"北京华信科技"——如果是在"公司简介"章节里,可能是该写的公司介绍;如果是在"技术方案"章节里,大概率是脱敏残留。
python3 bid_qa.py 标书文件.docx --ai-check
AI只需要看"公司名+出现位置+附近50字上下文"就能判断。一个标书调用一次,token消耗不到全文检查的10%。
而且你可以用最便宜的模型做这事——DeepSeek V4-Flash就够了,不需要顶流模型。
四、踩坑记录(我们实际翻过车的)
踩坑1:正则一个 \text 打翻一船
写脱敏检查时,第一版用的是 in 操作符,结果正文里写"中科华信(北京)科技有限公司",不包含"华信"二字,漏检了。(这公司名我随便说的哈)
教训:公司名的写法五花八门,正则匹配的时候把各种可能写法都列进去——全称/简称/加括号的/带城市的。
踩坑2:docx文件里"看不见"的幽灵
python-docx读Word文件时,有些分节符不会出现在段落列表里。我们曾以为"页数检查"没问题,结果打印出来多了一页空白。
from docx import Document doc = Document("标书.docx") for i, section in enumerate(doc.sections): print(f"第{i+1}节:页码从" f"{section.start_page_number}开始")
踩坑3:让AI做第一遍检查,一个月多花200块
最早一版让DeepSeek V4全文检查标书,每审一份要读2-3万token。一天审5份,一个月多花了200块。改成"正则打头阵+AI补位"后,一个月token成本从200降到了20块。准确率反而更高了。
踩坑4:表格里的合并单元格
Word表格里的合并单元格,用python-docx读出来时会被跳过——原本4列变成3列,数字提取全歪了。解决方案:读取表格前先检测合并单元格,用 grid_span 属性解析真实位置。
五、总结
花一个下午写这个质检脚本,换来的是一劳永逸:
脱敏检查:从"人眼扫三遍还可能漏"到"2秒出报告,零漏检"
格式检查:从"排版工一页一页翻"到"脚本自动扫,精准命中"
AI调用:从"每份几块钱"到"每份几毛钱"
最关键的是:机器做机器擅长的(规则/数字/格式),人做人擅长的(判断/策略/修改)。你不需要让AI替你干活,你只需要让AI替你筛出"需要你看的地方"。
其实这不是手把手搭一个agent,这只是前面AI踩过的坑,让后面的AI少踩。
本文代码基于宗门bid_review_v3.py(两遍制质检引擎)的实战经验写成。
公众号:硅基聊斋
夜雨聆风