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

相关推荐
菩提树下的凡夫2 小时前
基于C++语言的Onnx CUDA加速部署推理
linux·运维·人工智能
丝斯20112 小时前
AI学习笔记整理(74)——Python学习3
笔记·python·学习
Century_Dragon2 小时前
探索车身修复的数字化课堂 —汽车车身诊断与校正仿真教学软件
学习
柯儿的天空2 小时前
【OpenClaw 全面解析:从零到精通】第 010 篇:OpenClaw多渠道接入:WhatsApp、Telegram、飞书等
人工智能·chatgpt·ai作画·aigc·飞书·ai编程·ai写作
人机与认知实验室2 小时前
频率主义 vs 贝叶斯主义中的态、势、感、知
人工智能·机器学习·概率论
qq_571099352 小时前
学习周报三十七
人工智能·深度学习·学习
乾元2 小时前
未来展望: 当 AGI(通用人工智能)出现,网络安全是否会消失?
网络·人工智能·安全·机器学习·网络安全·架构·安全架构
兮℡檬,2 小时前
OpenCV
人工智能·opencv·计算机视觉
2501_918126912 小时前
学习所有6502游戏的系统
java·汇编·嵌入式硬件·学习·游戏