hacomata

构建可控的 LLM 数据生成系统:从 Prompt 到验证闭环

在过去的一段时间里,我利用大语言模型(LLM)从零构建了一套极其复杂的“法语动词变位(French Conjugation)”数据集引擎。

懂法语的朋友可能知道,法语的动词变位是一个令人生畏的系统:8 个人称、10 + 种时态、错综复杂的不规则词根、需要性数配合的过去分词,以及极其严苛的省音规则(Elision)。

最初,我不认为只要写一个 Prompt,“hey帮我生成faire的动词变位数据”,就能让 LLM 乖乖吐出完美的 JSON 数据。LLM 是概率模型,而数据工程需要的是绝对的确定性。 哪怕模型有 99% 的准确率,在生成数万条结构化数据时,那 1% 的幻觉(格式错乱、逻辑矛盾、瞎编词根)也足以摧毁整个数据库。

经过反复测试,我总结出了一套经验。这篇文章虽然以“生成法语数据”为背景,但我的最终目的,是与大家探讨一个普适的 AI 工程命题:如何设计一套“可验证 LLM 输出(Verifiable LLM Outputs)”的通用框架?

无论你是做代码Agent、法律合同提取,还是垂直领域的数据管道,这套框架都同样适用。


第一步:Prompt 不只是提示,而是“强约束伪代码”

在构建可验证的管道时,第一道步骤就是 Prompt。不要把 Prompt 当作和机器聊天,要把 Prompt 当作一种带有强约束和边界条件的伪代码

为了确保生成的数据(JSON)可以直接被下游代码解析,我的 Prompt 并不是简单地要求“生成练习题”,而是做到了以下几点:

1. 精确的量化指标 (Deterministic Math)

“The “puzzles” array must contain EXACTLY 80 objects (8 persons × 10 tenses). distractorStems MUST be an array of EXACTLY 3 strings.”
为什么重要? 模糊的指令(“生成几道题”)无法测试。精确的数字让我在第二步可以立刻写出断言 assert len(json_data['puzzles']) == 80。这是通往“可验证”的第一步。

2. 定义边界条件与 Schema 柔性 (Graceful Nullability)

现实世界的数据是不规则的。很多 Prompt 失败的原因,在于强迫 LLM 将不规则数据塞进固定格式,导致 LLM 为了凑格式而产生“幻觉”。
在我的 Schema 中,我将后缀定义为 correctEnding: string|null。并在规则中明确指出:对于高度不规则的词(如 Être, Avoir),不要强行拆分词根和词缀,直接将完整词放入 correctStem,并设置 correctEnding = null。
这种允许模型“留空”的权力,极大增强了生成结果的健壮性。

3. 负面约束与“逻辑防呆” (Negative Prompting)

LLM 有时候会过于“发散”。需要提前预判并封死了这些路径:

  • 禁止多余输出: “Output MUST be valid JSON only (no explanations, no extra text)”.
  • 逻辑防呆: 明确规定“干扰项绝对不能与正确答案相同(None of the distractor items may equal the correctStem)”。这直接从源头切断了最容易出现的业务逻辑 Bug。

第二步:防御性 AI 编程 (Defensive AI Programming)

在 AI 工程界,有一个非常重要的核心理念:“对 LLM 保持天然的不信任感”

你要默认它一定会出错,但你要让错误变得可控。即便有了上述完美的 Prompt,LLM 输出的 JSON 也绝对不能直接存入数据库。我们必须引入一个确定性的 Python 校验脚本(Hardcoded Validator)来兜底。我们用概率模型(LLM)去生成,用确定性代码去守护边界。

我的验证脚本不仅仅做了简单的 JSON Schema 类型检查,它还实现了更深度的验证:

1. 跨字段的语义交叉验证 (Cross-Field Relational Validation)

大部分初级的 JSON 验证只能校验“A 是不是 String”。但真正的坑都在业务逻辑里。要确保数据可信,就需要深度的业务交叉校验,例如:

  • 时态冲突检查: 如果 tense 是简单时态,那么 auxStem(助动词)就绝对不能存在。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if is_compound:
if not p.get("auxStem"):
errors.append(
fatal(
base_path,
"Compound tense requires auxStem and auxEnding",
)
)

if is_simple:
if p.get("auxStem") is not None:
errors.append(
strong(
base_path,
"Simple tense must not include auxiliary fields",
)
)
  • 语言规则硬编码: 如果 person 是 “je”,且 stem 是元音开头,那么 pronoun 必须发生省音变为 “j’”。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 示例:通过硬编码逻辑校验 LLM 生成的法语省音规则
if p.get("person") == "je":
stem = p.get("auxStem") or p.get("correctStem") or ""
verb_start = stem[:1].lower()
should_elide = verb_start in "aeiouhé"

pronoun = p.get("pronoun") or ""
has_elided = (
pronoun.startswith("J'")
or pronoun.startswith("j'")
or pronoun.startswith("Que j'")
)

if should_elide and not has_elided:
errors.append({
"level": Level.STRONG.value,
"path": f"{base_path}.pronoun",
"message": "Je must elide to j' before vowel or mute h",
"context": context,
})

if not should_elide and has_elided:
errors.append({
"level": Level.STRONG.value,
"path": f"{base_path}.pronoun",
"message": "J' used incorrectly before consonant",
"context": context,
})


这种跨字段的 If-Else 校验,完美补足了 LLM 容易“前后矛盾”的短板。

2. 错误分级与优雅降级 (Error Triaging)

如果我们要求 LLM 做到 100% 完美,一点小错就全盘重试,API 成本将高得离谱。因此,我将错误进行了分级:

  • FATAL (致命错误): JSON 结构破坏、未知的时态。直接丢弃或阻断流水线。
  • STRONG (严重瑕疵): 逻辑冲突(如正确答案出现在了干扰项里)。
  • WARNING (警告): 格式正确但可能有微小瑕疵。

分级制度让数据管道具有了极高的弹性。配合精准到具体字段的错误路径定位(如 puzzles[12].pronoun),这不仅是一份验证报告,更是未来让 LLM 进行“自我修复(Self-Correction)”的精准导航。


第三步:事实性核查与 AI 工程的“知止”

通过前两步的 Prompt 约束和 Python 代码验证,我们已经过滤掉了 90% 的“格式错误”和“逻辑错误”。但这套框架还缺少最后一块拼图:内容事实性错误(Factual Errors)

格式再完美的 JSON,如果告诉用户错误的动词变位,在教育产品中也是灾难性的。针对这类错误,可以用一个三步走的验证思路:

1. 引入外部“真理之源” (External Ground Truth)

不要盲目相信 LLM 的“记忆”。既然市面上已经有很多成熟的法语动词变位网站和 API,我们完全可以在验证层引入爬虫或 API 调用。这就如同 RAG(检索增强生成)的逆向应用——LLM 先生成,我们再去权威数据库做字符串比对的交叉验证。

2. 专家系统兜底 (Expert System Injection)

这是克制 LLM 幻觉最有效的终极武器。LLM 极容易犯“过度泛化(Over-generalization)”的错误。

举个我遇到的极其经典的例子:法语中有一个动词 falloir(必须)。在语法上,它是个无人称动词,只有 il faut 这一种形式。但 LLM 常常会“自作聪明”地按照规则造出 je fauxtu faux 这种根本不存在的变位。
面对这种情况,传统的规则引擎(硬编码的专家黑白名单)永远是最高效的解法。

3. Human-in-the-Loop 与工程 ROI 的权衡

在这个阶段,其实是可以去写一个全自动的数据 Pipeline,LLM生成数据,测试程序测试数据质量,把报错信息自动喂回给 LLM 让它重试。但我最终没有做全自动化。

为什么?因为当 Tier 1(代码脚本验证)过滤掉绝大多数杂音后,剩下的事实性错误其实极少(通常只是几个干扰项不够完美)。与其花费巨大的开发精力和 Token 成本去追求 100% 的端到端自动化,不如在此刻引入人工审核(Human-in-the-Loop)。AI 工程中最务实的智慧,是知道何时把控制权交还给人类。


结语:通用验证架构的终极公式

回顾这套生成复杂法语变位数据的工程实践,我们可以把“设计可验证 LLM 输出的通用框架”抽象为一个简单的公式:

**Verifiable LLM Output = (强约束 Prompt + 边界防呆) + (确定性的代码逻辑验证 + 错误分级) + (专家规则与多源事实核查)

当我们探讨 AI Agent 或大模型落地时,我们往往过度关注“如何让模型变得更聪明”。但实际上,走向工业级的关键,在于**“如何应对模型变愚蠢的时刻”**。

当我们不再盲目迷信 LLM 的生成能力,而是用传统的软件工程思维(契约测试、防御性编程、专家系统)去包裹它时,大模型才能真正从一个“充满惊喜但也充满惊吓的玩具”,变成一台可靠的“生产力引擎”。

Please enter a keyword to search.