← 返回日记列表科技学习日记

把 30 年致股东信压成可点击的剧本:LLM 信息抽取的实战记录

11 分钟阅读
最近在做一个叫"投资大师之路"的小项目——把巴菲特、段永平、李录这些长期投资者的真实决策时刻,做成可以"重走一遍"的互动剧本。一个章节大概 5-8 分钟玩完,里面有对话、选项、头条卡、信息卡和大师当时的真实想法引用。 听起来挺简单。但当我开始动手时,第一个问题就把我顶住了:**史料怎么来?**巴菲特从 1965 写到今天,60 多封致股东信,加上无数次股东大会问答、各类访谈和年报,公开的原始材料不下千万字。把它压成一个 5 分钟的互动剧本,比 LLM 写小说要难得多——它**不能虚构,每一句台词都得对得上原文**。 ### 为什么不能直接让 LLM"写一个" 最早我尝试过最朴素的做法:把 1973 年华盛顿邮报案例的背景告诉 GPT,让它直接生成一个完整剧本。15 分钟我就拿到了一个像模像样的 JSON。但仔细一对,三个致命问题: 1. **细节错得离谱**。巴菲特 1973 实际买入价是 $6.36 美元(拆股调整后约 $0.80),LLM 给的是 $25。年报里的 ROIC 数据、华盛顿邮报当时的发行量、苏珊给巴菲特的婚姻矛盾——LLM 全都凭感觉写。 2. **大师的口吻不像**。巴菲特致股东信的语调极其特殊:用 "a wonderful business at a fair price" 这种比喻、自嘲式幽默、保险术语夹杂哈姆雷特引文。LLM 写出来都是"教科书巴菲特"——平庸的格言罗列。 3. **决策点全是合成**。巴菲特 1973 的决策不是一次性的,是分多次加仓的。LLM 直接给我一个"是否买入"的二元选择,把真实的复杂性烧没了。 > Karpathy 在讨论 "Software 2.0" 时说过:**"LLM 是一个有损压缩器,它把整个互联网压成 1750 亿参数。但有损压缩本质上是会忘东西的。"** 当我让它写巴菲特剧本时,它从内部权重里采样出一个"巴菲特画像",但这个画像是模糊的——细节是它脑补的。 ### 转换思路:让 LLM 做"信息抽取"而不是"创作" 真正的工程师做法是:**严格分离原始素材和模型创意**。LLM 不应该负责"写"剧本,它应该负责"从原文里挑出能成为剧本节点的关键句"。 我重新设计了流水线,分成四步: **第一步 · 全语料 ingestion** 把巴菲特 1965-2014 全部致股东信、年报、访谈、传记摘录都灌进一个大目录,每篇打上时间戳和来源标签。这一步不动 LLM,纯文本预处理。 **第二步 · 决策点定位(LLM 做摘要 + 提取)** 针对一个特定时间段(比如 1973-04 到 1985-12,即巴菲特持有华盛顿邮报到第一次大幅减仓),我给 LLM 一个非常具体的 prompt: > 你是投资史研究员。从下面这一段巴菲特原文里,提取出所有满足以下条件的句子: > 1. 表达了一次明确的买入/卖出/加仓/减仓动作 > 2. 或表达了一次清晰的判断(关于公司、估值、市场) > 3. 或回忆了一次具体的对话(巴菲特 vs 苏珊、芒格、Kay Graham) > 输出 JSON:[{date, type, originalText, source}] 这个 prompt 的关键是**"提取"而不是"创作"**。LLM 不能改原话,只能把符合条件的句子按结构挑出来。我用 Claude 跑了三遍,每篇致股东信都得到 5-15 条结构化条目。 **第三步 · 时间轴聚类** 拿到几百条 raw 条目后,我用一个简单脚本按日期排序、按主题聚类。一个剧本节点应该是"一个时间点 + 一组相关原始素材"。比如"1973-09"这个时间点会聚集到: - 巴菲特 1985 年信里的回忆"我那时刚买完邮报" - Alice Schroeder 传记里描述的同期场景 - 当时华盛顿邮报实际股价数据 - 同期媒体头条
文章配图
**第四步 · 节点 schema 化(LLM 做"翻译")** 最后才让 LLM 介入做创作——但严格在原素材的边界内。给它一个聚类好的时间点,让它生成符合我自定义 JSON schema 的剧本节点: ```typescript type ScriptNode = | { type: "narration"; text: string; basedOn: string[] } | { type: "dialogue"; speaker: string; text: string; sourceQuote: string } | { type: "choice"; prompt: string; options: ChoiceOption[] } | { type: "macroEvent"; newsId: string } | { type: "advanceTime"; unit: "month" | "year"; count: number }; ``` 每个节点都强制要求 `basedOn` 或 `sourceQuote` 字段——LLM 必须把它"为什么这么写"指向原素材。我写了一个校验脚本,所有 sourceQuote 字段必须能在原始语料里 fuzzy match 到。**对不上的节点直接 reject 重新生成**。 ### 这套方法的真实成本 看起来流程很冗长。但实际效果是: - 一个 5-8 分钟的章节大概 80-120 个剧本节点 - 整个生产链路从史料 → 可玩剧本,单章节大约 4-6 小时 - LLM 调用费用:单章节 $3-5(用 Claude Haiku 做摘要 + Claude Sonnet 做 schema 翻译) - **但产出的剧本基本可以做到"每句话都能引回原文"** 最大的收获不是节省时间——这种活让人自己写也能写。**最大的收获是"创作幻觉"被消除了**。当大师角色说"我宁愿用合理的价格买伟大的公司"时,旁边总能附上 `source: 巴菲特致股东信 1989`,玩家点进去就能看到原文 PDF。这种"可追溯性"才是教育产品和娱乐产品的本质区别。 ### 一个意外的副作用 做这个项目时我顺手积累了一份不错的"大师真实语料库"——所有从原文里提取的 5000+ 决策时刻、judgment 引用、对话片段,都按 master、time、category 索引好了。 这本身就是一个挺有用的数据集——下一个项目想做"巴菲特 vs 段永平的方法论 diff"或者"芒格的 mental models 实战调用",都能直接基于这个基础。 > **最强的 LLM 应用,往往不是让模型"扮演专家",而是让模型"挖出被埋藏的专家知识"。** 前者依赖模型的虚构能力,后者依赖模型的检索和摘要能力——后者目前比前者靠谱得多。