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 分钟前
2026年复利精进:我的每日觉醒与成长密码
学习·思维模型·知识复利·复利·独立
云天AI实战派13 分钟前
Agentic AI 全流程实战:用 OpenAI on AWS 搭一个餐饮补货智能体,从 API 调用到容器化上线
人工智能·云计算·aws
万岳科技程序员小金22 分钟前
2026智慧药店系统源码趋势:药店APP+小程序开发新方向
人工智能·电子处方小程序·药店软件开发·药店系统源码·药店app开发·药店平台搭建·药店小程序
sakiko_27 分钟前
UIKit学习笔记4-使用UITableView制作滚动视图
笔记·学习·ios·swift·uikit
xingyuzhisuan32 分钟前
稳定性考验:连续跑7天,哪家云主机不重启、不掉线?
服务器·人工智能·gpu算力
sanshanjianke42 分钟前
AI辅助网文创作理论研究笔记(十):软件框架设计——模块化B/S架构
人工智能·ai写作
云天AI实战派1 小时前
AI 智能体问题排查指南:ChatGPT、API 调用到 Agent 上线失灵的全流程修复手册
大数据·人工智能·python·chatgpt·aigc
晓梦林1 小时前
MAZESEC-X1靶场学习笔记
笔记·学习
Tutankaaa2 小时前
知识竞赛题库设计全攻略
人工智能·算法
我的xiaodoujiao2 小时前
API 接口自动化测试详细图文教程学习系列15--项目实战演练2
python·学习·测试工具·pytest