欢迎光临
我们一直在努力

舞台工艺 AI 交付物措辞闸门:同一套词表为什么必须拆成 opinion / system 双模式

舞台工艺 AI 交付物措辞闸门:同一套词表为什么必须拆成 opinion / system 双模式

一、交付物的措辞不是文风问题,是责任边界问题

舞台工艺审图的交付物分两大类:一类是审核意见——面向设计单位和业主方,回答"这张图哪些地方不符合、依据是什么、怎么改";另一类是体系/方法论文件——面向内部团队,回答"这套审查是怎么组织的、依据哪些标准、每一步怎么走"。两类文档都由 AI 参与起草,再由人工复核定稿,流程上共用同一条出包链路。

问题出在措辞上。AI 在起草时会自然地带出两类不该出现在交付物里的话术:

第一类是读图过程词。 比如"文字层"“文本层”“提取内容”“自检表”“坐实”“疑为”。这些词描述的是"我是怎么把这张图读出来的",而外发意见的读者只关心"这张图有什么问题"。把过程词写进交付,等于把内部作业痕迹塞给了对方;更麻烦的是"疑为"“疑是”"疑为笔误"这类不定性表述——审核结论必须给确定判断,"疑为笔误"把核实成本原样推回给读者,等于这条意见没写完。

第二类是自证语。 比如"经复核"“经核对”“已确认达标”“未发现”“无问题”。审核意见的效力来自"问题—依据—建议"三要素,不来自审核人替自己的过程背书。"经复核"三个字不改变任何一条意见的成立与否,却让整份文档带上自我担保的语气;一旦后续发现漏项,这句话反而成为追责时的把柄。

这两类词不是人工写错,而是 AI 起草时的系统性倾向。读图过程词来自链路本身:图纸要先转成可读文本才能被理解,中间产物里到处是"文字层"“提取内容”“覆盖率自检"这类说法,模型起草时很自然地把上游语境带进正文。自证语则来自语料风格:公开的审查报告、验收纪要里满是"经复核”"未发现问题"这类套话,模型学到的就是这套腔调。既然是系统性倾向,靠提示词"请避免使用……"是压不住的——提示词只能降低频率,不能给出确定性;要确定性只能上闸门。

所以必须在出包前加一道机器闸门:对交付 docx 全文做禁词扫描,命中即阻断,退出码非 0 就不许打包。

但真正难的不是"加闸门",而是词表该长什么样。第一版我们把所有禁词做成一张平表,一把梭扫全文,结果两头不讨好:意见稿里的自证语确实被拦住了,可体系文件里"3.1 审查依据""4.2 审查方法"这些合法章节名也被判违例,导致体系文件根本过不了门。修法不是删词,而是把词表按文档类型分区、按类型路由。这就是 –type opinion|system 双模式存在的唯一理由。

本文讲的门禁脚本 audit_delivery_gate.py 实测 384 行(wc -l,16537 字节),禁词表拆成 4 组,退出码三值。下面按"为什么拆—怎么拆—怎么实现—怎么接入"的顺序讲透,所有数字均取自脚本真源或本机实测。

二、同一个词,两套合法性:一把梭的词表为什么必然两头不讨好

先看一组实测。构造一份同时满足两类文档形态的样本:抬头齐备、六列意见表干净、正文里写"审查依据为 WH/T 系列与 GB/T 相关标准,审查方法为逐项比对"。同一份文件,只切换 –type:

$ python audit_delivery_gate.py sample_A_dual.docx –type opinion
文档类型: opinion(审核意见类)
[FAIL] sample_A_dual.docx (意见行数=2;复核列已填=0)
– 禁词命中: 审查依据、审查方法

==== 门禁结果 ====
通过 0/1
存在违例,交付被阻断。请修正后重跑。
rc=1

$ python audit_delivery_gate.py sample_A_dual.docx –type system
文档类型: system(体系/方法论类)
[OK] sample_A_dual.docx (表格列数分布=6列×1;已标注空白模板=0)

==== 门禁结果 ====
通过 1/1
全部通过,可出文/出包。
rc=0

同一份文件、同一段文字,opinion 模式判违例,system 模式判通过。这不是门禁的双标,是这两类文档对同一个词的合法性要求本来就相反。

拆开看"审查依据"这个词在两种语境里的作用:

  • 在审核意见里,它出现在哪都没用。意见的读者要的是"哪一条不符、违反了哪条规范、改成什么"。写"审查依据为 WH/T 系列"既没有定位到具体条款,也没有指向具体不符项,是一句无法执行的空话;更糟的是它会让整份意见看起来像在声明"我是按规矩查的",这正是自证语的典型形态。
  • 在体系文件里,它是章节名。体系文件的作用就是交代这套审查怎么组织,第一章不写"审查依据"、不写"审查方法",这份文件的骨架就塌了。把它禁掉,等于逼着体系文件改名换词,最后写出"审查所依照的东西"这种更糟的表述。

同样的分裂也发生在"经复核"“未发现”"无问题"上:意见稿里是自证语,体系文件里是合法的流程描述与结论句。

于是得到一个判据:一个词是不是禁词,不取决于词本身,而取决于它所在的文档在交付链里扮演什么角色。 词表的第一行注释应该写清楚"这个词在哪一类文档里合法",而不是只写"这个词不许出现"。

再往深一层,被禁的这些词实际上分三种性质,混在一张平表里会让后续维护失去依据:

  • 读图过程词(文字层、文本层、可读文字层、提取内容、覆盖率自检、自检表……)——泄露的是作业痕迹,任何交付物都不该有。
  • 自证语(经核对、经复核、已确认达标、未发现、无问题……)——泄露的是立场越权,只在意见类里越权;体系文件里如果是客观描述流程,则不越权。
  • 不定性表述(疑为、疑是、疑为笔误、疑是笔误、疑为误写、疑是误写……)——泄露的是判断未完成,任何交付物都不该有。
  • 只有第 2 类的合法性会随文档类型翻转,第 1、3 类恒定。这就是分区的依据:会翻转的进专用组,不翻转的进通用组。

    把三类拆开还有第二个好处:出现新的污染词时,归档位置是确定的。有一次模型在意见稿里写出"端口容量无溢出"——这既不是读图过程词也不是自证语,而是把齐套性核对的内部结论写进了单条意见(齐套性是整册图纸层面的结论,不该出现在逐条意见里)。这类词该进哪一组?按上面的判据一问即有答案:体系文件里当然可以写"端口容量核对"“无溢出”,所以它们进 OPINION_ONLY。如果没有这套分区,新词只能随手追加到平表末尾,不出三个月就没有人记得某条词当初为什么进表、能不能删。

    三、四组词表的分区依据与合并规则

    脚本把词表拆成 4 组,每组承担一种独立语义。以下条数均为本机实测 len() 值。

    OPINION_ONLY_PATTERNS(意见类专用,实测 13 条)
    审查依据、审查方法、审查方式、审查标准、经核对、经复核、经…复核、经核对其、经对其、齐套性核对、齐套性结论、端口容量、无溢出。
    判定口径:这些词在体系文件里全部是合法表述(章节名或流程描述),只在意见稿里越权。

    COMMON_PATTERNS(两类通用,实测 17 条)
    文字层、文本层、可读文字层、提取内容、覆盖率自检、自检表、坐实、无误判、无漏检、漏检、已确认达标、疑为、疑是、疑为笔误、疑是笔误、疑为误写、疑是误写。
    判定口径:读图过程词 + 不定性表述 + 通用自证语,任何一类交付物出现都应拦截。

    OPINION_EXTRA_PATTERNS(意见类附加自证语,实测 2 条)
    未发现、无问题。
    判定口径:这两个词在体系文件里是正常结论句(“现场核查未发现问题”),但在意见稿里属于替自己下结论,故单独成组、只进意见类全量。

    PLACEHOLDER_PATTERNS(体系文件占位符残留,实测 6 条)
    ####、TODO、TBD、XXX占位、待补充、待补齐。
    判定口径:这不是措辞问题,是"没写完"问题。它走独立通道(体系类专用),不参与意见类的措辞判定。

    合并规则只有两条:

    • 意见类全量 R17_PATTERNS = OPINION_ONLY + COMMON + OPINION_EXTRA,实测 32 条;
    • 体系类只取 COMMON_PATTERNS,实测 17 条。

    判定一个词该进哪一组,只需问一句:“它有没有可能是体系文件里的合法表述?” 答案是"是",进 OPINION_ONLY 或 OPINION_EXTRA;答案是"绝不可能是",进 COMMON。这条判据看起来简单,却是后面几次词表修订里唯一没被推翻过的东西——2026-10-13 那次把"待补"改成"待补充/待补齐"的修订,本质上就是因为"待补"这个词有可能是合法状态词的一部分(待补证=待补证据),违反了这条判据。

    顺带说一个容易被忽略的配套项:位置列禁读图坐标。六列意见表的"位置"列如果写成 line 12、p003、第 3 页,那是把读取过程的坐标和页面锚点带进了交付——对改图毫无意义,图纸一重排、一换版,这些数字立刻失效。脚本用一条正则拦三类形态:

    POSITION_ANCHOR_RE = re.compile(r"(?i)\\bline\\s*\\d+\\b|\\bp\\s*\\d{3,}\\b|第\\s*\\d+\\s*[页行]")

    本机实测 7 例:

    • line 12 → 命中 line 12(\\s* 兼容无空格写法 line12)
    • p003 → 命中(三位以上数字)
    • p12 → 不命中(两位数字,避免误伤轴网编号之类的短码)
    • 第 3 页 / 第 5 行 → 命中
    • 轴网 3-4 交 A → 不命中(真正该写进位置列的内容)

    这条正则的写法体现了同一条原则:锚点形态要写得足够"窄",宁可放过,不可误伤。 位置列真正该写的是"面光桥 A 区"“耳光室”“栅顶 3 轴至 5 轴"这类图纸自身的定位语言——它在版次变更后依然成立,改图的人拿着它就能找到位置。锚点坐标和页码则相反,它们定位的是"AI 当时读到的是哪一页”,换个软件、换个版本立刻失效。

    四组之外还有一点值得记下来:PLACEHOLDER 组是独立于措辞闸门的另一条通道。措辞闸门管"说了不该说的话",占位符检查管"该说的话还没写"。两者混在一张表里会造成一个尴尬后果——命中报告里"待补充"和"经复核"并列出现,前者要补内容,后者要删词,处置动作完全相反,却共用同一条违例列表。分成两组之后,输出的处置指令才能一句话说清。

    四、Python 实现:路由、取文、编译、定位

    4.1 词表定义与双模式路由

    # audit_delivery_gate.py —— 禁词表四组分区
    OPINION_ONLY_PATTERNS = [
    "审查依据", "审查方法", "审查方式", "审查标准",
    "经核对", "经复核", "经…复核", "经核对其", "经对其",
    "齐套性核对", "齐套性结论",
    "端口容量", "无溢出",
    ] # 实测 len = 13

    COMMON_PATTERNS = [
    "文字层", "文本层", "可读文字层", "提取内容",
    "覆盖率自检", "自检表",
    "坐实", "无误判", "无漏检", "漏检", "已确认达标",
    "疑为", "疑是", "疑为笔误", "疑是笔误", "疑为误写", "疑是误写",
    ] # 实测 len = 17

    OPINION_EXTRA_PATTERNS = ["未发现", "无问题"] # 实测 len = 2

    PLACEHOLDER_PATTERNS = ["####", "TODO", "TBD", "XXX占位", "待补充", "待补齐"] # len = 6

    R17_PATTERNS = OPINION_ONLY_PATTERNS + COMMON_PATTERNS + OPINION_EXTRA_PATTERNS # 实测 len = 32

    def route_patterns(doc_type: str) –> list[str]:
    """双模式词表路由:system 只取通用组,opinion 取全量。

    路由前先校验类型值——未知类型必须报错退出,不能静默降级为 opinion,
    否则体系文件会被按意见类标准误拦。
    """
    if doc_type not in ("opinion", "system"):
    raise ValueError(f"未知文档类型: {doc_type}(应为 opinion 或 system)")
    return COMMON_PATTERNS if doc_type == "system" else R17_PATTERNS

    路由函数里的类型校验不能省。–type 拼错成 opinon 时,如果代码走 if doc_type == "system" 的否定分支默认取全量表,体系文件就会被意见类标准拦下一大片,而且报错信息完全指不到根因。真源把这段放在 main() 里,用 sys.exit(2) 退出。

    4.2 取文:表格单元格必须进扫描范围

    def collect_text(doc) –> str:
    """汇总 docx 全文字:段落 + 所有表格单元格。

    ★ 只扫 doc.paragraphs 会让六列意见表整张漏检——
    禁词最常藏在"问题"列里,而表格文字不在 doc.paragraphs 中。
    """
    out = [p.text for p in doc.paragraphs]
    for t in doc.tables:
    for r in t.rows:
    for c in r.cells:
    out.append(c.text)
    return "\\n".join(out)

    这段看着平庸,实际是整道闸门里最容易踩空的一环。实测对比:构造一份文档,段落只有一句"本页无禁词,段落干净。“,六列意见表的"问题"列里写"标注数值疑为笔误”——

    • 只扫 doc.paragraphs:命中 0 条,闸门放行;
    • 走 collect_text():命中 疑为、疑为笔误、文字层(实测 3 条),闸门阻断。

    漏掉表格的扫描等于闸门形同虚设,因为审核意见的实质内容 100% 在表里。

    4.3 编译:长度降序,避免短词吞长词

    真源当前的判定是 collect-all 子串法:

    hits = sorted([kw for kw in patterns if kw in full])

    这种写法下短词不会"吞掉"长词——实测对"标注文字疑为笔误"一句,疑为 和 疑为笔误 都会被报出来(命中列表长度 2)。它的问题是只报词、不报位置,回改时还得自己全文搜索。

    一旦要把闸门升级成"定位"或"自动改写",就必须引入正则,此时词表必须按长度降序编译:

    import re

    def compile_banned(words) –> re.Pattern:
    """按长度降序编译。

    交替分支 `|` 在 Python re 里是最左优先匹配:短词排在前面会先命中,
    把长词吞掉("疑为" 吃掉 "疑为笔误"),导致定位结果把长词截断成短词。
    """
    return re.compile("|".join(re.escape(w) for w in sorted(words, key=len, reverse=True)))

    def scan_banned(full_text: str, words) –> list[tuple[str, int, int]]:
    """返回 (命中词, 起始下标, 结束下标),供回改定位。"""
    rx = compile_banned(words)
    return [(m.group(0), m.start(), m.end()) for m in rx.finditer(full_text)]

    实测同一句"该标注疑为笔误,另一处疑为尺寸标注缺失。":

    • 不排序直接 | 拼接:首个命中是 疑为(长度 2),位置 3;
    • 按长度降序编译:首个命中是 疑为笔误(长度 4),位置 3;
    • 降序后 finditer 不重叠枚举:['疑为笔误', '疑为']。

    两条路径的起始位置相同,但匹配到的词长度不同。对"只报有没有"的门禁没差别;对"改成什么"的回改脚本差别巨大——按短词定位去替换,会把"疑为笔误"的"笔误"两个字留在原地,产出一句更不通的话。

    4.4 回改而不是删词

    命中之后该做什么,是这道闸门最容易被做错的地方。两个极端都要避开:

    • 删词:把"疑为笔误"四个字删掉,问题列变成"标注数值",语义残缺,这条意见等于废了。
    • 整条丢弃:把含禁词的那一行意见整行删掉,会连真正属于本职工作的判断一起丢掉——"疑为笔误"底下往往藏着一个真实的不符项。

    正确的动作是拦截 + 定位 + 回改:闸门只负责说"第几表第几行第几列、命中哪个词、在全文第几个字符",由人或上游脚本把"疑为笔误"改成"与图例不一致"、"经复核"改成具体条款引用。实测:把样本里的问题列从"标注数值疑为笔误"改成"标注数值与图例不一致"后,同一份文档 –type opinion 从 rc=1 变 rc=0:

    $ python audit_delivery_gate.py sample_C_fixed.docx –type opinion
    文档类型: opinion(审核意见类)
    [OK] sample_C_fixed.docx (意见行数=2;复核列已填=0)
    ==== 门禁结果 ====
    通过 1/1
    全部通过,可出文/出包。
    rc=0

    所以闸门的输出格式要设计成可直接回改的:报表名/行号/列号比只报词有用得多,报字符下标比只报行数有用得多。

    4.5 主入口与退出码

    import sys
    from docx import Document

    def gate_one(path: str, doc_type: str = "opinion") –> int:
    """单文档门禁。返回 0=通过;1=存在违例;2=用法/路径错误。"""
    try:
    doc = Document(path)
    except Exception as exc:
    print(f"打开失败(非 docx 或文件损坏): {path} -> {exc}")
    return 2

    words = route_patterns(doc_type)
    hits = scan_banned(collect_text(doc), words)
    if hits:
    for w, s, e in hits:
    print(f"[禁词] {w} @字符 {s}–{e}")
    return 1
    return 0

    if __name__ == "__main__":
    argv = sys.argv[1:]
    dtype = "opinion"
    if "–type" in argv:
    i = argv.index("–type")
    try:
    dtype = argv[i + 1]
    except IndexError:
    print("–type 缺少取值(opinion|system)")
    sys.exit(2)
    del argv[i:i + 2]
    if dtype not in ("opinion", "system"):
    print(f"未知文档类型: {dtype}(应为 opinion 或 system)")
    sys.exit(2)
    if not argv:
    print("用法: python audit_delivery_gate.py <根目录 | 单个.docx> [–type opinion|system]")
    sys.exit(2)
    sys.exit(gate_one(argv[0], dtype))

    三个退出码各司其职:0 全部通过可出包;1 存在违例、交付被阻断;2 用法或路径错误(区别于 1 的意义在于——2 是"门没跑起来",不该被当成"内容有问题"去改文档)。

    五、闸门接入:退出码是唯一判据

    门禁脚本单独能跑只是第一步,真正让它生效的是接入打包链路并把退出码当成唯一判据。三条接入纪律:

    第一,不接受"口头通过"。 上游打包脚本必须检查子进程返回码,不允许靠人看一眼终端输出就说"过了"。subprocess.run(…).returncode != 0 就中止出包,不打印"警告但继续"。任何形式的"警告级违例"都会在三周内退化成"默认忽略"。

    第二,区分 1 和 2 的处置路径。 返回 1 是内容问题,去改文档;返回 2 是调用问题,去改命令行。把两者混成一个非零分支,最常见的后果是路径写错时反复去改文档,改了半天毫无进展。

    第三,文档类型由目录位置决定,不由人记。 不要依赖执行者每次记得敲 –type。可行的做法是让打包脚本按输出目录自动判定:_审核输出/05_正式审核意见 下的走 opinion,体系文件目录走 system,并把判定结果打印在门禁输出的第一行(真源就打印了"文档类型: opinion(审核意见类)"这一行),出问题时一眼能看出是不是类型选错。

    接入之后还有一件事:门禁自身也要能被回归测试。每次改词表,都应该拿一批已知结论的样本跑一遍——哪些该红、哪些必须不红。词表越改越大时,真正会出问题的不是"漏了新禁词",而是"新加的词把合法表述误伤了"。

    漏词的后果是显性的:它没被拦住,人一眼能看见,改一次词表就补上了。误伤的后果是隐性的:体系文件过不了门,执行者最直接的反应是"这闸门太严了",于是加白名单、加豁免、甚至干脆绕开——闸门在一次次妥协里被架空。所以回归样本里"必须放行"的那些,比"必须拦下"的那些更值得花力气维护。

    还有一条经验:门禁的报错文案要写清"怎么改",而不是只写"哪里错了"。禁词命中: 疑为、疑为笔误 这条信息对第一次遇到它的人毫无帮助,他不知道该删、该改、还是该整条重写。真源在体系类的空表违例里就写得更好——“若为空白表单模板,须在上文标注『空白/模板/示例』”,一句话把处置动作说完了。措辞违例同样应该带一句处置提示,比如"疑为/疑是类:改为确定性表述(与图例不一致 / 数值应为 X)"。

    最后一点是门禁的执行时机。它必须是出包前的最后一道,而不是起草后的第一道。放在起草后立刻跑,会拦截大量还在编辑中的半成品,噪音淹没信号;放在出包前跑,被拦的都是真的要发给对方的东西,每一条违例都值得处理。真源的定位写得很明确——“出包前必跑、零违例方可出文”。

    六、上线前验收清单

    按以下七条逐项打勾,全部满足才算这道闸门可用:

  • 词表分区有据可查:每一条禁词都能说清"它在体系文件里是否可能合法",说不清的一律不进表。抽查方式:随机挑 5 条词,让维护者当场说出分区理由,说不出两条以上就说明词表已经失控。
  • 双模式路由已接:–type 必传或被自动判定,未知类型报错退出(rc=2),不静默降级。验证方式:故意传 –type opinoin,必须 rc=2 且提示"应为 opinion 或 system"。
  • 取文覆盖表格单元格:用"禁词只写在意见表问题列"的样本验证,闸门必须报红;只扫段落的样本必须放行。这一条最容易在重构取文函数时被破坏,必须固化成断言。
  • 词表按长度降序编译(若使用正则定位):验证"疑为笔误"被整体命中,而不是被"疑为"截断。同时验证不重叠枚举能同时报出 疑为笔误 与 疑为 两条。
  • 命中输出可回改:至少包含表名/行号或字符下标,能直接定位到待改的那一段文字;并附带一句处置提示(改成什么,而不是只说不许用什么)。
  • 回改后复跑通过:同一份文档改掉禁词后 rc 从 1 变 0,且语义不残缺(不是靠删词通过的)。这一条用来防止"为了过门而把意见改空"。
  • 误伤样本有回归:至少固化"体系文件含审查依据"“文档含待补证”“空白表单模板”"正文含 #### 分隔符"四类必须放行的样本,每次改词表后全绿。
  • 第 7 条值得单独强调一次。前六条保证闸门"拦得准",第 7 条保证闸门"活得下去"。一道每次改词表都会误伤一批合法文档的闸门,最终结局一定是被人绕过;而一套稳定的回归样本,能让每一次词表修订都变成"改完跑一遍、全绿就合并"的确定性动作,维护者不再需要凭记忆判断"这次改的词会不会碰坏什么"。

    最后回到开头那句话:交付物的措辞不是文风问题,是责任边界问题。禁词闸门的价值不在于它拦下了多少词,而在于它把"这份文档到底是给谁看的、该说哪些话、不该说哪些话"这件原本靠人凭经验把握的事,变成了一条可复跑、可回归、可追责的机器判据。词表要分区、路由要显式、命中要能定位——这三件事做到了,这道闸门才真正立得住。

    赞(0)
    未经允许不得转载:171主机测评 » 舞台工艺 AI 交付物措辞闸门:同一套词表为什么必须拆成 opinion / system 双模式
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址