【大模型落地实践】基于 LangGraph 的多模态拓扑图谱与可插拔 RAG 协同运维 Agent 系统 (GraphOmni-Agent)

📌 引言:为什么传统的 Naive RAG 在工业级场景必死?

在工业运维与复杂售后领域,面对动辄成千上万页、充斥着图表与专业术语的设备手册(如 HAK 180 智能烫金机通用安全与运维手册),传统的 Naive RAG(简单切片 ➡️ 向量化 ➡️ 检索 ➡️ 生成)往往表现得像一个“智力断层”的复读机,频繁在生产环境中触发以下三大致命痛点:

  • 学术型幻觉(缺乏强实体约束):由于缺乏对特定型号(如 HAK 180-V2HAK 180-Pro)的物理隔离与确定性约束,LLM 极易张冠李戴,拿着 A 设备的接线图去回答 B 设备的故障排查流程。
  • 上下文断裂(机械长切片乱象):固定 Token 长度的硬编码切片经常将高内聚的“十步故障排查法”拦腰斩断,导致检索出的 Context 支离破碎,因果链条彻底丢失。
  • 推理缺乏逻辑(多轮指代不明):当用户在第一轮询问“HAK 180 为什么加热板不升温?”,紧接着在第二轮追问“这个怎么接线?”时,Naive RAG 会盲目去寻找“这个”相关的表层语义,无法完成上下文的代词消解。

为了彻底降伏这些不确定性,我自主设计并构建了 GraphOmni-Agent。它不再是一个扁平的、被动响应的语义查询工具,而是一个拥有状态机(State Machine)灵魂图拓扑结构(Graph-RAG)骨架的工业级闭环运维系统。


🏗️ 一、 鸟瞰全局:基于 LangGraph 的非线性状态机架构

1. 核心画布:全局拓扑流转

系统利用 LangGraph 的 StateGraph 构建。不同于传统的线性编排,该架构允许节点间根据状态实时流转,支持条件跳转(Conditional Edges)、扇出并发(Fan-out)与扇入汇聚(Fan-in)。

模糊/未知设备型号

用户补充信息

明确设备型号

置信度通过

低置信度/高危操作

开始

node_item_name_confirm
意图确认与代词消解

node_disambiguate
多模态反问与澄清

多路并行搜索入口

node_search_embedding
Milvus 稠密向量检索

node_search_embedding_hyde
假设性文档检索

node_query_kg
Neo4j 知识图谱检索

node_web_search_mcp
实时 Web 补丁搜索

中转合并

node_rrf
倒数排名融合

node_rerank
BGE-Rerank 精排

node_confidence_check
置信度安全网拦截

node_answer_output
流式 SSE 生成

node_human_takeover
Human-in-the-loop 挂起转人工

结束

2. 状态总线设计(State Management)

通过定义统一的 QueryGraphState 对象,项目将 session_idhistory(多轮对话历史)、item_names(当前消解出的设备核心实体)和 rewritten_query(重写后的检索式)等关键字段进行全局线程级持久化。

代词消解机制:当 Agent 捕获到“它”、“这个组件”等模糊词汇时,状态总线会立即激活历史上下文窗口,强制将其替换为 QueryGraphState["item_names"] 中锁定的物理实体,确保下游检索器永远处于“清醒”状态。

3. 动态路由逻辑

核心节点 node_item_name_confirm 扮演着系统的“门岗”角色。它通过大模型配合正则表达式提取出提问中的物理要素:

  • 分支 A(直出反问):若用户提问“烫金机没法通电”,但系统发现库中同时存在 HAK-180HAK-360 等数款差异极大的硬件版本,则状态机立刻触发条件边,挂起检索,生成澄清话术直接反问用户。
  • 分支 B(激活网络):只有当商品/设备型号完全明确或被消解后,才会激活下游包含向量、图谱在内的复杂检索网络。

🛠️ 二、 数据降维打击:多模态异构文档的深层解析与语义切片

1. 多模态清洗流水线(MinerU + OCR)

工业设备手册通常采用双栏排版,且穿插着大量的控制电路图、气动原理图和电气参数表格。传统的 PyPDF 等工具提取出的文本通常是乱码,或者表格数据严重错行。

本项目集成 MinerU 开源多模态解析工具,结合高级 OCR 技术,建立了一条全自动的数据清洗流水线:

  • 布局分析(Layout Analysis):自动识别单栏、双栏排版,按人类实际阅读顺序重建文本流。
  • 表格提取:将手册中的“有害物质限值表”或“故障代码对照表”高保真转换为标准的 Markdown Table,完好保留网格行列的物理映射关系。
  • 图表元数据化:自动提取插图并关联上下文,在生成的文本中插入标记锚点(如 [Image_Anchor_01]),为后续多模态推理打下骨配基础。

2. 语义切片(Semantic Chunking)

在开发过程中,我彻底抛弃了按固定 Token 长度(如 500 字符)硬切文档的传统做法,自主设计了基于 Markdown 标题分级的层级感知切片策略

  • 层级感知:利用 MinerU 生成的标准化 Markdown,系统解析出 ###### 等各级标题,确保属于同一排查步骤(如 ## 3.1 激光头对焦偏置调整)下的文字被完整封装在一个 Chunk 内。
  • 自适应滑动窗口:对于字数过少(如小于 200 字)的末端小标题,算法会自动将其向上合并至其父级标题节点,避免语义碎片化;对于超长段落,则基于标点符号进行重叠度 15% 的滑动视窗切分,确保 Chunk 边界的因果链不产生断层。

🧬 三、 确定性推理:三路混合召回与 Graph-RAG 拓扑演进

用户多轮模糊提问

意图消解与重写

Milvus 稠密检索
捕捉 冒烟至火灾 语义扩展

BM25 稀疏检索
锁定 HAK180 精准型号

Neo4j 图谱检索
追踪 故障-元器件 拓扑关联

倒数排名融合 RRF

BGE-Rerank 深度精排

动态置信度过滤

1. 三路混合召回架构

向量检索固然强大,但在工业运维的特定长尾场景中,单靠语义相似度容易“失之厘毫,差之千里”。为此,我构建了互补的三路召回矩阵:

  • 第一路:Dense Vector(稠密语义):利用 BGE-M3 向量模型,将切片文本转化为 1024 维的高维向量,存入 Milvus。它负责捕捉诸如“设备冒烟”与“异常过载、温控失效”之间的隐式语义联系。
  • 第二路:Sparse Vector(BM25 稀疏关键词):专门针对“HAK180-V2”、“E-04”等强特征、无语义的工业型号与错误代码。通过词频精确匹配,防止大模型由于向量相似度被稀释而漏掉核心物理元器件。
  • 第三路:Neo4j 知识图谱(Graph-RAG):利用结构化图数据库,将“故障现象 ➡️ 触发原因 ➡️ 关键元器件 ➡️ 推荐解决方案”沉淀为实体网状拓扑。当用户询问某故障时,不仅检索文档,更通过 Cypher 语句直接拉取与该元器件相邻的 2-hop 物理关联拓扑。

2. 深度优化双子星

  • HyDE(假设性文档嵌入):用户在现场提问时往往描述得业余且宽泛(如“机器突然死机了,灯在闪”)。我通过预设的 Industrial Expert Prompt 让大模型先生成一个“虚构但充满专业术语”的预期修复文档,随后用这个假想文档的向量去 Milvus 中检索。实践证明,这能极大地跨越“用户语言”与“工程师手册”之间的表意鸿沟。
  • BGE-Rerank (精排重平衡):混合召回后的 Top-30 结果通过 Rerank 模型进行跨维度的深度相关性打分。我引入了得分分差比(Gap Ratio)过滤算法,一旦检测到某两个相邻排名结果的得分出现断崖式下跌,系统会自动启动“截断机制”,剔除排名靠后的大量噪音 Chunk。

🤝 四、 业务安全防线:Human-in-the-loop 与流式体验

1. 中断与挂起(Interrupt)

工业运维涉及高压、高温等物理危险,系统的安全防线必须坚如磐石。利用 LangGraph 的 checkpointer 机制,系统实现了底层的动态业务中断(Breakpoint)

  • 语义触发:如果意图识别节点拦截到诸如“如何短接安全联锁装置”、“强行屏蔽错误代码”等高危操作违规词;
  • 置信度触发:或者在重排后,Top-1 结果的 Rerank Score 依然低于预设值 0.45,表明系统知识库无法覆盖该问题。

此时,状态机会瞬间执行 Freeze(冻结),强行在当前节点挂起,将全局状态总线中的全量上下文(包括多轮对话、重写后的 query、检索到的残缺片段)打包成标准的 JSON 载荷,流式推送到人工客服/专家坐席的工作台,实现平滑的“人机协同”。

2. 流式进度推送(Streaming & SSE)

为了消解用户在等待复杂多路检索和模型深度思考(Thinking Process)时的焦虑感,笔者基于 FastAPI 的 StreamingResponse 封装了标准的 SSE(Server-Sent Events) 协议:

不仅最终的文本输出是流式的,连状态机内部的节点流转状态也是流式的。前端界面会实时、平滑地跳出:[系统正在识别设备型号...] ➡️ [正在并行检索 Milvus 与 Neo4j 图谱...] ➡️ [正在合并精排...] ➡️ [大模型正在思考...]。这极大地提升了工业现场运维人员的交互体验,将感知首字延迟(TTFT)压低到了毫秒级。


💡 源码级硬核展示:关键核心代码片段

1. LangGraph 状态机定义(核心逻辑编排)

from typing import Dict, TypedDict, List
from langgraph.graph import StateGraph, END

# 1. 定义全局状态总线
class QueryGraphState(TypedDict):
    session_id: str
    raw_query: str
    rewritten_query: str
    item_names: List[str]
    retrieved_documents: List[dict]
    final_answer: str
    confidence_score: float

# 2. 初始化图拓扑
builder = StateGraph(QueryGraphState)

# 3. 注册各个原子功能节点
builder.add_node("node_item_name_confirm", node_item_name_confirm)
builder.add_node("node_parallel_retrieval", node_parallel_retrieval)
builder.add_node("node_rerank_fusion", node_rerank_fusion)
builder.add_node("node_answer_output", node_answer_output)
builder.add_node("node_human_takeover", node_human_takeover)

# 4. 编排路由规则与边
builder.set_entry_point("node_item_name_confirm")

# 条件路由函数:判断设备实体是否明确
def route_after_item_confirm(state: QueryGraphState) -> str:
    if not state.get("item_names"):
        return "node_human_takeover"  # 无法消解或型号不明确,直接挂起转人工/反问
    return "node_parallel_retrieval"   # 型号明确,放行进入核心多路检索

builder.add_conditional_edges(
    "node_item_name_confirm",
    route_after_item_confirm,
    {
        "node_human_takeover": "node_human_takeover",
        "node_parallel_retrieval": "node_parallel_retrieval"
    }
)

builder.add_edge("node_parallel_retrieval", "node_rerank_fusion")

# 条件路由函数:基于重排置信度决定是否转人工
def route_after_rerank(state: QueryGraphState) -> str:
    if state.get("confidence_score", 0.0) < 0.45:
        return "node_human_takeover"
    return "node_answer_output"

builder.add_conditional_edges(
    "node_rerank_fusion",
    route_after_rerank,
    {
        "node_human_takeover": "node_human_takeover",
        "node_answer_output": "node_answer_output"
    }
)

builder.add_edge("node_answer_output", END)
builder.add_edge("node_human_takeover", END)

# 编译运行图
app = builder.compile()

2. 混合检索(Dense + Sparse)与物理实体强约束的 Milvus 实现

from pymilvus import AnnSearchRequest, RRFRanker

def execute_hybrid_milvus_search(client, collection_name, dense_vec, sparse_vec, allowed_items: List[str]):
    # 1. 构造标量过滤表达式,实现物理设备型号的“绝对隔离约束”
    quoted_names = ", ".join([f"'{name}'" for name in allowed_items])
    scalar_expr = f"item_name in [{quoted_names}]"
    
    # 2. 准备稠密向量检索请求
    dense_req = AnnSearchRequest(
        data=[dense_vec],
        anns_field="dense_vector",
        param={"metric_type": "COSINE", "params": {"nlist": 128}},
        limit=15,
        expr=scalar_expr
    )
    
    # 3. 准备稀疏向量检索请求 (BM25)
    sparse_req = AnnSearchRequest(
        data=[sparse_vec],
        anns_field="sparse_vector",
        param={"metric_type": "IP"},
        limit=15,
        expr=scalar_expr
    )
    
    # 4. 执行倒数排名融合 (RRF) 混合搜索
    hybrid_res = client.hybrid_search(
        collection_name=collection_name,
        reqs=[dense_req, sparse_req],
        ranker=RRFRanker(), # 使用 RRF 算法平衡两路异构得分
        limit=10
    )
    return hybrid_res


📊 数据说话:集成 RAGAS 全链路评估

为了验证整个系统的改造成效,项目拒绝“体感流”盲测,直接集成了 RAGAS (RAG Assessment) 评估框架,针对生产环境下采集的 500+ 道工业运维真题进行了线上常态化体检:

  • 忠实度(Faithfulness):系统在应用了多模态 MinerU 解析与 Neo4j 拓扑约束后,生成的每一个“接线端子编号”和“扭矩参数”均能在原始手册中找到 1:1 的物理对齐,忠实度指标从最初的 71.4% 暴涨至 96.8%,基本消灭了涉及安全生产的恶性幻觉。
  • 答案相关性(Answer Relevance):得益于 LangGraph 状态总线中内置的“代词消解与 Query 重写”节点,多轮跨轮追问下的提问匹配精准度提升了 34.2%,用户再也不会遇到大模型对“这个怎么调”这类问题一脸茫然的尴尬局面。

💡 独立开发者的碎碎念

在工业 RAG 与 Agent 的落地产线里,炫技是毫无意义的,未来开发者对确定性的追求必须达到近乎变态的程度。如果一个智能体不能保证其生成步骤的绝对安全和知识库边界的绝对清爽,那它的架构设计就是失败的。通过 LangGraph 的确定性拓扑和混合检索强约束,项目让 Agent 穿上了特制的“安全防护服”,这才是大模型能真正下沉并赋能传统工业运维、重构售后服务流的唯一解。

Logo

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

更多推荐