百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

AI代理驱动的财务文档处理管线

ztj100 2025-01-02 20:33 21 浏览 0 评论

SEC 10-K 报告等财务文件通常处理起来复杂而繁琐,但有了正确的工作流程,整个流程就可以改变。

我开发了一个集成 Phidata、n8n 和 Qdrant 的简化解决方案,以自动执行财务文件分析。此工作流程无缝处理报告,提取精确且结构化的见解,并将其存储在强大的矢量数据库 Qdrant 中,从而实现高级搜索和检索功能。结果是一种更快、更有效地处理财务数据的方式,使决策者可以访问和采取行动。

通过这种方法,管理大规模财务运营不仅变得更简单,而且更智能,利用人工智能和自动化来推动有影响力的结果。

1、架构

该架构集成了各种组件,以创建高效且简化的流程来处理 SEC 10-K 财务文件。它从本地文件触发器开始,该触发器监视本地系统上的指定目录以查找新的财务文件。一旦检测到文件,它就会启动工作流程。系统的核心是使用 phidata 设计的 Sec10k 代理,phidata 是一个定制的财务分析代理,由 Claude 3.5 Sonnet 模型提供支持。该代理专门用于分析财务文件并生成结构化的 JSON 输出,使用 PDF 分析工具和精确指令集的组合。该代理还配置了调试和流式传输功能,以确保可靠且动态的分析过程。

然后,处理后的数据流入 Qdrant Vector Store,这是一个强大的矢量数据库,用于存储和管理嵌入。这些嵌入由 Embedding Ollama 模块生成,该模块将提取的内容转换为适合高级搜索和检索的矢量化表示。为了确保文档得到有效处理,默认数据加载器负责文档的准备工作,确保它们满足后续操作的必要要求。内容通过递归字符文本分割器进一步细化,它将文本分解为可管理的块,同时保留其语义完整性。

2、测试文档

为了撰写本文,我考虑了一份 sec 10-Q 文档 ,你可以下载 pdf 进行实验。导入以下 JSON 文件以创建 n8n 工作流。

{
  "name": "filechange2qdrant",
  "nodes": [
    {
      "parameters": {
        "mode": "insert",
        "qdrantCollection": {
          "__rl": true,
          "value": "multi_document_agent",
          "mode": "list",
          "cachedResultName": "multi_document_agent"
        },
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.vectorStoreQdrant",
      "typeVersion": 1,
      "position": [
        60,
        -100
      ],
      "id": "988b5468-8483-49db-832f-15d77333f391",
      "name": "Qdrant Vector Store",
      "credentials": {
        "qdrantApi": {
          "id": "jbqGna16O2L9iR8V",
          "name": "QdrantApi account"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://f309-2401-4900-889d-f100-6539-35f9-5e06-1638.ngrok-free.app/api/v1/analyze",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "query",
              "value": "analyse the financial statement and provide the final response as a structured JSON"
            },
            {
              "name": "file_path",
              "value": "={{ $json.path }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -160,
        -100
      ],
      "id": "1657cd1d-246f-4b28-975b-fcc5b1a6edfc",
      "name": "sec10k agent"
    },
    {
      "parameters": {
        "triggerOn": "folder",
        "path": "YOUR_DATA_PATH",
        "events": [
          "add"
        ],
        "options": {
          "usePolling": true
        }
      },
      "type": "n8n-nodes-base.localFileTrigger",
      "typeVersion": 1,
      "position": [
        -440,
        -100
      ],
      "id": "bd57a74a-4d96-4d86-97f2-b376505da7ad",
      "name": "Local File Trigger"
    },
    {
      "parameters": {
        "jsonMode": "expressionData",
        "jsonData": "={{ $('sec10k agent').item.json }}",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader",
      "typeVersion": 1,
      "position": [
        240,
        100
      ],
      "id": "e4e22dd9-cac9-4bcc-a893-d5339f99a49c",
      "name": "Default Data Loader"
    },
    {
      "parameters": {
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.textSplitterRecursiveCharacterTextSplitter",
      "typeVersion": 1,
      "position": [
        340,
        260
      ],
      "id": "bcbfa95e-a1c2-4144-8525-c7e43b231b3c",
      "name": "Recursive Character Text Splitter"
    },
    {
      "parameters": {
        "model": "nomic-embed-text:latest"
      },
      "type": "@n8n/n8n-nodes-langchain.embeddingsOllama",
      "typeVersion": 1,
      "position": [
        100,
        100
      ],
      "id": "e634d2e6-c6ca-4e31-9dfd-f63d182731c5",
      "name": "Embeddings Ollama",
      "credentials": {
        "ollamaApi": {
          "id": "3fAFU0fFchwovvbD",
          "name": "Ollama account"
        }
      }
    }
  ],
  "pinData": {},
  "connections": {
    "sec10k agent": {
      "main": [
        [
          {
            "node": "Qdrant Vector Store",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Local File Trigger": {
      "main": [
        [
          {
            "node": "sec10k agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Default Data Loader": {
      "ai_document": [
        [
          {
            "node": "Qdrant Vector Store",
            "type": "ai_document",
            "index": 0
          }
        ]
      ]
    },
    "Recursive Character Text Splitter": {
      "ai_textSplitter": [
        [
          {
            "node": "Default Data Loader",
            "type": "ai_textSplitter",
            "index": 0
          }
        ]
      ]
    },
    "Embeddings Ollama": {
      "ai_embedding": [
        [
          {
            "node": "Qdrant Vector Store",
            "type": "ai_embedding",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "e4f7ee4a-b657-46a5-b2ff-82b7cd6c3aad",
  "meta": {
    "instanceId": "e711fbe877d128d86a078d3ddcaeb0c456781dc70945c5f7c313501777f80a45"
  },
  "id": "FHzKIgnbgnbIcZu8",
  "tags": []
}

3、代理实现

首先,项目脚手架如下:

├── api_server.py
├── data
│   └── 0001437749-24-035313.pdf
├── phidata
│   ├── __init__.py
│   ├── anthropic_utility.py
│   ├── financial_agent.py
│   └── financial_models.py
└── requirements.txt

requirements.txt 文件如下所示,该文件具有使用 fastapi 将代理公开为 API 的依赖模块。

phidata==2.7.5
anthropic==0.42.0
openai==1.58.1
python-dotenv==1.0.1
pypdf==5.0.1
fastapi==0.115.6
uvicorn==0.34.0

代理需要 Anthropic API 密钥才能与 Anthropic API 交互。在根文件夹中创建一个 .env 文件并将 API 密钥保存在那里。我的情况是,我同时尝试了 OpenAI 和 Anthropic,所以我两者都有。

OPENAI_API_KEY=sk-proj-****
ANTHROPIC_API_KEY=sk-ant-****

现在让我们从如何构建 Agentic 工具的提示开始。整个系统依赖于 2 个提示 system_prompt 和 user_prompt

def _create_user_prompt(self, file_type: str, page_count: int) -> str:
        """Create the user prompt for the financial analysis."""
        return f"""I'm sending you a {file_type} document as {page_count} images. Please analyze all pages and extract the following financial metrics:
        - EBIT
        - EBITDA
        - Net Income
        - Revenue
        - Currency
        - Units: (Actuals | Thousands)
        - Depreciation
        - Amortization
        - Filing Date
        - Fiscal Year End
        - Language
        - Country
        
        For each metric, please provide:
        1. The exact value
        2. Where it was found (coordinates/page numbers)
        3. The relevant snippet of text
        4. Your reasoning for the extraction
        5. A confidence score
        6. Whether the value was derived or directly extracted
        7. Any calculations performed
        8. Detect the language and country and populate the fields accordingly.
        
        Please provide the output in valid JSON format matching the provided class structure. No other text is needed just the JSON is sufficient
        """
def _create_system_prompt(self) -> str:
        """Create the system prompt for the financial analysis."""
        return """You are a specialized financial analyst with deep expertise in interpreting 
        corporate filings across multiple languages. Your strength lies in identifying, extracting, and 
        validating financial metrics like EBIT, EBITDA, Net Income, and other key performance indicators. 
        You have been trained to provide detailed documentation of your findings, including coordinate references 
        and contextual snippets. You are meticulous about explaining your reasoning and providing confidence scores 
        for each extraction. No other text is needed just the JSON is sufficient.
        
        You MUST provide your analysis in the following JSON structure:
        {
            "company_name": str,
            "filing_date": str,
            "filing_type": str,
            "currency": str,
            "fiscal_year_end": str,
            "language": str,
            "country": str,
            "unity": str
            "metrics": [
                {
                    "attribute": str,
                    "value": float,
                    "coordinates": str | null,
                    "snippet": str,
                    "reasoning": str,
                    "confidence_score": float,
                    "translation": str | null,
                    "is_derived": bool,
                    "calculation_details": {str: float} | null,
                    "unit": str
                }
            ],
            "confidence_summary": float
        }    
        """

现在,该工具将考虑上述提示,使用下面的pydantic模型制作更结构化的输出:

from pydantic import BaseModel
from typing import Optional, Dict, List


class FinancialMetric(BaseModel):
    attribute: str
    value: float
    coordinates: Optional[str]
    snippet: str
    reasoning: str
    confidence_score: float
    translation: Optional[str]
    is_derived: bool
    calculation_details: Optional[Dict[str, float]]
    unit: str


class FinancialAnalysis(BaseModel):
    company_name: str
    filing_date: str
    filing_type: str
    currency: str
    country: str
    language: str
    unit: str
    fiscal_year_end: str
    metrics: List[FinancialMetric]
    confidence_summary: float

实际调用anthropic的工具以及 pdf 上传,如下所示:

def analyze_financial_filing_pdf(self):
        """Analyze financial filings PDF file and return the financial analysis."""
        try:
            # Load and encode the PDF
            with open(self.pdf_path, "rb") as f:
                pdf_data = base64.b64encode(f.read()).decode("utf-8")
                self.logger.info(f"{self.pdf_path} converted to base64")

            reader = PdfReader(stream=self.pdf_path)
            self.logger.info("creating the Anthropic message contract and calling API")

            message = self.client.messages.create(
                model="claude-3-5-sonnet-20241022",
                max_tokens=1024,
                system=self._create_system_prompt(),
                messages=[
                    {
                        "role": "user",
                        "content": [
                            {
                                "type": "document",
                                "source": {
                                    "type": "base64",
                                    "media_type": "application/pdf",
                                    "data": pdf_data
                                },
                                "cache_control": {"type": "ephemeral"}
                            },
                            {
                                "type": "text",
                                "text": self._create_user_prompt("PDF", len(reader.pages))
                            }
                        ]
                    }
                ],
            )

            try:
                response_text = message.content[0].text
                print(response_text)
                response_dict = json.loads(response_text)
                analysis = FinancialAnalysis(**response_dict)
                self.logger.info(json.loads(analysis.model_dump_json(indent=2)))
                return analysis.model_dump_json(indent=2)
            except Exception as e:
                self.logger.error(f"Error parsing response: {e}")
                self.logger.info("Raw response:")
                self.logger.info(message.content
                raise

        except Exception as e:
            self.logger.error(f"Error analyzing PDF: {e}")
            raise

现在让我们创建代理,使用上述工具正确分析财务文件(本例中为 sec-10Q)并生成指定的输出:

from phi.agent import Agent, RunResponse  # noqa
from phi.model.anthropic import Claude

from phidata.anthropic_utility import FinancialAnalyzer
from phidata.financial_models import FinancialAnalysis


def init_financial_analyzer():
    analyzer = FinancialAnalyzer()
    return analyzer


def financial_agent(analyzer: FinancialAnalyzer):
    # Agent that uses JSON mode
    json_mode_agent = Agent(
        model=Claude(id="claude-3-5-sonnet-20241022"),
        name="financial Agent",
        description="financial filings Analysis Agent",
        response_model=FinancialAnalysis,
        tools=[analyzer.analyze_financial_filing_pdf],
        show_tool_calls=True,
        tool_call_limit=5,
        reasoning=False,
        instructions=["Your task is to get the financial analysis in a specified json format"],
        stream=True,
        debug_mode=True,
        structured_outputs=False
    )
    return json_mode_agent

来自代理的响应

上述过程确保代理生成的整个响应被矢量化并存储在 Qdrant 中。这种方法不仅便于高级搜索和检索,而且还支持使用 RAG 流式处理相似性分析等下游任务。将生成的内容集成到 Qdrant 的矢量数据库中,简化了组织、分析和检索上下文和相关信息的过程。

4、结束语

本文介绍的架构和工作流程展示了将人工智能、自动化和矢量化相结合用于财务文档处理的变革潜力。通过无缝集成 Phidata、n8n 和 Qdrant 等工具,该系统不仅简化了复杂文件(如 SEC 10-K 报告)的分析,而且还确保提取的数据结构化、存储并准备好进行高级检索。

该工作流程是朝着更智能、更高效的财务运营迈出的一步,使组织能够精确轻松地做出数据驱动的决策。随着财务数据的复杂性和数量不断增长,这样的解决方案为未来管理此类信息变得更快、可扩展且高度可访问铺平了道路。


原文链接:财务文档的AI处理管线 - 汇智网

相关推荐

sharding-jdbc实现`分库分表`与`读写分离`

一、前言本文将基于以下环境整合...

三分钟了解mysql中主键、外键、非空、唯一、默认约束是什么

在数据库中,数据表是数据库中最重要、最基本的操作对象,是数据存储的基本单位。数据表被定义为列的集合,数据在表中是按照行和列的格式来存储的。每一行代表一条唯一的记录,每一列代表记录中的一个域。...

MySQL8行级锁_mysql如何加行级锁

MySQL8行级锁版本:8.0.34基本概念...

mysql使用小技巧_mysql使用入门

1、MySQL中有许多很实用的函数,好好利用它们可以省去很多时间:group_concat()将取到的值用逗号连接,可以这么用:selectgroup_concat(distinctid)fr...

MySQL/MariaDB中如何支持全部的Unicode?

永远不要在MySQL中使用utf8,并且始终使用utf8mb4。utf8mb4介绍MySQL/MariaDB中,utf8字符集并不是对Unicode的真正实现,即不是真正的UTF-8编码,因...

聊聊 MySQL Server 可执行注释,你懂了吗?

前言MySQLServer当前支持如下3种注释风格:...

MySQL系列-源码编译安装(v5.7.34)

一、系统环境要求...

MySQL的锁就锁住我啦!与腾讯大佬的技术交谈,是我小看它了

对酒当歌,人生几何!朝朝暮暮,唯有己脱。苦苦寻觅找工作之间,殊不知今日之事乃我心之痛,难道是我不配拥有工作嘛。自面试后他所谓的等待都过去一段时日,可惜在下京东上的小金库都要见低啦。每每想到不由心中一...

MySQL字符问题_mysql中字符串的位置

中文写入乱码问题:我输入的中文编码是urf8的,建的库是urf8的,但是插入mysql总是乱码,一堆"???????????????????????"我用的是ibatis,终于找到原因了,我是这么解决...

深圳尚学堂:mysql基本sql语句大全(三)

数据开发-经典1.按姓氏笔画排序:Select*FromTableNameOrderByCustomerNameCollateChinese_PRC_Stroke_ci_as//从少...

MySQL进行行级锁的?一会next-key锁,一会间隙锁,一会记录锁?

大家好,是不是很多人都对MySQL加行级锁的规则搞的迷迷糊糊,一会是next-key锁,一会是间隙锁,一会又是记录锁。坦白说,确实还挺复杂的,但是好在我找点了点规律,也知道如何如何用命令分析加...

一文讲清怎么利用Python Django实现Excel数据表的导入导出功能

摘要:Python作为一门简单易学且功能强大的编程语言,广受程序员、数据分析师和AI工程师的青睐。本文系统讲解了如何使用Python的Django框架结合openpyxl库实现Excel...

用DataX实现两个MySQL实例间的数据同步

DataXDataX使用Java实现。如果可以实现数据库实例之间准实时的...

MySQL数据库知识_mysql数据库基础知识

MySQL是一种关系型数据库管理系统;那废话不多说,直接上自己以前学习整理文档:查看数据库命令:(1).查看存储过程状态:showprocedurestatus;(2).显示系统变量:show...

如何为MySQL中的JSON字段设置索引

背景MySQL在2015年中发布的5.7.8版本中首次引入了JSON数据类型。自此,它成了一种逃离严格列定义的方式,可以存储各种形状和大小的JSON文档,例如审计日志、配置信息、第三方数据包、用户自定...

取消回复欢迎 发表评论: