文章前言

随着国内电力市场化改革持续推进,各省电力交易中心持续发布交易规则、政策文件、市场通知。电力从业者需要持续跟踪海量PDF公告、网页政策文本,人工整理、对比政策差异、提取核心条款成本极高。传统单一大模型应用无法完成网页采集→文档解析→文本抽取→知识入库→智能分析完整流水线。

本文基于LangGraph构建多智能体协同系统,拆分爬虫智能体、文档解析智能体、规则抽取智能体、知识库更新智能体。整套工作流自动完成网页抓取、PDF解析、图片OCR识别、政策条款结构化抽取,结合RAG向量存储实现电力政策持续跟踪。整套代码可直接工程改造,适配能源、电力交易业务场景。

技术栈:Python3.10+ | LangGraph | LangChain | Pytesseract(OCR) | PyPDF2 | BeautifulSoup | Chroma向量库 | OpenAI兼容大模型接口

一、系统整体架构设计

1.1 多智能体角色划分

我们将整个业务流程拆解为4个独立Agent,由LangGraph负责状态流转、路由调度:

  1. 爬虫Agent:目标网页发起请求,抓取电力政策公告链接,过滤重复文档地址
  2. 文档解析Agent:区分网页文本、PDF文件、图片附件;PDF文本提取,图片执行OCR识别
  3. 规则抽取Agent:调用大模型,从原始文档中结构化提取政策生效时间、交易规则、政策要点、变更条款
  4. 知识库Agent:将结构化数据向量化,存入向量数据库,完成电力交易知识库更新

1.2 工作流流转逻辑

起始节点 → 爬虫Agent → 文档下载分发 → 文档解析Agent(分支:网页/PDF/OCR图片)
→ 规则抽取Agent → 知识库写入Agent → 任务结束
出现异常自动重试,解析失败文档标记,存入异常队列。

1.3 状态定义(State)

LangGraph依靠State在各个节点之间传递全局数据,包含:待抓取URL列表、原始文档内容、结构化政策信息、错误日志。

二、环境依赖安装

先执行依赖包安装

pip install langgraph langchain langchain-openai chromadb beautifulsoup4 requests pypdf2 pytesseract python-dotenv

注意:OCR识别需要本地安装Tesseract-OCR程序,并配置环境变量。Windows用户下载安装包,Linux执行sudo apt install tesseract-ocr

三、完整代码实现

3.1 全局状态定义与基础初始化

新建power_policy_agent.py

import os
import requests
import pytesseract
from PyPDF2 import PdfReader
from bs4 import BeautifulSoup
from typing import TypedDict, Annotated, Sequence, Any
import operator
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
from langchain.prompts import PromptTemplate
from dotenv import load_dotenv

load_dotenv()

# 配置大模型,支持通义千问、DeepSeek等兼容OpenAI接口模型
llm = ChatOpenAI(
    model="deepseek-chat",
    api_key=os.getenv("LLM_API_KEY"),
    base_url=os.getenv("LLM_BASE_URL"),
    temperature=0.1
)

# 向量数据库初始化
embedding = OpenAIEmbeddings(
    model="text-embedding",
    api_key=os.getenv("EMBED_KEY"),
    base_url=os.getenv("EMBED_BASE")
)
vector_db = Chroma(persist_directory="./power_policy_db", embedding_function=embedding)

# ========== 定义全局状态 ==========
class AgentState(TypedDict):
    url_list: Annotated[list[str], operator.add]       # 需要抓取的政策网页链接
    raw_documents: Annotated[list[dict], operator.add] # 原始文档数据 {url, content, doc_type}
    structured_policy: Annotated[list[dict], operator.add] # 大模型抽取结构化政策
    error_log: Annotated[list[str], operator.add]      # 错误日志

3.2 Agent节点1:爬虫智能体(CrawlerAgent)

负责访问电力交易网站,抓取政策页面内容,区分普通网页、PDF附件链接。

def crawler_agent(state: AgentState) -> AgentState:
    """爬虫智能体:遍历url,获取网页内容,识别PDF附件"""
    new_raw_docs = []
    error = []
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
    }
    for url in state["url_list"]:
        try:
            resp = requests.get(url, headers=headers, timeout=15)
            resp.raise_for_status()
            # 判断是否为PDF文件
            if url.endswith(".pdf") or "application/pdf" in resp.headers.get("Content-Type", ""):
                new_raw_docs.append({
                    "source_url": url,
                    "doc_type": "pdf",
                    "binary_data": resp.content,
                    "text": ""
                })
            else:
                soup = BeautifulSoup(resp.text, "html.parser")
                # 清除网页标签,提取正文
                text = soup.get_text(strip=True, separator="\n")
                new_raw_docs.append({
                    "source_url": url,
                    "doc_type": "webpage",
                    "binary_data": None,
                    "text": text
                })
        except Exception as e:
            error.append(f"抓取失败 {url}: {str(e)}")
    return {"raw_documents": new_raw_docs, "error_log": error}

3.3 Agent节点2:文档解析智能体(DocumentParserAgent)

分流处理三类数据:网页文本直接使用;PDF文本提取;图片附件调用OCR识别。

def document_parser_agent(state: AgentState) -> AgentState:
    """文档解析Agent:PDF解析 + OCR图片识别"""
    processed_docs = []
    error = []
    for doc in state["raw_documents"]:
        try:
            if doc["doc_type"] == "webpage":
                processed_docs.append(doc)
            elif doc["doc_type"] == "pdf":
                # PDF解析
                from io import BytesIO
                pdf_stream = BytesIO(doc["binary_data"])
                reader = PdfReader(pdf_stream)
                full_text = ""
                for page in reader.pages:
                    page_text = page.extract_text()
                    if page_text:
                        full_text += page_text + "\n"
                doc["text"] = full_text
                processed_docs.append(doc)
            elif doc["doc_type"] == "image":
                # OCR图片识别,适用于扫描版政策文件
                from PIL import Image
                from io import BytesIO
                img = Image.open(BytesIO(doc["binary_data"]))
                ocr_text = pytesseract.image_to_string(img, lang="chi_sim")
                doc["text"] = ocr_text
                processed_docs.append(doc)
        except Exception as e:
            error.append(f"文档解析失败 {doc['source_url']}: {str(e)}")
    return {"raw_documents": processed_docs, "error_log": error}

3.4 Agent节点3:政策抽取智能体(PolicyExtractAgent)

利用大模型从非结构化文本提取电力政策结构化信息,也是本项目核心业务节点。

extract_prompt = PromptTemplate(
    input_variables=["document_text"],
    template="""
你是电力市场政策分析师,请阅读下面电力交易政策文档,严格输出JSON格式,不要额外解释。
文档内容:
{document_text}

输出JSON字段要求:
{{
"province": "政策所属省份",
"policy_name": "政策文件全称",
"effective_date": "生效时间",
"publish_date": "发布时间",
"core_rules": ["规则要点1","规则要点2"],
"target_subject": "适用市场主体(发电企业/售电公司/电力用户)",
"change_content": "相较于旧政策的变更内容,如果无对比填暂无",
"impact_analysis": "该政策对电力交易收益、报价策略带来的影响简述"
}}
只返回标准JSON,禁止增加markdown、注释。
"""
)

def policy_extract_agent(state: AgentState) -> AgentState:
    """大模型抽取政策结构化信息"""
    structured_result = []
    error = []
    for doc in state["raw_documents"]:
        text = doc["text"]
        if len(text.strip()) < 20:
            error.append(f"文档{doc['source_url']}有效文本过短,跳过抽取")
            continue
        try:
            chain = extract_prompt | llm
            resp = chain.invoke({"document_text": text[:12000]}) # 截断超长文本
            json_str = resp.content.strip()
            import json
            policy_data = json.loads(json_str)
            policy_data["source_url"] = doc["source_url"]
            policy_data["original_text"] = text[:5000]
            structured_result.append(policy_data)
        except Exception as e:
            error.append(f"政策抽取失败 {doc['source_url']}: {str(e)}")
    return {"structured_policy": structured_result, "error_log": error}

3.5 Agent节点4:知识库入库智能体(KnowledgeBaseAgent)

将结构化政策存入向量库,构建电力交易知识库,支持后续RAG检索。

def knowledge_base_agent(state: AgentState) -> AgentState:
    """将结构化政策写入向量知识库"""
    error = []
    texts = []
    metadatas = []
    for policy in state["structured_policy"]:
        content = f"""
省份:{policy['province']}
政策名称:{policy['policy_name']}
生效时间:{policy['effective_date']}
核心规则:{";".join(policy['core_rules'])}
影响评估:{policy['impact_analysis']}
        """
        texts.append(content)
        metadatas.append({
            "source_url": policy["source_url"],
            "province": policy["province"],
            "publish_date": policy["publish_date"]
        })
    if texts:
        vector_db.add_texts(texts=texts, metadatas=metadatas)
        vector_db.persist()
    return {"error_log": error}

3.6 LangGraph 构建工作流图,启动任务

def build_graph():
    graph = StateGraph(AgentState)
    # 注册节点
    graph.add_node("crawler", crawler_agent)
    graph.add_node("parser", document_parser_agent)
    graph.add_node("extractor", policy_extract_agent)
    graph.add_node("knowledge_store", knowledge_base_agent)
    # 设置流转顺序
    graph.set_entry_point("crawler")
    graph.add_edge("crawler", "parser")
    graph.add_edge("parser", "extractor")
    graph.add_edge("extractor", "knowledge_store")
    graph.add_edge("knowledge_store", END)
    return graph.compile()

if __name__ == "__main__":
    app = build_graph()
    # 输入待抓取电力政策链接,替换为各省电力交易中心公告地址
    init_input = {
        "url_list": [
            "https://example.com/jiangsu_power_policy.pdf",
            "https://example.com/henan_trade_notice.html"
        ],
        "raw_documents": [],
        "structured_policy": [],
        "error_log": []
    }
    result = app.invoke(init_input)
    print("====任务执行完成====")
    print("错误日志:")
    for err in result["error_log"]:
        print(err)
    print(f"成功解析政策数量:{len(result['structured_policy'])}")

四、代码运行说明与工程优化方案

4.1 基础运行问题解决

  1. Tesseract OCR报错:必须安装本地程序,代码中添加路径 pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
  2. 超长文档报错:增加文本滑动窗口分段抽取,避免大模型上下文溢出
  3. 爬虫封禁:增加代理池、请求间隔,随机UA

4.2 生产环境优化方向

  1. 路由分支优化:当前为线性流程,可改造LangGraph条件分支,PDF、网页、图片并行解析,提升速度
  2. 去重机制:增加URL哈希、文档文本相似度比对,避免重复入库相同政策
  3. 增量更新:定时爬虫轮询电力交易网站,只抓取最新发布政策,实现持续跟踪
  4. Function Calling增强:扩展Agent能力,新增政策差异对比Agent,自动对比新旧版本规则变化
  5. 持久化状态:结合Redis存储LangGraph运行状态,支持中断任务续跑

五、业务落地思考:电力政策Agent的价值

传统方案使用固定爬虫+正则表达式提取政策条款,缺点十分明显:电力政策文件格式不统一、扫描版PDF无法提取文本、各省文件行文规范差异巨大,正则很难适配。

基于LangGraph多智能体方案优势:

  1. 模块化解耦:爬虫、解析、抽取、知识库模块独立,单独迭代维护
  2. 自适应非结构化文档:OCR+PDF双方案兼容扫描件、电子版政策
  3. 自然语言理解:依靠大模型理解政策语义,不需要编写大量规则表达式
  4. 可扩展:后续可以新增电价数据采集Agent、收益测算Agent,实现多智能体协同决策,对接电力交易业务系统

六、现存痛点与避坑指南

  1. 抽取稳定性问题:大模型偶尔输出非法JSON,工程中增加重试节点、JSON清洗工具;
  2. 向量库检索精度:单纯段落切片效果差,后续可引入Hierarchical切片、父文档检索提升RAG效果;
  3. 长PDF文件:数百页政策文件一次性传入LLM会超限,需要实现文档分块、分批抽取;
  4. 扫描件识别:模糊图片OCR准确率下降,可引入OpenCV图片预处理(降噪、二值化)提升识别效果。

七、拓展延伸

本套多智能体框架不局限于电力政策场景,可以快速迁移至:新能源政策跟踪、储能行业文件解析、招投标公告抓取。结合LangGraph的条件分支、循环能力,还可以实现自主搜索Agent:当政策信息缺失时,智能体主动生成关键词再次检索网页,形成闭环自主调研。


Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐