假设你手头刚拿到一份一百多页的上市公司年度报告,需要快速梳理出这家公司过去一年的核心财务表现、经营亮点和潜在风险。最直接的想法是把整份年报丢给一个支持超长上下文窗口的大语言模型(LLM),让它直接给结论。这个思路我试过,而且不止一次,结果并不乐观:模型会一本正经地编出原文里没有的营收数字,会把"归母净利润"和"净利润"混为一谈,同一个模型回答两个问题时结论还能自相矛盾。问题不在于模型不够强,而在于长文本场景下,LLM 的注意力会被大量低信息密度内容稀释。后来我把方案改成 one-shot 方式——给模型一个标准示例,让它按示例的格式和口径去分析同一体例的财报片段,再配合文档分段与分层汇总。实测下来,输出稳定性和可核对性都明显提升。这篇就把这套方法完整拆开讲,适合正在做财报自动化分析、投资研究辅助或审计底稿预审的读者参考。
1. 长文本财报分析的核心痛点:信息密度低与注意力衰减
1.1 年报这种文档,为什么让长上下文模型吃药
我在处理某制造业上市公司年度报告时把文档结构梳理了一遍:重要提示、公司简介、会计数据与业务摘要、管理层讨论与分析、重要事项、股份变动及股东情况、董事监事高管情况、公司治理、审计报告、财务报表、财务附注。其中财务附注经常占一半篇幅,里面大量是会计政策和说明性质的重复内容。对 LLM 来说这类文本最不友好:字数多、格式重复、有效信号稀薄。一个关键信号,比如"应收账款占营业收入比重从 18% 升到 25%",可能只出现在一处,但如果上下文里有大段"遵循会计政策""按历史成本计量"之类的模板句子,模型真正分配给关键数字的注意力就容易被摊薄。这就是"低信息密度"问题的由来。
还有一个维度是"关键信号离散"。同样一个经营风险,可能在管理层讨论里有一段定性描述,在财务报表附注里有一串明细数字,在审计报告的关键审计事项里又有一个侧面印证。人翻年报会在三个位置之间来回跳,但一次性把所有内容放进上下文后,模型往往只会盯住最后一个出现的位置,前面的证据就丢了。
1.2 整本直读时的三个典型翻车症状
把一整本年报直接灌进上下文,我的实测观察到三类高频问题。
第一类是数字幻觉。我拿某段"预付账款变动"的文本做测试时,模型输出"预付账款同比增长 23.5%",回到原文发现根本没有这个增长率,原文写的是"预付款项占总资产比例为 23.5%"。模型把附近一个数字挪过来充作增长率,看起来很真,实际是编的。这种情况在长文本里远比短文本严重,因为模型需要从几十万个 token 里做"信息补全",相邻位置的数字很容易被串用。
第二类是口径漂移。年报里"净利润"可能既有"归属于上市公司股东的净利润",也有"净利润(含少数股东损益)",两个数值不同。无约束提问时,模型可能这一轮用归母口径,下一轮又用总额口径,导致同一份报告的后续分析数值之间对不上。
第三类是结论自相矛盾。同样一份文本,先问"现金流状况如何"再问"公司偿债压力大不大",如果上下文太长,模型容易遗忘前一次回答中使用过的证据,给出两根互相打架的结论。
我把症状整理成一张速查表:
| 症状 | 表现 | 根因 |
|---|---|---|
| 数字幻觉 | 编出原文不存在的增长率、占比 | 长文本下模型采用"补全"而非"引用"策略 |
| 口径漂移 | 归母净利润与净利润混用 | 文中两种口径并存,提问未锁定 |
| 结论漂移 | 两次回答使用不同证据链 | 注意力被过多 token 稀释 |
| 指令遗漏 | 要求只输出表格却夹带长段解读 | 指令被淹没在文本尾部 |
需要强调一下,这些症状不是某一款模型独有的。我在多个支持长窗口的模型上都复现过,只是严重程度不同。有的模型在前 1 万 token 内表现正常,到了第 10 万 token 附近就开始"犯迷糊",重复提问会得到完全不同的答案。
1.3 结论:先把分析范式固定下来,再谈塞内容
所以长文本场景的问题并不是"窗口不够大",而是"输入太杂、指令太松、输出不可控"。当我们把 100 页年报塞进一个大窗口时,表面上是给足了信息,实际上是让模型在一个信号稀疏、格式杂乱的超大文本里自由发挥。它发挥得越自由,越容易把"看起来合理"的内容当成"事实"。
正确的做法反过来:先固定分析范式——用哪套指标、口径怎么定义、按什么格式输出——然后只把与任务相关的文本片段交给模型,并给一个可模仿的标准示例。这就是 one-shot 方法在财报分析中真正发挥作用的原因。它不是在和上下文长度较劲,而是把模型的"自由发挥空间"压缩到最低。
2. oneshot 提示词的作用机制与骨架设计
2.1 为什么是 one-shot 而不是 zero-shot 或 few-shot
日常多数人用 LLM 的方式是 zero-shot:直接问"分析一下这段财报"。它的缺陷很明显,输出完全靠模型临场发挥,今天可能是表格,明天可能是一篇小作文,数字口径五花八门。一次性任务倒无所谓,但做财报分析时我们需要对十几段、几十段文本执行同一套分析标准,如果每段输出格式都不同,后面的合并和核对会变成一场灾难。
few-shot(多个示例)理论上更稳,一个示例让模型理解格式,两个示例消除歧义,三个示例基本稳定。但问题也现实:一个示例占用几百 token 问题不大,三到五个示例可能上千 token,而且示例越多,选择成本越高,如果示例之间存在细微不一致,反而会误导模型。
one-shot 恰好是平衡点。财报分析的核心分析框架并不复杂:先抽指标,再算变化,再看风险。一个精心设计的示例足以把"输出格式、口径定义、边界处理"三件事一次性说清楚。我在多个项目里对比过:one-shot 配合结构化输出模板,效果已经接近 few-shot 的八成,而提示词维护成本只有 few-shot 的三分之一。
2.2 一个可复用的 one-shot 提示词骨架
下面这个骨架我用了大半年,任何财报分析任务都可以往里套:
你是[角色]。任务是[任务描述]。 ===示例(仅作格式示范)=== 【样本文本】 (一小段与目标文本同体例的财报文字,包含典型的科目与数字) 【标准输出】 (按你希望模型返回的格式,写好的答案示例,包括表格列名、口径、分级) ===任务要求=== 1. 只依据给定的【待分析文本】作答,禁止补充文本中不存在的数据。 2. 输出格式与【标准输出】完全一致。 3. 金额单位、年份口径以文本为准。 4. 如果文本中缺少某个指标,不补充、不猜测,直接省略或标注"未披露"。 【待分析文本】 (这里填入需要分析的财报片段)这个骨架的关键用处是让模型"先看到一套完整答题示范,再去做题"。
角色部分要写清楚分析视角,比如财务分析师、审计视角风险筛选员,不同视角对同一段文本的关注点完全不同。任务描述必须是一句话,明确"抽取指标"还是"识别风险",避免模型自己扩大任务范围。示例部分必须包含"样本文本"和"标准输出"两个成对内容,模型会把二者作为模仿对象。任务要求里最重要的其实是"禁止补充文本中不存在的数据",这句话我建议在每个提示词里都出现,它比任何系统提示都能直接压住幻觉。
2.3 示例文本的"言传身教":在示例里埋边界处理
我看过不少人写的 one-shot 提示词,最常见的问题是示例写得太"干净":只展示理想情况,没有展示边界情况。这会导致模型遇到不理想文本时不知道怎么处理。好的示例应该故意埋几个边界信号。
比如抽指标的示例里,如果有一段文本没有披露某个指标,标准输出里就应该体现出"该指标未披露,不列出"的做法;风险识别示例里,如果文本没有风险信号,标准输出就写"未识别到明显风险信号"。模型在模仿示例的同时,会把这些边界行为一并学会。
我自己有一个习惯:准备一条 one-shot 提示词时,先手工分析一段真实的财报片段,把人工分析结果写成标准输出,然后再拿这段真实文本做一次回测,看模型是不是能复现我写的内容。这个过程往往能暴露出提示词里没说清楚的地方。比如第一次写风险识别示例时,我忘记了要求"每个风险都必须引用文本数字作为依据",模型给出的风险判断虽然大方向对,但缺少数据支撑,无法核对。把这条要求补进任务要求后,输出质量立刻不一样。
3. 财报长文本装不进上下文窗口:分段解析与两层汇总
3.1 先做文档粗筛,把年报压减到可读核心
one-shot 解决的是"输出范式"问题,但还有一个更现实的问题:100 页年报可能几十万字,即使分 chunk,如果每段都硬塞低价值内容,依然浪费 token、稀释信号。我的做法是分析前先做文档粗筛,把年报里真正对分析有用的部分摘出来。
对多数财报分析任务,真正需要读的其实只有几块:会计数据与业务摘要、管理层讨论与分析、重要事项、审计报告中的审计意见段落、财务报表主表(资产负债表、利润表、现金流量表、所有者权益变动表)、财务附注里关于重要科目变动的说明。股票发行、公司治理、股东情况这些章节和财务风险分析的关系不大,可以跳过。
实际操作中,可以用 PDF 解析脚本把年报转成带页码的纯文本,然后用规则匹配章节标题,把目标章节切出来再拼接。一份 100 页左右的年报,筛完后大概剩下 20 到 40 页。这个步骤看起来增加工作量,实际上大幅降低了后续每个 chunk 的信号噪音,也节约了大量 token。
3.2 切分策略:表格完整优先,章节边界优先
切片的原则只有一条:永远让一个完整表格或一个完整科目块完整落在一个 chunk 里。按固定字符数硬切是最省事的,也是最容易出问题的。比如利润表被切成两段,前半段只有营业收入、营业成本,后半段才有净利润,模型在单独分析每一段时都不可能给出完整结论。
我常用的切分顺序是这样的:先用章节标题作为一级边界,再用表格作为二级边界,最后才允许按段落切。切出来的 chunk 控制在 3000 到 5000 个中文字符左右,太短则上下文利用率低,太长又会回到注意力被稀释的老问题。每段开头加一行来源标签,比如"【来源:管理层讨论与分析,第 5 页】",这个标签会在后面的汇总层里变成可追溯的证据锚点。
相邻 chunk 之间我还习惯保留 10% 到 15% 的重叠。比如前一个 chunk 结尾是"存货账面余额为 82,500 万元",后一个 chunk 最好也能包含这句话,防止切分边界恰好把数字和科目描述隔开。
3.3 两层解析流程:逐段抽取后统一汇总
分段之后,如果直接把几十段分析结果拼在一起丢给模型做总结,还是会乱。我采用两层结构。
第一层叫"逐段抽取"。对每个 chunk 独立调用一次 one-shot 提示词,得到一份结构化的中间结果。指标抽取任务输出表格,风险识别任务输出分级列表。这一层的每一份结果都对应原始文本的一个局部,可以直接回溯。
第二层叫"统一汇总"。把所有中间结果拼接成一份较短的新文本,再换一个汇总式的 one-shot 提示词,让模型基于这些结构化结果做全局判断。汇总层的输入是已经"清洗"过的信息,而不是原始年报,所以上下文长度大幅缩短,模型注意力不会被原始噪音干扰。
为什么不在第一层直接做全局分析?原因有两个。一是第一层如果要做全局,就必须把所有 chunk 一次性放进上下文,等于回到整本直读的老路,注意力依旧被稀释。二是分层结构自带"审计线索":任何汇总结论都可以拆回某个 chunk 的中间结果,再拆回原始文本的某页某段,人工复核非常方便。在投资分析和审计这类场景里,可追溯性比模型给出的"聪明结论"更重要。
3.4 token 成本算账:直读全文 vs 分段 one-shot
用成本算一笔账会更有说服力。假设一份年报筛完核心部分后仍有约 12 万字(约折合 18 万 token),如果选择整本直读:
| 方案 | 每次输入 token | 说明 |
|---|---|---|
| 整本直读 | 约 18 万 token | 一次调用贵,且超过窗口容易被截断 |
| 分段 one-shot(12 段) | 每段约 6000 token,总计约 7.2 万 token | 每段单独调用,中间结构再二次汇总 |
| 分段 + 结构汇总 | 7.2 万 + 约 2 万(汇总输入) | 全程可回溯,输入总量约 9.2 万 token |
以上数字是经验估算,实际因文本密度而异。
分段方案看上去调用次数多,但单次载荷小、每次调用都能在窗口的舒适区内完成,准确率比一次塞满全文高。更重要的是,第二层汇总输入的是几万 token 的结构化中间结果,模型可以轻装做判断。我在实际项目中测算,分段 one-shot 方案的 token 总消耗大约是直读全文的 50% 到 60%,但结果的可复核性远好于直读。
4. 三类高频财报分析任务,可直接复制的 oneshot 模板
下面三个模板是我整理过的可用版本,替换【待分析文本】即可直接跑。模板里的示例文本和数字均为虚构,作用只是示范格式和口径。
4.1 任务 A:财务指标抽取与格式化输出
你是一名财务分析助手。请根据给定的财报文本,抽取关键财务指标,并按示例格式整理成Markdown表格。 ===示例(仅作格式示范)=== 【样本文本】 本报告期,公司实现营业收入586,320万元,同比增长15.4%;实现归属于上市公司股东的净利润50,127万元,同比下降6.8%;经营活动产生的现金流量净额为78,240万元。截至报告期末,公司总资产1,032,500万元,归属于上市公司股东的净资产412,600万元,资产负债率48.9%,加权平均净资产收益率9.6%。 【标准输出】 | 指标 | 数值 | 备注 | | --- | --- | --- | | 营业收入 | 586,320万元 | 同比+15.4% | | 归属于上市公司股东的净利润 | 50,127万元 | 同比-6.8% | | 经营活动产生的现金流量净额 | 78,240万元 | — | | 总资产 | 1,032,500万元 | 期末数 | | 归属于上市公司股东的净资产 | 412,600万元 | 期末数 | | 资产负债率 | 48.9% | 期末数 | | 加权平均净资产收益率 | 9.6% | — | ===任务要求=== 1. 只抽取文本中明确出现的金额与比率,不得推断或编造。 2. 数值与单位保持原样,年份口径在备注中说明。 3. 未出现的指标不列出,不要补全。 4. 输出格式必须是上述Markdown表格。 【待分析文本】 (这里填入截取的财报片段)这个模板有几个容易被忽略的设计点。指标全称我建议写完整,比如"归属于上市公司股东的净利润"而不是"净利润",这样模型在复制列名时会把口径一并带上,后续汇总阶段才不会混淆。备注列特意留出来承接"同比+15.4%"这类增量信息,而不是要求单独再算一个增长率,减少模型做额外运算而引入幻觉的机会。示例里也只给了一期数据,没有写同比,模型会从示例中学到"文本没给同比就不输出同比增长"。
4.2 任务 B:管理层讨论中的亮点与风险识别
你是一名审计视角的财务风险分析助手。请从给定的财报文本中识别风险信号,并按示例格式输出。 ===示例(仅作格式示范)=== 【样本文本】 报告期末公司应收账款余额较期初增长42.3%,达到156,300万元,占营业收入的比重由上年同期的18.2%上升至25.1%。报告期内前五大客户销售收入占比合计61.5%,较上年提高8.1个百分点。存货账面余额82,500万元,其中库存商品占比72.6%。公司因产品质量问题被一主要客户起诉,仍在审理中。 【标准输出】 风险信号: - 高优先级:应收账款增速(+42.3%)远超营业收入增速,占营收比重由18.2%升至25.1%,回款与坏账风险突出。 - 中优先级:客户集中度偏高,前五大客户占比61.5%且继续上升,存在大客户依赖。 - 中优先级:存货以库存商品为主(72.6%),若销售不及预期,存在存货跌价压力。 - 高优先级:存在一宗产品质量诉讼,结果尚不确定,可能影响公司声誉与赔付负担。 (以上结论均依据给定文本,未加入外部信息) ===任务要求=== 1. 每个风险信号都要引用文本中的关键数字或表述作为依据。 2. 按高/中/低分级。 3. 如文本中没有风险信息,直接写"未识别到明显风险信号"。 4. 不要补充文本之外的假设。 【待分析文本】 (这里填入财报片段)风险识别任务最容易出两种情况:要么模型把什么都当成风险,草木皆兵;要么它过度平滑,把所有风险都写成"需关注"。我特意在标准输出里安排了四个风险点,覆盖财务比率风险、集中度风险、存货风险和诉讼风险,让模型看到"非财务信息也可以作为风险信号"。第四级风险用的是诉讼事项,这是为了告诉模型:风险不只是报表数字层面的,还包含法律、合规、经营层面的文字描述。
4.3 任务 C:跨期数据对比与异常波动诊断
你是一名财务数据分析师。请对比两个会计期间的数据,计算变化幅度,并将异常波动单独列出。 ===示例(仅作格式示范)=== 【样本文本】 2023年度毛利率28.5%,2022年度为26.9%,上升1.6个百分点。2023年销售费用率12.8%,2022年为15.2%,下降2.4个百分点。2023年研发投入占营业收入比重5.6%,2022年为4.1%,提升1.5个百分点。2023年经营活动现金流量净额为-12,400万元,2022年为8,900万元。 【标准输出】 正常变化: - 毛利率:26.9% → 28.5%,上升1.6个百分点,盈利能力改善。 - 销售费用率:15.2% → 12.8%,下降2.4个百分点,费用控制有效。 - 研发投入占营收比重:4.1% → 5.6%,提升1.5个百分点,创新投入加大。 异常关注: - 经营活动现金流量净额由正转负:8,900万元 → -12,400万元,与毛利率改善趋势背离,需进一步核查回款节奏、存货占用或预付款变化。 ===任务要求=== 1. 先列正常变化,再单独列异常波动。 2. 每个变化必须给出两个期间的具体数值和变化方向。 3. 异常波动要说明"为什么觉得异常"(如与其它指标趋势背离)。 4. 如果只有单期数据,请直接说明无法对比。 【待分析文本】 (这里填入跨期财报片段)我把输出分成"正常变化"和"异常关注"两块,是希望模型一开始就把"值得进一步查的事"和"普通业务波动"分开。示例里我故意放了一个净利润涨、现金流转负的矛盾组合,让模型学会从指标背离中发现问题。要求第 3 条"说明为什么觉得异常"是防止模型只甩结论不负责任——它必须把推理依据写出来,人工复核时才能快速判断这个"异常"是真异常还是误报。
4.4 建模板时容易犯的三个错误
第一个错误是示例太理想化。只看过顺利案例的模型,遇到一段"没有风险、没有异常"的财报时会不知所措,硬挤出两句不痛不痒的话凑数。所以每个模板里都要有一条处理"空结果"的要求,比如"未披露则不列出""未识别到明显风险信号"。
第二个错误是示例与目标文档体例不一致。如果示例写得像新闻稿,而目标文本是财务附注的平淡风格,模型会把附注里略带警报意味的表述理解成风险,或者反过来。示例文本最好就是从同类型财报里挑出来改写成的,表达习惯要和真实目标文本一致。
第三个错误是要求与示例互相矛盾。比如任务要求里写"禁止编造",示例输出里却处处写着"同比增长 X%",而样本文本并未给出上期数,这等于默认模型可以自己算出一个上期基准。限制条件要和示例行为完全对齐,模型才不会在"模仿示例"和"遵守要求"之间摇摆。
5. 实测翻车复盘:最容易出问题的四个环节与对策
模板搭好、流程跑通之后,真正的挑战才开始。我把反复踩过的坑复盘一下,尤其是那些不那么容易一眼看穿的问题。
5.1 数字幻觉:模型会把附近数字"安"到错误指标上
我之前遇到过一次很典型的案例。让模型抽取某段预付账款信息时,它输出了"预付账款同比增长 23.5%"。回看原文,根本没有这个增长率,原文只有"预付款项占总资产比例为 23.5%"。模型把比例数字挪过来,临时造了一个增长率。这个现象在财报这种数字密集的文本里特别常见,因为"补全"是生成式模型的底层本能,它看到主语后面缺一个修饰成分,就会就近抓一个数字填上。
对策有三条,缺一不可。第一,所有抽取任务都要求输出附带"来源定位",至少标注到章节或页码,没有定位的数据视为不可信。第二,对营业收入、归母净利润、经营现金流这几类关键数字做程序化校验,用脚本从原始文本里正则提取数字,跟模型输出比对,不一致就标红。第三,人工抽查不少于三个核心指标,尤其是涉及到百分比和同比的字段。
5.2 上下文污染:前一个 chunk 的结论串到后一个
分段处理时,如果你在同一个对话会话里连续发送多个 chunk,模型会把前一轮的分析结果当成新的背景材料,于是第二段分析里可能会冒出第一段才有的风险点。我遇到过一份文本,第二个 chunk 里明明没有提到应收账款,输出的风险列表里却出现了应收账款相关的表述,一看就是从第一个 chunk 的结论里带过来的。
对策很简单但很容易被忽视:每个 chunk 必须走独立的 API 调用,不携带历史消息。第二层汇总时再把这些独立结果合并。另外,在第二层汇总的提示词里要求"每个结论必须标注来源 chunk 编号",这样即使某条结论串了位,也能在复核阶段快速定位,而不是整份重跑。
5.3 表格被切碎:科目分离导致分析断头
切分时最怕碰到大表格。我在第一次跑利润表抽取时,按每 4000 字切一段,结果把一张衔接紧密的利润表拦腰截断,营业收入出现在 chunk 2,营业成本和净利润在 chunk 3。模型分别对两个 chunk 做指标抽取时,前一段缺了利润结果,后一段缺了收入来源,单独看都像残缺的中间态。
后来我改成了"表格优先"的切分策略:PDF 转纯文本时先把表格块识别出来,一个大表格无论多长都整体放进同一个 chunk;如果表格确实太长,就先按二级科目拆分,比如把利润表拆成"收入成本段"和"利润段",但不能把营业外收支这类小科目单独切飞。这条规则对财务报表这种结构性极强的文档特别重要。
5.4 指标口径漂移:归母净利润与净利润混用
最后一个坑是口径问题。很多年报同时存在"归属于上市公司股东的净利润"和"净利润(含少数股东损益)",数值差可能不小。模型如果只是按字面找到"净利润"三个字,就可能在表格里填一个口径,下一段文本又填另一个口径,导致汇总出来的整体判断失真。
我现在的做法是,在示例里把指标全称写死,并强制要求输出列名必须包含完整口径。比如表格里只能写"归属于上市公司股东的净利润",不允许简写成"净利润"。汇总层的提示词里再加一句"遇到口径模糊的数值,标注'口径待确认'",而不是让模型自己猜。这类问题无法靠单次提示词彻底根除,但只要示范到位,模型绝大多数情况会照着完整口径走。
最后说一点这两年跑下来的个人体会。one-shot 的本质不是把正确知识塞给模型的魔法,而是把规范作答方式演示给模型的小技巧。它在长文本场景里的价值,是让分析过程变得可预期、可审计。你不需要让它每次从零发挥,只需要让它照着你验证过的格式去答题。
对一个新的行业或新的财报版式,我的建议是先手工分析一小段真实文本,把这段人工分析结果写成示例,再拿同一段文本回测一遍,确认模型能复现出你的分析质量,之后才批量铺开。平时把验证过的提示词连同标准输出一起存成一个 prompt 库,遇到同类任务直接复用,会比每次重新写一遍稳定得多。后续如果想继续扩展,把这一套 one-shot 逻辑平移到季度报告对比、同行横向分析、甚至公司公告风险扫描上,都是顺手的事。