用 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 设计,欢迎同方向的朋友交流。

相关推荐
fuquxiaoguang17 小时前
中间件的“价值重估”:传统同质化竞争终结,AI智能编排时代开启
人工智能·中间件
触底反弹18 小时前
🔥 前端也能玩转 AI 流式输出!从二进制流到打字机效果,一篇讲透
javascript·人工智能·node.js
腾渊信息科技公司18 小时前
工业数据运维痛点根治方案:基于AI Agent的产线自动化台账系统落地
运维·人工智能·自动化·个人开发·ai编程
西安老张(AIGC&ComfyUI)18 小时前
第030章:ComfyUI视频制作LTX-2.3模型文生视频工作流详解(三)
人工智能·aigc·comfyui
苦猿的大模型日记18 小时前
Day25 | 模型量化横评 GPTQ vs AWQ vs GGUF vs INT8——同一个 Qwen3-8B 压四遍,谁还活着
人工智能
benchmark_cc18 小时前
如何用 Python + QuantDash 快速构建高胜率“配对交易(Pairs Trading)”策略?
开发语言·人工智能·python·pandas·量化交易·quantdash
深海鱼肝油ya18 小时前
小说自动生成系统(二)
人工智能·大模型·agent·智能体·自动化编程·小说生成系统
通问AI19 小时前
Apple Intelligence 国行备案深度技术解析:阿里千问如何被集成到苹果端侧AI架构
人工智能·架构
视***间19 小时前
算力赋能零售与创意新生态:视程空间Pandora,解锁线下场景智能化无限可能
人工智能·边缘计算·智慧零售·ai算力·视程空间·创意开发
冬奇Lab19 小时前
MCP 系列(08):企业治理——Registry、路由与可观测性
人工智能·llm·mcp