什么是‘全自动财务审计 Agent’:利用 LangGraph 处理数万张发票,并自动对照税务法规发现异常项

全自动财务审计Agent:利用LangGraph处理数万张发票,并自动对照税务法规发现异常项

各位同仁,各位技术先锋,大家好!

今天,我们聚焦一个既传统又充满挑战的领域:财务审计。当谈到“审计”,许多人脑海中浮现的可能是堆积如山的文件、熬夜核对的疲惫面孔,以及那份与日俱增的、对效率和准确性的渴望。在数字经济时代,企业每天产生数万、乃至数十万张发票及交易数据,传统的人工审计模式已经举步维艰,面临着效率低下、成本高昂、错误率高和难以规模化等诸多瓶颈。

然而,技术的浪潮从不停歇。大语言模型(LLMs)的崛起,结合Agentic Workflow的理念,为我们描绘了一幅全新的自动化审计图景。今天,我将为大家深入剖析,如何利用LangGraph这个强大的工具,构建一个“全自动财务审计Agent”,它不仅能处理海量的发票数据,还能智能地对照复杂的税务法规,自动发现潜在的异常项和合规风险。

一、 数字化审计的未来与挑战

传统审计的痛点显而易见:

  • 效率瓶颈: 人工审核速度慢,难以应对大体量数据。
  • 成本高昂: 大量人力投入,且专业审计师资源稀缺。
  • 错误率与遗漏: 人为操作易出错,难以发现所有细微异常。
  • 滞后性: 审计周期长,风险发现往往滞后于业务发生。
  • 知识更新: 税务法规频繁变动,审计人员难以实时掌握最新规定。

而AI在财务审计中的潜力是巨大的:

  • 自动化数据处理: 替代重复性高、耗时长的任务。
  • 智能分析与洞察: 从海量数据中发现人眼难以察觉的模式和异常。
  • 实时性与预警: 缩短审计周期,提前预警风险。
  • 知识赋能: 快速学习并应用最新的法规知识。

我们需要的,不仅仅是一个简单的自动化工具,而是一个能够像人类审计师一样思考、判断、执行,并能与外部系统交互的智能“Agent”。LangGraph,正是构建这种复杂Agent系统,特别是涉及多步骤决策和协作的Agent工作流的理想框架。

二、 核心组件与技术栈概览

构建一个全自动财务审计Agent,我们需要一套强大的技术栈,并以Agentic Workflow的理念进行设计。这套系统将不再是简单的线性处理,而是多个Agent协同工作,每个Agent专注于特定任务,并通过共享状态和工具进行沟通。

整体架构设计理念:Agentic Workflow

我们的Agent系统将模拟一个审计团队。不同的“审计师”(Agent)负责不同的环节,如“数据录入员”负责发票识别,“法规专家”负责合规性检查,“风险分析师”负责异常评估。LangGraph的图结构天然适合这种多Agent协作的编排。

关键技术栈:

组件 功能描述 核心技术/工具
认知核心 理解、推理、决策、生成文本 大语言模型 (LLMs),如GPT系列、Claude、文心一言等
Agent编排 管理Agent的行为、状态、工具调用和工作流逻辑 LangChain, LangGraph
数据摄取 纸质/图片发票数字化,文本提取 OCR技术 (Tesseract, Azure AI Vision, Google Cloud Vision, PaddleOCR)
数据结构化 从非结构化文本中提取关键实体,转化为结构化数据 LLMs, Pydantic, 正则表达式
知识库管理 存储和检索税务法规、行业标准、企业政策等 向量数据库 (Chroma, Pinecone, Weaviate), RAG (Retrieval Augmented Generation)
业务数据源 存储发票原始数据、企业内部交易数据(如采购订单、入库单、付款记录) 关系型数据库 (PostgreSQL, MySQL), NoSQL数据库
外部工具接口 与外部系统(如国家税务总局发票查验平台、ERP系统)交互 API调用封装、函数调用 (Function Calling)
结果展示 审计报告生成、异常项可视化 LLMs, 数据可视化库 (Matplotlib, Seaborn), Web框架 (Streamlit, Dash)

三、 发票数据摄取与预处理

审计的第一步是获取数据。数万张发票可能以各种形式存在:扫描件、PDF、图片、甚至手写票据。面对异构的发票格式和潜在的低质量扫描件,我们需要一个鲁棒的数据摄取层。

挑战:

  • 格式多样性: 增值税专用发票、普通发票、电子发票、不同行业发票模板差异巨大。
  • 图像质量: 模糊、倾斜、光照不均、印章遮挡等。
  • 信息密度: 一张发票上包含大量结构化和半结构化信息。

OCR层:
光学字符识别(OCR)是实现发票数字化的核心。主流的OCR服务各有特点:

  • Tesseract: 开源,本地部署,配置复杂,对高质量图像识别效果好,但对复杂版面和低质量图像表现一般。
  • 云服务OCR (Azure AI Vision, Google Cloud Vision, AWS Textract): 识别精度高,支持多语言,自带版面分析和关键信息提取能力,但成本较高。
  • PaddleOCR: 百度开源,支持中英文,对复杂场景和低质量图像有良好优化,易于部署和使用。

为了通用性,我们通常会结合使用,或者选择一个性能卓越的云服务。OCR的输出通常是文本块、坐标和置信度,但这些原始输出往往需要进一步的后处理。

OCR结果的后处理与清洗:
原始OCR结果可能存在以下问题:

  1. 错别字: “发票”识别成“发栗”,“金额”识别成“金领”。
  2. 格式不统一: 日期格式各异(2023-10-26, 2023年10月26日, 23/10/26)。
  3. 信息缺失或冗余: 某些关键信息未识别,或识别出不相关的文本。
  4. 行项错位: 商品明细行可能被错误地识别和组合。

清洗策略:

  • 词典纠错: 针对财务常用词汇建立词典进行模糊匹配和纠错。
  • 正则表达式: 标准化日期、金额、税号等格式。
  • 上下文推理: 利用LLM的强大理解能力,结合上下文纠正识别错误。

代码示例:模拟OCR输出及初步清洗

import re
from typing import Dict, Any

# 假设这是一个简化的OCR服务接口
def mock_ocr_invoice(image_path: str) -> Dict[str, Any]:
    """
    模拟OCR服务对发票图片的识别结果。
    实际中,这里会调用真正的OCR API。
    """
    print(f"正在模拟OCR处理文件: {image_path}")
    # 模拟不同发票内容的OCR结果
    if "invoice_001.png" in image_path:
        return {
            "full_text": """
            增值税普通发票
            发票代码: 110023456789
            发票号码: 00123456
            开票日期: 2023年10月26日
            购方名称: 北京智慧科技有限公词
            购方税号: 91110000789012345X
            销方名称: 上海创新软件服务有限公司
            销方税号: 91310000123456789Y
            金额: 10000.00
            税额: 1300.00
            价税合计(大写): 壹万壹仟叁佰元整
            价税合计(小写): 11300.00
            货物或应税劳务、服务名称  规格型号  单位  数量  单价    金额    税率  税额
            软件开发服务                /       项    1     10000.00  10000.00  13%   1300.00
            备注: 远程服务费
            """,
            "blocks": [
                {"text": "发票代码: 110023456789", "bbox": [10, 50, 200, 60]},
                {"text": "开票日期: 2023年10月26日", "bbox": [250, 70, 400, 80]},
                {"text": "购方名称: 北京智慧科技有限公词", "bbox": [10, 100, 300, 110]}, # 故意制造错别字
                {"text": "金额: 10000.00", "bbox": [300, 200, 400, 210]},
                {"text": "税额: 1300.00", "bbox": [410, 200, 500, 210]},
                {"text": "软件开发服务", "bbox": [10, 250, 100, 260]},
                {"text": "10000.00", "bbox": [300, 250, 350, 260]},
                {"text": "13%", "bbox": [400, 250, 430, 260]},
                {"text": "1300.00", "bbox": [450, 250, 500, 260]},
            ],
            "confidence": 0.95 # 总体置信度
        }
    elif "invoice_002.pdf" in image_path:
         return {
            "full_text": """
            增值税专用发票
            开票日期: 2023/11/01
            购方名称: 上海未来科技公司
            购方税号: 91310000ABCDEFGHIJ
            销方名称: 深圳创新电子厂
            销方税号: 91440000KLMNOPQRST
            金额: 5000.00
            税额: 650.00
            商品名称  数量  单价  金额  税率  税额
            电子元器件  100   50.00 5000.00 13%   650.00
            """,
            "confidence": 0.92
        }
    return {"full_text": "", "confidence": 0}

def post_process_ocr_text(ocr_output: Dict[str, Any]) -> str:
    """
    对OCR识别出的原始文本进行初步清洗和标准化。
    """
    text = ocr_output.get("full_text", "")
    # 1. 常见错别字替换
    text = text.replace("有限公词", "有限公司")
    text = text.replace("金领", "金额")

    # 2. 标准化日期格式
    text = re.sub(r'(d{4})[年/-](d{1,2})[月/-](d{1,2})[日]?', r'1-2-3', text)

    # 3. 去除多余的空格和换行
    text = re.sub(r's+', ' ', text).strip()

    return text

# 示例调用
ocr_raw_output = mock_ocr_invoice("invoice_001.png")
cleaned_text = post_process_ocr_text(ocr_raw_output)
print("n--- 原始OCR输出 ---")
print(ocr_raw_output["full_text"])
print("n--- 清洗后的文本 ---")
print(cleaned_text)

# 进一步处理:提取发票类型
def identify_invoice_type(text: str) -> str:
    if "增值税专用发票" in text:
        return "专用发票"
    elif "增值税普通发票" in text or "增值税电子普通发票" in text:
        return "普通发票"
    return "未知"

invoice_type = identify_invoice_type(cleaned_text)
print(f"n识别到的发票类型: {invoice_type}")

上述代码模拟了OCR的输出,并展示了如何进行简单的文本清洗和发票类型识别。在实际系统中,这将是一个更加复杂和精细的过程。

四、 智能发票信息提取与结构化

仅仅获取文本是不够的,我们需要将这些非结构化的文本转化为计算机可理解和处理的结构化数据,如发票号码、开票日期、购销方信息、商品明细等。这是LLM大显身手的地方。

LLM的威力:
大语言模型具有强大的命名实体识别(NER)和关系抽取能力。通过精心设计的Prompt,LLM可以从复杂的发票文本中准确地识别出各种实体,并理解它们之间的关系。

Pydantic模型:强制LLM输出结构化数据
为了确保LLM输出的数据格式统一且符合预期,我们通常会结合Pydantic这样的数据校验库。LangChain/LangGraph提供了与Pydantic模型集成的能力,可以自动生成Schema并指导LLM输出JSON格式的数据。

Agentic Approach:多Agent协作提取
发票信息提取可以分解为多个子任务,由不同的Agent负责,提高准确性和鲁棒性。

  • Agent 1: 基础信息提取Agent

    • 任务: 提取发票代码、发票号码、开票日期、购销方名称、税号、总金额、总税额、价税合计等关键基础信息。
    • Prompt策略: 明确指示LLM提取哪些字段,并提供Pydantic模型作为输出格式指导。
  • Agent 2: 商品明细提取Agent

    • 任务: 识别发票中的商品清单,包括商品名称、规格型号、单位、数量、单价、金额、税率、税额等。
    • 挑战: 商品明细通常是表格形式,但OCR可能将其识别成连续文本。LLM需要理解表格结构。
    • Prompt策略: 强调这是表格数据,要求逐行提取,并提供Pydantic模型定义每一行商品。
  • 错误处理与重试机制:

    • 如果LLM输出的JSON不符合Pydantic模型,或者某些关键字段缺失,Agent可以自动重试,或者调整Prompt再次尝试。
    • 可以设计一个“验证Agent”来检查提取结果的逻辑一致性(例如,总金额是否等于商品明细金额之和)。

代码示例:Pydantic模型定义与LLM信息提取概念

首先,定义数据模型:

from pydantic import BaseModel, Field, ValidationError
from typing import List, Optional
import json

# 定义商品明细模型
class LineItem(BaseModel):
    name: str = Field(..., description="货物或应税劳务、服务名称")
    specification: Optional[str] = Field(None, description="规格型号")
    unit: Optional[str] = Field(None, description="单位")
    quantity: float = Field(..., description="数量")
    unit_price: float = Field(..., description="不含税单价")
    amount: float = Field(..., description="不含税金额")
    tax_rate: str = Field(..., description="税率,例如 '13%', '6%', '免税'")
    tax_amount: float = Field(..., description="税额")

# 定义发票主体信息模型
class Invoice(BaseModel):
    invoice_code: str = Field(..., description="发票代码")
    invoice_number: str = Field(..., description="发票号码")
    issue_date: str = Field(..., description="开票日期,格式为YYYY-MM-DD")

    buyer_name: str = Field(..., description="购方名称")
    buyer_tax_id: str = Field(..., description="购方税号")

    seller_name: str = Field(..., description="销方名称")
    seller_tax_id: str = Field(..., description="销方税号")

    total_amount_excluding_tax: float = Field(..., description="不含税金额合计")
    total_tax_amount: float = Field(..., description="税额合计")
    total_amount_including_tax: float = Field(..., description="价税合计")

    line_items: List[LineItem] = Field(..., description="发票商品或服务明细列表")

    invoice_type: str = Field(..., description="发票类型,如 '专用发票', '普通发票'")
    note: Optional[str] = Field(None, description="备注信息")

# 模拟一个LLM调用的函数,它会尝试解析文本并生成Pydantic模型
# 在真实场景中,这里会使用 LangChain 的 create_structured_output_runnable 或 with_structured_output
def llm_extract_invoice_data(text: str) -> Optional[Invoice]:
    """
    模拟LLM根据Prompt和Schema提取发票数据。
    """
    # 实际中,这里会构建Prompt,包含文本和Pydantic Schema的JSON描述,
    # 然后调用LLM,并使用Output Parser解析结果。

    # 简化示例:直接硬编码一个符合Pydantic模型的结果
    if "北京智慧科技" in text and "软件开发服务" in text:
        try:
            extracted_data = {
                "invoice_code": "110023456789",
                "invoice_number": "00123456",
                "issue_date": "2023-10-26",
                "buyer_name": "北京智慧科技有限公司", # 已纠正错别字
                "buyer_tax_id": "91110000789012345X",
                "seller_name": "上海创新软件服务有限公司",
                "seller_tax_id": "91310000123456789Y",
                "total_amount_excluding_tax": 10000.00,
                "total_tax_amount": 1300.00,
                "total_amount_including_tax": 11300.00,
                "line_items": [
                    {
                        "name": "软件开发服务",
                        "specification": "/",
                        "unit": "项",
                        "quantity": 1.0,
                        "unit_price": 10000.00,
                        "amount": 10000.00,
                        "tax_rate": "13%",
                        "tax_amount": 1300.00
                    }
                ],
                "invoice_type": "普通发票",
                "note": "远程服务费"
            }
            return Invoice(**extracted_data)
        except ValidationError as e:
            print(f"LLM输出数据验证失败: {e}")
            return None
    elif "上海未来科技公司" in text and "电子元器件" in text:
        try:
            extracted_data = {
                "invoice_code": "未知", # 模拟未识别到
                "invoice_number": "未知",
                "issue_date": "2023-11-01",
                "buyer_name": "上海未来科技公司",
                "buyer_tax_id": "91310000ABCDEFGHIJ",
                "seller_name": "深圳创新电子厂",
                "seller_tax_id": "91440000KLMNOPQRST",
                "total_amount_excluding_tax": 5000.00,
                "total_tax_amount": 650.00,
                "total_amount_including_tax": 5650.00,
                "line_items": [
                    {
                        "name": "电子元器件",
                        "specification": None,
                        "unit": None,
                        "quantity": 100.0,
                        "unit_price": 50.00,
                        "amount": 5000.00,
                        "tax_rate": "13%",
                        "tax_amount": 650.00
                    }
                ],
                "invoice_type": "专用发票",
                "note": None
            }
            return Invoice(**extracted_data)
        except ValidationError as e:
            print(f"LLM输出数据验证失败: {e}")
            return None
    return None

# 结合之前的清洗文本进行提取
extracted_invoice_1 = llm_extract_invoice_data(cleaned_text)
if extracted_invoice_1:
    print("n--- 提取到的发票数据 (发票1) ---")
    print(extracted_invoice_1.model_dump_json(indent=2, exclude_none=True))
    print(f"购方名称: {extracted_invoice_1.buyer_name}")
    print(f"商品明细数量: {len(extracted_invoice_1.line_items)}")

ocr_raw_output_2 = mock_ocr_invoice("invoice_002.pdf")
cleaned_text_2 = post_process_ocr_text(ocr_raw_output_2)
extracted_invoice_2 = llm_extract_invoice_data(cleaned_text_2)
if extracted_invoice_2:
    print("n--- 提取到的发票数据 (发票2) ---")
    print(extracted_invoice_2.model_dump_json(indent=2, exclude_none=True))
    # 模拟对缺失字段的处理,例如:如果发票代码缺失,可以标记为异常或尝试通过其他方式补充
    if extracted_invoice_2.invoice_code == "未知":
        print("警告: 发票代码未识别,可能需要人工复核或通过其他Agent尝试补充。")

在这个阶段,我们已经将发票图片转化为结构化的数据对象,为后续的审计工作打下了坚实基础。

五、 税务法规知识库的构建与管理

审计的核心在于对照规则。税务法规复杂多变,是审计Agent进行合规性检查的“圣经”。

挑战:

  • 复杂性: 税法条文众多,不同地区、不同行业、不同税种的规定各异。
  • 更新频繁: 税务政策时常调整,知识库需保持最新。
  • 语义模糊: 某些条文可能存在解读空间。

RAG (Retrieval Augmented Generation) 策略:
为了让LLM能够准确、及时地应用税务法规,RAG是最佳实践。

  1. 文档切块 (Chunking): 将大量的税务法规文档(PDF、Word、网页)切分成小块(chunks)。
  2. 嵌入 (Embedding): 使用文本嵌入模型将这些文本块转化为高维向量。
  3. 存储 (Storage): 将这些向量存储到向量数据库中(如Chroma, Pinecone, Weaviate)。
  4. 检索 (Retrieval): 当Agent需要查询某个税务问题时,将问题也转化为向量,然后在向量数据库中检索与问题最相关的法规片段。
  5. 增强生成 (Augmented Generation): 将检索到的法规片段作为上下文,与用户问题一起提供给LLM,让LLM基于这些“事实”生成答案或进行判断。

知识库更新机制:

  • 自动化爬取: 定期从国家税务总局、地方税务局官网爬取最新的法规文件。
  • 人工审核与标注: 对新爬取或更新的法规进行人工审核,确保准确性,并可以进行关键信息标注,提高检索质量。
  • 增量更新: 只更新变动的法规,避免全量重新嵌入。

Agent的工具:访问知识库、查询数据库
Agent本身不“记忆”所有法规,而是通过工具调用来“查阅”知识库。我们可以为Agent提供一个search_tax_regulations工具。

代码示例:模拟RAG查询税务法规

from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import OpenAIEmbeddings # 示例,实际可替换为其他嵌入模型
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_core.documents import Document
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI # 假设使用OpenAI模型

# 假设有一些税务法规文本
tax_docs_raw = [
    "根据《中华人民共和国增值税暂行条例》规定,增值税税率分为13%、9%、6%等。",
    "小规模纳税人增值税征收率为3%。",
    "发票开具应符合《发票管理办法》相关规定,包括内容完整、印章清晰等。",
    "纳税人取得增值税专用发票,符合抵扣条件的,可以凭票抵扣进项税额。",
    "对于软件开发服务,一般适用6%的增值税税率,除非另有规定。",
    "企业取得发票后,应在规定期限内完成认证或确认。",
    "发票金额与实际交易不符的,不得作为税收凭证。",
    "2023年关于小型微利企业所得税优惠政策有调整,年应纳税所得额不超过100万元的部分,减按25%计入应纳税所得额,按20%的税率缴纳企业所得税。"
]

# 1. 切块与嵌入(仅首次或更新时执行)
def setup_tax_knowledge_base(docs: List[str]):
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=50)
    documents = [Document(page_content=doc) for doc in docs]
    chunks = text_splitter.split_documents(documents)

    # 假设我们已经配置了OpenAI API Key
    # embeddings_model = OpenAIEmbeddings() 
    # Mock for demonstration without actual API calls
    class MockEmbeddings:
        def embed_documents(self, texts: List[str]) -> List[List[float]]:
            return [[hash(t) % 1000 / 1000.0 for _ in range(1536)] for t in texts] # 模拟向量
        def embed_query(self, text: str) -> List[float]:
            return [hash(text) % 1000 / 1000.0 for _ in range(1536)]

    embeddings_model = MockEmbeddings()

    # 创建一个内存中的Chroma向量数据库
    vectorstore = Chroma.from_documents(chunks, embeddings_model, collection_name="tax_regulations")
    return vectorstore

# 2. 检索函数
def retrieve_tax_info(query: str, vectorstore: Chroma) -> List[str]:
    retriever = vectorstore.as_retriever(search_kwargs={"k": 3}) # 检索最相关的3个文档块
    relevant_docs = retriever.invoke(query)
    return [doc.page_content for doc in relevant_docs]

# 3. 模拟RAG查询工具
class TaxRegulationTool:
    def __init__(self, vectorstore: Chroma, llm: Any): # llm用于生成最终答案
        self.vectorstore = vectorstore
        self.llm = llm

    def search_tax_regulations(self, query: str) -> str:
        """
        根据查询内容检索税务法规,并使用LLM生成摘要或答案。
        Args:
            query (str): 用户的税务问题或需要查询的法规点。
        Returns:
            str: 相关税务法规的摘要或答案。
        """
        print(f"n[TaxRegulationTool]: 正在检索税务法规,查询内容: '{query}'")
        relevant_texts = retrieve_tax_info(query, self.vectorstore)

        if not relevant_texts:
            return "未找到相关税务法规信息。"

        context = "n".join(relevant_texts)

        # 使用LLM综合检索到的信息
        prompt = f"""
        你是一名税务专家。请根据以下提供的税务法规片段,回答问题:

        税务法规片段:
        {context}

        问题:{query}

        请结合法规片段,清晰地给出你的回答。如果法规片段中没有直接答案,请说明。
        """

        # 模拟LLM调用
        # response = self.llm.invoke(HumanMessage(content=prompt)).content
        response = f"模拟LLM根据提供的法规片段回答 '{query}':n" + context[:100] + "..." # 简化
        return response

# 初始化知识库和工具
tax_vectorstore = setup_tax_knowledge_base(tax_docs_raw)
# 假设我们有一个LLM实例
mock_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) # 实际LLM
tax_tool = TaxRegulationTool(tax_vectorstore, mock_llm)

# 模拟Agent调用工具
result = tax_tool.search_tax_regulations("软件开发服务适用什么增值税税率?")
print(result)

result_2 = tax_tool.search_tax_regulations("如何确保发票的有效性?")
print(result_2)

result_3 = tax_tool.search_tax_regulations("关于企业所得税小型微利企业的最新政策是什么?")
print(result_3)

通过RAG,我们的Agent不再受限于其训练数据,能够动态地访问和利用最新的、专业的税务法规知识,极大地提高了审计的准确性和时效性。

六、 审计逻辑编排与异常发现

这是整个Agent系统的核心,利用LangGraph来编排复杂的审计工作流,实现智能的异常发现。

LangGraph:构建多步骤、带记忆、可回溯的审计工作流
LangGraph是LangChain家族中用于构建健壮、有状态、多步骤Agent的利器。它以图(Graph)的形式定义Agent的行为流程,每个节点(Node)可以是一个Agent、一个工具调用、一个LLM调用或一个自定义函数。边(Edge)定义了节点之间的流转逻辑。

Agent角色定义:
我们可以定义多个专门的Agent,每个Agent拥有特定的工具集和职责。

  1. 数据核对Agent (DataVerificationAgent):

    • 职责: 对比发票信息与企业内部系统数据(如采购订单、入库单、合同、付款记录)。
    • 工具:
      • query_erp_po(po_number): 查询ERP系统中的采购订单详情。
      • query_wms_grn(grn_number): 查询WMS系统中的入库单详情。
      • query_contract_info(contract_id): 查询合同信息。
      • query_payment_record(invoice_number): 查询付款记录。
    • 异常类型: 发票与采购订单不符(数量、金额)、无对应入库单、无有效合同、付款金额与发票不符等。
  2. 税务合规Agent (TaxComplianceAgent):

    • 职责: 依据税务法规检查发票的合规性。
    • 工具:
      • search_tax_regulations(query): 访问税务法规知识库(前面定义的TaxRegulationTool)。
      • verify_invoice_online(invoice_code, invoice_number, issue_date, amount): 调用国家税务总局发票查验接口。
      • get_company_tax_status(tax_id): 查询企业税务状态(例如,是否一般纳税人)。
    • 异常类型: 税率错误、计算错误(金额、税额)、发票查验不通过、购销方税务状态不符等。
  3. 异常分析Agent (AnomalyAnalysisAgent):

    • 职责: 对发现的异常项进行深度分析,提供解释、潜在风险和建议处理方案。
    • 工具:
      • get_audit_history(entity_id): 查询历史审计记录。
      • risk_scoring_model(anomaly_type, amount): 调用内部风险评估模型。
    • 输出: 详细的异常报告片段。

审计规则示例:

我们通过LLM的推理能力和工具的调用,实现对这些规则的自动化检查。

  • 基本算术校验:
    • 发票不含税金额合计 == sum(所有商品明细不含税金额)
    • 发票税额合计 == sum(所有商品明细税额)
    • 价税合计 == 不含税金额合计 + 税额合计
  • 税率校验:
    • 根据商品名称销方行业,结合search_tax_regulations工具,判断商品明细税率是否符合国家规定。例如,软件开发服务是否按6%或其他优惠税率。
  • 发票真实性校验:
    • 调用verify_invoice_online工具,核对发票代码、号码、开票日期、金额等信息是否与税务系统一致。
  • 业务合理性校验:
    • 调用query_erp_po等工具,检查发票商品、数量、金额是否与采购订单、入库单匹配。
    • 检查发票开具日期是否在合同有效期内。
  • 重复发票校验:
    • 检查是否存在相同发票代码、号码、金额、日期的发票(在数据库中)。

LangGraph工作流设计:

LangGraph的核心是StateGraph,它定义了整个工作流的状态和节点。

from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated, List, Tuple
import operator
from langchain_core.messages import BaseMessage, HumanMessage, ToolMessage
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
import random

# 1. 定义工作流的状态 (State)
# 这是在Agent之间传递和共享的信息
class AgentState(TypedDict):
    invoice_id: str # 当前处理的发票ID
    raw_ocr_text: str # 原始OCR文本
    extracted_invoice_data: Optional[Invoice] # 结构化发票数据
    audit_results: List[str] # 审计发现的异常或结论
    anomalies: List[Dict[str, Any]] # 具体的异常项列表
    loop_count: int # 循环计数,用于重试等

# 2. 定义工具 (Tools)
# 这些工具会被Agent调用
@tool
def verify_invoice_online(invoice_code: str, invoice_number: str, issue_date: str, total_amount_including_tax: float) -> str:
    """
    调用国家税务总局发票查验接口,验证发票的真实性。
    Args:
        invoice_code (str): 发票代码。
        invoice_number (str): 发票号码。
        issue_date (str): 开票日期,格式YYYY-MM-DD。
        total_amount_including_tax (float): 价税合计。
    Returns:
        str: 查验结果,如 "查验成功", "发票不存在", "信息不符" 等。
    """
    print(f"[Tool Call] 正在查验发票: {invoice_code}-{invoice_number}")
    # 模拟外部API调用
    if invoice_code == "110023456789" and invoice_number == "00123456" and issue_date == "2023-10-26" and abs(total_amount_including_tax - 11300.00) < 0.01:
        return "查验成功:发票信息与税务系统一致。"
    elif invoice_code == "未知" or invoice_number == "未知":
        return "查验失败:发票代码或号码缺失,无法查验。"
    else:
        return "查验失败:发票信息与税务系统不符或发票不存在。"

@tool
def query_erp_po(po_number: str) -> str:
    """
    查询企业ERP系统中的采购订单信息。
    Args:
        po_number (str): 采购订单号。
    Returns:
        str: 采购订单详情的JSON字符串或描述。
    """
    print(f"[Tool Call] 正在查询ERP采购订单: {po_number}")
    # 模拟ERP数据
    if po_number == "PO2023102001":
        return json.dumps({
            "po_number": "PO2023102001",
            "item": "软件开发服务",
            "quantity": 1,
            "unit_price": 10000.00,
            "total_amount": 10000.00,
            "status": "已完成"
        })
    return "未找到对应采购订单。"

# 3. 定义Agent类(每个Agent是一个LLM和一组工具的组合)
class AuditAgent:
    def __init__(self, llm_model, tools, name):
        self.llm = llm_model.bind_tools(tools)
        self.tools = tools
        self.name = name

    def __call__(self, state: AgentState):
        current_invoice = state["extracted_invoice_data"]
        messages = [HumanMessage(content=f"你是一个{self.name}。当前正在审计发票ID: {state['invoice_id']}。发票数据如下:n{current_invoice.model_dump_json(indent=2)}n请根据你的职责和可用工具进行分析。")]

        # 模拟LLM思考和工具调用
        print(f"n--- {self.name} 正在工作 ---")

        # 简化:直接模拟Agent的逻辑,不实际调用LLM进行决策
        new_anomalies = []
        new_audit_results = []

        if self.name == "DataExtractionAgent":
            # 这里的逻辑已经在llm_extract_invoice_data中模拟了,这里可以用于重试
            pass

        elif self.name == "TaxComplianceAgent":
            # 1. 查验发票真实性
            if current_invoice and current_invoice.invoice_code != "未知" and current_invoice.invoice_number != "未知":
                check_result = verify_invoice_online(
                    current_invoice.invoice_code,
                    current_invoice.invoice_number,
                    current_invoice.issue_date,
                    current_invoice.total_amount_including_tax
                )
                new_audit_results.append(f"发票真实性查验结果: {check_result}")
                if "失败" in check_result:
                    new_anomalies.append({
                        "type": "发票真实性风险",
                        "description": check_result,
                        "severity": "高",
                        "related_field": "发票代码/号码/日期/金额"
                    })
            else:
                new_audit_results.append("发票代码或号码缺失,无法进行在线真实性查验。")
                new_anomalies.append({
                    "type": "发票信息缺失风险",
                    "description": "发票代码或号码缺失,无法进行在线真实性查验。",
                    "severity": "中",
                    "related_field": "发票代码/号码"
                })

            # 2. 算术校验
            if current_invoice:
                calculated_total_amount_ex_tax = sum(item.amount for item in current_invoice.line_items)
                calculated_total_tax_amount = sum(item.tax_amount for item in current_invoice.line_items)
                calculated_total_inc_tax = calculated_total_amount_ex_tax + calculated_total_tax_amount

                if abs(current_invoice.total_amount_excluding_tax - calculated_total_amount_ex_tax) > 0.01:
                    new_anomalies.append({
                        "type": "金额计算错误",
                        "description": f"发票总不含税金额 {current_invoice.total_amount_excluding_tax} 与明细汇总 {calculated_total_amount_ex_tax} 不符。",
                        "severity": "中",
                        "related_field": "total_amount_excluding_tax, line_items"
                    })
                if abs(current_invoice.total_tax_amount - calculated_total_tax_amount) > 0.01:
                    new_anomalies.append({
                        "type": "税额计算错误",
                        "description": f"发票总税额 {current_invoice.total_tax_amount} 与明细汇总 {calculated_total_tax_amount} 不符。",
                        "severity": "中",
                        "related_field": "total_tax_amount, line_items"
                    })
                if abs(current_invoice.total_amount_including_tax - calculated_total_inc_tax) > 0.01:
                    new_anomalies.append({
                        "type": "价税合计计算错误",
                        "description": f"发票价税合计 {current_invoice.total_amount_including_tax} 与计算值 {calculated_total_inc_tax} 不符。",
                        "severity": "中",
                        "related_field": "total_amount_including_tax"
                    })

                # 3. 税率合规性检查 (利用RAG工具)
                for i, item in enumerate(current_invoice.line_items):
                    tax_rate_query = f"商品或服务 '{item.name}' 适用什么增值税税率?"
                    tax_regulation_response = tax_tool.search_tax_regulations(tax_rate_query)

                    # 简化判断:如果税率为13%,但RAG说适用6%,则标记异常
                    # 真实场景需要LLM理解RAG结果并进行复杂推理
                    if "软件开发服务" in item.name and item.tax_rate != "6%":
                        new_anomalies.append({
                            "type": "税率不合规",
                            "description": f"商品'{item.name}'税率应为6%,发票显示为{item.tax_rate}。法规依据: {tax_regulation_response}",
                            "severity": "高",
                            "related_field": f"line_items[{i}].tax_rate"
                        })

        elif self.name == "DataVerificationAgent":
            # 模拟业务核对,例如与采购订单核对
            # 假设我们从发票备注或某个字段中获取采购订单号
            po_number_from_invoice = "PO2023102001" # 简化,实际需LLM或正则提取
            if po_number_from_invoice:
                po_info = query_erp_po(po_number_from_invoice)
                new_audit_results.append(f"ERP采购订单核对结果: {po_info}")
                if "未找到" in po_info:
                     new_anomalies.append({
                        "type": "业务关联缺失",
                        "description": f"未找到与发票关联的采购订单号: {po_number_from_invoice}。",
                        "severity": "中",
                        "related_field": "备注/业务关联"
                    })
                else:
                    po_data = json.loads(po_info)
                    if current_invoice and current_invoice.line_items and po_data["item"] != current_invoice.line_items[0].name:
                        new_anomalies.append({
                            "type": "业务内容不符",
                            "description": f"发票商品 '{current_invoice.line_items[0].name}' 与采购订单 '{po_data['item']}' 不符。",
                            "severity": "中",
                            "related_field": "line_items, 采购订单"
                        })

        elif self.name == "AnomalyAnalysisAgent":
            if state["anomalies"]:
                analysis_prompt = f"请分析以下发票ID {state['invoice_id']} 的异常项:n{json.dumps(state['anomalies'], indent=2)}n请提供每个异常的可能原因、潜在风险和建议处理措施。"
                # 模拟LLM分析
                analysis_report = f"模拟分析报告:发票ID {state['invoice_id']} 发现 {len(state['anomalies'])} 项异常。其中一项是税率不合规,涉及商品'软件开发服务',税率应为6%但发票显示13%。建议核实交易性质并与供应商沟通。"
                new_audit_results.append(analysis_report)
            else:
                new_audit_results.append("未发现显著异常。")

        # 更新状态
        return {
            "audit_results": state["audit_results"] + new_audit_results,
            "anomalies": state["anomalies"] + new_anomalies
        }

# 4. 初始化LLM模型
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

# 5. 初始化Agent实例
data_extraction_agent = AuditAgent(llm, [], "DataExtractionAgent") # 提取Agent主要依赖LLM的结构化输出能力,工具较少
tax_compliance_agent = AuditAgent(llm, [verify_invoice_online, tax_tool.search_tax_regulations], "TaxComplianceAgent")
data_verification_agent = AuditAgent(llm, [query_erp_po], "DataVerificationAgent")
anomaly_analysis_agent = AuditAgent(llm, [], "AnomalyAnalysisAgent")

# 6. 构建LangGraph图
workflow = StateGraph(AgentState)

# 定义节点
workflow.add_node("extract_data", lambda state: {"extracted_invoice_data": llm_extract_invoice_data(state["raw_ocr_text"])})
workflow.add_node("tax_compliance_check", tax_compliance_agent)
workflow.add_node("data_verification_check", data_verification_agent)
workflow.add_node("anomaly_analysis", anomaly_analysis_agent)

# 定义边
# 初始入口
workflow.set_entry_point("extract_data")

# 提取数据后,判断是否成功提取
def should_continue_audit(state: AgentState):
    if state["extracted_invoice_data"]:
        print("n--- 数据提取成功,进入税务合规检查 ---")
        return "continue_audit"
    else:
        print("n--- 数据提取失败,结束审计或转交人工 ---")
        return "fail_or_manual"

workflow.add_conditional_edges(
    "extract_data",
    should_continue_audit,
    {
        "continue_audit": "tax_compliance_check",
        "fail_or_manual": END # 实际可以指向一个人工介入节点
    }
)

# 税务合规检查后,无论是否有异常都进行数据核对
workflow.add_edge("tax_compliance_check", "data_verification_check")

# 数据核对后,进入异常分析
workflow.add_edge("data_verification_check", "anomaly_analysis")

# 异常分析后,结束
workflow.add_edge("anomaly_analysis", END)

# 7. 编译图
app = workflow.compile()

# 8. 运行审计流程
print("n--- 启动发票1审计流程 ---")
initial_state_1 = {
    "invoice_id": "inv_001",
    "raw_ocr_text": cleaned_text, # 使用之前清洗过的文本
    "extracted_invoice_data": None,
    "audit_results": [],
    "anomalies": [],
    "loop_count": 0
}
final_state_1 = app.invoke(initial_state_1)
print("n--- 发票1审计完成 ---")
print("最终审计结果:", final_state_1["audit_results"])
print("发现异常:", final_state_1["anomalies"])

print("nn--- 启动发票2审计流程 ---")
initial_state_2 = {
    "invoice_id": "inv_002",
    "raw_ocr_text": cleaned_text_2,
    "extracted_invoice_data": None,
    "audit_results": [],
    "anomalies": [],
    "loop_count": 0
}
final_state_2 = app.invoke(initial_state_2)
print("n--- 发票2审计完成 ---")
print("最终审计结果:", final_state_2["audit_results"])
print("发现异常:", final_state_2["anomalies"])

在上述LangGraph示例中,我们定义了AgentState来存储发票处理过程中的所有信息。AuditAgent类模拟了不同职责的Agent,它们通过更新AgentState来传递信息。StateGraph定义了发票从数据提取到税务合规检查,再到业务数据核对,最终到异常分析的完整流程。add_conditional_edges允许我们根据状态(例如,数据是否成功提取)动态地决定下一步的走向。

这个结构化、可追溯的工作流,是处理数万张发票并确保每张发票都经过一致且全面的审计的关键。

七、 异常报告生成与风险评估

当Agent发现异常后,最终需要向人类审计师或管理层提供一份清晰、专业的报告。

LLM的报告能力:
LLM非常擅长将结构化的数据转化为自然语言描述。我们可以设计Prompt,让LLM根据anomalies列表生成一份审计报告。

报告内容:

  • 概览: 涉及发票ID、审计日期、总异常数量。
  • 异常详情:
    • 异常类型: 如“税率不合规”、“发票查验失败”。
    • 涉及金额: 关联的金额数据。
    • 潜在风险: 对企业可能造成的税务风险(补税、罚款、信用影响)。
    • 法规依据: 引用相关税务法规条文(通过RAG获取)。
    • 建议处理方案: 如“与供应商核实并要求重开”、“进行风险评估并准备自查”。
  • 风险等级划分: 基于异常的严重程度和频率,LLM可以辅助进行高中低风险的划分。

Agent的工具:

  • 报告模板: 提供标准的报告结构。
  • 风险评估模型: 如果有,Agent可以调用一个简单的规则引擎或机器学习模型来量化风险。

代码示例:报告生成Prompt与LLM调用

from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI # 假设使用OpenAI模型

def generate_audit_report(invoice_id: str, audit_results: List[str], anomalies: List[Dict[str, Any]]) -> str:
    """
    根据审计结果和异常列表,使用LLM生成一份审计报告。
    """
    llm_report_generator = ChatOpenAI(model="gpt-4o-mini", temperature=0.7) # 温度可以调高一点,让报告更自然

    # 构建报告内容
    report_parts = [
        f"### 全自动财务审计Agent报告 - 发票ID: {invoice_id}n",
        f"**审计日期:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}n",
        "**审计概览:**n"
    ]

    if not anomalies:
        report_parts.append("    本张发票未发现显著异常项,符合初步审计要求。n")
    else:
        report_parts.append(f"    本张发票共发现 **{len(anomalies)}** 项潜在异常,需要进一步关注和处理。n")
        report_parts.append("n**详细异常项:**n")
        for i, anomaly in enumerate(anomalies):
            report_parts.append(f"#### {i+1}. 异常类型: {anomaly.get('type', '未知')}n")
            report_parts.append(f"    - **描述:** {anomaly.get('description', '无')}n")
            report_parts.append(f"    - **严重程度:** {anomaly.get('severity', '中')}n")
            report_parts.append(f"    - **相关字段:** {anomaly.get('related_field', '无')}n")

            # 模拟LLM对每个异常进行风险评估和建议
            risk_assessment_prompt = f"""
            你是一名专业的财务风险分析师。请根据以下异常信息,评估其潜在风险并提供具体处理建议。
            异常信息:
            类型: {anomaly.get('type', '未知')}
            描述: {anomaly.get('description', '无')}
            严重程度: {anomaly.get('severity', '中')}

            请给出:
            1. 潜在税务/财务风险(例如:补税、罚款、无法抵扣、业务中断)
            2. 建议的处理措施和后续步骤
            """

            # 模拟LLM调用
            # risk_analysis = llm_report_generator.invoke(HumanMessage(content=risk_assessment_prompt)).content
            risk_analysis = f"**潜在风险:** 根据'{anomaly.get('type')}',可能导致无法抵扣进项税额或面临税务机关罚款。n**建议措施:** 立即与供应商核实情况,要求重开合规发票,并内部审查采购流程。"

            report_parts.append(f"    - **风险评估与建议:**n        {risk_analysis.replace('\n', '\n        ')}n")

    report_parts.append("n**审计过程记录:**n")
    for res in audit_results:
        report_parts.append(f"    - {res}n")

    final_report_prompt = "请将以下内容整理成一份专业、清晰的财务审计报告,突出重点和建议:n" + "".join(report_parts)

    # 最终报告由LLM生成
    # final_report = llm_report_generator.invoke(HumanMessage(content=final_report_prompt)).content
    final_report = final_report_prompt # 简化,直接输出拼接内容

    return final_report

from datetime import datetime
# 使用发票1的最终状态生成报告
report_1 = generate_audit_report(
    final_state_1["invoice_id"],
    final_state_1["audit_results"],
    final_state_1["anomalies"]
)
print("n--- 发票1审计报告 ---")
print(report_1)

# 使用发票2的最终状态生成报告
report_2 = generate_audit_report(
    final_state_2["invoice_id"],
    final_state_2["audit_results"],
    final_state_2["anomalies"]
)
print("n--- 发票2审计报告 ---")
print(report_2)

通过LLM的报告生成能力,我们不仅能发现异常,还能提供可执行的洞察和建议,极大地提升了审计的价值。

八、 性能优化与可扩展性

处理数万张发票,性能和可扩展性是必须考虑的。

  • 并发处理:
    • 异步I/O: 大部分LLM调用和外部API调用都是网络密集型操作,应采用asyncio进行异步处理。
    • 批量处理: 对于OCR和LLM调用,可以尝试将多张发票的文本打包成一个请求进行批量处理(如果API支持),减少网络延迟。
    • 分布式任务队列: 使用Celery、Apache Kafka等分布式任务队列,将每张发票的审计任务分发到多个Worker进行并行处理。
  • 成本控制:
    • LLM API调用优化: 针对不同的任务选择合适的模型(例如,数据提取可能需要更强的模型,而简单的文本清洗可以使用更轻量级的模型)。
    • 缓存机制: 对于重复的RAG查询或工具调用结果进行缓存。
    • 批处理与Token优化: 尽可能在一个LLM请求中处理更多信息,减少请求次数。
  • 可解释性与透明度:
    • 日志记录: 详细记录Agent的每一个决策、工具调用、LLM的输入和输出。
    • 决策路径追踪: LangGraph的图结构天然支持追踪Agent的工作流路径,可以可视化展示每张发票的审计过程。
    • Human-in-the-Loop (HIL): 在关键决策点(如高风险异常发现、LLM置信度低时)引入人工复核机制,确保审计的准确性和合规性。
  • 安全性与数据隐私:
    • 数据加密: 存储和传输敏感财务数据时必须进行加密。
    • 访问控制: 严格控制对Agent系统和底层数据源的访问权限。
    • 匿名化/假名化: 在非生产环境或进行模型训练时,对敏感数据进行匿名化处理。
    • 合规性: 确保系统设计符合GDPR、国内数据安全法等相关法规。
  • 持续学习与迭代:
    • 反馈循环: 人工审计师对Agent报告的复核结果,可以作为Agent的“训练数据”,用于微调LLM或优化Agent的决策逻辑。
    • A/B测试: 部署新版本的Agent时,可以进行小范围的A/B测试,评估其效果。
    • 法规自动更新: 完善RAG知识库的自动更新和版本管理机制。

九、 展望:未来发展与挑战

全自动财务审计Agent的未来充满无限可能:

  • 更深度的语义理解与推理: LLM将能够理解更复杂的业务情境和非结构化文本(如合同条款、邮件沟通记录),进行更高级的风险推理。
  • 多模态审计: 不仅仅是文本和结构化数据,未来Agent可能结合图像、语音、视频等多种模态数据进行审计(例如,通过视频监控识别货物进出库异常)。
  • 与ERP/财务系统无缝集成: Agent将更紧密地融入企业的现有财务生态系统,实现数据的实时同步和自动化操作。
  • 预测性审计: 基于历史数据和业务模式,Agent能够预测未来可能发生的风险,从“事后审计”转向“事前预警”。
  • 法律与伦理考量: 随着AI在审计中扮演越来越重要的角色,如何明确责任、确保算法公平、避免偏见,将是需要持续关注和解决的问题。

自动化审计,赋能财务未来

全自动财务审计Agent的出现,不是为了取代人类审计师,而是为了赋能他们。它将审计师从繁重、重复的劳动中解放出来,让他们能够将更多精力投入到高价值的风险判断、策略制定和复杂问题解决上。通过LangGraph这类Agent编排框架,我们能够构建出更加智能、高效、可扩展的自动化审计系统,为企业带来前所未有的财务透明度和风险控制能力,共同迈向一个更智能、更高效的财务未来。谢谢大家!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注