用 LLM 做数据提取踩了 6 个坑,我加了 6 层防御——15000 张发票的实战总结

背景

我给一家公司做财务发票对账系统,核心任务是批量提取非结构化票据字段(金额、税号、开票日期等),然后做跨单据的语义比对。

系统上线后累计处理了 15000 张发票,消耗约 1 亿 token

过程中踩了 6 个坑,每踩一个加一层防御。回头看,架构就是这样长出来的。

这篇文章是写给同样想用 LLM 做数据提取的人的------不是教你怎么设计,是告诉你会在哪里摔跤。


核心前提:把 LLM 当作不可信组件

LLM 本质上是一个根据上下文猜下一个 token 的机器,它不保证输出稳定

这句话说起来简单,但真正把它内化为工程决策,需要亲自踩坑。

我踩完之后的结论是:用 LLM 做提取任务,你的代码要为它的每一种失败模式准备出路。

以下是 6 层防御,按我踩坑的顺序来的。


第 1 层:输出内容不可信 → 让代码算,别让 LLM 算

:我让 LLM 直接输出含税金额、不含税金额、税额,然后发现三个数字加起来经常对不上。

原因:LLM 在 JSON 里的几个 token 里做算术,精度和稳定性都不可靠。

防御:只让 LLM 提取原始字段(字符串形式),数值计算由我的代码完成。降低 LLM 的任务负担,把"计算"这件事还给确定性代码。

python 复制代码
# ❌ 让 LLM 直接算
{"含税金额": 113.0, "不含税金额": 100.0, "税额": 13.0}

# ✅ 只让 LLM 提取原始值,代码来算
{"含税金额_str": "113.00", "税率_str": "13%"}
tax_amount = calculate_tax(含税金额_str, 税率_str)  # 代码算

第 2 层:输出结构不可信 → 用 Pydantic 锁死 Schema

:LLM 返回的 JSON 字段名会漂移------有时是 invoice_date,有时是 date,有时干脆没有这个字段。我写了一堆 if key in response 的校验代码,冗余且脆弱。

防御:全面换用 Pydantic。Schema 即契约,结构在定义时就锁死,不符合的直接报错,不给它漂移的空间。

python 复制代码
from pydantic import BaseModel
from datetime import date

class InvoiceExtraction(BaseModel):
    invoice_code: str
    invoice_date: date
    amount_str: str
    tax_rate_str: str

比手写 JSON 校验少写 80% 的代码,还更可靠。


第 3 层:输出值域不可信 → 在 Validator 里写断言

:结构对了,但值不对。税率出现过 "130%",金额出现过负数,日期出现过 "2099-01-01"

防御 :在 Pydantic 的 @validator 里直接写业务断言。通不过测试,实例就不生成。

python 复制代码
from pydantic import validator

class InvoiceExtraction(BaseModel):
    tax_rate_str: str
    amount_str: str

    @validator("tax_rate_str")
    def validate_tax_rate(cls, v):
        rate = float(v.strip("%")) / 100
        assert 0 < rate <= 0.3, f"税率异常: {v}"
        return v

    @validator("amount_str")
    def validate_amount(cls, v):
        amount = float(v.replace(",", ""))
        assert amount > 0, f"金额不能为负: {v}"
        return v

Validator 本质上是把业务规则写进类型系统,比写在外面的 if 判断更不容易被绕过。


第 4 层:输出错了 → 把错误喂回去,让它重试

:上面的 Validator 报错了,然后呢?直接丢掉这张发票损失太大。

防御:把报错信息提取出来,塞回 Prompt,让 LLM 再来一次。

python 复制代码
def extract_with_retry(llm_client, invoice_text: str, max_retries=3):
    error_context = ""
    for attempt in range(max_retries):
        prompt = build_prompt(invoice_text, error_context)
        raw = llm_client.extract(prompt)
        try:
            return InvoiceExtraction.parse_raw(raw)
        except Exception as e:
            error_context = f"上次输出有误,错误信息:{e},请修正后重新输出。"
    return None  # 进入第 5 层

实测大部分结构错误在第 2 次重试时能自我修正,成本增加有限,准确率提升明显。


第 5 层:实在不行 → 不静默失败,打标记交人工

:有些发票版面复杂,重试 3 次还是过不了 Validator。如果静默丢弃,下游对账就会有漏洞,而且你不知道漏在哪。

防御 :最后一次失败,把 LLM 原始输出保留下来,打上 需人工复核 标记,进入人工队列。

css 复制代码
if result is None:
    fallback = {
        "status": "需人工复核",
        "raw_llm_output": last_raw_output,
        "invoice_id": invoice_id,
    }
    human_review_queue.append(fallback)

不输出比输出错更危险。 让系统知道自己不知道,比系统假装知道要安全得多。


第 6 层:不要设计太复杂的架构 → 架构是跑出来的

这是最后一条,也是我觉得最反直觉的。

我的性格是"想不清楚不动手"。但这个系统我是直接跑起来的:

复制代码
发现数字不对   →  加第 1 层(代码算)
发现字段漂移   →  加第 2 层(Pydantic)
发现值域异常   →  加第 3 层(Validator)
发现结构报错   →  加第 4 层(错误重试)
发现静默丢失   →  加第 5 层(打标记)

回头看,这 5 层防御覆盖了 LLM 输出失败的所有主要模式。但我在开始之前完全没有预见到这个架构------它是被坑"逼"出来的。

对于 LLM 工程,"先跑起来发现坑,再加防御"比"先设计再执行"更有效。 因为 LLM 的失败模式在真实数据上才会暴露,沙盘推演推不出来。


总结

防御目标 工具
1 内容不可信 代码算,LLM 只提取
2 结构不可信 Pydantic Schema
3 值域不可信 Pydantic Validator
4 偶发出错 错误回注重试
5 兜底失败 Fail-loud + 人工标记
6 架构过设计 跑起来再加层

如果你也在用 LLM 做数据提取,这套框架可以直接拿去用。有问题欢迎评论区讨论。


目前在做 AI Agent 工程方向,专注 LLM 落地与 Harness 设计,欢迎同方向的朋友交流。

相关推荐
沉浸式学习ing1 小时前
播客和视频怎么变成知识库里的笔记?音视频转结构化笔记完整方案
人工智能·笔记·gpt·学习·ai·音视频·notion
Soari1 小时前
终结 Vibe Coding(Harness Engineering)!深度拆解 ralph:以交付所有 PRD 为生命周期的自主 AI Agent 闭环
自动化测试·人工智能·软件工程·aiagent·ralph·harnesseng·prd驱动
yezannnnnn1 小时前
ToAgent:下一个被颠覆的不是某个行业,是"App"这个概念本身
人工智能
Marvel__Dead1 小时前
微调 Gemma 4 识别腾讯天御全系列验证码【解决方案-一个模型识别 滑块|文字点选|图标点选|空间点选】
人工智能·爬虫·python·验证码识别·ai 大模型
Agent手记1 小时前
成品发货全流程自动化,落地实操与错发漏发规避方案 | 2026企业级Agent端到端落地指南
运维·人工智能·ai·自动化
元让_vincent1 小时前
论文Review SLAM II-NVM | RA-L 2025 | 面向室内双面墙问题的法向量辅助建图方法
人工智能·机器人·自动驾驶·法向量·激光slam·室内
一个帅气昵称啊1 小时前
.Net基于NetCoreKevin框架 AI 与 Hangfire 集成:实现AI智能自动任务调度
人工智能·.net·hangfire
byte轻骑兵1 小时前
【LE Audio】CAP精讲[6]: 控制中枢操盘指南,Commander协同全流程拆解
人工智能·音视频·le audio·低功耗音频
189228048611 小时前
NV236美光MT29F32T08GWLBHD6-24TES:B
大数据·服务器·人工智能·科技·缓存