跳到主要内容

LLM 作为数据工程师:AI 驱动的 ETL 中的静默失败

· 阅读需 13 分钟
Tian Pan
Software Engineer

你手写的 ETL 管道处理了 95% 的记录。那些边界情况——带有逗号的货币字符串、格式不一致的日期、不统一的国家代码——流向了你的数据仓库,并悄悄地破坏了你的仪表板。直到季度报告看起来不对劲时,才有人注意到。你又在管道中添加了一个特殊情况。循环往复。

LLM 可以解决这个问题。它们能从原始样本中推断模式(schema),处理任何工程师都预料不到的杂乱边界情况,并能以极短的开发时间将非结构化文档转换为结构化记录。已经有几个团队推出了这种方案。其中一些团队也经历过 LLM 悄无声息地将 "$1,200,000" 转换成 1200 而不是 1200000,在结构完全有效的情况下将严重程度分数从 "high" 切换到 "low",以及以通过了所有模式检查的方式连接了错误的业务外键。

问题不在于 LLM 不擅长数据工程。而在于它们的失败模式对 ETL 来说恰恰是完全错误的:高置信度、不报错、且输出结构有效。

LLM 在 ETL 中到底擅长什么

在列举失败模式之前,有必要准确说明 LLM 在哪些方面比手写管道真正增加了价值。

从杂乱样本中进行模式推断。 给定 50 行非结构化的发票数据,前沿模型可以提出一个合理的理想模式,识别哪些字段可能是可选的,并标记出模糊的情况供人工审核。这比花三天时间编写那些仍然会遗漏边界情况的正规表达式(regex patterns)要好得多。

数据转换中的边界情况处理。 手写的管道擅长处理已知模式。LLM 则擅长处理未知模式:来自你的管道未涵盖的国家/地区的地址格式、包含价格的产品描述、包含七种不同格式(其中一些还是人为输入导致的格式错误)的时间戳。打破基于规则的系统的长尾变体正是语言模型的强项。

模式间的语义映射。 当字段名称不匹配时,从一个模式迁移到另一个模式很难通过规则实现自动化。"billing_address_line_1" 映射到 "bill_addr1" 映射到 "Invoice.BillingAddress.Street" —— 当模式作为上下文提供时,LLM 可以高精度地处理这些映射。

非结构化到结构化的提取。 财务报表、医疗记录、合同、工单 —— 使用手写解析器大规模地将自由文本转换为结构化记录是不切实际的。对于这些用例,LLM 将开发时间从数月缩短到了数天。

像 Unstract(开源)和 Snowflake Cortex 这样的工具已经将这些能力产品化了。Cortex 的 EXTRACT_ANSWERCOMPLETE 函数允许你直接在 SQL 管道中运行这些转换。dbt 与 MotherDuck 的集成提供了一个 prompt() 函数,可以转换 dbt 模型中的文本。工程生态系统在这里的发展非常迅速。

会让你陷入困境的失败模式

这是一个手写管道中数据转换失败的样子:抛出异常,任务停止,Airflow 将任务标记为红色,触发告警,工程师介入调查。

这是一个 LLM 增强管道中数据转换失败的样子:任务成功完成,所有记录都已处理,模式验证通过,行数匹配。而三周后,有人注意到某个产品类别的单价偏离了 100 倍,因为模型一致地删除了欧洲格式数字中的小数点。

研究 LLM 代码生成的学者发现,GPT-4 在复杂提取任务上的无效响应率为 11.97%,而 GPT-4o-mini 经常会重构输入对象而不是保留所有原始字段 —— 这导致了在没有错误信号的情况下发生了无声的数据丢失。这些并不是灾难性的失败。它们是悄无声息积累起来的失败。

这些失败模式可以分为三类:

高置信度的类型强制转换错误。 货币字符串、百分比值和特定区域的数字格式经常会让模型出错。"$500,000" 可能变成 500000(正确),也可能变成 500(将逗号误认为小数点而删除),或者 5000(区域设置混淆)。模型完全意识不到自己选错了。因为输出是有效的整数,结构验证会通过。语义验证只有在你定义了正确的检查项时才会失败。

语义字段映射错误。 当给定两个相似的模式时,LLM 可能会映射听起来相关但实际上不相关的字段。customer_segment 在一个上下文中可能映射到 account_tier,而在另一个上下文中可能映射到 risk_category。两者都是有效的字符串;两者都通过了模式验证;但其中一个是错误的。特别是在 Text-to-SQL 领域,模型经常混淆外键关系 —— 连接到在文档中共同出现但实际上并不是模式中正确连接键的字段。

上下文不完整时的自信幻觉。 当模型缺乏进行正确转换所需的元数据时,它不会停下来询问。它会根据现有信息生成一个看起来合理的答案。如果要求模型根据它从未见过的分类法对记录进行分类,它会编造出看起来合理的类别名称。模型在填充缺失值时会使用统计直觉,而不是你的业务规则。输出通过了所有不专门测试模型编造内容的检查。

构建能够捕捉模型错误的验证层

解决方案并不是要避开 ETL 流水线中的 LLM。而是要像对待来自不可信外部源的数据一样对待 LLM 的输出:在信任之前进行验证。

在生产环境中行之有效的验证模式是三层级联:

第一层 —— 结构化验证。 Pydantic schema(或同类工具)强制执行字段类型、必填字段和取值范围。这可以捕捉格式错误的 JSON、缺失的必填字段以及明显的类型错误。像 Instructor 这样的库通过自动的 Pydantic 验证和重试逻辑封装了 LLM 调用。在这里实施防御性强制转换:一个能处理 "$500,000"、"500000"、"500,000.00" 和 "5e5" 并将它们归一化为标准浮点数的解析器,而不是依赖模型始终产生正确的格式。

第二层 —— 语义验证。 这是大多数团队跳过且随后后悔的一层。语义验证检查输出是否 有意义,而不只是 合法。技术手段包括:

  • 基于规则的矛盾检测:如果 severity = "low"financial_impact > 1000000,则标记以供人工审核
  • 跨字段一致性:end_date 必须在 start_date 之后;discount_percent 必须在 0 到 100 之间
  • 参考数据查询:国家代码必须存在于你的国家/地区表中;产品 SKU 必须存在于你的产品目录中
  • 针对高风险字段的 LLM 作为裁判 (LLM-as-judge) 调用:通过第二次模型调用来评估提取的内容在语义上是否连贯

PARSE 框架在复杂的提取任务上实现了 64.7% 的准确率提升,它实施了系统化的三阶段语义检查:验证没有缺失必要的属性,验证提取的值存在于源文档中,以及验证是否符合业务规则。

第三层 —— 统计异常检测。 在流水线级别,跟踪多次运行之间的分布情况:行数、分类字段的基数、数值分布、空值率。当这些指标偏离预期阈值时发出警报。对数值分布使用 Kolmogorov-Smirnov 检验,对分类分布使用 Jensen-Shannon 散度。Great Expectations(现已发布 v1.0 版本)将这些大部分功能实现为配置而非代码,并集成了 Airflow 和 dbt。

黄金数据集 (Golden dataset) 回归测试完成了闭环。维护一组经过精选的输入/输出对,这些对代表了已知的正确转换,包括已知的边缘情况。在每次流水线更改和模型版本升级时运行这些测试。提升平均性能的模型升级有时会在特定的边缘情况下发生退化;黄金测试能在这些问题到达生产环境之前捕捉到它们。

为 AI 生成的转换代码构建沙箱

一些 LLM 增强的 ETL 模式生成的是转换代码(Python、SQL),而不是直接生成结构化数据。Apache Spark 的英文 SDK、生成 SQL 模型的 dbt 集成以及 AI 辅助的 pandas 转换都属于这一类。

在没有隔离的情况下在生产环境中运行 AI 生成的代码具有重大的安全风险。CVE-2024-5565 表明,数据工具中的 LLM 可能会通过正在处理的数据进行提示词注入,从而被诱导生成恶意代码 —— 数据记录中包含一个精心构造的字符串,它劫持了 LLM 的下一步生成,使其产生与预期不同的代码。基准测试研究发现,在评估的模型中,诱导生成易受攻击代码的攻击成功率高达 84%。

深度防御方案:

AST 级别限制。 在执行前解析生成的代码,并仅允许操作白名单。仅调用 pandas 操作的 Python 转换不需要文件系统访问或网络调用。拒绝包含 import osimport subprocessopen() 调用的代码可以消除最常见的攻击面。

容器隔离。 在丢弃了 Linux capabilities、使用只读根文件系统和网络隔离的容器中运行生成的代码。Firecracker 微型虚拟机 (microVM) 的启动时间不到 125 毫秒,如果你的威胁模型有要求,它可以提供硬件级别的隔离。

试运行验证。 在物化结果之前,针对生产数据的样本运行转换并禁用写入。dbt 的 --empty 标志可以实现零行物化;Spark 中也存在类似的模式。在触达生产表之前捕捉模式错误和明显的语义错误。

幂等性。 所有 AI 生成的转换都应该是幂等的:对相同的数据运行两次相同的转换会产生相同的结果,且没有副作用。这使得安全的重试、数据补录以及在转换出错时所需的重放成为可能。

将其集成到你的流水线堆栈中

对于已经在运行 dbt 或 Airflow 的团队,实际的集成模式如下:

dbt 中,LLM 生成的 SQL 模型应该像手写模型一样经过相同的测试基础设施 —— 唯一性约束、非空检查、关系测试以及针对语义不变性的自定义数据测试。dbt Semantic Layer 可以将你的模型暴露给自然语言查询接口,但转换层本身需要与任何其他 dbt 模型一样的严谨性。使用 LLM 自动生成文档和测试建议;在提交前进行人工审核。

Airflow 中,Common AI Provider 通过 XCom 返回类型化的 Entities 对象,这比原始字符串更好,因为它在接口边界强制执行了模式。将 LLM 算子与下游验证任务配对,在继续执行前运行 Great Expectations 检查。利用 OpenLineage 集成将数据质量故障溯源到特定的源记录和 LLM 调用 —— 当使用 LLM 的流水线出现问题时,你需要血缘关系来了解故障是发生在模型输出、源数据还是验证逻辑中。

Databricks 针对 AI ETL 提出了所谓的 "30% 规则":用 AI 自动化 30% 的决策,同时让内部人员参与处理复杂的逻辑和异常。这并非保守 —— 而是来自生产部署的经验观察。LLM 能很好地处理高置信度、结构良好的案例。超出训练分布的边缘情况会被升级到人工审核队列,而不是被静默地错误处理。

你需要的监控栈

将 LLM 引入 ETL 流水线会产生新的可观测性需求。标准指标(作业耗时、行数、错误率)是必要的,但还不够。你需要:

单次模型调用的延迟和 Token 使用量。 LLM 调用比传统的转换慢几个数量级,且延迟波动较大。跟踪流水线每个阶段的 p50/p95/p99。设定严格的超时限制;模型端点可能会卡住。

提取置信度跟踪。 如果你的提取框架提供置信度评分或结构化的不确定性信号,请将其存储。低置信度的提取内容应进入人工审核队列,或使用不同的提示词(Prompt)重新提取。

按字段统计的验证失败率。 跟踪哪些字段最常发生语义验证失败。持续失败的字段通常意味着源数据质量差、提示词欠佳或 Schema 假设错误。这是你迭代提示词的反馈循环。

模型版本跟踪。 记录哪个模型版本产生了哪个输出。当你从一个模型升级到另一个模型时,你需要了解哪些输出是由哪个模型生成的,以便识别任何性能退化。

OpenTelemetry 追踪在这里非常适用:每次 LLM 调用都成为一个 span,并将提示词、响应、延迟和 Token 计数作为属性。ZenML 等工具提供端到端的数据血缘(Lineage),将生产环境的错误链接回特定的源数据和模型调用。当你调试一个运行了数月却突然开始产生错误结果的转换任务时,这种血缘追踪至关重要。

把握平衡

成功交付 AI 增强型 ETL 流水线的团队都有一个共同点:他们将 LLM 视为产生概率性输出的、不可信的外部服务,而不是可靠的确定性函数。他们在需要之前就构建了验证层。在正式切换之前,他们在影子模式(Shadow Mode)下运行 LLM 转换,与现有流水线并行。在依赖模型输出“正确”之前,他们先在各自领域内定义了什么是“错误”。

那些遭遇失败的团队则将 LLM 输出视为可信的,只测试了结构有效性并假设了语义正确性,直到三周后下游消费者发现数据不对劲时,才察觉到这种失效模式。

LLM 在 ETL 流水线中确实非常有用。它们在边界情况处理、Schema 推断以及非结构化到结构化提取方面的能力,相比于纯基于规则的方法是真正的进步。但其失效模式——高置信度、无错误信号、结构有效但内容错误的输出——意味着验证、沙箱和监控基础设施并非可选项。这是“实用的系统”与“在被察觉前就悄悄损害数据仓库数月的系统”之间的本质区别。

先构建验证层,再添加 LLM。

References:Let's stay in touch and Follow me for more thoughts and updates