从零构建工业视觉巡检Agent系统:RAG深度拆解 + LangGraph状态机实战
当YOLOv11拥有"大脑",它就不再只是目标检测框,而是能自主思考的巡检工程师
本文手把手带你实现"缺陷检测 → 知识检索 → 风险评估 → 报告生成"全流程自动化,深度剖析RAG工程化与LangGraph多Agent协作
目录
- 为什么需要视觉巡检Agent?
- 系统全景架构
- Agent核心设计:ReAct范式与多角色协作
- Agent的"眼睛":YOLOv11缺陷检测模块
- Agent的"记忆":RAG知识库深度拆解
- Agent的"骨架":LangGraph状态机编排
- [实时交互:FastAPI + WebSocket流式推送](#实时交互:FastAPI + WebSocket流式推送)
- Docker一键部署
- 实测效果与总结
一、为什么需要视觉巡检Agent?
在工业巡检领域,许多团队已成功部署YOLO、ResNet等模型,但落地效果往往不尽人意。根本原因在于:单纯的视觉模型只是"眼睛",它只会输出坐标和类别,却不知道下一步该做什么。
来看两个真实场景的对比:
| 场景 | 传统方案的反应 | 我们期望的智能体反应 |
|---|---|---|
| 裂纹置信度仅0.45 | 直接丢弃或人工复核,效率低下 | Agent根据设备重要等级,自动决定是否启用超分辨率重检,或调取同一设备的历史影像做对比分析 |
| 检测到锈蚀但面积较小 | 仅记录"锈蚀×1",无后续动作 | Agent自动检索该设备所在区域的湿度历史数据,结合维修规范,判断是否需要立即处理,并生成带标准条款的工单 |
为此,我们构建了视觉巡检Agent系统 ------它将YOLOv11封装为Agent的"感知器官",将RAG知识库作为"长期记忆体",将Qwen大模型作为"决策中枢",并引入ReAct(Reasoning + Acting)和Multi-Agent协作机制,真正实现了:
"看见 → 理解 → 检索 → 决策 → 执行 → 反思" 的完整认知闭环。
技术栈一览:
| 模块 | 技术选型 | 作用 |
|---|---|---|
| 目标检测 | PyTorch + YOLOv11 | 高精度工业缺陷识别 |
| Agent框架 | Qwen API + LangGraph | 自主决策与多Agent协作 |
| 知识检索 | RAG + HyDE + Cross-Encoder | 精准检索维修案例与标准 |
| 后端服务 | FastAPI + WebSocket | 异步推理、实时推送 |
| 部署交付 | Docker + Docker Compose | 一键镜像部署 |
二、系统全景架构
2.1 整体架构图
下图展示了系统的完整架构分层。自顶向下,用户通过前端上传图片和提问请求,经由 FastAPI 网关统一接入;网关将任务交给 Supervisor Agent 进行调度与状态管理。Supervisor 之下是一个多 Agent 协作集群 ,由 LangGraph 负责编排:巡检员 Agent 负责调用 YOLOv11 推理引擎完成视觉感知,分析师 Agent 通过向量数据库检索故障案例与维修规范,文秘 Agent 负责生成 PDF 报告和工单。三个子 Agent 之间不直接通信,而是通过全局消息总线共享中间结果和上下文信息。最终 Supervisor 将处理进度和结果通过 WebSocket 流式推送给前端用户。
#mermaid-svg-vhr8gTlQ9y8IQSRp{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vhr8gTlQ9y8IQSRp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vhr8gTlQ9y8IQSRp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vhr8gTlQ9y8IQSRp .error-icon{fill:#552222;}#mermaid-svg-vhr8gTlQ9y8IQSRp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vhr8gTlQ9y8IQSRp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vhr8gTlQ9y8IQSRp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vhr8gTlQ9y8IQSRp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vhr8gTlQ9y8IQSRp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vhr8gTlQ9y8IQSRp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vhr8gTlQ9y8IQSRp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vhr8gTlQ9y8IQSRp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vhr8gTlQ9y8IQSRp .marker.cross{stroke:#333333;}#mermaid-svg-vhr8gTlQ9y8IQSRp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vhr8gTlQ9y8IQSRp p{margin:0;}#mermaid-svg-vhr8gTlQ9y8IQSRp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vhr8gTlQ9y8IQSRp .cluster-label text{fill:#333;}#mermaid-svg-vhr8gTlQ9y8IQSRp .cluster-label span{color:#333;}#mermaid-svg-vhr8gTlQ9y8IQSRp .cluster-label span p{background-color:transparent;}#mermaid-svg-vhr8gTlQ9y8IQSRp .label text,#mermaid-svg-vhr8gTlQ9y8IQSRp span{fill:#333;color:#333;}#mermaid-svg-vhr8gTlQ9y8IQSRp .node rect,#mermaid-svg-vhr8gTlQ9y8IQSRp .node circle,#mermaid-svg-vhr8gTlQ9y8IQSRp .node ellipse,#mermaid-svg-vhr8gTlQ9y8IQSRp .node polygon,#mermaid-svg-vhr8gTlQ9y8IQSRp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vhr8gTlQ9y8IQSRp .rough-node .label text,#mermaid-svg-vhr8gTlQ9y8IQSRp .node .label text,#mermaid-svg-vhr8gTlQ9y8IQSRp .image-shape .label,#mermaid-svg-vhr8gTlQ9y8IQSRp .icon-shape .label{text-anchor:middle;}#mermaid-svg-vhr8gTlQ9y8IQSRp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vhr8gTlQ9y8IQSRp .rough-node .label,#mermaid-svg-vhr8gTlQ9y8IQSRp .node .label,#mermaid-svg-vhr8gTlQ9y8IQSRp .image-shape .label,#mermaid-svg-vhr8gTlQ9y8IQSRp .icon-shape .label{text-align:center;}#mermaid-svg-vhr8gTlQ9y8IQSRp .node.clickable{cursor:pointer;}#mermaid-svg-vhr8gTlQ9y8IQSRp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vhr8gTlQ9y8IQSRp .arrowheadPath{fill:#333333;}#mermaid-svg-vhr8gTlQ9y8IQSRp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vhr8gTlQ9y8IQSRp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vhr8gTlQ9y8IQSRp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vhr8gTlQ9y8IQSRp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vhr8gTlQ9y8IQSRp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vhr8gTlQ9y8IQSRp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vhr8gTlQ9y8IQSRp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vhr8gTlQ9y8IQSRp .cluster text{fill:#333;}#mermaid-svg-vhr8gTlQ9y8IQSRp .cluster span{color:#333;}#mermaid-svg-vhr8gTlQ9y8IQSRp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vhr8gTlQ9y8IQSRp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vhr8gTlQ9y8IQSRp rect.text{fill:none;stroke-width:0;}#mermaid-svg-vhr8gTlQ9y8IQSRp .icon-shape,#mermaid-svg-vhr8gTlQ9y8IQSRp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vhr8gTlQ9y8IQSRp .icon-shape p,#mermaid-svg-vhr8gTlQ9y8IQSRp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vhr8gTlQ9y8IQSRp .icon-shape .label rect,#mermaid-svg-vhr8gTlQ9y8IQSRp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vhr8gTlQ9y8IQSRp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vhr8gTlQ9y8IQSRp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vhr8gTlQ9y8IQSRp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 多Agent协作集群 - LangGraph编排
上传图片 + 提问
创建任务
分发视觉任务
分发分析任务
分发报告任务
调用
检索
生成
中间结果
中间结果
中间结果
流式推送
用户 / 前端
FastAPI 网关
Supervisor Agent
任务调度与状态管理
巡检员Agent
工具: YOLO检测/增强/ROI
分析师Agent
工具: RAG检索/统计/推理
文秘Agent
工具: PDF生成/邮件/工单
YOLOv11 推理引擎
向量数据库
故障案例/维修规范
PDF报告/工单
全局消息总线
短期记忆/上下文
WebSocket 连接
架构图清晰地体现了系统的核心设计理念:关注点分离。视觉感知、知识检索、报告生成三者各司其职,由 Supervisor 统一调度,避免了单体 Agent 在面对复杂任务时"顾此失彼"的问题。同时,虚线箭头表示的中间结果共享机制,确保了上下文不会被割裂------比如分析师在评估风险时,可以直接引用巡检员已经提取到的缺陷位置和置信度信息。
2.2 核心数据流时序图
为了更直观地展示一次完整巡检任务中各组件之间的交互顺序,下面用时序图来呈现核心数据流。整个流程可概括为**"三步走"**:
- 检测阶段 :用户通过 HTTP 提交图片后,Supervisor 创建任务并立即返回
task_id,同时建立 WebSocket 长连接。Supervisor 将检测任务派发给巡检员 Agent,巡检员先进行图像预处理(增强/缩放),再调用 YOLO 推理,将结构化检测结果(缺陷列表 + 置信度)回传。 - 分析阶段:Supervisor 拿到检测结果后,将风险评估任务派发给分析师 Agent。分析师先向 RAG 知识库发起相似案例检索,获取 Top-3 案例和相关维修规范,再结合检测结果与检索内容,由 LLM 进行综合推理,输出风险评估等级和维修建议。
- 报告阶段:Supervisor 将分析结果交给文秘 Agent,文秘负责将内容结构化为 Markdown,再转换为 PDF 文件,最终将 PDF 路径推送给用户下载。
文秘Agent RAG知识库 分析师Agent 巡检员Agent Supervisor FastAPI 用户 文秘Agent RAG知识库 分析师Agent 巡检员Agent Supervisor FastAPI 用户 #mermaid-svg-AAntajxZvjJfJCf2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AAntajxZvjJfJCf2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AAntajxZvjJfJCf2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AAntajxZvjJfJCf2 .error-icon{fill:#552222;}#mermaid-svg-AAntajxZvjJfJCf2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AAntajxZvjJfJCf2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AAntajxZvjJfJCf2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AAntajxZvjJfJCf2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AAntajxZvjJfJCf2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AAntajxZvjJfJCf2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AAntajxZvjJfJCf2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AAntajxZvjJfJCf2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AAntajxZvjJfJCf2 .marker.cross{stroke:#333333;}#mermaid-svg-AAntajxZvjJfJCf2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AAntajxZvjJfJCf2 p{margin:0;}#mermaid-svg-AAntajxZvjJfJCf2 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-AAntajxZvjJfJCf2 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-AAntajxZvjJfJCf2 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-AAntajxZvjJfJCf2 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-AAntajxZvjJfJCf2 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-AAntajxZvjJfJCf2 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-AAntajxZvjJfJCf2 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-AAntajxZvjJfJCf2 .sequenceNumber{fill:white;}#mermaid-svg-AAntajxZvjJfJCf2 #sequencenumber{fill:#333;}#mermaid-svg-AAntajxZvjJfJCf2 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-AAntajxZvjJfJCf2 .messageText{fill:#333;stroke:none;}#mermaid-svg-AAntajxZvjJfJCf2 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-AAntajxZvjJfJCf2 .labelText,#mermaid-svg-AAntajxZvjJfJCf2 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-AAntajxZvjJfJCf2 .loopText,#mermaid-svg-AAntajxZvjJfJCf2 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-AAntajxZvjJfJCf2 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-AAntajxZvjJfJCf2 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-AAntajxZvjJfJCf2 .noteText,#mermaid-svg-AAntajxZvjJfJCf2 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-AAntajxZvjJfJCf2 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-AAntajxZvjJfJCf2 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-AAntajxZvjJfJCf2 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-AAntajxZvjJfJCf2 .actorPopupMenu{position:absolute;}#mermaid-svg-AAntajxZvjJfJCf2 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-AAntajxZvjJfJCf2 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-AAntajxZvjJfJCf2 .actor-man circle,#mermaid-svg-AAntajxZvjJfJCf2 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-AAntajxZvjJfJCf2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} POST /inspect (图片+设备ID) 创建任务并启动 返回 task_id (WebSocket 建立) 派发"执行缺陷检测" 图像预处理 (增强/缩放) YOLO推理 返回检测结果(缺陷列表+置信度) 推送进度 "检测完成" 派发"风险评估与根因分析" 检索相似故障案例 返回Top-3案例+维修规范 LLM推理(结合检测结果+案例) 返回风险评估+维修建议 推送进度 "分析完成" 派发"生成正式报告" 结构化内容 → Markdown Markdown → PDF 返回PDF文件路径 推送最终报告 (含下载链接) 下载PDF
从时序图中可以看到,每一步中间结果都会通过 WebSocket 实时推送给前端,而非等所有步骤执行完毕再一次性返回。这种流式推送的设计对用户体验至关重要------工业巡检往往是时间敏感场景,操作人员需要实时感知 Agent 的推理进度,而不是面对一个"黑盒等待"的状态。
三、Agent核心设计:ReAct范式与多角色协作
3.1 ReAct(推理+行动)范式
我们的Agent并非简单地顺序调用工具,而是基于ReAct模式进行动态推理------每一步都包含"思考-行动-观察"的循环。
一个真实的Agent执行日志:
text
[Step 1]
Thought: 用户上传的是汽轮机轴承座图片,光照偏暗,可能影响YOLO检出率。
Action: enhance_image(image_path, method="clahe")
Observation: 图像对比度提升,暗部细节增强。
[Step 2]
Thought: 现在调用检测工具,鉴于该设备经常出现微裂纹,我将阈值调低至0.35。
Action: detect_defects(image_path, conf_threshold=0.35)
Observation: 检出裂纹×1 (conf=0.42, bbox=[120,340,180,390]),锈蚀×2。
[Step 3]
Thought: 裂纹置信度只有0.42,但位置在主轴颈部,属于高风险区域。需要对该区域进行超分辨率放大再确认。
Action: super_resolution(bbox=[120,340,180,390], scale=4)
Observation: 放大后裂纹纹理清晰,重新检测置信度升至0.89。
[Step 4]
Thought: 确认为真阳性。现在查询该设备的历史维修记录和行业标准。
Action: query_rag("汽轮机 主轴 裂纹 维修标准")
Observation: 返回《DL/T 5210-202X》第4.2节,规定主轴不允许存在任何裂纹,需立即停机更换。
[Step 5]
Thought: 风险等级为"紧急",报告需突出标准原文,并生成工单。
Action: generate_emergency_order(...)
Final Answer: 报告与工单已生成,链接:...
3.2 多角色协作(Multi-Agent Collaboration)
为了更贴近真实运维场景,我们将单体Agent拆分为三个专业化子Agent ,由一名Supervisor Agent进行调度:
| Agent角色 | 职责 | 绑定的工具链 |
|---|---|---|
| 巡检员Agent | 负责视觉感知与目标检测 | YOLO推理、图像预处理、ROI裁剪 |
| 分析师Agent | 负责风险评估与根因分析 | 缺陷统计、RAG知识检索、相似度匹配 |
| 文秘Agent | 负责文案撰写与格式输出 | Markdown转PDF、邮件发送、工单系统对接 |
交互流程:Supervisor接收到图片后,先派发给"巡检员Agent"提取特征;再将结构化检测结果交给"分析师Agent"结合RAG进行深度推理;最后汇总给"文秘Agent"输出正式公文。三者通过**全局消息总线(Memory)**共享上下文,解决了单一模型上下文窗口不足和角色混淆的问题。
四、Agent的"眼睛":YOLOv11缺陷检测模块
4.1 模型训练与优化
我们针对工业裂纹、锈蚀、异物、划痕、油污五类目标,采集并标注了超过2万张现场图片,基于YOLOv11进行了深度调优。
关键优化点:
- Backbone改进:引入RepVGG结构,提升小目标(细小裂纹)的感知能力
- 损失函数:采用WIoU v3损失,降低低质量样本的梯度干扰
- 数据增强:Mosaic、MixUp、随机亮度对比度,模拟不同光照和拍摄角度
python
# train.py 核心片段
from ultralytics import YOLO
model = YOLO("yolo11n.pt")
results = model.train(
data="industrial_defect.yaml",
epochs=200,
imgsz=640,
batch=16,
device=0,
augment=True,
patience=20,
project="runs/train_defect"
)
4.2 封装为Agent工具
我们将检测模块封装为Agent可调用的工具,不仅返回坐标,还返回可读性语义描述:
python
from ultralytics import YOLO
import cv2
import numpy as np
class DefectDetector:
def __init__(self, model_path="best.pt"):
self.model = YOLO(model_path)
self.class_names = ["crack", "rust", "foreign_body", "scratch", "oil_stain"]
def __call__(self, image_path, conf_threshold=0.45, iou_threshold=0.5,
roi=None, super_res=False):
"""
Agent可调用的检测函数。
:param roi: 可选,[x1,y1,x2,y2] 只检测该区域
:param super_res: 是否先对该区域进行超分放大
"""
img = cv2.imread(image_path)
if roi:
x1,y1,x2,y2 = roi
if super_res:
roi_img = img[y1:y2, x1:x2]
roi_img = cv2.resize(roi_img, (0,0), fx=4, fy=4, interpolation=cv2.INTER_CUBIC)
img[y1:y2, x1:x2] = cv2.resize(roi_img, (x2-x1, y2-y1))
results = self.model(img, conf=conf_threshold, iou=iou_threshold)
detections = []
for r in results:
for box in r.boxes:
cls_id = int(box.cls[0])
conf = float(box.conf[0])
bbox = box.xyxy[0].tolist()
detections.append({
"class": self.class_names[cls_id],
"confidence": conf,
"bbox": bbox,
"position_desc": self._describe_position(bbox, img.shape)
})
return {
"detections": detections,
"total_count": len(detections),
"class_counts": {cls: sum(1 for d in detections if d["class"]==cls) for cls in self.class_names},
"visualized": self._draw_boxes(img, detections)
}
def _describe_position(self, bbox, shape):
"""将坐标转为语义描述,便于Agent理解"""
h,w = shape[:2]
cx, cy = (bbox[0]+bbox[2])/2, (bbox[1]+bbox[3])/2
x_desc = "左侧" if cx < w/3 else "中部" if cx < 2*w/3 else "右侧"
y_desc = "上部" if cy < h/3 else "中部" if cy < 2*h/3 else "下部"
return f"{y_desc}-{x_desc}"
五、Agent的"记忆":RAG知识库深度拆解
RAG不是简单的"向量检索+LLM生成"。在工业场景中,检索的精度直接决定了维修建议的可靠性。我们设计了一套**"混合检索 + HyDE + 重排序"**的三阶段流水线。
5.1 知识库分层架构
单一向量库无法同时满足"覆盖面广"和"答案精确"两个要求。我们采用双层知识库架构:
| 层级 | 类型 | 数据来源 | 检索方式 | 目标 |
|---|---|---|---|---|
| L1 权威层 | FAQ精标库 | 专家审核的标准问答对(SOP、安全规程) | 关键词 + 语义精确匹配 | 高频问题"零偏差"回答 |
| L2 广域层 | 向量文档库 | 设备手册、维修日志、故障案例 | 向量语义检索 | 覆盖长尾与复杂查询 |
工作流程:用户提问 → 先查L1权威层(命中则直接返回)→ 未命中则降级到L2向量库检索 → LLM生成答案。这种"先精后广"的策略,提高高频问题的准确率。
5.2 文档预处理与索引构建
python
from langchain.document_loaders import DirectoryLoader, TextLoader, PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import Chroma
import hashlib
# 1. 多格式文档加载
loaders = [
DirectoryLoader("./knowledge_base/manuals", glob="**/*.pdf", loader_cls=PyPDFLoader),
DirectoryLoader("./knowledge_base/cases", glob="**/*.md", loader_cls=UnstructuredMarkdownLoader),
DirectoryLoader("./knowledge_base/standards", glob="**/*.txt", loader_cls=TextLoader),
]
docs = []
for loader in loaders:
docs.extend(loader.load())
# 2. 智能切分(保留章节标题作为元数据)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=512,
chunk_overlap=64,
separators=["\n\n", "\n", "。", ";", ",", " ", ""],
length_function=len,
)
chunks = text_splitter.split_documents(docs)
# 3. 为每个chunk添加丰富的元数据
for chunk in chunks:
chunk.metadata["doc_id"] = hashlib.md5(chunk.page_content.encode()).hexdigest()[:8]
chunk.metadata["source_type"] = chunk.metadata.get("source", "").split(".")[-1]
chunk.metadata["category"] = infer_category(chunk) # 故障处理/操作指南/维护保养等
# 4. 向量化与存储
embeddings = HuggingFaceEmbeddings(
model_name="shibing624/text2vec-base-chinese",
model_kwargs={"device": "cuda"},
encode_kwargs={"normalize_embeddings": True},
)
vectordb = Chroma.from_documents(
chunks,
embeddings,
persist_directory="./chroma_db",
collection_metadata={"hnsw:space": "cosine"}
)
vectordb.persist()
5.3 HyDE(假设性文档嵌入)
HyDE的核心思想是:让LLM先根据问题"编"一段假设的标准答案,再用这段假设答案去检索,而不是直接用原始问题检索。这能显著提升语义漂移场景下的召回率。
python
def hyde_generate(query: str, defect_info: dict) -> str:
"""让LLM生成一段假设性的故障描述文档"""
prompt = f"""
你是一位资深的设备维修工程师。请根据以下缺陷信息,撰写一段假设性的故障诊断报告(200-300字),
内容包括:可能的故障原因、典型的故障现象、以及标准的处理流程。
设备类型:{defect_info.get('equipment_type', '未知')}
缺陷类型:{defect_info.get('defect_type', '未知')}
缺陷位置:{defect_info.get('position', '未知')}
置信度:{defect_info.get('confidence', 0)}
要求:使用专业术语,风格类似设备维修手册。
"""
hypothetical_doc = llm.generate(prompt)
return hypothetical_doc
5.4 混合检索 + 重排序(三阶段流水线)
我们采用 "向量检索 + BM25关键词检索 + Cross-Encoder重排序" 三路并行的策略:
python
from rank_bm25 import BM25Okapi
from sentence_transformers import CrossEncoder
# 初始化 BM25 索引
tokenized_corpus = [chunk.page_content.split() for chunk in chunks]
bm25 = BM25Okapi(tokenized_corpus)
# 初始化 Cross-Encoder 重排序模型
reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
def hybrid_retrieve(query: str, defect_info: dict, top_k: int = 10) -> List[Document]:
# 1. HyDE生成假设文档
hypo_doc = hyde_generate(query, defect_info)
# 2. 向量检索(原始问题 + HyDE文档双路召回)
vec_results_1 = vectordb.similarity_search(query, k=top_k)
vec_results_2 = vectordb.similarity_search(hypo_doc, k=top_k)
vec_results = merge_deduplicate(vec_results_1, vec_results_2)
# 3. BM25关键词检索
tokenized_query = query.split()
bm25_scores = bm25.get_scores(tokenized_query)
bm25_indices = sorted(range(len(bm25_scores)), key=lambda i: bm25_scores[i], reverse=True)[:top_k]
bm25_results = [chunks[i] for i in bm25_indices]
# 4. 合并去重
candidates = merge_deduplicate(vec_results, bm25_results)
# 5. Cross-Encoder 重排序
pairs = [[query, doc.page_content] for doc in candidates]
scores = reranker.predict(pairs)
sorted_candidates = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)
return [{"document": doc, "relevance_score": score} for doc, score in sorted_candidates[:5]]
5.5 检索结果的结构化标注
检索到的文档需要带上分类标签,让LLM知道每段信息的性质:
python
def build_rag_context(retrieved_docs: List[dict]) -> str:
context_parts = []
for i, item in enumerate(retrieved_docs, 1):
doc = item["document"]
category = doc.metadata.get("category", "通用知识")
type_map = {
"故障处理": "故障处理信息",
"操作指南": "操作指南",
"维护保养": "维护保养信息",
"安全规程": "安全规程信息",
}
info_type = type_map.get(category, "知识库信息")
context_parts.append(f"""
【{info_type} {i}】
- 来源: {doc.metadata.get('source', '未知')}
- 章节: {doc.metadata.get('section', '未知')}
- 重要程度: {doc.metadata.get('severity', '中')}
- 内容: {doc.page_content}
""")
return "\n".join(context_parts)
这样,LLM在生成维修建议时,能清晰分辨哪些是刚性标准 (安全规程)、哪些是参考案例(历史故障),大幅降低"幻觉"风险。
六、Agent的"骨架":LangGraph状态机编排
我们选用LangGraph 作为Agent的底层编排引擎。LangGraph的核心优势在于将Agent逻辑建模为有向图(Graph),每个节点是一个处理步骤,每条边定义流转条件。
6.1 LangGraph核心概念
| 特性 | 传统Chain | LangGraph |
|---|---|---|
| 流程控制 | 线性顺序执行 | 条件分支、循环、并行 |
| 状态管理 | 隐式传递 | 显式State对象,跨节点共享 |
| 多Agent协作 | 不支持 | 原生支持Supervisor模式 |
| 人工介入 | 困难 | 支持Human-in-the-Loop |
6.2 定义状态与节点
python
from langgraph.graph import StateGraph, END
from typing import TypedDict, List
# 1. 定义状态(所有节点共享的数据结构)
class AgentState(TypedDict):
messages: List[dict] # 对话历史
image_path: str # 当前处理的图片
detections: List[dict] # YOLO检测结果
retrieved_docs: List[dict] # RAG检索结果
risk_level: str # 风险评估等级
report_content: str # 生成的报告
iteration: int # 当前迭代次数
max_iterations: int # 最大迭代次数
# 2. 定义节点函数
def node_detect(state: AgentState) -> AgentState:
detector = DefectDetector()
result = detector(state["image_path"])
state["detections"] = result["detections"]
return state
def node_retrieve(state: AgentState) -> AgentState:
defect_type = state["detections"][0]["class"] if state["detections"] else "未知"
docs = hybrid_retrieve(defect_type, {"equipment_type": state.get("equipment", "未知")})
state["retrieved_docs"] = docs
return state
def node_analyze(state: AgentState) -> AgentState:
# 调用LLM分析风险
prompt = f"""
检测结果:{state['detections']}
知识库内容:{build_rag_context(state['retrieved_docs'])}
请评估风险等级(紧急/高/中/低)并给出维修建议。
"""
response = llm.generate(prompt)
state["risk_level"] = extract_risk_level(response)
state["report_content"] = response
return state
6.3 构建带条件分支的图
python
# 3. 定义条件边
def should_continue(state: AgentState) -> str:
if state.get("risk_level") == "紧急":
return "generate_urgent_report"
elif state.get("iteration", 0) < state.get("max_iterations", 3):
return "continue_analysis"
else:
return "end"
# 4. 构建图
graph = StateGraph(AgentState)
graph.add_node("detect", node_detect)
graph.add_node("retrieve", node_retrieve)
graph.add_node("analyze", node_analyze)
graph.add_node("report", node_report)
graph.add_node("urgent_report", node_urgent_report)
graph.set_entry_point("detect")
graph.add_edge("detect", "retrieve")
graph.add_edge("retrieve", "analyze")
graph.add_conditional_edges(
"analyze",
should_continue,
{
"continue_analysis": "retrieve", # 循环:再次检索
"generate_urgent_report": "urgent_report",
"end": END,
}
)
graph.add_edge("urgent_report", END)
graph.add_edge("report", END)
# 5. 编译并运行
app = graph.compile()
result = app.invoke(initial_state)
6.4 基于LangGraph的ReAct Agent
LangGraph官方提供了构建ReAct Agent的标准范式:
python
from langgraph.prebuilt import ToolExecutor, create_react_agent
from langchain_openai import ChatOpenAI
from langchain.tools import tool
# 1. 定义工具
@tool
def detect_defects(image_path: str, conf_threshold: float = 0.45) -> dict:
"""对设备图片进行缺陷检测"""
detector = DefectDetector()
return detector(image_path, conf_threshold)
@tool
def query_rag(query: str, top_k: int = 3) -> List[dict]:
"""检索知识库,返回相关文档"""
return hybrid_retrieve(query, {}, top_k)
tools = [detect_defects, query_rag]
tool_executor = ToolExecutor(tools)
# 2. 构建ReAct Agent图
llm = ChatOpenAI(model="qwen-max", temperature=0)
agent = create_react_agent(llm, tools)
# 3. 运行Agent
result = agent.invoke({
"messages": [("user", "请分析这张设备图片的缺陷情况并给出维修建议")],
"image_path": "./uploads/IMG_1024.jpg"
})
6.5 多Agent协作:Supervisor模式
对于Multi-Agent场景,LangGraph提供了Supervisor模式的开箱即用支持:
python
from langgraph_supervisor import create_supervisor
# 1. 创建三个专家Agent
inspector_agent = create_react_agent(llm, [detect_defects, enhance_image])
analyst_agent = create_react_agent(llm, [query_rag, statistics_defects])
secretary_agent = create_react_agent(llm, [generate_pdf, send_email])
# 2. 创建Supervisor(自动路由)
supervisor = create_supervisor(
agents=[inspector_agent, analyst_agent, secretary_agent],
model=llm,
prompt="""你是一位工业巡检任务调度专家。根据用户需求,将任务分配给最合适的专家:
- 巡检员专家:负责图像检测与预处理
- 分析师专家:负责风险评估与知识检索
- 文秘专家:负责报告生成与分发
"""
)
# 3. 运行
result = supervisor.invoke({
"messages": [("user", "对汽轮机轴承座进行巡检,生成完整报告")]
})
Supervisor内部工作流程:
Supervisor 是整个多 Agent 协作的"大脑"。当用户发出"对汽轮机轴承座进行巡检,生成完整报告"的指令后,Supervisor 并不会一次性把任务全部分配出去,而是逐步判断、逐级路由。下面这张流程图展示了 Supervisor 的决策链路:
- Supervisor 首先判断用户需求中是否包含图像检测任务,若有则路由给巡检员 Agent。
- 巡检员完成检测并返回结果后,Supervisor 判断是否需要进一步的风险分析------如果检测到了缺陷,则自动路由给分析师 Agent 进行深度推理。
- 分析师完成风险评估后,Supervisor 判断是否需要生成正式报告,若需要则路由给文秘 Agent 完成最终的 PDF 输出。
整个过程中,Supervisor 在每一轮都会重新评估当前状态,而不是按固定顺序执行。这意味着如果分析师发现某缺陷已有明确历史结论,可以直接跳过文秘环节结束流程,避免了不必要的计算开销。
#mermaid-svg-d8v5P3WGqu6LyVlx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-d8v5P3WGqu6LyVlx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-d8v5P3WGqu6LyVlx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-d8v5P3WGqu6LyVlx .error-icon{fill:#552222;}#mermaid-svg-d8v5P3WGqu6LyVlx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-d8v5P3WGqu6LyVlx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-d8v5P3WGqu6LyVlx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-d8v5P3WGqu6LyVlx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-d8v5P3WGqu6LyVlx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-d8v5P3WGqu6LyVlx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-d8v5P3WGqu6LyVlx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-d8v5P3WGqu6LyVlx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-d8v5P3WGqu6LyVlx .marker.cross{stroke:#333333;}#mermaid-svg-d8v5P3WGqu6LyVlx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-d8v5P3WGqu6LyVlx p{margin:0;}#mermaid-svg-d8v5P3WGqu6LyVlx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-d8v5P3WGqu6LyVlx .cluster-label text{fill:#333;}#mermaid-svg-d8v5P3WGqu6LyVlx .cluster-label span{color:#333;}#mermaid-svg-d8v5P3WGqu6LyVlx .cluster-label span p{background-color:transparent;}#mermaid-svg-d8v5P3WGqu6LyVlx .label text,#mermaid-svg-d8v5P3WGqu6LyVlx span{fill:#333;color:#333;}#mermaid-svg-d8v5P3WGqu6LyVlx .node rect,#mermaid-svg-d8v5P3WGqu6LyVlx .node circle,#mermaid-svg-d8v5P3WGqu6LyVlx .node ellipse,#mermaid-svg-d8v5P3WGqu6LyVlx .node polygon,#mermaid-svg-d8v5P3WGqu6LyVlx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-d8v5P3WGqu6LyVlx .rough-node .label text,#mermaid-svg-d8v5P3WGqu6LyVlx .node .label text,#mermaid-svg-d8v5P3WGqu6LyVlx .image-shape .label,#mermaid-svg-d8v5P3WGqu6LyVlx .icon-shape .label{text-anchor:middle;}#mermaid-svg-d8v5P3WGqu6LyVlx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-d8v5P3WGqu6LyVlx .rough-node .label,#mermaid-svg-d8v5P3WGqu6LyVlx .node .label,#mermaid-svg-d8v5P3WGqu6LyVlx .image-shape .label,#mermaid-svg-d8v5P3WGqu6LyVlx .icon-shape .label{text-align:center;}#mermaid-svg-d8v5P3WGqu6LyVlx .node.clickable{cursor:pointer;}#mermaid-svg-d8v5P3WGqu6LyVlx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-d8v5P3WGqu6LyVlx .arrowheadPath{fill:#333333;}#mermaid-svg-d8v5P3WGqu6LyVlx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-d8v5P3WGqu6LyVlx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-d8v5P3WGqu6LyVlx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-d8v5P3WGqu6LyVlx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-d8v5P3WGqu6LyVlx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-d8v5P3WGqu6LyVlx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-d8v5P3WGqu6LyVlx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-d8v5P3WGqu6LyVlx .cluster text{fill:#333;}#mermaid-svg-d8v5P3WGqu6LyVlx .cluster span{color:#333;}#mermaid-svg-d8v5P3WGqu6LyVlx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-d8v5P3WGqu6LyVlx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-d8v5P3WGqu6LyVlx rect.text{fill:none;stroke-width:0;}#mermaid-svg-d8v5P3WGqu6LyVlx .icon-shape,#mermaid-svg-d8v5P3WGqu6LyVlx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-d8v5P3WGqu6LyVlx .icon-shape p,#mermaid-svg-d8v5P3WGqu6LyVlx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-d8v5P3WGqu6LyVlx .icon-shape .label rect,#mermaid-svg-d8v5P3WGqu6LyVlx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-d8v5P3WGqu6LyVlx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-d8v5P3WGqu6LyVlx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-d8v5P3WGqu6LyVlx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 判断: 需要检测
返回检测结果
判断: 需要分析
返回分析结果
判断: 需要报告
返回报告
用户提问
Supervisor Agent
巡检员Agent
分析师Agent
文秘Agent
这种 Supervisor 模式的核心优势在于解耦了任务划分逻辑和具体执行逻辑 :每个专家 Agent 只需专注于自己的领域(检测、分析、报告),而"谁来做、什么时候做、做完之后下一步是什么"这些调度决策全部由 Supervisor 承担。当后续需要新增一个"安全审计 Agent"时,只需在 agents 列表中注册并添加对应的路由规则即可,对现有 Agent 零侵入。
6.6 人机协同(Human-in-the-Loop)
工业场景中,关键决策必须有人工确认环节。LangGraph原生支持在任意节点暂停执行:
python
from langgraph.checkpoint import MemorySaver
# 在analyze节点后添加"人工确认"断点
graph.add_node("human_review", lambda state: state)
# 配置checkpointer
checkpointer = MemorySaver()
app = graph.compile(checkpointer=checkpointer)
# 执行到human_review节点时会暂停
config = {"configurable": {"thread_id": "task_123"}}
for event in app.stream(initial_state, config, stream_mode="values"):
if event.get("stop_reason") == "awaiting_human_input":
# 前端展示中间结果,等待人工确认
human_feedback = get_user_feedback()
app.update_state(config, {"human_feedback": human_feedback})
app.stream(None, config, stream_mode="values")
6.7 Agent的"自我反思"与记忆增强
我们为每个设备维护一个时序记忆库(基于InfluxDB),存储历史检测结果和Agent的决策:
python
class InspectionMemory:
def __init__(self, equipment_id):
self.equipment_id = equipment_id
self.history = self.load_history(limit=5)
def load_history(self, limit):
query = f'SELECT * FROM inspections WHERE equipment="{self.equipment_id}" ORDER BY time DESC LIMIT {limit}'
return db.query(query)
def get_context(self):
if not self.history:
return "该设备无历史巡检记录。"
lines = []
for rec in self.history:
lines.append(f"{rec['time']}: 检出 {rec['defects']}, 处理意见: {rec['action']}")
return "\n".join(lines)
在分析师Agent的提示词中强制加入记忆上下文:
python
analyst_prompt = """
你是设备故障分析师。以下信息供参考:
- 当前检测结果:{detections}
- 该设备历史记录(最近5次):
{history_context}
请根据以上信息,判断本次缺陷是否有恶化趋势,给出更精准的风险等级。
"""
七、实时交互:FastAPI + WebSocket流式推送
7.1 后端核心API
python
from fastapi import FastAPI, File, UploadFile, WebSocket, WebSocketDisconnect, BackgroundTasks
import uuid
import asyncio
app = FastAPI()
task_store = {} # task_id -> {status, result, progress}
@app.post("/api/v1/inspect")
async def start_inspection(file: UploadFile = File(...), equipment_id: str = None):
task_id = str(uuid.uuid4())
image_path = f"./uploads/{task_id}_{file.filename}"
with open(image_path, "wb") as f:
f.write(await file.read())
background_tasks.add_task(run_agent_task, task_id, image_path, equipment_id)
return {"task_id": task_id, "status": "pending", "ws_url": f"/ws/{task_id}"}
@app.websocket("/ws/{task_id}")
async def websocket_endpoint(websocket: WebSocket, task_id: str):
await websocket.accept()
if task_id in task_store and task_store[task_id]["status"] == "completed":
await websocket.send_json(task_store[task_id]["result"])
await websocket.close()
return
task_store.setdefault(task_id, {})["ws_connections"] = task_store.get(task_id, {}).get("ws_connections", []) + [websocket]
try:
while True:
await websocket.receive_text()
except WebSocketDisconnect:
task_store[task_id]["ws_connections"].remove(websocket)
7.2 Agent运行时的进度推送
python
async def run_agent_task(task_id, image_path, equipment_id):
task_store[task_id] = {"status": "running", "progress": []}
async def push(step_type, content, detail=None):
msg = {"type": step_type, "content": content, "detail": detail}
task_store[task_id]["progress"].append(msg)
for ws in task_store[task_id].get("ws_connections", []):
try:
await ws.send_json(msg)
except:
pass
await push("system", "开始处理任务...")
# Step 1: 检测
await push("thought", "正在执行图像预处理与YOLO检测", {"threshold": 0.4})
detect_result = inspector.run(image_path, equipment_id)
await push("observation", f"检测到 {detect_result['total_count']} 处缺陷", detect_result['class_counts'])
# Step 2: 分析
await push("thought", "正在进行风险评估与知识库检索")
analysis = analyst.run(detect_result, equipment_id)
await push("observation", f"风险等级: {analysis['risk_level']}", analysis['summary'])
# Step 3: 生成报告
await push("thought", "正在生成正式报告")
report_path = secretary.run(analysis)
await push("done", "报告生成完毕", {"download_url": f"/reports/{report_path}"})
task_store[task_id]["status"] = "completed"
task_store[task_id]["result"] = {"report_url": f"/reports/{report_path}"}
八、Docker一键部署
8.1 Dockerfile
dockerfile
FROM pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
COPY models/ /app/models/
COPY chroma_db/ /app/chroma_db/
FROM builder AS runtime
COPY . /app
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
8.2 docker-compose.yml
yaml
version: '3.8'
services:
agent:
build: .
ports:
- "8000:8000"
environment:
- QWEN_API_KEY=${QWEN_API_KEY}
- REDIS_URL=redis://redis:6379
volumes:
- ./uploads:/app/uploads
- ./reports:/app/reports
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
redis:
image: redis:7-alpine
ports:
- "6379:6379"
influxdb:
image: influxdb:2.7
environment:
- INFLUXDB_DB=inspection
ports:
- "8086:8086"
8.3 一键启动
bash
# 一条命令,即可拥有一个具备硕士级知识水平的巡检Agent
docker-compose up -d
九、总结
9.1 核心技术总结
| 模块 | 关键技术 | 核心价值 |
|---|---|---|
| 视觉感知 | YOLOv11 + WIoU损失 | 高精度检测工业缺陷 |
| RAG知识库 | HyDE + 混合检索 + Cross-Encoder重排序 | 精准检索维修标准与案例 |
| Agent编排 | LangGraph状态机 + Supervisor模式 | 多Agent协作与自主决策 |
| 人机协同 | Human-in-the-Loop + Checkpointer | 关键决策人工确认 |
| 实时交互 | FastAPI + WebSocket流式推送 | 透明化Agent思考过程 |
9.2 未来规划
- 多模态感知升级:接入红外热成像和振动传感器数据,让Agent拥有"触觉"和"温感"
- 基于人类反馈的强化学习(RLHF):收集专家的复核修改记录,微调Agent的决策偏好
- 边缘端轻量化:通过ONNX和TensorRT加速,将Agent推理端部署至工业边缘网关
写在最后
这套体系从零到一的搭建过程,让我最深刻的体会是------工程化Agent的难点不在于单个模型有多强,而在于如何把"视觉、语言、记忆、决策"四个维度的高不确定性子系统串联成一个稳定、可解释、可干预 的整体。每增加一个组件都会引入新的延迟和失败点,因此在架构上我们刻意做了大量"向后兼容"的设计,比如每一步都保留中间结果的快照,让Supervisor可以在任何环节重新调度。
如果你也准备在自己的业务里复现这套方案,我的建议是先跑通最小闭环(检测 → RAG检索 → 简单分析),再逐步加多Agent协作、人机协同这些高阶能力。
如果大家在构建工业Agent过程中遇到工具调用幻觉、RAG检索不准确、LangGraph状态管理等问题,欢迎在评论区留言,我们一起探讨!