派大猩 · Agent 工程笔记

Back

金融长文本的高效问答与记忆压缩

views

阿里天池 AFAC 2026 挑战组·赛题四「金融长文本 Agent 的动态记忆压缩与高效问答」,做完了。 100 道题、五个领域、五种题型,官方按 0.5 / 0.3 / 0.2 的权重把准确率、推理过程分和 token 效率分算成总分。最后一次提交 92.1747 分,1704 支队伍里排第 38, 一次提交用掉 577,565 tokens。

做完最要紧的一件事,是发现这道题的答案不在「怎么压缩文本」,而在「让它读什么」。 该压的是读进来的范围,不是文本本身。

92.1747 加权总分 准确率 0.5 + 过程分 0.3 + 效率分 0.2 100 道题 · 5 个领域 · 5 种题型 38 / 1704 排名 1704 支队伍里第 38 577,565 这次提交的 token 用量 prompt 527,782 · completion 49,783
提交结果:加权总分 92.1747,1704 支队伍里排第 38,一次提交用掉 577,565 tokens。

评分表先读,别急着读文档

推理过程分由官方侧用 LLM judge 按逻辑、完整、清晰三维算术平均给出, 推理短于 20 字直接记 0 分。真正牵着架构走的是另一项:token 效率分。

用量在 50 万以内,按实际用量除以 500,000 再乘 100 线性给分; 越过 50 万之后换成(5,000,000 − 实际用量)÷ 5,000,000,此后一路衰减到零。

反推几个点位,形状就清楚了:100 万剩 80 分,250 万剩 50 分,500 万归零。 这次落在降段那一侧,效率分是 88.45((5,000,000 − 577,565) ÷ 5,000,000), 只占总分 0.2 的权重,等于这一档 20 分里拿到 17.69 分。 再往上看,过线之后每多读 10 万,这一档就掉 0.4 分。

省 token 的收益是按权重乘过的,不是按字数平摊的。 这一句决定了后面所有的取舍方向。

赛制还留了几条绕不过去的约束:只能用白名单模型;token 必须取服务端返回的原始用量, 不许估算、不许换个分词器重算;同一道题的草稿、推理、验证、纠错、重试、摘要、改写、 格式化调用全部计入本题;响应里缺了用量,整题判负。

三项得分之间还藏着一处拧巴:过程分要把推理写得细,效率分要用量少。 两项叠起来只剩一句结论 —— 推理不能省,参与推理的证据必须极少。

于是两条看上去稳妥的路线都变成负收益。靠多轮自查把答错的题救回来,每次重试都原样计费; 靠扩大召回补证据,多读的每一个 token 都在扣效率分。 「多读一点更保险」这条默认策略失效之后,后面的设计才有了存在的理由。

想明白之后,路线只剩一条

语料是 573 份清洗后的金融文档,监管占 513 份(513 ÷ 573 ≈ 89.5%)。 而监管文档恰恰是最不适合整篇读的一类:长、条款多、上下文互相引用。

在这上面谈「记忆压缩」,第一直觉是把文本压短 —— 摘要一层,抽取一层, 让模型读压缩后的版本。这条路在这里走不通:被压掉的那部分里, 往往正藏着题目要找的那一个数字;而摘要这一层本身也要花 token。

能站住的判断其实只有四条。压缩不是缩短文本,是把文本挑出来: 从 573 份文档收敛到几份文档里的十几块,字数降下去准确率还能涨, 因为模型要判断的东西变少了,剩下的每一块都更像证据。

还有一条是能算出来的一律交给代码。比率、增幅、分红这类关系, 让模型心算是在拿一整段推理加上随时可能发生的重试,换一个几十字符的结论。 算错了可定位可复现,推理错了连错在哪都说不清。

拼写、排序、复算、归一这些决策留在程序里。多选答案把 AC 拼成 CA 是模型的自由, 把拼写错误带进答案是链路的失职。

最后是契约先定,模型不许改形状。题目进来先推导出一份答案契约:几个槽位、 每个槽位是什么值类型、几位小数、带不带百分号、要不要排序、排序朝哪边。 之后不管走哪条路线,槽位数和格式都不许动,契约被写死在提交校验里 —— 否则模型顺手多给一个槽,整题就废了。

四条指向同一件事。把不确定的部分从模型手里拿走,让它在已经挑出来的证据上, 只做擅长的一件事 —— 写出一段逻辑清楚、完整、可读的推理。

做起来才明白,顺序比阈值值钱

顺序是先把文档切对,再定位到文档,然后检索、闭包、预算、作答, 最后才是追溯与提交校验。前两步比后面重,因为任何一处的失败都会顺着链路往下放大, 到作答那一步再补救,钱已经花过两遍。

1 语料:573 份清洗后的金融文档 合同、财报、保险、监管、研报五个领域,监管占近九成 2 朝结构下刀,不朝句号 标题路径、条文编号、超长整段都算切点;条款行和列表行各自独立,不进合并 3 文档级定位,先认出是「哪一份」 从题干和选项抽身份槽,块得分聚合回文档,收敛成几份候选 4 词面检索,词典定口径 每个领域分库建索引,金融词典灌进切词器;复杂题让模型先只吐检索词 5 确定性事实闭包,能算的一律交给代码 合同字段、条款存在性、财报同口径比较、分红回报闭包,都由程序生成 6 证据预算卡在拼 prompt 之前 确定性事实优先,保覆盖、保命中,剩余额度不够就停手 7 题型化作答与答案归一化 多选逐项裁决后由程序按字母序汇总;计算题以程序复算覆盖模型心算 8 提交与用量审计 100 题共 577,565 tokens(prompt 527,782 + completion 49,783),完整性校验通过
全链路:每一步都在收缩候选,而不是压缩文字。真正决定成本的,是走到最后还剩多少东西能进 prompt。

先沿条款下刀,不沿句号

这层最贵的判断不是切多大,是切在哪。按句号切是默认做法,条款正文从中间断开, 召回出来的半句话既没法判断也没法引用。链路换成朝结构下刀:标题路径一变、 条文编号一出现,或者整段超长,都算切点。

一段带着标题路径,让证据连得上上下文;命中检索的是里面的短块,让证据进得了预算; 表格另按行成块,把精确指标钉死、完整表留着补单位。 模型看到的一块文本里,上下文和命中是同一件事,不用再猜哪部分才是要看的。

短块里还有个合并缓冲:同标题路径、同属一段、累计不超过一档长度的短文本才并进来。 条款行和列表行永远不进这个缓冲,它们各自独立成义, 合并等于把两条义务写成一条,引用的时候对不上号。

表格是一条单独的路。先留一份整表大纲,再决定要不要下沉到行级。 之所以要先留住「这张表长什么样」,是因为脱离表头和行列关系的一行数字, 既看不出单位也看不出归属。只有大表和复杂表值得下沉,其余交回大纲。

先认出是哪本书,再找哪一页

候选榜不告诉答案在哪份文档里,候选池就是全库。直接对全库做块级检索有两个麻烦: 搜索空间太大,而且「这道题问的是哪家公司、哪一年的报告」这类身份信息会被相似度冲淡 —— 相似度看的是词面分布,不看主体是谁。

链路里因此插了一层文档级定位。先从题干和选项里抽出身份槽, 每条查询先在同领域的库里跑一轮,再把块得分聚合回文档。 最后按查询覆盖、身份精确匹配、文档前部命中排下来,并做领域化分散, 免得几份相似的噪声文档把候选占满。

财报这一档有个刻意的克制:出现明确的「公司 + 年度报告」槽时,允许不把候选补满, 因为补满只会把别家公司的年报拉进计算题的上下文。一个候选都定位不到, 就直接抛定位失败,而不是退化成全库检索。 宁可明确地答不上来,也不要用一次模糊的兜底假装检索过了。

这是整套系统里最便宜的一道闸门。代价只是文档标题级别的一次检索, 换来的却是把候选空间从全库压到几份文档之内, 后面的补召回、闭包、预算都在一个干净得多的池子里跑。

词面定口径,问法交给模型

建索引前把金融词典灌进切词器,长指标名必须保持完整,拆开就只剩一堆通用词。 词面因此成了判别力的全部 —— 同一个指标的 2022 年和 2023 年, 只要切词把年份丢掉,两条记录就长得一模一样;「万元」和「亿元」、 「第一季度」和「一季度」也必须在词面上分得开。

财报再叠一层纯规则的精度加分:指标别名行命中且数字命中时加分,噪声行降权。 这一步几乎不花成本,作用是把期初期末的数字锚住 —— 这类题丢分几乎都不是检索不到,而是检索到了错误的那一期。

问法不同是另一件事,交给模型先说该搜什么。财报多选、合同多选、带排序或反向推算的计算题, 先让模型吐出几个短检索词,复杂指标拆成「主体 + 年份 + 单一指标」, 它只出检索词,不答题目,这次调用的用量照样计入本题。 其余题型直接走规则查询,不为省几行代码多花一次调用。

每个领域还叠了一层专项补召回,补通用检索够不到的东西。 合同补发行人和募资要素,财报补年度主表与核心指标行,保险先绑住选项里的产品再找责任条款。 监管连着法条术语不放,研报要求跨文档题每份报告各自有证据。

能算出来的一律交给代码

这是整条链路最硬的一条纪律:凡是能从原文算出来的事实,由程序生成,不由模型抽取。 合同字段矩阵、条款存在性、财报指标在统一主体与年份口径之后的比较、 分红回购的回报闭包、跨文档存在性、数字方向关系,都从这里来。 发布日期与生效日期的区分、监管义务的主体和报送对象、保险绑产品之后的免责与免赔核验, 也是同一类产物。

这些事实有个共同特征:它们是判断「题面说的对不对」所需的真值,不是答案本身。 真值交给代码,模型就只剩一件事 —— 拿这些真值凑出题目要的那个形状。

过程里有两条不能破的规矩。财务类的数只在财务类文档包里取, 通用数值检查分不清合并报表和母公司、年度和季度,这种歧义只能靠文档类型消掉。 另一条是缺任何一个操作数就返回不可计算,不让模型补数。 几十字符的结论交给代码算,比交给模型推更划算 —— 算错了可定位可复现,推理错了连错在哪都说不清。

精度上也统一了:百分比参与乘除前先转小数,中间不舍入,最后统一四舍五入; 工作日和自然日按题面区分;中期分红与年末口径相加,全年直接披露的不重复计。

预算卡在拼 prompt 之前

组装顺序是排好的:确定性事实优先,然后每个候选文档各取一条保覆盖。 接着是词面查询的最佳命中、每个选项的相关命中、绑定文档里的指标行, 最后才是全局高分兜底。越确定、越便宜、越能撑住结论的东西排得越靠前。

这条停止线背后是一个判断:半截证据比没有证据更危险, 它能让模型编出一段看起来步步有依据的推理。把整个超长上下文交给模型, 等于把「删掉哪一段」的决策权也一起交出去,而这份删除名单没有人查得回来。

题型只改问法,不改流程

单选和判断先走一次综合回答,拿到结论和推理就归一化提交。 结构非法时只用上一次的响应和解析错误构造一次最小修复, 不增加任何新事实,也不重新检索一遍; 两次都拿不到合法结构,这题才判失败。

多选多一道闸:先查结论覆盖、几项结论之间是否自相矛盾、 有没有「可能」「预计」这类不确定措辞、财报题逐项复算、合同题看原文依据、 保险题看条款有没有绑到对应产品。任一不过就换逐项路线, 为每个选项分别选证据、分别问一次对错、用各领域的确定性规则校正一遍, 最后由程序按字母序汇总布尔值,并写成一致的推理。 题目问的是「选错误的」时,先把每条真值整体取反再走同一套流程。

计算和抽取是另一条:先按扩展查询拆出待提取的方面, 每个方面只让模型抽主体、年份、口径、原值、单位、日期这几个字段。 结构化复算之后才让它基于摘要和复算结果合成答案,再过一遍多槽审计, 结论与复算冲突时做一致性重写,最后按答案契约逐槽校验。

这一圈每一步都是一次真实调用。它们值得,是因为每一票都以它所替代的那次重试计价 —— 每多一次修正,就多一份用量和一份方差。

每一步都留得下凭证

这套系统里最不显眼、也最要紧的一层是追溯。每题一份凭证, 记下候选文档与排名、每条查询与召回轨迹、最终进了 prompt 的证据及其渲染顺序、 模型原始响应、归一化后的答案槽、累计用量和整条重试轨迹。

于是出问题时排查有了固定顺序:先翻文档定位,再翻查询与召回,然后看最终证据, 接着看重试原因和原始响应,最后才看答案。 问题发生在文档发现、块召回、事实闭包、模型裁决还是格式归一化, 靠这份顺序就能定,不用重跑一遍去猜。

提交前还有一道独立校验脚本,跟答题链路完全分开,它先检查表头和总计行、题号唯一性 与模板顺序、每题 token 的加法与总量上限、推理长度;再查必填槽与多余槽、选项字母、 数值精度、百分号、日期与排序、凭证必需字段与提示词哈希,以及 CSV 与凭证在答案、 推理、token 三处的一致性。任一不过就不给提交标记。

token 的账也按同一口径累计:收到响应就计入,结构无效也一样计。 只统计最终采纳的那一次,会让人误以为重试是免费的, 而重试恰恰是这套赛制里最贵的一种操作。

五十七万 token 花在哪儿了

100 道题跑完,577,565 tokens,平均每题 5,776(577,565 ÷ 100)。 按官方效率公式反推,这一档落在 88.45 分。

财务报告 197,087 · 34.1% · 题均 9,854 金融合同 134,827 · 23.3% · 题均 6,741 保险 93,263 · 16.1% · 题均 4,663 研究报告 76,468 · 13.2% · 题均 3,823 监管规则 75,920 · 13.1% · 题均 3,796 题均 = 该领域用量 ÷ 20 题;占比 = 该领域用量 ÷ 577,565
五个领域各 20 题,钱花在哪一眼能看出来:财报与合同两家拿走近六成,监管文档最多却最便宜。

这张分布图里最值钱的信息是两端。财报和合同两家拿走 57.5% 的用量(331,914 ÷ 577,565), 因为它们动辄要在多张表之间对齐口径;监管虽然占了文档数的近九成,用量却最低, 条款命中之后往往一块就够,不需要铺开。

另一个反差在输出侧:completion 只占 49,783 ÷ 577,565 ≈ 8.6%,不到十分之一。 这套系统的钱几乎全花在读上,不花在写。

提交结果 92.1747 分,1704 支队伍里排第 38。 再往回看这一路,值得记下来的是顺序而不是阈值 —— 先切对,再定位,预算卡在拼 prompt 之前。 压缩不是把文本压短,是把文本挑出来;压缩再狠, 也不如一次准确的下刀和一次准确的定位省。