🤖 第六章 应用实战:采购订单智能识别与ERP自动化填报助手
📄 一句话概括

前几章的智能体是主角(让 GPT 写公众号、让文心批量出稿、让知乎智能体写图文)。本章的智能体是一个配角——它是【异常处理台】:正常件根本不需要跟它说话,只有被挑出来的那几条才需要它。而它能不能答好,八成不取决于它多聪明,取决于你喂给它什么。

📝 本节和前面几节的关系

6.3.1 造好了"识别"这条工作流(机器部分);本节造那个"人机对话"的入口(人部分)。 它落在 6.2 那张流程图的第 ④ 格(人工复核)——这一格是"人"的位置,而智能体的作用是让人在这一格里看得更快。 与第二章 2.3.3 的对照:那一节的智能体是个"前台"(服务人的对话入口);本节的智能体是"异常答疑台"——同样服务人,但服务的是一个非常具体的场景。

一、本章的分工:智能体是配角,工作流是主角

先把角色摆正,这是本节最重要的一件事。

前几章的智能体都是主入口:2.3.3 的图文助手、3.3.1 的公众号 GPT、4.3.1 的文心智能体、5.3.2 的知乎智能体——用户是要跟它对话的。

本章不一样:正常件由工作流自动处理(识别 → 校验 → 填报),根本不经过对话。

前面几章 本章
工作流 服务机器(管道) 服务机器(管道)—— 主体
智能体 服务人(前台)—— 主体 服务人(异常答疑)—— 配角
什么时候用 每次都经过 只有异常经过

为什么本章是这个分工? 回到 6.2 那条规律:本章的批量是【行数 × 单数】,正常件太多,人看不完也问不完。

📝 这个分工的变化,本身就是"内容侧 → 数据侧"的又一次体现

内容侧:每一篇都需要人做判断(写得好不好、有没有人味),所以对话入口是刚需。 数据侧:绝大多数单据不需要判断(数字抄对就行),所以对话入口只在出问题时才被用到。 一个反过来说的判据:如果你发现"每条都要跟智能体聊两句",那说明你的校验规则写得不够——该交给规则的事,别推给人。

二、人设设计:一个"审单员"助手

沿用第二章那套四段式人设,但每一段的内容都要按本章重写:

段 内容
我是谁 采购订单录入助手,负责解释某条为什么被标异常、并给出处理建议
我能做什么 解释校验发现;对照主数据说明"哪个字段有问题";给出建议值(并说明依据)
我不能做什么 ① 不能替人确认;② 不能擅自改金额、税率、数量;③ 不能补全图上看不见的字段;④ 不能凭印象报数据
怎么开场 收到一条异常记录的完整上下文后,先复述"这条卡在哪",再给建议

第二段和第三段是这个助手的全部价值所在。

第三段("我不能做什么")为什么最重要

这是全课反复出现的一条(2.4.3 的合规闸门、5.4.3 的创作声明都是同一个思路):限制比能力更重要。

具体到本章,那四条限制各自防着一个具体的坏结果:

限制 防的是什么
不能替人确认 防"责任无人承担"——6.1.2 的合规账说过:账要有人签字
不能擅自改金额、税率、数量 防"智能体把错数改成一个看起来对的数"——金额和税率是审计直接看的东西
不能补全看不见的字段 防"它替你猜"——6.3.1 那条"禁止虚构、推断、补全"的同一条规矩
不能凭印象报数据 防"它编一个像真的供应商、像真的单价"
⚠️ 为什么"不能替人确认"要放在第一条

因为它是唯一一条"能力越大越危险"的。 一个只会解释的助手是安全的;一个"能替你把单子提交了"的助手,是本章最不能有的东西——因为它会抹掉那个签字的人。 这也正好接上 6.1.2 的合规账:这一章的价值里有一笔叫"合规",而合规的核心就是"谁负责"。

三、知识库放什么:本章的知识库比前几章都重要

前几章的知识库放的是"参考材料"(代表作、写作风格、平台规则)。本章的知识库放的是"这家公司的内部真相"——没有它,助手什么都答不了。

放什么 为什么必须放
物料主数据(编码 ↔ 名称 ↔ 规格) 助手要把"图上写的名字"和"系统里的编码"对上
供应商主数据(名称 ↔ 编码 ↔ 结算方式) 同上,且用于判断"这个供应商名是不是编的"
税率规则(哪些税率合法、什么品类适用) 助手要能说清"14 为什么不对"
常见异常处理 SOP 让建议保持一致,而不是每次现编
历史处理记录(同一供应商/物料的过往) 本节 Demo 会专门演示它带来的差别

注意最后一行:历史处理记录是本课之前所有章节都没有的一类知识——因为前几章写文章不需要"过去怎么写",而录单据非常需要"上次遇到同样情况怎么处理的"。

📝 为什么"历史处理记录"特别值钱

因为它把"只能靠老师傅"的那部分经验变成了可检索的资料。 6.1.1 的现状三提到一个风险:RPA 干得越久,组织越可能失去懂这条流程的人(SME 流失)。 把历史处理记录沉淀进知识库,正是对抗这个风险的唯一办法——否则机器人一坏,没人知道当初为什么这么设计。

四、提示词的关键三段

本章的提示词比前几章短,但要更"硬":

第一段:输入是"完整上下文",不是"一个问题"

不要这样问:"这条为什么异常?" ← 助手只能反问你

要这样喂(6.2 那张异常表的五个块):

【单据】来源、文件名
【识别结果】完整的字段 JSON
【校验发现】系统发现了什么(可能有几条)
【主数据对照】每个编码/供应商在系统里命中了没有
【历史处理】同一供应商/物料以前怎么处理的(可选)

第五块可以没有,但前四块一块都不能少——缺一块,助手就会用猜来补。

第二段:输出是"给人看的说明",不是 JSON

这一条容易搞反:很多人会让智能体也输出 JSON(因为下游是 RPA)。 但本章的智能体下游是人——它的输出应该是一段人话:

这条卡在:第 1 行的物料编码置信度只有 0.62(低于 0.80)。
建议:请对照原图确认该编码。系统里 M1001 对应"内六角螺栓 M6×20",与图上描述一致。
依据:该供应商上月同款成交价 2.45 元,本次 2.5 元属正常浮动。

注意它有三段:卡在哪 / 建议什么 / 依据是什么。 第三段是它区别于"随口一说"的关键。

第三段:禁忌(那四条限制的提示词版)

禁止替人做最终确认。
禁止修改金额、税率、数量,只能【建议】。
禁止补全上下文中没有的字段。
所有数据必须来自给出的上下文,不得凭印象补充。

五、什么时候用智能体、什么时候用规则

这一张表是本节最实用的部分:

问题类型 交给谁 为什么
编码在不在主数据里 规则 确定性的查表
合计对不对 规则 确定性的算术
税率合不合法 规则 确定性的集合判断
这条为什么被判异常 智能体 需要综合上下文生成解释
该怎么改 智能体(给建议) 需要结合历史与主数据推理
改完能不能放行 人 要担责
点保存 人 要担责

规律很清楚:前三行都是"机器能算出来的",后四行都是"要解释或要担责的"。 而中间那两行(为什么错、该怎么改)正是智能体的全部地盘——不多不少。

⚠️ 一个判据:如果智能体在"算",你就用错了

如果你发现智能体在做加法、在比对字符串、在查表——那它随时可能算错,而且它的错很难发现。 智能体的地盘是"解释"和"建议",不是"计算"和"判断"。 这与第一章 1.2.3 那条"确定的外框 + 弹性的内芯"是同一句话:能确定的就别交给模型。

六、动手 Demo:异常上下文装配器

它不是"演示智能体",是演示"怎么把料备齐"——因为这一节真正的技术含量在这里。

# -*- coding: utf-8 -*-
"""6.3.2 Demo:异常上下文装配器(纯标准库)
把一条异常记录装配成一段【给智能体看的上下文】,并检查五块是否齐全。
"""
import json

# 五块内容:① 单据 ② 识别结果 ③ 校验发现 ④ 主数据对照 ⑤ 历史处理(可选)
EXC = [
    {"id": "E01", "src": "传真扫描件", "file": "scan_0913_02.jpg",
     "recog": {"supplier": "宜昌三峡五金有限公司", "po_no": "PO-2026-0912",
               "item": "M1001", "qty": 100, "unit": "个", "price": 2.5, "conf": 0.62},
     "check": ["第 1 行 item_code 字符置信度 0.62,低于 0.80"],
     "master": {"M1001": "命中|内六角螺栓 M6×20", "M1002": "未命中"},
     "history": ["该供应商上月同款物料成交价 2.45 元"]},

    {"id": "E02", "src": "Excel 报价单", "file": "quote_0913.xlsx",
     "recog": {"supplier": "湖北兴发物资", "po_no": "PO-2026-0913",
               "item": "M2001", "qty": 20, "unit": "箱", "price": 45.0, "conf": 0.97},
     "check": ["行小计 900.00 与合计栏 950.00 不相等"],
     "master": {"M2001": "命中|工业清洗剂 20kg"},
     "history": []},

    {"id": "E03", "src": "微信截图", "file": "wx_0913_1147.png",
     "recog": {"supplier": "某五金公司", "po_no": "", "item": "M4001",
               "qty": 30, "unit": "个", "price": 6.0, "conf": 0.91},
     "check": ["供应商名含模糊指代【某五金公司】", "缺 po_no"],
     "master": {},
     "history": []},

    {"id": "E04", "src": "手写送货单", "file": "hand_0913_03.jpg",
     "recog": {"supplier": "宜昌长江轴承", "po_no": "PO-2026-0914",
               "item": "M3001", "qty": 10, "unit": "", "price": 120.0, "conf": 0.41},
     "check": ["整体置信度 0.65 低于阻断线", "unit 未识别"],
     "master": {"M3001": "命中|深沟球轴承 6205"},
     "history": ["该供应商惯用【套】作单位"]},

    {"id": "E05", "src": "PDF 电子单", "file": "po_0915.pdf",
     "recog": {"supplier": "宜昌恒达机电", "po_no": "PO-2026-0916",
               "item": "M5001", "qty": 15, "unit": "个", "price": 9.5, "conf": 0.96},
     "check": [],
     "master": {"M5001": "命中|接线端子 6mm²"},
     "history": ["该供应商历史税率均为 13"]},
]

def build(r):
    return (
        "【单据】来源=%s;文件=%s\n" % (r["src"], r["file"])
        + "【识别结果】%s\n" % json.dumps(r["recog"], ensure_ascii=False)
        + "【校验发现】%s\n" % ";".join(r["check"])
        + "【主数据对照】%s\n" % ";".join("%s → %s" % (k, v) for k, v in r["master"].items())
        + "【历史处理】%s" % (";".join(r["history"]) if r["history"] else "(无)")
    )

def missing(r):
    miss = []
    if not r["src"] or not r["file"]:
        miss.append("单据")
    if not r["recog"]:
        miss.append("识别结果")
    if not r["check"]:
        miss.append("校验发现")
    if not r["master"]:
        miss.append("主数据对照")
    return miss

print("=" * 78)
print("一、装配结果(每条一段上下文)")
print("=" * 78)
for r in EXC[:2]:
    print()
    print("  ── [%s] ──" % r["id"])
    for ln in build(r).split("\n"):
        print("  " + ln)

print()
print("  (以下略去 E03~E05 的全文,只做完整性检查)")

print()
print("=" * 78)
print("二、完整性检查:五块缺哪块")
print("=" * 78)
print("  %-5s %-8s %-8s %-8s %-10s %s" % ("编号", "单据", "识别", "校验", "主数据", "历史"))
for r in EXC:
    miss = missing(r)
    def mk(name):
        return "  ✗   " if name in miss else "  ✓   "
    his = "有" if r["history"] else "无"
    print("  %-5s %s %s %s %s      %s" % (
        r["id"], mk("单据"), mk("识别结果"), mk("校验发现"), mk("主数据对照"), his))

print()
print("=" * 78)
print("三、为什么这五块一段都不能少")
print("=" * 78)
print("  本次装配里 E03 缺【主数据对照】、E05 缺【校验发现】—— 这两条在交给助手")
print("  之前就该补齐,否则助手只能靠猜,而它猜出来的话会被人当成事实。")
print()
print("  · 缺【单据】→ 助手说不清这条是从哪来的,人也回不到原图去核。")
print("  · 缺【识别结果】→ 助手只能泛泛地说【置信度低】,说不出【哪个字段低】。")
print("  · 缺【校验发现】→ 助手不知道系统已经发现了什么,会重复报一遍。")
print("  · 缺【主数据对照】→ 助手会以为【M1002 未命中】是识别错了,")
print("    其实那是系统里本来就没有这个编码 —— 两件事的处置完全不同。")
print("  · 【历史处理】可有可无,但有它时答案质量明显不同:")
print("    E01 有了【上月同款 2.45 元】,助手就能提示【本次 2.5 元属正常浮动】;")
print("    E04 有了【惯用套作单位】,助手就能直接建议 unit 填【套】。")
print()
print("  一句话:智能体答得好不好,八成取决于【你喂给它什么】,而不是【它多聪明】。")

实际运行输出:

==============================================================================
一、装配结果(每条一段上下文)
==============================================================================

  ── [E01] ──
  【单据】来源=传真扫描件;文件=scan_0913_02.jpg
  【识别结果】{"supplier": "宜昌三峡五金有限公司", "po_no": "PO-2026-0912", "item": "M1001", "qty": 100, "unit": "个", "price": 2.5, "conf": 0.62}
  【校验发现】第 1 行 item_code 字符置信度 0.62,低于 0.80
  【主数据对照】M1001 → 命中|内六角螺栓 M6×20;M1002 → 未命中
  【历史处理】该供应商上月同款物料成交价 2.45 元

  ── [E02] ──
  【单据】来源=Excel 报价单;文件=quote_0913.xlsx
  【识别结果】{"supplier": "湖北兴发物资", "po_no": "PO-2026-0913", "item": "M2001", "qty": 20, "unit": "箱", "price": 45.0, "conf": 0.97}
  【校验发现】行小计 900.00 与合计栏 950.00 不相等
  【主数据对照】M2001 → 命中|工业清洗剂 20kg
  【历史处理】(无)

  (以下略去 E03~E05 的全文,只做完整性检查)

==============================================================================
二、完整性检查:五块缺哪块
==============================================================================
  编号    单据       识别       校验       主数据        历史
  E01     ✓      ✓      ✓      ✓         有
  E02     ✓      ✓      ✓      ✓         无
  E03     ✓      ✓      ✓      ✗         无
  E04     ✓      ✓      ✓      ✓         有
  E05     ✓      ✓      ✗      ✓         有

==============================================================================
三、为什么这五块一段都不能少
==============================================================================
  本次装配里 E03 缺【主数据对照】、E05 缺【校验发现】—— 这两条在交给助手
  之前就该补齐,否则助手只能靠猜,而它猜出来的话会被人当成事实。

  · 缺【单据】→ 助手说不清这条是从哪来的,人也回不到原图去核。
  · 缺【识别结果】→ 助手只能泛泛地说【置信度低】,说不出【哪个字段低】。
  · 缺【校验发现】→ 助手不知道系统已经发现了什么,会重复报一遍。
  · 缺【主数据对照】→ 助手会以为【M1002 未命中】是识别错了,
    其实那是系统里本来就没有这个编码 —— 两件事的处置完全不同。
  · 【历史处理】可有可无,但有它时答案质量明显不同:
    E01 有了【上月同款 2.45 元】,助手就能提示【本次 2.5 元属正常浮动】;
    E04 有了【惯用套作单位】,助手就能直接建议 unit 填【套】。

  一句话:智能体答得好不好,八成取决于【你喂给它什么】,而不是【它多聪明】。

读结果,五个值得注意的地方:

  1. 装配器的输出里,第一块是【单据】——来源 + 文件名。 它看起来很不起眼,但它是唯一能让人"回到现场"的东西。 没有它,助手说"置信度 0.62",人不知道去哪张图上看;有了它,人可以直接打开 scan_0913_02.jpg 对照。这一块的作用不是给助手看的,是给人看。

  2. E01 与 E02 的对比说明了"历史处理"那块的意义。 E02 的历史是空的——助手只能说"合计不相等,请核对"。这句正确但没用,因为人本来就知道要不相等。 E01 有历史——助手能说"上月同款 2.45 元,本次 2.5 元属正常浮动"。这一句是人真正需要的信息。 同一段上下文结构,多一块,答案从"复述"变成"判断"。

  3. E03 和 E05 在完整性检查里各缺一块,而这两块的性质不同。 E03 缺【主数据对照】是"漏查"——它明明该去查(供应商名可疑,更该查),但装配时没查; E05 缺【校验发现】是"漏跑"——它连校验都没跑,所以助手会以为"这条没问题"。 两种都该在交给助手之前补上——因为助手不会说"你没给我数据",它只会用上下文里有的东西硬答。

  4. 那句"它猜出来的话会被人当成事实"是本节最该记住的一句。 智能体的失败方式和识别不一样:识别失败你看得见(置信度低),智能体的失败是"流畅地说出一段听起来很合理的话"——而人最不容易怀疑的,恰恰是流畅的话。 这就是为什么前四块必须齐:不是为了让答案更好,是为了让它没有猜的空间。

  5. 最后那句"八成取决于你喂它什么",其实还可以再推一层:剩下的两成才是模型能力。 所以在设计上,投入产出比最高的做法不是"换更强的模型",而是"把上下文装配得更完整"——换模型是花钱,补上下文是花心思,而后者的效果通常更明显。

30 秒实验

把 Demo 里 E04 的 history 从 ["该供应商惯用【套】作单位"] 改成 [],再跑一次。

会发生什么:

结论:这一块"可选",但它是"可选里最值钱的一块"。

顺着这个思路想一层:为什么把它设计成可选?因为很多情况确实没有历史(新供应商、新物料、头一回遇到这种异常)。如果强制要求,装配器会卡在"没有历史"上。

而设计成可选之后,需要一条纪律:上下文里没有的那一块,提示词里要明确说"没有"——Demo 里写的是 (无),而不是留空。留空和"填(无)"看起来差不多,但对模型的意义完全不同:留空它可能以为是漏了、从而自己猜一个;写着"(无)",它就知道这里确实没有。

常见误区

  1. 【本章的智能体像前几章一样是主角】——错,它是配角。 正常件由工作流自动处理(识别 → 校验 → 填报),根本不经过对话;只有被挑出来的异常才需要它。这个分工变化的理由在 6.2:本章的批量是【行数 × 单数】,正常件太多,人看不完也问不完。

  2. 【人设里"我不能做什么"是客套话】——错,那是全段最重要的一句。 四条限制各防一个具体的坏结果:不能替人确认(防责任无人承担)、不能改金额税率数量(防它把错数改成一个看起来对的数)、不能补全看不见的字段(防它替你猜)、不能凭印象报数据(防它编)。 一个只会解释的助手是安全的;一个"能替你把单子提交了"的助手,是本章最不能有的东西。

  3. 【让智能体直接输出 JSON 给 RPA 用】——错,搞反了上下游。 本章的智能体下游是人,所以它的输出应该是一段人话(卡在哪 / 建议什么 / 依据是什么);给 RPA 用的结构化结果由 6.3.1 那条工作流负责。两套东西服务两个对象,不要混。

  4. 【知识库随便放点平台文档就行】——错,本章的知识库必须是"只有这家公司知道"的东西。 物料主数据、供应商主数据、税率规则、异常处理 SOP、历史处理记录——没有这些,助手什么都答不了(它既不知道 M1001 是什么,也不知道 14% 为什么不对)。

  5. 【异常都交给智能体处理就行】——错,它只负责"解释与建议",最终判断在人。 而且有一条更实用的判据:如果发现智能体在"算"(做加法、比字符串、查表),那就用错了地方——那些是规则的活。智能体的地盘是"解释"和"建议",不多不少。

自测三问(点开对答案) 1. **为什么本章的智能体是"配角",而前面几章的智能体是"主角"?** 因为**本章的批量是【行数 × 单数】**(6.2 的结论):一批 500 张单、每张 20 行,**正常的那 425 张不需要任何对话**——**工作流直接处理掉,人只在被挑出来的少数几条上介入。** 而前几章不一样:内容侧**每一篇都需要人做判断**(写得好不好、有没有人味),所以对话入口是刚需。 **一个反过来说的判据**:**如果你发现"每条都要跟智能体聊两句",那说明校验规则写得不够**——该交给规则的事,别推给人。 2. **上下文的那五块里,哪一块是"给人看的"而不是"给助手看的"?为什么?** 是**【单据】(来源 + 文件名)**。 因为它是**唯一能让人"回到现场"的东西**:助手说"置信度 0.62",**人得知道去哪张图上看**——有了文件名,可以直接打开 `scan_0913_02.jpg` 对照。 **它的作用不是提高答案质量,是让"看不看得懂"这件事变得可能**——**没有它,人只能相信助手说的。** 3. **为什么前四块"一块都不能少"?智能体的失败方式和识别有什么不同?** 因为**助手不会说"你没给我数据",它只会用上下文里有的东西硬答**——**缺主数据对照,它会以为"编码未命中"是识别错了;缺校验发现,它会以为"这条没问题"。** **失败方式的不同**:识别失败你看得见(**置信度低,数字摆在那里**);**智能体的失败是"流畅地说出一段听起来很合理的话"**——**而人最不容易怀疑的,恰恰是流畅的话。** 所以保证前四块齐全,**不是为了让答案更好,是为了让它没有猜的空间**。

相关笔记