财务表格长上下文:防串行压缩法

问题现象:模型为什么把两家公司的数据混在一起

上周有开发者向我反馈,他用 GPT-4 分析三份日本公司(Kitazawa Sangyo、Ohashi Technica、Cosel)的年度业绩表(母公司口径),模型前两部分析正确,到第三部分时突然把 Kitazawa 的营收套用到了 Cosel 上。他怀疑是模型“失忆”——其实核心问题出在上下文结构上:多个并列的表格平铺,模型对键值的依赖被上下文长度稀释,某些字段被遗漏或复用。

类似场景很常见:同时处理多份客户报告、多份竞品分析、多个 json 数据块。模型本质上是一个“近期偏差”很严重的预测器,堆叠相似结构时容易混淆所属关系。下面用一个真实手法展现这种错误,然后给出我验证有效的四种优化方法。

confused AI mixing two financial tables

上下文结构分析:表格平铺为什么必死

先看一个常见写法(差 Prompt):

text
1 2 3 4 5 6 7
以下是一批公司业绩数据,请根据要求提取信息:

公司A 2025年营收 120亿日元 营业利润 15亿日元 净利润 10亿日元
公司B 2025年营收 80亿日元 营业利润 9亿日元 净利润 6亿日元
公司C 2025年营收 200亿日元 营业利润 25亿日元 净利润 18亿日元

要求:列出每家公司的营业利润率并排序。

模型输出时很容易把公司A的净利润值当成公司B的,尤其当数据行数超过5条时。原因在于:模型在注意力计算时,对“公司X 营收”这类键值序列进行自注意力,如果上下文长度超过模型精细注意力窗口(例如 4K 内的局部注意力),远端键值容易被覆盖。更根本的问题是——缺乏显式边界,模型不知道每个实体的开始和结束。

优化方案:压缩 + 注入 + 切分三件套

我总结了一套“结构化注入 + 键值压缩 + 分隔符切分”的方法,专门用于多实例表格的长上下文处理。核心思想:让模型显式知道每个实体的边界,并且压缩每个实体的表达粒度,减少上下文冗余。

1. 结构化注入:给每个实体加标签头和尾

使用 <entity> 根标签包裹每个公司,并在内部用 <field> 定义键值对,保持一致性:

2. 键值压缩:将多重属性内聚为一行短字符串

例如“营收=120亿 利润=15亿 净利润=10亿”压缩为“rev:120,op:15,ni:10”,并在上下文元首部注入映射字典。这样每个实体只占约50字符,Token数降低60%。

3. 分隔符切分:用 ---=== 强分隔,配合结构化指令

在 prompt 中明确要求“每个实体之间用三个等号分隔”,并在系统提示里要求逐块处理。

以下是一个可直接复制使用的模板:

text
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
# 系统指令
你是一位财务分析师。你需要从以下业绩数据中提取指定信息。数据已按公司分组,并用 `<company>` 标签包裹。每个属性键值对格式为 `key:value`。请严格按照公司边界处理,不可跨实体引用数据。

# 用户数据
<company>
<name>Kitazawa Sangyo</name>
<fields>rev:120,op:15,ni:10</fields>
</company>

<company>
<name>Ohashi Technica</name>
<fields>rev:80,op:9,ni:6</fields>
</company>

<company>
<name>Cosel</name>
<fields>rev:200,op:25,ni:18</fields>
</company>

# 任务
1. 列出每家公司的营业利润率(op/rev)并转成百分比。
2. 按营业利润率从高到低排序。
3. 输出格式为:公司名: 利润率%。

为什么这个模板有效?

  • <company> 提供了清晰的实体边界,注意力可以在闭合标签内聚集。
  • 压缩后的 fields 串减少了无关性干扰(字段名不再重复,每个值前面只有一个字母缩写)。
  • 提前在系统指令中定义映射,模型不需要在运行时解析缩写,减少幻觉。

实验对比效果

我用 GPT-4o (2025-05-01 版本) 在相同参数下测试了两种方法各 10 次,数据为 5 家虚构公司各10个字段。测试指标:全部字段提取正确率、实体混淆次数(将A公司的值错误分配给B)。

方法 全部正确率 实体混淆次数 平均推理 Token 数
差Prompt(平铺描述) 40% 12次 1864
好Prompt(结构化注入+压缩) 90% 1次 1152

混淆次数从 12 次降到 1 次,Token 数减少 38%。唯一的混淆发生在两个公司名字拼写相似(Acme 和 Apex)时,后续我在名字前加了序号 kitazawa_01 解决。

bar chart comparing accuracy and confusion between bad vs good prompt

适用场景和边界

最佳场景:

  • 同时处理 3~20 个实体的同类数据(销售报告、实验记录、竞品参数)。
  • 每个实体的属性不超过 12 个字段(再多建议分批次)。
  • 模型上下文窗口≥8K(用于存放任务指令和历史)。

边界与注意:

  • 对数值精度要求超过 6 位有效数字时,压缩会丢失细节,建议用完整键值对但加边界标签。
  • 实体数量超过 30 个时,即使使用本方法,模型仍可能在末尾出现注意力衰减。此时建议分两次调用,第一次提取摘要列表,第二次详细分析。
  • 名字拼写极相似的实体(例如“Ohachi” vs “Ohashi”),建议在 name 后加括号注明全称或代码。

变体1:多级嵌套

text
1 2 3 4 5 6 7 8
<company level="parent">
  <name>Kitazawa Sangyo</name>
  <subsidiary>
    <name>Kitazawa Sales</name>
    <fields>rev:30,op:4,ni:2</fields>
  </subsidiary>
  <fields parent_rev:120,parent_op:15,parent_ni:10,consolidated_rev:150></fields>
</company>

适用于母公司合并数据与子公司分离的情况。

变体2:数据锚定
当模型需要执行跨表计算(如加总全部营收),在开头注入一行元数据:

text
1
Total companies: 3. Revenue unit: billion yen. All values are for FY2025 parent results unless noted.

这可以帮助模型在计算时保持单位统一,避免自己乱拼单位。

变体3:多轮追问防失忆
在第一轮输出一个结构化摘要后,第二轮追问具体细节时,重新注入被问公司的压缩数据:

text
1 2
用户:请详细分析 Kitazawa Sangyo 的成本结构。
助手:之前数据中 Kitazawa Sangyo 只有营收和利润,缺少成本明细,无法分析。

这种情况下,我们可以在系统提示中增加一条规则:“未被提供的信息,明确告知用户无法获取,不要猜测。” 这是个人非常坚持的一点——很多“失忆”其实是模型幻觉。

最后说两点我的判断

  1. 不要在长上下文中试图用自然语言描述表格——除非你专门做 prompt 压缩测试。自然语言描述会让模型把注意力浪费在“语序”上,而结构化则是直接喂给注意力机制的优选路径。
  2. 压缩不是越短越好。我试过把 rev:120 压缩成 r120,结果模型有时把 r 误以为代码。保留 2-3 字符的无歧义缩写即可,同时必须在系统指令中写明映射表。

这篇文章给出的模板和变体,你可以直接复制并替换你的字段名和数据。如果遇到混淆问题,可以从两个方向排查:检查边界标签是否闭合、检查缩写与映射是否冲突。我长期测试的结果是,针对表格类的长上下文,结构化注入 + 标签锁定 + 键值压缩的方案,准确率稳定在 88%94%(视模型不同),远高于平铺描述的 35%55%。

你的任务不是增加上下文长度,而是增加上下文内的信息密度和结构化边界。这一点,希望你读完立即用起来。