WPS AI 的“AI 全文总结”和“文档问答”能快速提炼 PDF,回答还会标注原文出处。但摘要里的数字仍然可能写错、张冠李戴或标错页。 这篇做
summary_check.py:逐句提取摘要中的数字,核对它是否出现在原 PDF 中、是否在所标的页上、是否属于同一个指标。
先看清成品
- 我写的“摘要”中埋了 3 处错误,全部被找出:16.8(原文 18.6)、毛利率标为第 1 页(实际在第 2 页)、15%(原文没有)。
- “研发投入 20 万元”:20 在原文中出现过,但研发投入其实是 150,通过上下文检查被发现。
- 修复了一个误报:“线上渠道收入 760”被“营业收入 1,280”误判。pytest 5 项通过。
合规提醒:WPS AI 需要登录 WPS 账号,在官方客户端或网页版中使用,功能、次数和会员权益以官方说明为准(本文不列价格)。本文不调用任何非官方接口,也不自动化操作 WPS;脚本只在本地检查你自己的文件。把公司文档交给 AI 处理之前,请确认符合公司的数据安全规定。示例数据都是虚构的。
运行说明:box 上没有 WPS 客户端和账号,没有真正调用 WPS AI。本地脚本和测试都在 box 上真实运行过(LibreOffice 25.2.3、poppler-utils、python-pptx 1.0.2、openpyxl 3.1.5)。“AI 生成的”公式、摘要和 PPT 都是我自己写的样例,用来演示核对方法。
做完之后你会得到什么?
| 成果 | 说明 |
|---|---|
summary_check.py |
核对摘要;有问题时退出码为 1 |
report.html → report.pdf |
两页的虚构季度报告 |
summary.txt |
带错误的示例摘要(我写的) |
test_summary.py |
5 项测试 |
需要准备哪些环境?
| 项目 | 说明 |
|---|---|
| Python | 3.9 以上,pip install openpyxl python-pptx pytest |
| LibreOffice | A 篇用于无界面重算公式(apt install libreoffice-calc) |
| poppler-utils | B 篇用于提取 PDF 文字(apt install poppler-utils) |
| WPS Office | 最新版客户端,登录账号;旧版本可以在 ai.wps.cn 升级 |
关键事实
| 事实 | 来源 |
|---|---|
| 入口:WPS 文字 / PDF → WPS AI → AI 全文总结 / AI 文档问答 | WPS 社区官方教程 |
| 文档问答会标注引用的原文出处,可以点击跳转 | WPS 社区官方教程 |
| PDF 对话可以通过页面引用验证答案 | WPS 官网 AI 办公套件页面 |
文件结构
69-wps-ai-B-src/
├── summary_check.py
├── report.html
├── summary.txt
└── test_summary.py
第 1 步:生成示例 PDF
soffice --headless --convert-to pdf report.html # 得到两页的 report.pdf
<html><head><meta charset="utf-8"></head><body>
<h1>2026 年第三季度运营报告(示例,内容为虚构)</h1>
<p>第三季度公司营业收入为 1,280 万元,同比增长 12.5%。其中线上渠道收入 760 万元,占比 59.4%。</p>
<p>本季度新增注册用户 3.2 万人,月活跃用户达到 18.6 万人。客服平均响应时间从 42 秒缩短到 28 秒。</p>
<h2 style="page-break-before: always">二、成本与计划</h2>
<p>营业成本为 905 万元,毛利率 29.3%。研发投入 150 万元,较上季度增加 20 万元。</p>
<p>公司计划于 2026 年 11 月 15 日上线新版会员系统,预计第四季度营业收入达到 1,400 万元。</p>
</body></html>
第 2 步:示例摘要(故意带错误)
第三季度营业收入 1280 万元,同比增长 12.5%(第1页)。线上渠道收入占比 59.4%。
新增注册用户 3.2 万人,月活跃用户 16.8 万人(第1页)。毛利率为 29.3%(第1页)。
新版会员系统将于 2026 年 11 月 15 日上线;第四季度营业收入预计 1,400 万元,同比增长 15%。
第 3 步:核对脚本
"""核对 WPS AI“AI 全文总结 / 文档问答”给出的摘要:摘要里的每个数字、百分比、日期,是否真的出现在原 PDF 中;
摘要标了“(第N页)”的,再核对是否出现在那一页。WPS AI 的回答会标注引用出处,可以点击跳转核对,
这个脚本把核对过程变成可以重复执行的检查。依赖 poppler-utils 的 pdftotext / pdfinfo。"""
import argparse, re, subprocess, sys
NUM = re.compile(r"\d[\d,]*(?:\.\d+)?%?")
CJK = r"[\u4e00-\u9fff,。、;:()%]"
def pages(pdf):
n = int(re.search(r"Pages:\s+(\d+)", subprocess.run(["pdfinfo", pdf], capture_output=True, text=True).stdout).group(1))
out = []
for p in range(1, n + 1):
t = subprocess.run(["pdftotext", "-f", str(p), "-l", str(p), "-layout", pdf, "-"], capture_output=True, text=True).stdout
out.append(t)
return out
def norm_num(s): return s.replace(",", "")
def numbers(text):
return [norm_num(m.group()) for m in NUM.finditer(text)]
FILLER = ("为", "是", "达到", "约", "预计", "达", "共", "了", "到")
def keyword_before(text, pos):
"""数字前面紧挨着的 2 个汉字(跳过“为、达到、约”等虚词),作为上下文关键词。"""
head = re.sub(r"\s+", "", text[:pos])
changed = True
while changed:
changed = False
for f in FILLER:
if head.endswith(f): head = head[: -len(f)]; changed = True
m = re.search(r"([\u4e00-\u9fff]{2,4})$", head)
return m.group(1) if m else None # 最多 4 个字,检查时再按 4→3→2 字缩短
def numbers_after(texts, kw):
"""在所有页面中找关键词的**每一次**出现,取它后面的第一个数字。关键词从 4 个字开始,找不到就逐步缩短到 2 个字。
(第一版只看第一次出现:“线上渠道收入 760”被“营业收入 1,280”误判。)"""
flat = [re.sub(r"\s+", "", t) for t in texts]
for L in range(len(kw), 1, -1):
k = kw[-L:]; got = []
for f in flat:
for m0 in re.finditer(re.escape(k), f):
m = NUM.search(f, m0.end())
if m: got.append(norm_num(m.group()))
if got: return k, got
return kw, []
def claims(summary):
"""按句切分,每句提取数字和页码标注。"""
res = []
for sent in re.split(r"(?<=[。;!?])", summary):
if not sent.strip(): continue
cited = [int(x) for x in re.findall(r"第\s*(\d+)\s*页", sent)]
body = re.sub(r"(第\s*\d+\s*页)", "", sent)
nums = [(norm_num(m.group()), keyword_before(body, m.start())) for m in NUM.finditer(body)]
res.append({"sentence": sent.strip(), "pages": cited, "numbers": nums})
return res
def check(pdf, summary):
texts = pages(pdf); page_nums = [set(numbers(t)) for t in texts]; out = []
for c in claims(summary):
for n, kw in c["numbers"]:
found = [i + 1 for i, s in enumerate(page_nums) if n in s]
if not found: out.append(("NOT_FOUND", n, c["sentence"], [])); continue
if c["pages"] and not set(c["pages"]) & set(found): out.append(("WRONG_PAGE", n, c["sentence"], found)); continue
# 上下文:原文中关键词后面的第一个数字,应该就是这个数字
if kw:
k, seen = numbers_after(texts, kw)
if seen and n not in seen: out.append(("CONTEXT", n, c["sentence"], [k] + seen))
return out
def main(argv=None):
ap = argparse.ArgumentParser(); ap.add_argument("pdf"); ap.add_argument("summary"); a = ap.parse_args(argv)
res = check(a.pdf, open(a.summary, encoding="utf-8").read())
for kind, n, sent, found in res:
if kind == "CONTEXT":
print(f"[上下文不符] {n}:原文“{found[0]}”后面的数字是 {found[1:]} ← {sent}"); continue
print(f"[{'原文中没有' if kind == 'NOT_FOUND' else '页码不对'}] {n}" + (f"(实际在第 {found} 页)" if found else "") + f" ← {sent}")
print("所有数字都能在原文找到" if not res else f"{len(res)} 处需要核对")
return 1 if res else 0
if __name__ == "__main__":
sys.exit(main())
第 4 步:测试
import shutil, pytest
import summary_check as S
pytestmark = pytest.mark.skipif(not shutil.which("pdftotext"), reason="需要 poppler-utils")
def kinds(text): return [(k, n) for k, n, _, _ in S.check("report.pdf", text)]
def test_planted_errors():
assert kinds(open("summary.txt", encoding="utf-8").read()) == [("NOT_FOUND", "16.8"), ("WRONG_PAGE", "29.3%"), ("NOT_FOUND", "15%")]
def test_comma_and_true_claims():
assert kinds("第三季度营业收入 1280 万元,同比增长 12.5%(第1页)。毛利率 29.3%(第2页)。") == []
def test_context_mismatch():
# 20 在原文中出现过(“增加 20 万元”),但研发投入其实是 150
assert kinds("研发投入 20 万元(第2页)。") == [("CONTEXT", "20")]
assert kinds("研发投入较上季度增加 20 万元。") == []
def test_every_occurrence_of_keyword():
# 调试时发现:只看“收入”的第一次出现(营业收入 1,280),把正确的“线上渠道收入 760”误报了
assert kinds("线上渠道收入 760 万元。") == []
def test_known_limitation_from_to():
# “从 42 秒缩短到 28 秒”:用旧值 42 当作现值,本工具查不出来
assert kinds("客服平均响应时间为 42 秒。") == []
第 5 步:运行
$ python summary_check.py report.pdf summary.txt
[原文中没有] 16.8 ← 新增注册用户 3.2 万人,月活跃用户 16.8 万人(第1页)。
[页码不对] 29.3%(实际在第 [2] 页) ← 毛利率为 29.3%(第1页)。
[原文中没有] 15% ← 第四季度营业收入预计 1,400 万元,同比增长 15%。
3 处需要核对
$ python -c "import summary_check as S; print(S.check('report.pdf','研发投入 20 万元(第2页)。'))"
[('CONTEXT', '20', '研发投入 20 万元(第2页)。', ['研发投入', '150'])]
$ python -m pytest -q
5 passed
怎么检查结果?
- 每一条提示都回到 PDF 对应的页核对。
1280和1,280被视为同一个数字。- 输出“所有数字都能在原文找到”只说明数字有出处,不代表摘要没有遗漏重点。
调试示例
1. 只匹配数字会漏掉张冠李戴。 第一版只检查“这个数字是否出现在 PDF 中”。我测试了两句错误的摘要:“研发投入 20 万元”(实际是 150,20 是增加额)和“客服平均响应时间为 42 秒”(42 是旧值,现在是 28),两句都通过了。
改进:取数字前面紧挨着的 2~4 个汉字(跳过“为、达到、约”等虚词)作为关键词,再检查原文中这个关键词后面的第一个数字是否就是这个数字。“研发投入 20”因此被发现。
2. 改进之后出现误报。 正确的“线上渠道收入 760 万元”被报错,原因是关键词“收入”在原文中第一次出现在“营业收入为 1,280”里。修复:查找关键词的每一次出现,并优先使用更长的关键词(4 字 → 3 字 → 2 字)。
仍然存在的局限: 对于“从 42 秒缩短到 28 秒”这种结构,用旧值 42 冒充现值的错误查不出来(测试 test_known_limitation_from_to 记录了这一点)。中文数字(如“三季度”)也不检查。
常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| 扫描版 PDF 提取不到文字 | PDF 中只有图片 | 先用 OCR 转出文字(WPS 有 PDF OCR 功能) |
| 页码不对 | 页码标注错误,或者是 PDF 页码与印刷页码不一致 | 以 PDF 阅读器显示的页码为准 |
| 数字被拆开 | pdftotext 换行时把数字切断了 | 用 -layout 参数,或检查原文 |
| 上下文误报 | 关键词太通用 | 人工确认 |
模板规则
- 摘要中的每个数字都要有出处。
- 标了页码的要核对页码。
- 工具查不到问题,不等于摘要完全正确。
项目资源
| 用途 | 地址 |
|---|---|
| WPS AI 详细体验教程(WPS 社区官方帖) | https://bbs.wps.cn/topic/2498 |
| WPS AI 官网 | https://ai.wps.cn/ |
| WPS 官网 AI 办公套件页面 | https://zh-hant.wps.com/feature/zh-CN-ai-office-suite/ |
用 WPS AI 节省阅读时间,再用脚本把数字核对一遍。