LLM长文本场景-多文档分布式分析示例

之前探索了LLM如何联合两份文档分析公司的年度财务数据。

https://blog.csdn.net/liliang199/article/details/159282803

针对多份文档难以一次性分析的困境,可以采用类似MapReduce分布式思路处理。

这里参考网络资料,尝试先map提取每个文档要素,然后reduce汇总所有要素,呈现分析结果。

1 问题说明

以下是问题说明。

在2024年10家A股上市公司的年度财务报告中,

哪家公司的研发投入最高?

具体数额是多少?

基于2024年真实财报数据,选取的10家公司,具体为

比亚迪 (002594) 、宁德时代 (300750) 、百济神州 (688235) 、中国移动 (600941) 、美的集团 (000333) 、海光信息 (688041) 、韦尔股份 (603501) 、中芯国际 (688981) 、中国汽研 (601965) 、研奥股份 (300923) 、

2 数据准备

2.1 准备目录

创建目录financial_reports,将10家公司的2024年年度报告PDF文件放入该目录。

文件名需与代码中 COMPANY_PDF_MAP 定义的名称一致,比如,

比亚迪 002594_2024_n.pdf

2.2 下载文件

需要获取这些公司的2024年年度报告PDF,这里假设文件已下载。

以下是待测试公司的pdf文件和对应的下载链接。

比亚迪 002594_2024_n.pdf

https://static.cninfo.com.cn/finalpage/2025-03-25/1222881496.PDF

宁德时代 300750_2024_n.pdf

https://static.cninfo.com.cn/finalpage/2025-03-15/1222806982.PDF

百济神州 688235_2024_n.pdf

https://static.cninfo.com.cn/finalpage/2025-04-29/1223387705.PDF

中国移动 600941_2024_n.pdf

https://static.cninfo.com.cn/finalpage/2025-03-21/1222856315.PDF

美的集团 000333_2024_n.pdf

https://static.cninfo.com.cn/finalpage/2025-03-29/1222951181.PDF

海光信息 688041_2024_n.pdf

https://static.cninfo.com.cn/finalpage/2024-04-12/1219582015.PDF

韦尔股份 603501_2024_n.pdf

https://static.cninfo.com.cn/finalpage/2025-04-16/1223104428.pdf

中芯国际 688981_2024_n.pdf

https://static.cninfo.com.cn/finalpage/2025-03-28/1222924320.PDF

中国汽研 601965_2024_n.pdf

https://static.cninfo.com.cn/finalpage/2025-04-26/1223305545.PDF

研奥股份 300923_2024_n.pdf

https://static.cninfo.com.cn/finalpage/2025-04-21/1223148777.PDF

2.3 PDF解析

这里使用pdfplumber解析PDF文本,示例代码如下

复制代码
import os
import json
import pdfplumber
import pandas as pd
from typing import Dict, Optional, List
from openai import OpenAI

# ==================== 配置区域 ====================
# PDF文件存放目录(请修改为实际路径)
PDF_DIR = "./financial_reports"
# 公司名称与PDF文件名映射(假设PDF文件名包含公司名,也可用其他规则)
# 格式:{公司名: 文件名}
COMPANY_PDF_MAP = {
    "比亚迪": "002594_2024_n.pdf",
    "宁德时代": "300750_2024_n.pdf",
    "百济神州": "688235_2024_n.pdf",
    "中国移动": "600941_2024_n.pdf",
    "美的集团": "000333_2024_n.pdf",
    "海光信息": "688041_2024_n.pdf",
    "韦尔股份": "603501_2024_n.pdf",
    "中芯国际": "688981_2024_n.pdf",
    "中国汽研": "601965_2024_n.pdf",
    "研奥股份": "300923_2024_n.pdf"
}
# =================================================

# 初始化OpenAI客户端
client = OpenAI()

def extract_text_from_pdf(pdf_path: str, max_pages: int = 500) -> str:
    """
    从PDF中提取文本,最多提取max_pages页(财报太长,只取前N页通常包含管理层讨论)
    """
    text_parts = []
    try:
        with pdfplumber.open(pdf_path) as pdf:
            # 财报关键数据通常在"管理层讨论与分析"章节,位于前几十页
            pages_to_read = min(len(pdf.pages), max_pages)
            for i in range(pages_to_read):
                page = pdf.pages[i]
                page_text = page.extract_text()
                if page_text:
                    text_parts.append(page_text)
    except Exception as e:
        print(f"解析PDF失败 {pdf_path}: {e}")
        return ""
    return "\n".join(text_parts)

txt = extract_text_from_pdf("./financial_reports/002594_2024_n.pdf")
print(txt[:100])

输出示例如下所示

比亚迪股份有限公司2024........

3 MapReduce处理

在做好上述准备后,就可以分别进行map信息抽取,然后进行reduce汇总分析了。

3.1 map-要素提取

通过精心设计的Prompt强制输出JSON,并处理markdown代码块包裹。

LLM在提取时直接将亿元、万元转换为元,确保数值可比。

这里将所有这些逻辑,分装到如下所示的map函数中。

复制代码
def map_extract_rnd_expense(company_name: str, pdf_text: str) -> Optional[Dict]:
    """
    使用LLM从PDF文本中提取研发费用数据
    返回字典包含:company_name, rnd_expense_total (单位元), unit, source_page
    """
    prompt = f"""
你是一位资深的财务分析师。请从以下公司年度报告文本片段中,提取该公司在2024年度的"研发投入"或"研发费用"数据。

要求:
1. 优先提取"研发投入"总额(包含费用化和资本化部分);如果未明确披露研发投入,则提取"研发费用"金额。
2. 如果金额以"亿元"、"万元"为单位,请转换为"元"的整数形式(例如:531.95亿元 → 53195000000)。
3. 输出必须是严格的JSON格式,包含以下字段:
   - "company_name": 公司全称(中文)
   - "rnd_expense_total": 数值(整数,单位:元)
   - "unit": 原始单位(如"元"、"亿元"、"万元")
   - "source_page": 页码(如果文本中没有页码,可填"未知")
4. 如果未找到任何研发相关数据,返回 {{"rnd_expense_total": null}}。

公司名称:{company_name}

文本内容:
{pdf_text[:]}  # 限制长度避免超过token上限
    """
    try:
        response = client.chat.completions.create(
            model=model_name,
            messages=[
                {"role": "system", "content": "你是一个专业的财务数据提取助手,只输出JSON格式的结果。"},
                {"role": "user", "content": prompt}
            ],
            temperature=0,
            max_tokens=500
        )
        content = response.choices[0].message.content.strip()
        # 尝试提取JSON(可能被markdown代码块包裹)
        if content.startswith("```json"):
            content = content.split("```json")[1].split("```")[0]
        elif content.startswith("```"):
            content = content.split("```")[1].split("```")[0]
        data = json.loads(content)
        return data
    except Exception as e:
        print(f"LLM调用失败 {company_name}: {e}")
        return None


data = extract_rnd_expense(company_name="比亚迪", pdf_text=txt)

输出示例如下

{'company_name': '比亚迪股份有限公司',

'rnd_expense_total': 54160964000,

'unit': '元',

'source_page': '33'}

3.2 reduce-信息汇总

使用Pandas展示排行榜,包含原始金额和亿元单位,方便阅读。

在这里,首先通过extract_text_from_pdf抽取每个文档的要素,类似于map过程。

而将抽取的所有文档的要素汇总的pandas dataframe的过程,就相当于reduce过程。

示例代码如下所示。

由于没有合适的mapreduce框架,在如下函数中没有将map和reduce进行分离。

但理论上如下map和reduce过程是可以分离,方便进行大批量分布式处理。

复制代码
def reduce_process_all_companies() -> pd.DataFrame:
    """
    遍历所有公司,提取研发费用,返回DataFrame
    """
    results = []
    for company, pdf_file in COMPANY_PDF_MAP.items():
        pdf_path = os.path.join(PDF_DIR, pdf_file)
        if not os.path.exists(pdf_path):
            print(f"警告:文件不存在 {pdf_path},跳过")
            continue
        
        print(f"正在处理 {company} ...")
        # 1. 提取PDF文本(仅前50页)
        pdf_text = map_extract_text_from_pdf(pdf_path, max_pages=100)
        if not pdf_text:
            print(f"  未能提取文本,跳过")
            continue
        
        # 2. LLM提取研发费用
        extracted = extract_rnd_expense(company, pdf_text)
        if extracted and extracted.get("rnd_expense_total"):
            results.append({
                "company": company,
                "rnd_2024": extracted["rnd_expense_total"],
                "unit": extracted.get("unit", "元"),
                "source_page": extracted.get("source_page", "未知")
            })
            print(f"  提取成功:{extracted['rnd_expense_total']} 元")
        else:
            print(f"  未能提取到研发费用数据")
    
    # 构建DataFrame
    df = pd.DataFrame(results)
    if df.empty:
        print("没有成功提取任何数据")
        return df
    # 确保数值列为整数
    df["rnd_2024"] = df["rnd_2024"].astype(int)
    return df


df = reduce_process_all_companies()
df

输出示例如下,由于海光信息提取异常,所以未最终显示,说明代码具备兼容异常的能力。

正在处理 比亚迪 ...

提取成功:54160964000 元

正在处理 宁德时代 ...

提取成功:18606756000 元

正在处理 百济神州 ...

提取成功:14139839000 元

正在处理 中国移动 ...

提取成功:34027000000 元

正在处理 美的集团 ...

提取成功:16232771000 元

正在处理 海光信息 ...

未能提取到研发费用数据

正在处理 韦尔股份 ...

提取成功:3245293134 元

正在处理 中芯国际 ...

提取成功:5447122000 元

正在处理 中国汽研 ...

提取成功:335308620 元

正在处理 研奥股份 ...

提取成功:23434286 元

company rnd_2024 unit source_page

0 比亚迪 54160964000 元 34

1 宁德时代 18606756000 千元 25

2 百济神州 14139839000 千元 37

3 中国移动 34027000000 百万元 77

4 美的集团 16232771000 千元 51

5 韦尔股份 3245293134 元 37

6 中芯国际 5447122000 千元 15

7 中国汽研 335308620 元 22/278

8 研奥股份 23434286 元 21

3.3 信息聚合呈现

这里借助于Pandas的Dataframe聚合分析数据,能有效避免LLM运行聚合统计分析的短板。

示例代码如下。

复制代码
def analyze_results(df: pd.DataFrame):
    """
    对结果进行排序并输出结论
    """
    if df.empty:
        return
    
    # 按研发费用降序排序
    df_sorted = df.sort_values(by="rnd_2024", ascending=False).reset_index(drop=True)
    # 添加亿元显示列
    df_sorted["rnd_亿元"] = df_sorted["rnd_2024"] / 100000000
    
    print("\n" + "="*60)
    print("2024年上市公司研发费用排行榜(单位:元)")
    print("="*60)
    print(df_sorted[["company", "rnd_2024", "rnd_亿元", "source_page"]].to_string(index=False))
    
    top_company = df_sorted.iloc[0]
    print("\n" + "="*60)
    print(f"✅ 结论:研发投入最高的公司是【{top_company['company']}】,")
    print(f"   投入金额为 {top_company['rnd_亿元']:.2f} 亿元({top_company['rnd_2024']:,} 元)。")
    print("="*60)




if __name__ == "__main__":
    # 检查PDF目录
    if not os.path.isdir(PDF_DIR):
        print(f"错误:PDF目录 {PDF_DIR} 不存在,请创建并放入财报PDF文件。")
        exit(1)
    
    # 分析结果
    analyze_results(df)

输出示例如下。

============================================================

2024年上市公司研发费用排行榜(单位:元)

============================================================

company rnd_2024 rnd_亿元 source_page

比亚迪 54160964000 541.609640 34

中国移动 34027000000 340.270000 77

宁德时代 18606756000 186.067560 25

美的集团 16232771000 162.327710 51

百济神州 14139839000 141.398390 37

中芯国际 5447122000 54.471220 15

韦尔股份 3245293134 32.452931 37

中国汽研 335308620 3.353086 22/278

研奥股份 23434286 0.234343 21

============================================================

✅ 结论:研发投入最高的公司是【比亚迪】,

投入金额为 541.61 亿元(54,160,964,000 元)。

============================================================

reference


LLM复杂数值的提取计算场景示例

https://blog.csdn.net/liliang199/article/details/159282803

LLM应对文档有效信息分散的策略探索

https://blog.csdn.net/liliang199/article/details/159159167

LLM长上下文和数值类有效输出的关系探索

https://blog.csdn.net/liliang199/article/details/159175752

LLM数值提取-计算场景示例

https://blog.csdn.net/liliang199/article/details/159244753

相关推荐
阿里云大数据AI技术2 小时前
构建高转化海外电商搜索:阿里云OpenSearch行业算法版的全链路智能优化策略实战
人工智能·搜索引擎
Awu12272 小时前
⚡从零开发 Agent CLI(五)实现一个可治理、可扩展的工具系统
前端·人工智能·claude
字节跳动视频云技术团队2 小时前
让 Agent 成为音视频工作台:AI MediaKit CLI + Skill 发布
人工智能·音视频开发
魏祖潇2 小时前
framework 整合实战——DDD/TDD/SDD 三件套在 framework 仓的真实落地
人工智能·后端
Token炼金师3 小时前
去噪扩散:从随机噪声到高保真图像的数学之路
人工智能·aigc
这个DBA有点耶3 小时前
AI写的SQL跑崩了生产库,这锅谁背?
数据库·人工智能·程序员
阿里云大数据AI技术3 小时前
阿里云 EMR AI 助手正式发布:从问答工具到全栈智能运维助手
运维·人工智能
Larcher4 小时前
从零搭建 MCP 服务——让 AI 拥有无限扩展能力
人工智能·程序员
zzzzzz3104 小时前
你的 AI 写的 React 烂透了?这个 8000+ Star 的开源工具能揪出 90% 的「Agent 屎山」
人工智能
小星AI4 小时前
MCP协议超详细教程,从入门到实战
人工智能