摘要:本文将分享如何使用Ollama本地大模型,配合VSCode+ClaudeCode插件,从零开始构建一个基于RAG架构的财报分析Agent。我们将直面PDF解析OOM、多线程优化等实战难题,经过与longcat-2.0-preview模型的多轮"言语拉扯"和极端测试,最终打造出能自动获取PDF年报、智能分析并生成Markdown报告的完整系统。


📌 目录

  1. 为什么需要财报分析Agent?
  2. 技术选型:为什么选择Ollama本地大模型?
  3. 开发环境搭建:VSCode + ClaudeCode + longcat-2.0-preview
  4. 项目架构设计
  5. 开发过程中的"血泪史"
  6. 最终实现:功能演示
  7. 项目亮点与优化
  8. 总结与展望

为什么需要财报分析Agent?

相信各位技术同仁都有过这种体验:

场景一:老板突然丢给你一份194页的PDF年报,“小王,帮我分析下这家公司2024年的营收增长率和净利润率,顺便对比下行业平均水平…”

场景二:你需要分析同行业5家公司的财报,每家平均200页,总共1000+页的PDF,手动提取关键指标?不存在的,这辈子都不可能手动的。

场景三:你终于写了一个Python脚本提取PDF文本,结果发现表格数据全乱了,财务报表的表格结构复杂到你怀疑人生…

于是,我决定:与其重复劳动,不如造个轮子。一个能自动下载、解析、分析财报并生成报告的Agent,它不香吗?


技术选型:为什么选择Ollama本地大模型?

为什么是Ollama?

方案 优点 缺点
Ollama本地模型 ✅ 数据隐私安全
✅ 无API调用费用
✅ 低延迟
✅ 可离线使用
❌ 需要GPU资源
❌ 模型能力受限
云端API(GPT-4等) ✅ 模型能力强
✅ 无需本地资源
❌ 数据泄露风险
❌ API费用高
❌ 网络依赖

最终选择:Ollama + 可外接API的双模式设计

这样既能保证数据隐私(财报数据可是商业机密!),又能在本地模型能力不足时切换到云端API。一举两得!

技术栈一览

后端框架: FastAPI + Uvicorn
PDF解析: Docling 2.92.0 + PyMuPDF
向量数据库: ChromaDB (嵌入式,无服务端)
大模型: Ollama (默认) / OpenAI兼容API (DeepSeek, Moonshot等)
Embedding: Ollama Embedding (默认 nomic-embed-text)
GPU加速: CUDA via onnxruntime-gpu 1.25 (RTX 4080 SUPER 16GB)

开发环境搭建:VSCode + ClaudeCode + longcat-2.0-preview

环境准备

步骤一:安装VSCode和ClaudeCode插件

  1. 下载安装 VSCode
  2. 在插件市场搜索并安装 ClaudeCode 插件
  3. 配置longcat-2.0-preview模型API密钥

步骤二:安装Ollama并拉取模型
如果你也是16G显存,以下两个本地可运行的模型刚刚好。

# Windows下安装Ollama
# 访问 https://ollama.com/download 下载安装

# 拉取模型和embedding
ollama pull qwen3.5:9b
ollama pull qwen3embedding:4b

步骤三:创建Python虚拟环境

# 创建虚拟环境
python -m venv venv

# 激活虚拟环境 (Windows)
venv\Scripts\activate

# 安装依赖
pip install -r requirements.txt

使用ClaudeCode进行AI编程

ClaudeCode插件让我能够:

  1. 代码生成:描述需求,模型自动生成代码框架
  2. 代码审查:自动发现潜在bug和优化点
  3. 重构建议:提供代码重构方案
  4. 文档生成:自动生成函数和类的文档字符串

实战技巧

  • 使用 /clear 命令定期清理上下文,保持对话聚焦
  • 将大型任务拆解为多个小任务,逐步完成
  • 遇到模型理解偏差时,提供更详细的上下文和示例

项目架构设计

整体逻辑架构图

自然语言请求

API调用

SSE流式输出

同步/异步

工具调用

顺序执行

Docling

ChromaDB

语义检索

百度搜索

Ollama/API

用户

Web UI

FastAPI API层

Agent模式

确定性流水线

工具集

处理步骤

download_pdfs

parse_pdfs

web_search

rag_query

save_report

下载PDF

解析PDF

向量化索引

RAG检索

联网搜索

LLM分析

生成报告

PDF解析引擎

向量数据库

联网搜索

大语言模型

架构图说明

  1. 用户交互层:支持Web UI和API两种方式
  2. 业务逻辑层:提供Agent模式和确定性流水线两种分析路径
  3. 工具/步骤层:具体的功能实现模块
  4. 基础设施层:PDF解析引擎、向量数据库、大模型等

RAG架构详解

PDF文档

文本提取

文本分块

向量化
nomic-embed-text

ChromaDB
向量数据库

用户提问

向量化查询

语义检索
Top-K

相关文档片段

上下文组装

联网搜索结果

Prompt工程

LLM生成

Markdown报告

RAG核心流程

  1. 索引构建阶段

    • PDF文档 → Docling解析 → 文本分块 → Ollama Embedding → ChromaDB存储
  2. 检索生成阶段

    • 用户提问 → 向量化 → ChromaDB语义检索 → 获取相关文档片段
    • 组装Prompt(文档片段 + 联网搜索结果 + 用户问题)
    • LLM生成分析报告

RAG是什么,为什么需要RAG?

  • RAG检索增强生成。它是一种架构方法,解决的是大语言模型(LLM)的三大核心缺陷:
  • 解决幻觉问题:基于真实文档生成,减少大模型编造内容
  • 知识更新快:无需重新训练模型,只需更新向量库
  • 可解释性强:能够追溯答案来源的具体文档片段

分层设计:API → Core → Infra

┌─────────────────────────────────────────────────────┐
│                   API Layer                        │
│  ┌─────────────┐  ┌─────────────┐                │
│  │  Web UI     │  │  REST API   │                │
│  │  (Jinja2)   │  │  (FastAPI)  │                │
│  └─────────────┘  └─────────────┘                │
└─────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────┐
│                   Core Layer                       │
│  ┌─────────────┐  ┌─────────────┐  ┌──────────┐ │
│  │   Agent     │  │   Services  │  │  Domain  │ │
│  │   Loop      │  │  (业务服务)  │  │  (领域)  │ │
│  └─────────────┘  └─────────────┘  └──────────┘ │
└─────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────┐
│                  Infra Layer                       │
│  ┌──────┐  ┌──────┐  ┌──────┐  ┌────────────┐ │
│  │ Ollama│  │Chroma│  │Docling│  │  External  │ │
│  │  LLM  │  │  DB  │  │ Parser│  │  API/Search│ │
│  └──────┘  └──────┘  └──────┘  └────────────┘ │
└─────────────────────────────────────────────────────┘

分层设计的优势

  1. API Layer:负责用户交互,处理HTTP请求,返回响应
  2. Core Layer:纯业务逻辑,无副作用,易于测试和复用
  3. Infra Layer:副作用隔离,所有外部依赖(大模型、数据库、文件系统等)的统一适配

AI开发过程

  • 明确需求目标

技巧:都知道token很贵的,你对大模型说的p话越多,token消耗就越多。为了省钱又高效,咱可以跟豆包、元宝、千问众神唠嗑,让他们替你总结需求生成大模型一听就懂的“提示词”

  • 给AI定规矩

上手就丢个规矩文档给他,让他先读一读,知道自己应该遵守什么。认识自己的处境(系统运行环境是windows还是liunx)。当然,文档也可以请众神协助。

  • 规矩定完搭骨架

让AI确认技术架构和技术栈后,该搭建文件夹构架,该部署开发环境安装各种依赖啊,包啊的,该干嘛干嘛。

  • 一切就绪写代码

准备就绪后就让AI开始按需求写代码了。聪明的AI基本可以从头到底一溜的给你写完,交付一个成品,并告知你启动方法。

  • 来回拉扯修bug

这时候重头戏来了,小点的项目也许就真的好了。而稍微复杂点,大点的项目多数会有报错,或者你不满意的地方。
没事,像训斥员工一样训斥他!平时老板怎么骂你的,你怎么骂他,骂到不报错了,骂道满足自己要求了,大家happy!
放心,AI是模范员工,绝对不会记仇!

开发过程中的"血泪史"

坑一:PDF解析OOM问题

问题描述

当我兴冲冲地扔给系统一份194页的财报PDF时,期待着它自动解析并分析,结果…

std::bad_alloc

没错,C++堆内存分配失败了!😱

原因分析

Docling使用pypdfium2(C++库)解码PDF页面。当单次处理过多页面(50-80+)时,C++堆内存碎片化会导致std::bad_alloc与系统可用内存无关

即使我的机器有32GB RAM + 16GB GPU显存,依然会挂!

Docling运行分析时本身会加载2个模型分析表格和层。然后会将页面生成bmp并一次性提交内存,如果系统设置的虚拟内存不是足够大(一个pdf几十G),分分钟内存崩溃。所以首先要让windows自己管理虚拟内存,或者自己指定个极大的值(同时分析4个pdf起码设置130G),其次极致优化Docling,方向就是压缩图片,pdf分页分线程,将模型强制运行在显存中并进行CUDA加速,以下分别描述

解决方案:自适应并行分块处理

# 核心思路:将大PDF拆分为30页的小块,每个块使用独立的子进程处理
PDF (194)[1-30] [31-60] ... [181-194]
                    ↓
           ThreadPoolExecutor (自适应worker数)
                    ↓
         ┌─────┬─────┬─────┐
         │子进程│子进程│子进程│  ← 每个chunk独立子进程
         │chunk│chunk│chunk│     converter构建一次,处理完退出
         └─────┴─────┴─────┘
                    ↓
           按页码顺序重组markdown

自适应并行度计算

# 每个30页面chunk约消耗44GB提交内存(commit memory)
# 系统提交内存上限 ≈ 166GB(32GB RAM + 100GB 页面文件)
# 并行度 = (安全上限 155GB - 当前基线) / 44GB

import psutil

def calculate_max_workers():
    COMMIT_LIMIT = 155  # GB
    CHUNK_MEMORY = 44   # GB per worker
    
    current_commit = psutil.virtual_memory().used / (1024**3)
    available = COMMIT_LIMIT - current_commit
    
    max_workers = max(1, int(available / CHUNK_MEMORY))
    return max_workers

优化效果

方案 处理时间 内存占用 稳定性
原始方案(单进程) ~300秒 峰值60GB+ ❌ 经常OOM
分块+子进程 ~143秒 峰值44GB/worker ✅ 稳定
自适应并行(3worker) ~50秒 峰值132GB ✅ 稳定且快速

坑二:多线程压缩执行时间

问题升级

解决了OOM问题后,我发现处理4个PDF(总共597页)需要16分钟,这还只是解析阶段!加上向量化和LLM分析,用户可能要等20分钟才能看到报告…

优化策略

  1. 自适应并行度:根据系统当前内存使用情况,动态调整并行worker数
  2. GPU加速:使用CUDA via onnxruntime-gpu进行布局/表格ONNX推理
  3. 参数调优
# 性能调优参数
ENV_VARS = {
    "OMP_NUM_THREADS": 20,           # 匹配物理核心数
    "ORT_INTER_OP_THREADS": 4,       # ONNX Runtime跨操作并行
    "ORT_INTRA_OP_THREADS": 8,       # ONNX Runtime操作内并行
}

PDF_PIPELINE_OPTIONS = {
    "layout_batch_size": 16,          # 布局模型批大小
    "table_batch_size": 16,           # 表格模型批大小
    "images_scale": 0.5,             # PDF渲染缩放(~4倍解码速度提升)
    "TableFormerMode": "FAST",        # 快速表格检测
    "do_cell_matching": False,        # 跳过单元格匹配(~2x提速)
}

修改Docling的CUDA检测逻辑

# Docling原始代码调用 torch.cuda.is_available()
# 但我安装的PyTorch是CPU-only版本 😅

# 解决方案:monkey-patch!
import docling.utils.accelerator_utils as utils

def custom_decide_device(accelerator_device, supported_devices):
    """自定义CUDA检测逻辑,改为检查ONNX Runtime的CUDAExecutionProvider"""
    import onnxruntime as ort
    
    if "cuda" in supported_devices and "CUDAExecutionProvider" in ort.get_available_providers():
        return "cuda"
    return "cpu"

# 运行时补丁,不修改docling库源码
utils.decide_device = custom_decide_device

最终性能

4个PDF,597页面,143秒 → 50秒(~3x提速)
0.24秒/页(GPU加速 + 多线程并行)

坑三:与longcat模型的多轮"拉扯"

场景还原
当你发现longcat运行着…运行着…突然开始跟你讲英文了,甚至是突然忘记了各种包已经装过,忙着开始找工具了,别犹豫他的健忘症又犯了

我(##!@#@#):回去复习下项目概况和你的任务列表...

longcat:yes ,sir

我:`不对,工具调用应该用这个格式...而且要考虑最大轮次限制...`

longcat:```python
# 修改后的代码,但又引入了新问题:
# 1. 消息历史管理混乱,导致上下文溢出
# 2. 工具结果没有正确追加到对话

我:😡 你看清楚我的需求!消息历史应该这样管理...

(虽然Claude有强大的记忆,依然要记得让AI经常保存任务进度生成TASK.MD,大模型万一重启个服务器什么的,你的session就断了)

经过N轮迭代,最终方案

class AgentLoop:
    """Agent主循环:LLM ↔ 工具执行"""
    
    def __init__(self, max_turns: int = 10):
        self.max_turns = max_turns
        self.messages = []  # 对话历史
        
    async def run(self, user_message: str) -> str:
        """执行Agent循环"""
        self.messages.append({
            "role": "user",
            "content": user_message
        })
        
        for turn in range(self.max_turns):
            # 1. LLM推理
            response = await self.llm.chat(messages=self.messages)
            
            # 2. 检查是否有工具调用
            if not response.tool_calls:
                # 无工具调用,返回最终答案
                return response.content
                
            # 3. 执行工具
            tool_results = []
            for tool_call in response.tool_calls:
                result = await self.execute_tool(tool_call)
                tool_results.append(result)
                
            # 4. 工具结果追加到对话
            self.messages.append({
                "role": "assistant",
                "content": response.content,
                "tool_calls": response.tool_calls
            })
            self.messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": json.dumps(tool_results)
            })
            
        # 达到最大轮次,强制返回
        return "分析超时,请简化问题或稍后重试。"

极端测试

为了验证Agent的稳定性,我设计了一系列极端测试:

  1. 循环陷阱测试:故意让工具返回诱导LLM重复调用相同工具的消息
  2. 超长对话测试:模拟20轮工具调用,检查上下文管理
  3. 工具异常测试:模拟工具执行失败,验证错误处理
  4. 并发请求测试:同时发起10个分析请求,检查资源竞争

测试结果

✅ 所有测试通过!Agent能够稳定地完成复杂的多步骤任务。


最终实现:功能演示

特点一:自动获取PDF年报

用户输入

分析以下财报 https://www.example.com/annual_report_2024.pdf 
该公司2024年的营收和净利润是多少?与2023年相比增长率如何?

给到财报地址,填写需要分析要点
**关注我,私信发送提示词生成器,自动获取年报url ^ ^

系统执行流程

  1. 自动下载PDF:从指定URL下载财报PDF到本地
  2. 智能解析:使用Docling提取文本和表格,保留文档结构
  3. 向量化索引:将文本内容分块,使用Ollama Embedding生成向量,存储到ChromaDB
  4. RAG检索:根据用户问题,语义检索相关文档片段
  5. 联网搜索:通过百度搜索补充实时数据(股价、行业对比等)
  6. LLM分析:将文档片段、搜索结果、用户问题组装成Prompt,调用大模型生成分析
  7. 生成报告:将分析结果保存为Markdown格式,支持SSE流式输出到前端

特点二:Agent模式自动编排

执行步骤

Agent模式的优势

  • 自动规划:LLM根据任务自动决定调用哪些工具、调用顺序
  • 动态决策:根据工具执行结果,动态调整后续行动
  • 多步骤任务:能够完成需要多个步骤的复杂任务

示例

用户:分析特斯拉2024年Q4财报,对比比亚迪同期表现,生成竞品分析报告

Agent思考过程:
1. 需要下载特斯拉2024 Q4财报 → 调用 download_pdfs
2. 需要解析PDF → 调用 parse_pdfs
3. 需要搜索比亚迪财报 → 调用 web_search
4. 需要检索特斯拉财报关键数据 → 调用 rag_query(多次)
5. 需要检索比亚迪数据 → 调用 rag_query(多次)
6. 需要生成对比报告 → LLM生成最终报告
7. 保存报告 → 调用 save_report

特点三:双模式支持(本地+外接)

设置界面支持本地部署大模型与外部api调用大模型

本地模式(Ollama)

# .env 配置
LLM_PROVIDER=ollama
OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_MODEL=qwen3.5:9b
OLLAMA_EMBEDDING_MODEL=qwen3embedding:4b

外部API模式(如DeepSeek)

# .env 配置
LLM_PROVIDER=external
EXTERNAL_LLM_BASE_URL=https://api.deepseek.com/v1
EXTERNAL_LLM_MODEL=deepseek-chat
EXTERNAL_LLM_API_KEY=your-api-key

无缝切换

系统通过LlmClientFactory工厂模式,根据配置自动选择对应的LLM客户端,业务代码无需修改!

class LlmClientFactory:
    @staticmethod
    def create(settings):
        if settings.llm_provider == "ollama":
            return OllamaLlmClient(settings.ollama_base_url, settings.ollama_model)
        elif settings.llm_provider == "external":
            return ExternalLlmClient(
                settings.external_llm_base_url,
                settings.external_llm_model,
                settings.external_llm_api_key
            )

项目亮点与优化

亮点一:Lazy-loading设计,节省启动内存

class _LazyDoclingAdapter:
    """惰性加载代理:Docling模型在首次使用时才加载"""
    
    def __init__(self) -> None:
        self._adapter: PdfParserAdapter | None = None

    def parse(self, file_path: str) -> tuple[str, int]:
        # Docling加载~200MB+的ML模型
        # 通过惰性加载,启动时不占用内存
        if self._adapter is None:
            logger.info("Lazy-loading Docling parser adapter (first use)...")
            self._adapter = DoclingParserAdapter()
            
        return self._adapter.parse(file_path)

    def release(self) -> None:
        """释放模型内存"""
        if self._adapter is not None:
            logger.info("Releasing Docling parser adapter to free memory...")
            self._adapter = None

效果

  • 启动时间:从10秒降低到1秒
  • 启动内存:从2GB降低到100MB
  • 模型在首次解析时才加载,用完后可以手动释放

亮点二:未修改Docling源码,全运行时补丁

为什么不直接修改Docling源码?

  • ✅ 方便升级:Docling版本更新时,无需手动合并修改
  • ✅ 可维护性:所有定制逻辑集中在项目代码中
  • ✅ 兼容性:通过monkey-patch和公开API实现,不依赖内部实现

定制点

  1. CUDA检测逻辑:monkey-patch docling.utils.accelerator_utils.decide_device
  2. 自适应并行调度:项目自有代码 docling_adapter.py
  3. 性能参数调优:通过Docling公开API配置 PdfPipelineOptions

亮点三:结构化日志,方便调试

# 使用JSON Formatter,日志包含时间戳、日志级别、模块名、结构化数据
{
  "timestamp": "2026-05-06 20:30:15.123",
  "level": "INFO",
  "module": "src.core.services.parser",
  "message": "PDF parsing completed",
  "data": {
    "file": "annual_report_2024.pdf",
    "pages": 194,
    "time_seconds": 50.2,
    "chunks": 582
  }
}

总结与展望

生成的MarkDown格式分析报告

项目成果

功能完整:自动下载、解析、分析财报并生成报告
性能优秀:597页PDF解析仅需50秒(GPU加速+多线程)
稳定可靠:自适应并行+子进程隔离,解决OOM问题
灵活扩展:支持本地Ollama和外部API双模式
用户友好:Web UI + SSE流式输出 + Markdown报告

技术收获

  1. RAG架构实战:深入理解检索增强生成的原理和优化方法
  2. 大模型应用开发:Agent模式、工具调用、提示词工程
  3. 性能优化:多线程并行、GPU加速、内存管理
  4. 软件开发:分层架构、依赖注入、工厂模式、惰性加载

未来展望

  • 🚀 支持更多文档格式:Word、Excel、PPT等
  • 🚀 多模态分析:支持图表、图片的解析和分析
  • 🚀 多语言支持:支持中英文财报分析
  • 🚀 部署优化:Docker容器化、云原生部署

**
项目开源

仓库地址:

https://gitcode.com/ddly2000/rag-financial-agent.git
**

参考资料(让AI自己琢磨)

  1. Ollama官方文档
  2. Docling PDF解析库
  3. ChromaDB向量数据库
  4. RAG论文:Retrieval-Augmented Generation
  5. FastAPI官方文档

如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、关注!你的支持是我持续创作的动力! 🙏 关注我私信索取年报分析提示词生成器

有任何问题或建议,欢迎在评论区留言讨论! 💬


作者:[谢绝采访]
发布时间:2026年5月
标签:#Ollama #RAG #财报分析 #AI编程 #VSCode #ClaudeCode

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐