Legora 如何用 GPT-6 Astra 在几分钟内审查 41 份财务文件

2026-09-03 20 预计阅读时间: 1 分钟
来源: openai.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

Legora 将 GPT-6 Astra 用于一项财务审查工作流:系统在几分钟内处理了 41 份文件,找出了全部 4 个预先植入的错误,并将该流程的性能提高了近 40%。这个案例的价值不只是“模型读得快”,而是展示了大模型如何进入高风险、强证据要求的专业审查环节。

不过,摘要没有说明“性能”具体指准确率、吞吐量、审查时间还是综合指标。因此,在评估这类结果时,不应直接把近 40% 解读为准确率提升。更可靠的做法是拆开衡量召回率、误报率、处理时长、成本和人工复核工作量。

真正困难的是可验证,而不是生成一段结论

财务文件审查不同于普通摘要。一个系统即使生成了流畅的分析,只要无法指出错误来自哪份文件、哪一页或哪一段,审查人员就很难采用它。

一个可落地的审查结果至少应该包含:

  • 问题类型,例如数字不一致、期间错配、遗漏披露或计算错误。
  • 原文证据,而不只是模型归纳后的表述。
  • 文件名、页码、章节或段落等定位信息。
  • 风险等级和判断理由。
  • 置信度,以及需要人工确认的条件。
  • 跨文件冲突,例如报表、附注和交易材料使用了不同数字。

“找出全部 4 个植入错误”说明测试关注了错误召回能力,但生产环境还必须观察误报。如果系统同时提出几十个无效问题,那么即使召回率达到 100%,人工负担也可能没有下降。

41 份文件应该被视为一个证据集合

逐份总结 41 个文档并不等于完成了一次财务审查。很多重要问题只有放在文件之间比较才能出现,例如资产负债表中的债务金额与附注不一致,或者管理层材料使用了不同的报告期间。

可以把工作流拆成三个阶段:

  1. 文档级提取:从每份文件中提取金额、日期、主体、会计口径和关键陈述,同时保留引用位置。
  2. 组合级核对:对标准化后的事实进行跨文件比较,检查金额、期间、币种和实体是否一致。
  3. 风险级复核:让模型针对候选异常重新读取附近原文,排除四舍五入、口径差异和合法例外。

这种设计比一次性把所有文件塞进提示词更容易调试。它还能区分“模型没有抽取到事实”和“模型抽取到了事实但没有识别冲突”这两类完全不同的失败。

一个可改造的并行审查脚本

下面是一个最小工作流骨架。它假设你使用的服务提供兼容 /v1/chat/completions 的 HTTP 接口;这只是实践示例,并非对 GPT-6 Astra 实际 API、模型名称或返回格式的描述。运行前需要将 LLM_BASE_URLLLM_API_KEYLLM_MODEL 替换为实际服务配置,并把待审查的 .txt 或已完成 OCR 的 Markdown 文件放进 documents/

#!/usr/bin/env python3
import json
import os
import sys
import urllib.request
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path

BASE_URL = os.environ.get("LLM_BASE_URL", "https://example.com/v1")
API_KEY = os.environ.get("LLM_API_KEY")
MODEL = os.environ.get("LLM_MODEL", "replace-with-your-review-model")
DOC_DIR = Path(sys.argv[1] if len(sys.argv) > 1 else "documents")

SYSTEM_PROMPT = """You are reviewing financial documents for factual inconsistencies.
Return JSON only with this shape:
{
  "findings": [
    {
      "category": "numeric_mismatch|period_mismatch|missing_disclosure|calculation_error|other",
      "severity": "low|medium|high",
      "claim": "short description",
      "evidence": "exact quotation from the document",
      "location": "page, section, or paragraph if available",
      "confidence": 0.0
    }
  ]
}
Do not invent evidence. If evidence is insufficient, return an empty findings array.
"""

def review(path: Path) -> dict:
    text = path.read_text(encoding="utf-8")
    payload = {
        "model": MODEL,
        "temperature": 0,
        "response_format": {"type": "json_object"},
        "messages": [
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": f"FILE: {path.name}\n\n{text}"},
        ],
    }
    request = urllib.request.Request(
        f"{BASE_URL.rstrip('/')}/chat/completions",
        data=json.dumps(payload).encode("utf-8"),
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json",
        },
        method="POST",
    )
    with urllib.request.urlopen(request, timeout=180) as response:
        result = json.load(response)
    parsed = json.loads(result["choices"][0]["message"]["content"])
    return {"file": path.name, "findings": parsed.get("findings", [])}

if not API_KEY:
    raise SystemExit("Set LLM_API_KEY before running")

paths = sorted([*DOC_DIR.glob("*.txt"), *DOC_DIR.glob("*.md")])
if not paths:
    raise SystemExit(f"No .txt or .md files found in {DOC_DIR}")

reports = []
with ThreadPoolExecutor(max_workers=4) as pool:
    futures = {pool.submit(review, path): path for path in paths}
    for future in as_completed(futures):
        path = futures[future]
        try:
            reports.append(future.result())
        except Exception as exc:
            reports.append({"file": path.name, "error": str(exc), "findings": []})

reports.sort(key=lambda item: item["file"])
Path("review-results.json").write_text(
    json.dumps(reports, ensure_ascii=False, indent=2),
    encoding="utf-8",
)
print(f"Reviewed {len(reports)} documents; results written to review-results.json")

运行方式:

export LLM_BASE_URL="https://your-provider.example/v1"
export LLM_API_KEY="your-api-key"
export LLM_MODEL="your-review-model"
python3 review_documents.py documents

这个脚本适合验证并行处理、结构化输出和错误隔离,但还不是完整的财务审查系统。生产版本应增加 PDF 解析、页码保留、重试与限流、跨文档事实表、敏感信息处理,以及针对每条高风险发现的二次验证。

不要只复现“几分钟”,要复现评测方法

团队采用类似方案时,可以建立一套固定的金标准文档集,并有控制地植入数字、期间、币种和披露错误。每次更换模型、提示词或解析器后,至少记录以下指标:

  • 错误召回率:已知错误中有多少被找到。
  • 精确率:模型提出的问题中有多少是真问题。
  • 证据有效率:引用是否真实存在并足以支持结论。
  • 文档覆盖率:是否有文件因解析、超时或格式问题被跳过。
  • 端到端耗时:从上传到形成可供人工复核的报告需要多久。
  • 人工分钟数:审查人员验证和处理模型输出实际花费的时间。

部署边界同样重要。财务材料可能包含交易信息、个人数据和未公开业绩,必须确认数据保留政策、访问控制、日志脱敏与适用的数据驻留要求。高风险发现不应自动成为最终结论,更不应在无人复核的情况下触发申报、交易或客户通知。

Legora 的案例说明,大模型可以显著压缩多文档审查的首轮筛查时间。但真正值得复制的不是某个孤立的速度数字,而是“已知错误测试、证据定位、结构化结果和人工复核”组成的闭环。只有这个闭环稳定,近 40% 的流程提升才有机会变成可持续的生产能力。


相关推荐