LLM-as-classifier prompt 设计
前三天(Day 121-123)走的是确定性规则线:FATF 签名 → BSA 规则分类器 → 混淆矩阵。但这条线有个结构性弱点——evalBaseline.ts 里「同一套规则既当被测、又当裁判」是循环自评,外部效度薄弱(规则给自己生成的数据打分,分数虚高)。
阶段: B13 · FATF typologies + BSA grader + 用户研究(Day 121-130) 标签: #llm-as-judge #few-shot-prompt #external-validity #rubric
今日导引(由浅入深)
前三天(Day 121-123)走的是确定性规则线:FATF 签名 → BSA 规则分类器 → 混淆矩阵。但这条线有个结构性弱点——evalBaseline.ts 里「同一套规则既当被测、又当裁判」是循环自评,外部效度薄弱(规则给自己生成的数据打分,分数虚高)。
今天换一条腿走路:用独立 LLM 做 typology 判定。它没见过本仓的生成规则,标签更接近第三方视角,给 eval 注入真正的外部效度。在 B1→B18 曲线上,这是从「自评」迈向「第三方评测」的转折点,也是 B14「杀死循环 baseline」的直接前奏。
最小可判定产出:写一份结构化 few-shot 分类 prompt,用 NAMED DeepSeek-V4 对 typed.csv 做 200 抽样,落盘 200 条 {case_id, predicted_typology, reason} JSON。
1. 机理精读
LLM-as-classifier 需要结构化 few-shot prompt,不是开放生成。 让 LLM 判 typology,prompt 必须满足三个硬约束:
- 明确 5 个标签的判定 rubric——逐类写清判定标准(structuring=临界阈值密集小额、layering=链式快进快出、mule=新户扇入扇出…),让模型有可对照的尺子;
- 每类给 1–2 个正例(few-shot),把抽象 rubric 锚定到具体样本;
- 强制单标签枚举输出——只能从固定 5 个标签里选一个,不能自由发挥。
少了任何一条,输出就无法稳定进混淆矩阵:缺 rubric 模型乱判、缺 few-shot 边界模糊、缺枚举约束输出无法离散化。
为什么独立 LLM 提供「外部效度」。 本仓的 evalBaseline.ts(evalRuleBaseline)拿 assessCase 的 topTypology 当预测,再跟同一套规则生成的 case.label 比——这是循环:被测系统(规则)和裁判(规则)是同一个,规则碰巧覆盖到的模式分数就虚高,规则的盲区永远测不出来。
引入独立 LLM grader 打破这个循环:LLM 没见过生成规则,它对 typology 的判断来自对交易语义的理解,是真正的「第三方」标签。两套标签(规则 vs LLM)一对比,规则的盲区就暴露了——这是外部效度的来源。
关键权衡:枚举约束 vs 表达力。 强制单标签枚举会牺牲一点表达力(LLM 没法说「这案件 60% layering + 40% structuring」),但换来的是可进混淆矩阵的结构化输出。这是必要的取舍:eval 需要离散标签来算 recall/precision,模糊的概率分布无法直接喂 confusionMatrix。
同时要求模型给 reason 字段(判定理由),有两个用处:一是便于人工抽查校准(看 LLM 判错时是理由不对还是数据本身歧义),二是为 Day 127 的失败归因留线索(区分 prompt 缺陷 vs 数据歧义 vs 模型能力不足)。
循环 vs 独立两套标签的对照。 把「谁评谁」摆清,外部效度的来源就一目了然:
- 循环自评(
evalBaseline.ts):预测=规则、真值=规则生成的 label → 同源,盲区永测不到,分数虚高。 - 独立 LLM grader(今天):预测=DeepSeek-V4(没见过生成规则)、真值=注入 label → 异源,规则盲区被暴露。
- 一旦规则和 LLM 在某 typology 上系统性分歧,就定位到了规则的覆盖缺口(Day 127 的缺口分析正是吃这个差)。
模型 id 纪律——这是诚信底线。 本仓 runner 是 provider-agnostic,默认 deepseek。但必须用 NAMED 模型 id:deepseek-v4-pro / deepseek-v4-flash。
legacy deepseek-chat / deepseek-reasoner 于 2026-07-24 退役(别名到 v4-flash),写代码/笔记不能再用旧 id,且执行日要重验退役状态。版本号显式是全局时效性硬规则的强制项——不能写「用 DeepSeek」,要写「用 deepseek-v4-pro」,否则实验在退役后既不可复现也无法溯源。
2. 代码走读:要替换的「循环自评」与要接的 runner
Read 了 src/aml/evalBaseline.ts,确认今天要打破的循环结构真实存在:
evalRuleBaseline(ds: AmlDataset): BaselineEval— 文件头注释直说:「以assessCase的topTypology为预测(null → 'normal')」。循环逐 case 调assessCase(c).topTypology ?? 'normal',跟c.label比,算perTypologyrecall、normalFalsePositiveRate、confusion(key 形如'label->predicted')。- 循环性的根:
predicted = assessCase(c).topTypology,而c.label又是同一套生成规则注入的——规则评规则生成的数据。这正是今天用独立 LLM 替换predicted来源要解决的问题(confusionMatrix的来源无关设计就是为此铺路)。 BaselineEval字段:perTypology: Record<TypologyId, {recall, n}>、normalFalsePositiveRate、confusion——LLM 版预测产出后,可喂同样的度量口径(或直接喂 Day 123 的confusionMatrix)做规则-vs-LLM 对比。
runner 侧(Day 116 已走读的 provider-agnostic 链路):buildModel({name, model, apiKey, baseURL}) 构造 OpenAI 兼容模型,makeModelGenerate(model, {system, modelName}) 发请求并在 MODEL_PRICES[modelName] 命中时用 estimateCost 算真实成本。
LLM 分类 200 条就是给这条链路传 deepseek-v4-pro/-flash 的 model id + 今天写的 rubric prompt——runner 不改,只换 model id 与 system prompt,与 B12 的「只换模型、锁死 harness」纪律一脉相承。
走读结论:evalRuleBaseline 是真实的循环自评(待被替换);provider-agnostic runner 已 BUILT(默认 deepseek)。今天的工作是「写 rubric prompt + 调 runner 跑 200 抽样 + 落盘」,而非改度量逻辑。
prompt 骨架(rubric + few-shot + 枚举输出)。 把上面三条硬约束落成一份可执行 prompt 的结构(伪代码骨架,真实 prompt 在实战时落盘):
[system]
你是反洗钱类型学分类器。只能输出以下 5 个标签之一:
structuring | layering | mule_network | normal | uncertain
判定 rubric:
- structuring:≤10 天内多笔贴 $10,000 CTR 门槛下沿($8,000–$9,900)的现金存款,刻意拆分规避申报。
- layering:外部流入后经多个账户链式过账,每跳停留 ≤2 天、转出 ≥80%、金额整千化(图结构特征)。
- mule_network:开户 <30 天新户,≤14 天内多个不同对手小额扇入,随后 1–2 笔大额扇出(快进快出)。
- normal:常规工资/消费/账单,无上述模式(含现金密集型商户的边界营业款)。
- uncertain:证据不足以判定时输出,宁可标 uncertain 也不要猜(避免污染召回)。
输出严格 JSON:{"case_id": "...", "predicted_typology": "<标签>", "reason": "<一句话依据>"}
[few-shot 正例 ×1-2/类,用自然语言描述的典型案件,不照搬生成器参数]
[user]
案件 case_id=C012 交易明细:<脱敏交易列表>
要点:(1) uncertain 标签是诚实出口——证据不足时强制猜会污染召回;(2) few-shot 正例用自然语言描述,不能照抄生成器注入参数(否则外部效度泄漏);(3) 输出强制 JSON schema,便于直接解析进 confusionMatrix 的 {label, predicted} 对。
3. 今日实战
- 写结构化 few-shot 分类 prompt:5 个标签的判定 rubric(structuring/layering/mule_network/normal,+ integration/TBML 若数据含)、每类 1–2 个正例、强制单标签枚举输出 +
reason字段。 - 用 NAMED DeepSeek-V4(provider-agnostic runner 默认 deepseek;用
deepseek-v4-pro/deepseek-v4-flash,不用 legacydeepseek-chat/-reasoner,2026-07-24 退役)对 typed.csv 做 200 抽样 typology 分类。 - 落盘 200 条
{case_id, predicted_typology, reason}JSON,字段对齐 Day 123confusionMatrix的{label, predicted}形状(label 取真值 typology,predicted 取 LLM 输出),便于直接出 LLM 版 5×5 矩阵。 - 记录单次真实成本,对照本仓基准 V4-Flash $0.0139/run。
4. 今日实测 / 产出
- 已完成:provider-agnostic runner 已 BUILT(默认 deepseek)。
- 待跑:LLM 分类 200 条 JSON = 待跑(需 key 跑,key 现已配置),将产出 200 条
{case_id, predicted_typology, reason}。 - 真实成本基准(仅供对照,不等于本日产出):V4-Flash $0.0139/run。
- 状态诚实:key 虽已配置,但 200 条 LLM 分类的具体输出未跑出前不得臆造 predicted 标签或召回数字。
5. 常见误区 / 陷阱
- 把 prompt 写成开放生成:typology 判定要枚举约束输出,否则模型可能输出「疑似 layering 但也像 mule」这类无法进混淆矩阵的自由文本。强制单标签。
- 用 legacy 模型 id:
deepseek-chat/deepseek-reasoner2026-07-24 退役,必须用deepseek-v4-pro/deepseek-v4-flash,执行日重验退役状态。 - few-shot 正例泄漏生成规则:若正例直接照搬本仓生成器的注入参数,等于把规则喂给 LLM,外部效度又没了。正例应是「自然语言描述的典型案件」,不是规则参数。
- 不要 reason 字段:缺
reason就无法人工抽查校准、也丢失 Day 127 失败归因的线索——LLM 判错时无从分辨是 prompt 缺陷还是数据歧义。 - 成本记 0 或不记:LLM 跑批要么如实算 $/run(命中
MODEL_PRICES+ 完整 usage),要么标 undefined,绝不静默记 0。本日有 V4-Flash $0.0139/run 的真实基准可对照——若实跑成本与基准量级差很多,先查 token 用量和定价表是否对得上,别直接采信。
6. 学习资源(每条带 YYYY-MM)
- Anthropic — Demystifying evals(2026-01):rubric 设计、二元/枚举输出、LLM-as-judge 的外部效度与循环自评的危害。
- DeepSeek-V4 模型卡 / API 文档(2026-04,执行当周复查
deepseek-v4-pro/-flashid 与 legacy 2026-07-24 退役状态):NAMED 模型 id 与定价。 - Anthropic — Building effective agents / tool & prompt 设计(2025,持续更新):结构化输出约束的工程做法。
- 本仓代码:
src/aml/evalBaseline.ts(evalRuleBaseline,循环自评,待替换)、provider-agnostic runner(buildModel/makeModelGenerate,已 built)。
SOTA检查 (2026-06 更新)
- 现役主线:LLM-as-classifier / LLM-as-judge 配结构化 rubric + few-shot + 枚举输出,是 2026 年评测软标签的现役做法;用独立模型打破循环自评、注入外部效度是当前最佳实践。
- 避免/禁用:模型 id 必须用
deepseek-v4-pro/deepseek-v4-flash;legacydeepseek-chat/deepseek-reasoner2026-07-24 退役,执行日重验。避免把 prompt 写成开放生成——枚举约束才能进混淆矩阵。 - 下次复查点:执行当周 WebSearch 「DeepSeek V4 model id 2026」确认 pro/flash id 与 legacy 退役日期;复查 Demystifying evals 是否有 2026 更新版;确认 key 配置后单次成本是否仍约 $0.0139/run(V4-Flash 基准)。
自测问答(讲得出才算掌握)
- Q:循环自评为什么不可信,独立 LLM 怎么修复它?
A:
evalBaseline.ts里规则既生成数据又评数据,规则的盲区永远测不到、覆盖到的模式分数虚高。独立 LLM 没见过生成规则,是异源第三方,它和规则一对比就把规则盲区暴露出来——这就是外部效度。 - Q:为什么 prompt 必须强制单标签枚举输出,还要
uncertain标签? A:枚举输出才能进confusionMatrix算 recall/precision;开放生成的自由文本无法离散化。uncertain是诚实出口——证据不足时标 uncertain 而非强行猜,避免猜测污染召回数字。 - Q:为什么模型 id 必须写
deepseek-v4-pro而不是「DeepSeek」? A:版本号显式是时效性硬规则的强制项。legacydeepseek-chat/deepseek-reasoner已于 2026-07-24 退役(别名到 v4-flash),写模糊的「DeepSeek」会让笔记/代码在退役后失效且无法复现实验。
衔接
- 昨天:Day 123 — 混淆矩阵与 per-typology 召回(规则分类器输出的 5×5 矩阵 + recall/precision/F1)。
- 今天:用独立 LLM(NAMED DeepSeek-V4)做 typology 判定,写结构化 rubric prompt,打破规则循环自评、注入外部效度。
- 明天:Day 125 — LLM grader 接入 eval harness(把循环自评换成独立 grader,算 LLM 标签 vs AMLSim 真值的 Cohen's κ)。