XBSTACK Tech Image - XBSTACK

AI 财报助手评测体系:如何用 Golden Dataset 发现 LLM 是否看错财报?

Release Date
2026-06-23
Reading Time
12分钟
Impact Factor
2,791
llm-evaluation
golden-dataset
ai-finance
workflows
Xiaobai's Note / 实验室笔记

这篇文章记录了我在贵阳实验室的实战过程。我坚信,在技术下行的时代,程序员唯一的护城河就是通过 AI 建立属于自己的数字资产。

本文解决的问题

  • 如何从零设计一套针对大模型财报抽取的自动化评测体系?
  • 怎么判断 AI 财报助手提取的数值究竟有没有看错期间、单位和报表位置?
  • 风险因素等非结构化文本如何计算召回率和准确率?
  • 如何量化大模型在严肃财务数据抽取中的幻觉率与编造概率?
  • 怎么通过回归测试保证 Prompt 或模型微调升级后,系统表现不发生退化?

适合谁读

  • AI 研发工程师:正在优化财务大模型抽取 pipeline,急需量化指标。
  • 全栈开发者:计划上线企业级 AI 财报助手,寻找质量防线的搭建方案。
  • 财务系统架构师:评估大模型在严肃审计与投研流程中的准确度边界。
  • 技术决策者:需要设计 AI 系统上线标准,确保数据合规与幻觉防范。

一、 为什么 AI 财报助手必须做评测?

做 AI 财报助手,真正上线前不能只看模型回答得像不像人,而要验证它有没有看错收入、现金流、风险因素和管理层表述。感觉好用不等于生产可用,在严肃的投研或审计场景下,哪怕一次数值错位或单位丢失,都可能导致投资判断发生南辕北辙的偏差。

在贵阳的数字避难所里,我调试过上百个不同版本的 Prompt 与大模型解析链。在实际业务中,我们经常会遇到大模型在抽取财报时发生的隐蔽故障。例如,模型可能会把第四季度的单季收入识别为全年收入,也可能在面对复杂的跨页折行表格时把列顺序读反,甚至可能会忽略备注里「以千为单位」的提示,直接漏掉三个零。

这些错误在普通的聊天界面里非常难以被肉眼察觉。页面显示流畅,排版优雅,生成出来的总结看起来极其具有分析师的腔调,但它底层的数据完全是错的。这就是所谓的「高鲁棒性格式下的事实崩溃」。因此,我们必须为大模型抽取套上一层物理级的质量安全门,即基于 Golden Dataset 的自动化评测体系。

二、 构建评测流程的生命线

评测体系的本质是建立一套可重复的科学比对管道。

graph TD
    A[Raw PDF / Page Source] --> B[PDF Table Parser]
    B --> C[LLM Structured Extractor]
    C --> D[JSON Response with Citations]
    D --> E[Golden Dataset Evaluation Engine]
    F[(Golden Dataset Standard Answer)] --> E
    E --> G{Evaluation Matrix}
    G -->|All Pass| H[Production Direct Display]
    G -->|Low Confidence / Discrepancy| I[Human Review Queue]
    G -->|Critical Fail / Format Break| J[Reject & Error Logs]

整个流程在物理上分为三个部分:抽取端、评测引擎与分流队列。在抽取端,PDF 经过解析器后,被传入大模型结构化抽取器,输出包含页码引用的 JSON。评测引擎则拉取本地预先标注好的 Golden Dataset 标准答案,与模型输出运行物理断言,计算出财务指标准确率、风险因素召回率等核心数据。最后,根据得分和置信度,将结果分流至直接展示、人工复核或直接拦截。

三、 Golden Dataset 的定义与标注规范

Golden Dataset 可以理解为一组带有人工标准答案的测试财报样本。它不是随便攒几份 PDF 扔给模型去总结,而是必须针对不同的数据类型和边界条件,建立标准化的标注 Schema。

在我的实践中,一份合格 of Golden Dataset 样本应当包含三类数据的标注: 第一类是严格数值型(财务指标,包含精确值、允许的相对误差范围、对应页码以及原文文本片段证据); 第二类是语义判断型(管理层语气、不确定性表述); 第三类是非结构化列表型(核心风险因素描述、审计意见)。

以下是一个用于评测的 Golden Dataset 标注 JSON 样例:

{
  "sample_id": "eval_001_nvda_fy2024",
  "document_name": "NVIDIA_2024_10K.pdf",
  "annotations": {
    "financial_metrics": [
      {
        "metric": "revenue",
        "expected_value": 60922,
        "unit": "USD million",
        "period": "FY2024",
        "tolerance": 0.0,
        "source_page": 58,
        "evidence": "Total revenue was $60,922 million for fiscal year 2024."
      },
      {
        "metric": "operating_income",
        "expected_value": 32977,
        "unit": "USD million",
        "period": "FY2024",
        "tolerance": 0.0,
        "source_page": 58,
        "evidence": "Operating income was $32,977 million."
      }
    ],
    "risk_factors": [
      {
        "risk_id": "risk_customer_concentration",
        "keywords": ["customer concentration", "percentage of revenue", "significant customer"],
        "severity": "high",
        "source_page": 13
      },
      {
        "risk_id": "risk_supply_chain",
        "keywords": ["supply chain", "single source", "manufacturing capacity", "tsmc"],
        "severity": "critical",
        "source_page": 15
      }
    ],
    "management_tone": {
      "topic": "demand_outlook",
      "tone": "cautious",
      "keywords": ["cautious", "visibility limited", "supply constraints"],
      "source_page": 42
    }
  }
}
小白实验室自研 / TOOL CONVERSION

如果你想直接测试 AI 财报助手,可一键跳转试用

支持 PDF 批量上传、管理层 Guidance 情绪审计、核心 KPI 指标抽取,免费免登录。

四、 核心评测指标与断言设计

财务数值准确率:等值断言与单位对齐

在财务指标准确率的检测中,我们不能直接进行简单的字符串全等比对。因为不同的模型或不同的 Prompt 可能会输出「60922」、「60,922」、「$60,922 million」甚至「60.922 billion」。

结论先行:数值抽取必须引入归一化计算和相对误差容忍度(Epsilon)。

在代码层面,我们需要先将所有的数值和单位统一转换成标准单位(如以元或以百万美元为基准)。然后,通过公式判断实际值与期望值是否在误差范围内:

def check_value_with_tolerance(expected, actual, tolerance=1e-5):
    if expected == 0:
        return actual == 0
    relative_error = abs(expected - actual) / abs(expected)
    return relative_error <= tolerance

风险因素召回率:关键词密度 vs 语义匹配

非结构化内容的评测是业界的难点。模型在提取风险时,往往会用很空泛的自然语言总结。

结论先行:文本召回率的判定必须结合「关键词重合度」与「语义包含断言」。

如果标注的标准风险包含「customer concentration(客户集中度)」,并且给出了三个核心触发关键词。我们可以在评测引擎中,计算实际输出文本中是否至少包含其中两个关键词,或者通过小型本地 embedding 模型计算实际输出与标准答案的余弦相似度。对于严格的业务抽取,我更倾向于使用硬性的关键词密度匹配,这能提供高确定性的断言。

source_page 与 evidence 准确率:证据链的物理校验

如果 AI 给出了正确的数值,但是引用的页码是错的,这依然属于潜在幻觉,无法让人工快速复核。

结论先行:页码和证据的匹配是判定「大模型没有猜答案」的物理标准。

我们要求大模型输出的每一个指标都必须包含 source_page(数据源页码)和 evidence(原文文本)。评测引擎会读取财报 PDF 的对应页码,执行 contains 校验。如果模型指出的 evidence 根本不在该页码中,此项得分直接判定为 0。

五、 Python 自动化评测引擎实现

下面是我在 XBSTACK 实验室中使用的自动化评测脚本核心代码。它基于 Pydantic 规范定义了比对模型,能够读取标准答案与实际生成结果,自动计算出各维度的精度指标与召回率。

from typing import List, Dict, Any
import re

class MetricEvaluator:
    @staticmethod
    def normalize_value(val_str: str) -> float:
        """
        清除非数字字符,统一处理百万与十亿的缩写转换
        """
        if not val_str:
            return 0.0
        cleaned = re.sub(r'[^\d\.\-eE]', '', val_str)
        try:
            val = float(cleaned)
        except ValueError:
            return 0.0

        # 判断原始字符串中的单位乘数
        lower_str = val_str.lower()
        if 'billion' in lower_str or 'b' in lower_str:
            return val * 1000.0
        return val

    @staticmethod
    def evaluate_financial_metrics(expected_list: List[Dict[str, Any]], actual_list: List[Dict[str, Any]]) -> Dict[str, Any]:
        """
        评测数值指标,计算精确度与页码匹配度
        """
        correct_count = 0
        page_match_count = 0
        total = len(expected_list)

        actual_map = {item['metric']: item for item in actual_list}

        for exp in expected_list:
            metric_name = exp['metric']
            if metric_name not in actual_map:
                continue

            act = actual_map[metric_name]

            # 提取并归一化数值
            exp_val = exp['expected_value']
            act_val = MetricEvaluator.normalize_value(str(act.get('value', '')))

            # 数值对齐校验
            is_val_correct = False
            if exp_val == 0:
                is_val_correct = (act_val == 0)
            else:
                relative_error = abs(exp_val - act_val) / abs(exp_val)
                is_val_correct = (relative_error <= exp.get('tolerance', 0.0))

            if is_val_correct:
                correct_count += 1

            # 页码对齐校验
            if int(exp.get('source_page', -1)) == int(act.get('source_page', -2)):
                page_match_count += 1

        return {
            "total_metrics": total,
            "value_accuracy": correct_count / total if total > 0 else 0.0,
            "page_accuracy": page_match_count / total if total > 0 else 0.0
        }

    @staticmethod
    def evaluate_risk_recall(expected_risks: List[Dict[str, Any]], actual_risks: List[Dict[str, Any]]) -> Dict[str, Any]:
        """
        基于关键词召回度评估非结构化风险因素提取
        """
        recalled_count = 0
        total_expected = len(expected_risks)

        # 将所有实际抽取的风险合并为一个大文本进行关键词匹配
        actual_text = " ".join([str(r.get('description', '')).lower() for r in actual_risks])

        for exp in expected_risks:
            keywords = exp.get('keywords', [])
            # 如果预设的关键词在模型输出文本中出现比例超过 50%,则判定为召回
            match_count = sum(1 for kw in keywords if kw.lower() in actual_text)
            if len(keywords) > 0 and (match_count / len(keywords)) >= 0.5:
                recalled_count += 1

        return {
            "total_expected_risks": total_expected,
            "risk_recall_rate": recalled_count / total_expected if total_expected > 0 else 0.0
        }

六、 常见坑与真实异常日志分析 (Error Logs)

在部署和运行评测体系时,以下是三类最容易遇到的系统错误与排查方案。

异常一:单位换算除零错误

ZeroDivisionError: division by zero in metric normalization
  File "scripts/eval_engine.py", line 45, in evaluate_financial_metrics
    relative_error = abs(exp_val - act_val) / abs(exp_val)
  • 原因分析:标准答案中某个财务指标的期望值为 0(例如某些亏损公司前期的某项债务或非控制性权益为 0),但大模型输出了一个微小数值,导致计算相对误差时分母为 0 崩溃。
  • 解决方案:在计算前加入对期望值的判断。若期望值为 0,则应直接进行绝对值比对而非相对值比对。

异常二:页码断言返回 null

ValueError: expected page 58, but got null in source_page assertion
  File "scripts/eval_engine.py", line 78, in verify_evidence_link
    raise ValueError("evidence link invalid: source page missing")
  • 原因分析:大模型在返回抽取结果时,虽然找对了数据,但在 JSON 中将页码写成了 null,或者由于 PDF 解析时合并了跨页数据,导致模型无法定位精确的单页来源。
  • 解决方案:增强 PDF 解析端的 Page Divider,确保每页文本被传入模型时都带上清晰的物理页码 Header;同时调整 JSON Schema 限制,强制 source_page 字段必须为大于 0 的整型,拒绝 null 值输出。

异常三:Schema 验证失败导致抽取异常

ValidationError: 1 validation error for FinancialReportSchema
  response -> risk_factors -> 0 -> severity
    value is not a valid enumeration member; permitted: 'low', 'medium', 'high', 'critical'
  • 原因分析:由于使用了新版本的模型或微调了 Prompt,模型输出的风险等级(severity)中出现了未预设的枚举值(如「very high」或「minor」),导致 Pydantic 解析端直接崩溃。
  • 解决方案:使用更严格的 JSON Schema 强约束,在 API 请求参数中利用 response_format 锁定限制;同时在评测引擎中将 Schema 校验失败直接归入 P0 级严重异常,拦截并触发人工复核。

七、 评测方案对比

在实际落地中,有三种常见的评测方式。以下是它们在成本、确定性和回归效率上的对比。

评估维度Golden Dataset 物理断言 (本项目采用)LLM-as-a-Judge (用大模型当裁判)完全人工双盲复核
测试成本极低(一次标注,无限次自动运行)中等(每次评估都需要消耗 API 费用)极高(需要资深财务人员逐字核对)
判定确定性100%(基于物理关键词与数值逻辑判定)较低(裁判大模型自身也存在幻觉和随机性)100%(人工最终确权)
回归效率秒级(适合跑在 CI/CD 流水线中)分钟级(需要等待大模型多轮推理)天级(受限于人工排期)
复杂语义理解一般(依赖预设规则与相似度算法)优秀(能理解复杂的文字语气转换)优秀(专业财务洞察)
幻觉拦截率高(强制要求页码与证据原文 contains 匹配)中等(容易被流畅的伪造结论欺骗)极高(人工双盲交叉验证)

八、 常见问题解答

为什么不能用大模型(如 Claude 3.5)直接去判定另一个模型的输出是否正确?

因为大模型做裁判存在自我证明和不可解释性的问题。大模型本身对于财务数值的微小偏差(如 10.5 亿 vs 10.05 亿)并不敏感,容易被流畅的段落描述所迷惑,甚至连裁判大模型自己也会发生幻觉。因此,在数值和证据链的评测中,必须使用基于 Golden Dataset 的物理代码断言作为红线。

评测体系上线后,会降低 AI 财报助手的运行速度吗?

不会。评测体系是完全异步运行的,或者只在 CI/CD 回归测试阶段运行,用来把控发布质量。对于线上用户的真实请求,我们可以通过评测中沉淀出来的置信度模型,对大模型输出进行实时轻量级校验,如果触发红线则将其静默归入人工复核队列,而不影响普通页面的响应速度。

我的财报 PDF 包含大量手写签名和模糊扫描件,这会影响评测准确率吗?

这会直接拉低 PDF 解析阶段的分数,进而影响最终得分。在 Golden Dataset 中,我们应当将扫描件归为「高难度测试集」。当评估引擎发现扫描件的数值抽取准确率大幅低于 baseline 时,系统会提醒你应当升级底层的 OCR 或表格解析引擎,而不是盲目地去修改大模型的 Prompt。

九、 继续阅读

在 XBSTACK 实验室中,本评测体系与以下技术模块共同构成了 AI 财报助手的完整闭环:

下一步 / NEXT READING

准备好分析你的第一份财报了吗?

你可以立刻上传一份 PDF 财报(如 NVIDIA 10-K),体验本工具自动生成的核心 KPI 报表、风险因素和复核清单。

喜欢这篇文章?
加入小白实验室的周刊

每周我都会分享最新的 AI 实战、产品构建心得以及程序员视角的投资笔记。不发废话,只发干货。已有 5000+ 开发者在此共同进化。

Comments