制造业AI应用实战:质检、预测性维护与工艺优化
制造业AI应用实战:质检、预测性维护与工艺优化
开篇故事:5%的漏检率,一次召回损失3200万
2023年下半年,赵强在某汽车零件厂负责MES系统的维护开发。
那个厂月产量约120万个零件,全靠8名质检员轮班目检。平均漏检率:5%。
听起来5%不高,但放到120万件里是6万件出了厂。出厂后被发现不良品触发召回,那一次因为一批轴承内圈有细微裂纹漏检,召回了3.2万辆车,直接损失超过3200万。
赵强被叫去开了一个会。会议结论是:引入AI视觉质检系统。
他是Java工程师,不是CV算法工程师。怎么办?
他的解法是:不自研视觉模型,调用视觉API(阿里云视觉智能/百度飞桨),Java层只做工程集成。
3个月后上线,漏检率从5%降到0.3%,节省了6名人工质检员的岗位(保留2名专门处理AI标注为可疑的case),单条生产线每年降低质量损失约480万元。
这就是制造业AI的现实:不是搞科研,而是把现有AI能力用工程化方法接进生产线。
一、制造业AI应用全景
ROI优先级(从高到低):
| 应用 | 典型ROI | 实施周期 | 难度 |
|---|---|---|---|
| AI视觉质检 | 6-18个月回本 | 3-6个月 | 中 |
| 预测性维护 | 12-24个月回本 | 6-12个月 | 中高 |
| 设备手册RAG | 2-4个月回本 | 1-2个月 | 低 |
| 工艺参数优化 | 12-36个月回本 | 6-18个月 | 高 |
二、视觉质检:图片缺陷识别系统
2.1 系统架构
2.2 图像质检服务实现
package com.manufacturing.ai.quality;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class VisualQualityInspectionService {
private final RestTemplate restTemplate;
private final QualityInspectionResultRepository resultRepo;
private final HumanReviewQueueService humanReviewQueue;
@Value("${vision.api.endpoint}")
private String visionApiEndpoint;
@Value("${vision.api.key}")
private String visionApiKey;
// 高置信度阈值:自动判决
private static final double HIGH_CONFIDENCE_THRESHOLD = 0.95;
// 低置信度阈值:低于此值重新采图
private static final double LOW_CONFIDENCE_THRESHOLD = 0.50;
/**
* 单件视觉质检
*
* @param imageData 图片二进制数据
* @param productType 产品类型(用于选择检测模型)
* @param productionBatchId 生产批次ID
* @param serialNumber 产品序列号
*/
public InspectionResult inspect(
byte[] imageData,
String productType,
String productionBatchId,
String serialNumber) {
long startTime = System.currentTimeMillis();
log.debug("开始视觉质检,序列号: {}, 产品类型: {}", serialNumber, productType);
// 1. 图像预处理
byte[] processedImage = preprocessImage(imageData, productType);
// 2. 调用视觉API
VisionApiResponse apiResponse = callVisionApi(processedImage, productType);
// 3. 解析结果
InspectionResult result = parseApiResponse(apiResponse, serialNumber, productionBatchId);
result.setInspectionTimeMs(System.currentTimeMillis() - startTime);
// 4. 根据置信度决定处置方式
applyDispositionPolicy(result);
// 5. 持久化记录
resultRepo.save(result);
log.info("质检完成,序列号: {}, 结果: {}, 置信度: {:.2f}, 耗时: {}ms",
serialNumber, result.getDisposition(),
result.getConfidence(), result.getInspectionTimeMs());
return result;
}
/**
* 批量质检(产线高速场景,使用线程池并发处理)
*/
public List<InspectionResult> batchInspect(
List<InspectionRequest> requests,
String productType,
String batchId) {
int parallelism = Math.min(requests.size(), 8); // 最多8个并发
ExecutorService executor = Executors.newFixedThreadPool(parallelism);
try {
List<CompletableFuture<InspectionResult>> futures = requests.stream()
.map(req -> CompletableFuture.supplyAsync(() ->
inspect(req.getImageData(), productType, batchId, req.getSerialNumber()),
executor
))
.collect(Collectors.toList());
return futures.stream()
.map(f -> {
try {
return f.get(10, TimeUnit.SECONDS);
} catch (Exception e) {
log.error("单件质检超时或失败", e);
return InspectionResult.error("质检超时");
}
})
.collect(Collectors.toList());
} finally {
executor.shutdown();
}
}
private byte[] preprocessImage(byte[] rawImage, String productType) {
// 图像预处理:裁剪ROI区域、亮度均衡、缩放到标准尺寸
// 生产环境使用 imgscalr 或 JavaCV
return rawImage; // 简化示例
}
private VisionApiResponse callVisionApi(byte[] image, String productType) {
// 调用视觉AI API(以阿里云视觉智能为例)
String base64Image = Base64.getEncoder().encodeToString(image);
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("image", base64Image);
requestBody.put("model", getModelForProductType(productType));
requestBody.put("categories", getDefectCategories(productType));
Map<String, String> headers = Map.of(
"Authorization", "Bearer " + visionApiKey,
"Content-Type", "application/json"
);
try {
return restTemplate.postForObject(
visionApiEndpoint + "/v1/inspect",
new org.springframework.http.HttpEntity<>(requestBody,
new org.springframework.http.HttpHeaders() {{
set("Authorization", "Bearer " + visionApiKey);
set("Content-Type", "application/json");
}}),
VisionApiResponse.class
);
} catch (Exception e) {
log.error("视觉API调用失败", e);
throw new VisionApiException("视觉检测服务不可用", e);
}
}
private InspectionResult parseApiResponse(
VisionApiResponse response,
String serialNumber,
String batchId) {
InspectionResult result = new InspectionResult();
result.setSerialNumber(serialNumber);
result.setBatchId(batchId);
result.setInspectedAt(java.time.LocalDateTime.now());
if (response == null || response.getDetections() == null || response.getDetections().isEmpty()) {
result.setDefectFound(false);
result.setConfidence(0.95);
result.setDefectType(null);
return result;
}
// 取置信度最高的检测结果
VisionDetection topDetection = response.getDetections().stream()
.max(Comparator.comparingDouble(VisionDetection::getConfidence))
.orElseThrow();
boolean isDefect = !"NORMAL".equals(topDetection.getLabel());
result.setDefectFound(isDefect);
result.setConfidence(topDetection.getConfidence());
result.setDefectType(isDefect ? topDetection.getLabel() : null);
result.setDefectLocation(topDetection.getBoundingBox());
result.setDefectSeverity(topDetection.getSeverity());
return result;
}
private void applyDispositionPolicy(InspectionResult result) {
if (result.getConfidence() < LOW_CONFIDENCE_THRESHOLD) {
result.setDisposition(Disposition.RECAPTURE);
} else if (result.getConfidence() >= HIGH_CONFIDENCE_THRESHOLD) {
result.setDisposition(result.isDefectFound()
? Disposition.REJECT : Disposition.PASS);
} else {
// 中间置信度:加入人工复核队列
result.setDisposition(Disposition.HUMAN_REVIEW);
humanReviewQueue.enqueue(result);
}
}
private String getModelForProductType(String productType) {
return switch (productType) {
case "bearing" -> "bearing-defect-v3";
case "gear" -> "gear-surface-v2";
case "pcb" -> "pcb-inspection-v4";
default -> "general-defect-v1";
};
}
private List<String> getDefectCategories(String productType) {
return switch (productType) {
case "bearing" -> List.of("crack", "scratch", "pit", "rust", "NORMAL");
case "gear" -> List.of("chip", "wear", "crack", "burr", "NORMAL");
default -> List.of("defect", "NORMAL");
};
}
public enum Disposition {
PASS, // 合格,放行
REJECT, // 不合格,剔除
HUMAN_REVIEW, // 人工复核
RECAPTURE // 重新采图
}
}2.3 质检统计与报表
package com.manufacturing.ai.quality;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class QualityMetricsService {
private final QualityInspectionResultRepository resultRepo;
private final AlertService alertService;
/**
* 实时不良率监控(每10分钟计算)
*/
@Scheduled(fixedRate = 600000)
public void monitorDefectRate() {
LocalDateTime oneHourAgo = LocalDateTime.now().minusHours(1);
QualityMetrics metrics = resultRepo.calculateMetrics(oneHourAgo, LocalDateTime.now());
double defectRate = (double) metrics.getDefectCount() / metrics.getTotalCount();
log.info("近1小时质检: 总计{}, 不良{}, 不良率{:.2f}%",
metrics.getTotalCount(), metrics.getDefectCount(), defectRate * 100);
// 不良率超过2%,触发告警
if (defectRate > 0.02) {
alertService.sendUrgentAlert(String.format(
"⚠️ 质检告警:近1小时不良率%.1f%%,超过2%%阈值!请检查生产参数。",
defectRate * 100
));
}
// 人工复核积压超过50件,提醒
long pendingReviews = resultRepo.countByDisposition("HUMAN_REVIEW");
if (pendingReviews > 50) {
alertService.sendAlert("人工复核队列积压" + pendingReviews + "件,请及时处理");
}
}
/**
* 生成日质量报告
*/
public DailyQualityReport generateDailyReport(LocalDate date) {
LocalDateTime start = date.atStartOfDay();
LocalDateTime end = date.atTime(23, 59, 59);
QualityMetrics metrics = resultRepo.calculateMetrics(start, end);
// 缺陷类型分布
List<DefectTypeStats> defectDistribution = resultRepo
.getDefectTypeDistribution(start, end);
// 按班次分析
List<ShiftQualityStats> shiftAnalysis = resultRepo
.getShiftAnalysis(start, end);
return DailyQualityReport.builder()
.date(date)
.totalInspected(metrics.getTotalCount())
.passCount(metrics.getPassCount())
.defectCount(metrics.getDefectCount())
.humanReviewCount(metrics.getHumanReviewCount())
.overallDefectRate(metrics.getDefectRate())
.aiAutoDecisionRate(metrics.getAutoDecisionRate())
.defectTypeDistribution(defectDistribution)
.shiftAnalysis(shiftAnalysis)
.build();
}
}三、预测性维护:设备传感器数据的异常预测
3.1 传感器数据接入架构
3.2 传感器数据消费与异常检测
package com.manufacturing.ai.maintenance;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
import java.time.LocalDateTime;
import java.time.Duration;
@Slf4j
@Service
@RequiredArgsConstructor
public class PredictiveMaintenanceService {
private final ChatClient chatClient;
private final SensorDataRepository sensorRepo;
private final EquipmentProfileRepository equipmentRepo;
private final MaintenanceAlertService alertService;
/**
* 消费传感器数据,实时检测异常
*/
@KafkaListener(topics = "sensor-data", groupId = "maintenance-ai")
public void processSensorData(SensorDataEvent event) {
EquipmentProfile equipment = equipmentRepo.findById(event.getEquipmentId())
.orElse(null);
if (equipment == null) return;
// 1. 存储传感器数据
sensorRepo.save(SensorReading.from(event));
// 2. 基于规则的快速异常检测(低延迟)
List<AnomalyAlert> ruleAlerts = checkRuleBasedAnomalies(event, equipment);
// 3. 获取近30分钟趋势数据,进行AI分析(每5分钟触发一次,不是每条数据都触发)
if (shouldTriggerAIAnalysis(event)) {
SensorTrendData trend = sensorRepo.getTrend(
event.getEquipmentId(),
LocalDateTime.now().minusMinutes(30),
LocalDateTime.now()
);
analyzeWithAI(equipment, trend, ruleAlerts);
}
// 4. 规则告警立即处理
ruleAlerts.forEach(alert -> {
if (alert.getSeverity() == AlertSeverity.CRITICAL) {
alertService.sendImmediateAlert(alert);
}
});
}
/**
* 基于规则的快速检测(毫秒级响应)
*/
private List<AnomalyAlert> checkRuleBasedAnomalies(
SensorDataEvent event,
EquipmentProfile equipment) {
List<AnomalyAlert> alerts = new ArrayList<>();
// 温度阈值检测
if (event.getTemperature() != null) {
double maxTemp = equipment.getMaxOperatingTemp();
if (event.getTemperature() > maxTemp * 1.1) {
alerts.add(AnomalyAlert.critical(
event.getEquipmentId(),
"温度过高",
String.format("当前%.1f°C,超过最大工作温度%.1f°C的10%%",
event.getTemperature(), maxTemp)
));
} else if (event.getTemperature() > maxTemp * 0.9) {
alerts.add(AnomalyAlert.warning(
event.getEquipmentId(),
"温度偏高",
String.format("当前%.1f°C,接近最大工作温度", event.getTemperature())
));
}
}
// 振动幅值检测
if (event.getVibrationRms() != null) {
double baselineVibration = equipment.getBaselineVibrationRms();
double ratio = event.getVibrationRms() / baselineVibration;
if (ratio > 3.0) {
alerts.add(AnomalyAlert.critical(
event.getEquipmentId(),
"振动异常",
String.format("振动RMS为基线的%.1f倍,请立即检查", ratio)
));
} else if (ratio > 2.0) {
alerts.add(AnomalyAlert.warning(
event.getEquipmentId(),
"振动偏大",
String.format("振动RMS为基线的%.1f倍", ratio)
));
}
}
// 电流异常检测(过流/欠流)
if (event.getCurrent() != null) {
double nominalCurrent = equipment.getNominalCurrent();
if (event.getCurrent() > nominalCurrent * 1.3) {
alerts.add(AnomalyAlert.critical(
event.getEquipmentId(),
"电流过大",
String.format("当前%.1fA,超过额定电流%.1f倍", event.getCurrent(), 1.3)
));
}
}
return alerts;
}
/**
* AI深度分析(基于趋势数据,识别早期故障征兆)
*/
private void analyzeWithAI(
EquipmentProfile equipment,
SensorTrendData trend,
List<AnomalyAlert> currentAlerts) {
// 获取历史故障记录(作为上下文)
List<MaintenanceRecord> recentMaintenance = sensorRepo
.getMaintenanceHistory(equipment.getId(), 6);
String prompt = String.format("""
你是一名设备维护专家,请分析以下设备传感器趋势数据:
设备信息:
- 设备ID:%s
- 设备类型:%s
- 运行时长:%d小时
- 上次维护:%s
近30分钟传感器趋势:
- 温度:最小%.1f°C,最大%.1f°C,趋势:%s
- 振动RMS:平均%.3f,基线%.3f,趋势:%s
- 电流:平均%.1fA,额定%.1fA
当前触发的规则告警:%s
历史维护记录:%s
请分析:
1. 设备当前健康状态(正常/轻微异常/需要关注/建议维护)
2. 若有异常,可能的故障原因(基于传感器模式)
3. 建议的维护动作和时间节点
4. 预计剩余正常运行时间(如果能判断的话)
用JSON格式输出:
{
"healthStatus": "NORMAL/MINOR_ANOMALY/NEEDS_ATTENTION/MAINTENANCE_RECOMMENDED",
"possibleCauses": ["可能原因1"],
"recommendation": "维护建议",
"estimatedRemainingHours": <小时数或null>,
"urgency": "IMMEDIATE/WITHIN_24H/WITHIN_WEEK/ROUTINE",
"confidence": <0-1>
}
""",
equipment.getId(),
equipment.getType(),
equipment.getRunningHours(),
equipment.getLastMaintenanceDate(),
trend.getMinTemp(), trend.getMaxTemp(), trend.getTempTrend(),
trend.getAvgVibration(), equipment.getBaselineVibrationRms(), trend.getVibrationTrend(),
trend.getAvgCurrent(), equipment.getNominalCurrent(),
currentAlerts.isEmpty() ? "无" : currentAlerts.stream()
.map(a -> a.getType() + ":" + a.getMessage())
.collect(java.util.stream.Collectors.joining(";")),
recentMaintenance.stream()
.limit(3)
.map(r -> r.getDate() + " " + r.getDescription())
.collect(java.util.stream.Collectors.joining(";"))
);
try {
String response = chatClient.prompt().user(prompt).call().content();
MaintenanceAIAnalysis analysis = MaintenanceAIAnalysis.fromJson(
cleanJson(response));
// 根据AI分析结果决定是否发告警
if (analysis.getUrgency() == MaintenanceUrgency.IMMEDIATE ||
analysis.getUrgency() == MaintenanceUrgency.WITHIN_24H) {
alertService.sendMaintenanceAlert(equipment.getId(), analysis);
}
// 存储AI分析结果
sensorRepo.saveAIAnalysis(equipment.getId(), analysis);
} catch (Exception e) {
log.warn("AI维护分析失败,设备: {}", equipment.getId(), e);
}
}
private boolean shouldTriggerAIAnalysis(SensorDataEvent event) {
// 每5分钟对同一设备触发一次AI分析
String cacheKey = "ai-analysis-" + event.getEquipmentId();
// 实际用Redis实现限流
return true; // 简化示例
}
private String cleanJson(String s) {
return s.replaceAll("^```json\\s*", "").replaceAll("\\s*```$", "").trim();
}
}四、工艺参数优化:基于历史数据的参数推荐
4.1 参数优化思路
不是让AI替代工程师调参,而是:
AI分析历史数据 → 找出最佳参数区间 → 提供给工程师参考
典型场景:
- 注塑工艺:温度/压力/时间的最优组合
- 焊接工艺:电流/速度/保护气体流量
- 热处理:温度曲线/时间/冷却速度4.2 工艺优化服务
package com.manufacturing.ai.process;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
import java.util.stream.*;
@Slf4j
@Service
@RequiredArgsConstructor
public class ProcessOptimizationService {
private final ChatClient chatClient;
private final ProcessDataRepository processDataRepo;
private final ProductionBatchRepository batchRepo;
/**
* 基于历史数据推荐最优工艺参数
*
* @param processType 工艺类型(如 injection_molding, welding)
* @param productModel 产品型号
* @param targetQualityMetrics 目标质量指标
*/
public ProcessParameterRecommendation recommendParameters(
String processType,
String productModel,
QualityTarget targetQualityMetrics) {
// 1. 查询历史高良品率的参数记录
List<ProcessRecord> historicalData = processDataRepo
.findTopPerformingRecords(processType, productModel, 100);
if (historicalData.isEmpty()) {
return ProcessParameterRecommendation.insufficient(
"历史数据不足,无法提供可靠建议");
}
// 2. 统计最优参数区间
ProcessParameterStats stats = calculateParameterStats(historicalData);
// 3. AI分析和解释
String aiInsight = generateAIInsight(stats, targetQualityMetrics, processType);
// 4. 构建推荐结果
return ProcessParameterRecommendation.builder()
.processType(processType)
.productModel(productModel)
.basedOnRecords(historicalData.size())
.parameterRanges(stats.getOptimalRanges())
.recommendedValues(stats.getModeValues())
.aiInsight(aiInsight)
.expectedDefectRate(stats.getMedianDefectRate())
.confidence(calculateConfidence(historicalData.size()))
.disclaimer("以上建议基于历史数据分析,实际应用前请工艺工程师确认。" +
"工艺调整须遵循变更管理流程。")
.build();
}
private ProcessParameterStats calculateParameterStats(List<ProcessRecord> records) {
// 只取良品率>95%的记录
List<ProcessRecord> highQualityRecords = records.stream()
.filter(r -> r.getYieldRate() > 0.95)
.collect(Collectors.toList());
ProcessParameterStats stats = new ProcessParameterStats();
// 计算各参数的中位数和四分位范围(作为推荐区间)
Map<String, List<Double>> paramValues = new HashMap<>();
highQualityRecords.forEach(record -> {
record.getParameters().forEach((param, value) ->
paramValues.computeIfAbsent(param, k -> new ArrayList<>()).add(value)
);
});
Map<String, ParameterRange> optimalRanges = new HashMap<>();
Map<String, Double> modeValues = new HashMap<>();
paramValues.forEach((param, values) -> {
values.sort(Double::compareTo);
int n = values.size();
double q1 = values.get(n / 4);
double q3 = values.get(3 * n / 4);
double median = values.get(n / 2);
optimalRanges.put(param, new ParameterRange(q1, q3));
modeValues.put(param, median);
});
stats.setOptimalRanges(optimalRanges);
stats.setModeValues(modeValues);
stats.setMedianDefectRate(1 - highQualityRecords.stream()
.mapToDouble(ProcessRecord::getYieldRate).average().orElse(0));
return stats;
}
private String generateAIInsight(
ProcessParameterStats stats,
QualityTarget target,
String processType) {
String prompt = String.format("""
分析以下%s工艺的历史最优参数数据,提供工艺优化洞察:
历史优质批次参数统计(良品率>95%%):
%s
目标质量要求:
- 良品率目标:%.1f%%
- 关键质量指标:%s
请分析:
1. 哪个参数对良品率影响最大?为什么?
2. 参数之间有什么协同效应(一个参数变化时,另一个也需要调整)?
3. 实施这些参数时需要注意的风险点?
4. 推荐的参数调整顺序(从最安全的调整开始)?
回答要对生产工程师有实际指导意义,150字以内。
""",
processType,
formatParameterStats(stats),
target.getTargetYieldRate() * 100,
target.getKeyMetrics()
);
return chatClient.prompt().user(prompt).call().content();
}
private String formatParameterStats(ProcessParameterStats stats) {
StringBuilder sb = new StringBuilder();
stats.getOptimalRanges().forEach((param, range) ->
sb.append(String.format("- %s:推荐区间[%.1f, %.1f],最优值%.1f\n",
param, range.getMin(), range.getMax(),
stats.getModeValues().get(param)))
);
return sb.toString();
}
private double calculateConfidence(int sampleSize) {
if (sampleSize >= 100) return 0.9;
if (sampleSize >= 50) return 0.75;
if (sampleSize >= 20) return 0.6;
return 0.4;
}
}五、操作手册数字化:设备手册的RAG问答系统
5.1 为什么设备手册RAG的ROI最高
某工厂:
- 设备手册:300本,总计80000页
- 工程师找资料平均耗时:45分钟/次
- 每天查资料次数:约30次
- 年节省工时 = 30 × 45/60 × 250工作日 = 5625小时
- 按工时成本200元/小时 = 年节省约112万元
- RAG系统建设成本:约10万元
- ROI = (112-10)/10 = 1020%,2个月回本5.2 设备手册知识库构建
package com.manufacturing.ai.knowledge;
import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.document.Document;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.FileOutputStream;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
@RequiredArgsConstructor
public class EquipmentManualIndexingService {
private final VectorStore vectorStore;
/**
* 将设备手册PDF导入知识库
*/
public ManualIndexingResult indexManual(
MultipartFile pdfFile,
String equipmentId,
String equipmentName,
String manualType,
String language) {
log.info("开始导入设备手册: {}, 设备: {}", pdfFile.getOriginalFilename(), equipmentName);
try {
// 1. 保存临时文件
File tempFile = File.createTempFile("manual-", ".pdf");
try (FileOutputStream fos = new FileOutputStream(tempFile)) {
fos.write(pdfFile.getBytes());
}
// 2. PDF文本提取
PagePdfDocumentReader reader = new PagePdfDocumentReader(
"file:" + tempFile.getAbsolutePath()
);
List<Document> rawDocs = reader.get();
// 3. 文本分块(设备手册专用配置)
TokenTextSplitter splitter = new TokenTextSplitter(
800, // chunk size - 稍大,避免切断操作步骤
100, // overlap - 较大,确保步骤连续性
10,
5000,
true
);
List<Document> chunks = splitter.apply(rawDocs);
// 4. 添加设备元数据
chunks.forEach(chunk -> {
chunk.getMetadata().put("equipment_id", equipmentId);
chunk.getMetadata().put("equipment_name", equipmentName);
chunk.getMetadata().put("manual_type", manualType);
chunk.getMetadata().put("language", language);
chunk.getMetadata().put("source", pdfFile.getOriginalFilename());
});
// 5. 向量化并入库
vectorStore.add(chunks);
// 6. 清理临时文件
tempFile.delete();
log.info("手册导入成功: {}, 共{}个分块", pdfFile.getOriginalFilename(), chunks.size());
return ManualIndexingResult.success(
equipmentId, pdfFile.getOriginalFilename(), chunks.size());
} catch (Exception e) {
log.error("手册导入失败: {}", pdfFile.getOriginalFilename(), e);
return ManualIndexingResult.failure(e.getMessage());
}
}
}5.3 设备手册问答服务
package com.manufacturing.ai.knowledge;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.document.Document;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class EquipmentKnowledgeQAService {
private final ChatClient chatClient;
private final VectorStore vectorStore;
private static final String QA_SYSTEM_PROMPT = """
你是一名工厂设备专家,帮助操作人员和工程师快速查找设备手册中的信息。
回答规范:
1. 只基于提供的手册内容回答,不要添加手册以外的信息
2. 操作步骤类问题:用编号列表,按顺序列出每个步骤
3. 参数类问题:精确给出数值,不要模糊化
4. 安全相关:特别标注⚠️安全警告
5. 若手册中没有明确说明,直接说"手册中未找到相关信息,建议联系设备厂商"
6. 在回答末尾注明:信息来源(手册名称和页码)
""";
/**
* 设备手册智能问答
*/
public ManualQAResponse answerQuestion(
String question,
String equipmentId,
String userId) {
log.info("设备手册问答 - 设备:{}, 问题:{}", equipmentId, question);
// 1. 构建过滤条件(只检索该设备的手册)
String filterExpression = equipmentId != null
? "equipment_id == '" + equipmentId + "'"
: null;
SearchRequest searchRequest = SearchRequest.query(question)
.withTopK(5)
.withSimilarityThreshold(0.65);
if (filterExpression != null) {
searchRequest = searchRequest.withFilterExpression(filterExpression);
}
// 2. 检索相关手册内容
List<Document> relevantChunks = vectorStore.similaritySearch(searchRequest);
if (relevantChunks.isEmpty()) {
return ManualQAResponse.notFound(question, equipmentId);
}
// 3. 构建上下文
String context = buildManualContext(relevantChunks);
// 4. AI回答
String answer = chatClient.prompt()
.system(QA_SYSTEM_PROMPT)
.user(String.format("手册内容:\n%s\n\n问题:%s", context, question))
.call()
.content();
// 5. 提取来源信息
List<ManualSource> sources = extractSources(relevantChunks);
return ManualQAResponse.builder()
.question(question)
.answer(answer)
.sources(sources)
.equipmentId(equipmentId)
.build();
}
private String buildManualContext(List<Document> docs) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < docs.size(); i++) {
Document doc = docs.get(i);
sb.append(String.format("[资料%d] 来源:%s(第%s页)\n%s\n\n",
i + 1,
doc.getMetadata().getOrDefault("source", "手册"),
doc.getMetadata().getOrDefault("page_number", "未知"),
doc.getContent()
));
}
return sb.toString();
}
private List<ManualSource> extractSources(List<Document> docs) {
return docs.stream()
.map(doc -> ManualSource.builder()
.fileName((String) doc.getMetadata().get("source"))
.equipmentName((String) doc.getMetadata().get("equipment_name"))
.pageNumber((String) doc.getMetadata().get("page_number"))
.manualType((String) doc.getMetadata().get("manual_type"))
.build())
.distinct()
.collect(Collectors.toList());
}
/**
* 故障代码快速查询
*/
public ManualQAResponse lookupErrorCode(
String errorCode,
String equipmentId) {
String question = String.format("错误代码 %s 的含义是什么?如何处理?", errorCode);
return answerQuestion(question, equipmentId, "system");
}
/**
* 保养周期查询
*/
public ManualQAResponse getMaintenanceSchedule(String equipmentId) {
String question = "这台设备的保养周期是什么?各保养项目的间隔和内容是什么?";
return answerQuestion(question, equipmentId, "system");
}
}六、供应链风险预警
6.1 风险识别服务
package com.manufacturing.ai.supply;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class SupplyChainRiskService {
private final ChatClient chatClient;
private final SupplierRepository supplierRepo;
private final InventoryRepository inventoryRepo;
private final ExternalDataService externalDataService;
/**
* 每天早上6点执行供应链风险扫描
*/
@Scheduled(cron = "0 0 6 * * *")
public void dailyRiskScan() {
log.info("开始供应链风险日度扫描...");
List<Supplier> criticalSuppliers = supplierRepo.findCriticalSuppliers();
criticalSuppliers.forEach(supplier -> {
SupplierRiskAnalysis risk = analyzeSupplierRisk(supplier);
if (risk.getRiskLevel() == RiskLevel.HIGH ||
risk.getRiskLevel() == RiskLevel.CRITICAL) {
notifyProcurementTeam(supplier, risk);
}
});
}
/**
* 综合分析供应商风险
*/
public SupplierRiskAnalysis analyzeSupplierRisk(Supplier supplier) {
// 1. 收集数据
InventoryStatus inventory = inventoryRepo.getStatus(supplier.getMaterialIds());
DeliveryHistory deliveryHistory = supplierRepo.getDeliveryHistory(supplier.getId(), 6);
ExternalSignals signals = externalDataService.getSupplierSignals(supplier.getId());
// 2. 规则评分
double riskScore = 0.0;
List<String> riskFactors = new java.util.ArrayList<>();
// 库存水位
if (inventory.getDaysOfStock() < supplier.getLeadTimeDays() * 1.5) {
riskScore += 0.25;
riskFactors.add(String.format("库存仅剩%.0f天,安全库存不足",
inventory.getDaysOfStock()));
}
// 交货准时率
double onTimeRate = deliveryHistory.getOnTimeDeliveryRate();
if (onTimeRate < 0.85) {
riskScore += 0.30;
riskFactors.add(String.format("近6个月准时交货率仅%.0f%%", onTimeRate * 100));
}
// 单一来源风险(只有一个供应商)
if (supplier.isSourceConcentrated()) {
riskScore += 0.20;
riskFactors.add("该物料为单一来源,无备选供应商");
}
// 3. AI分析外部信号(新闻、行业动态等)
String aiAnalysis = analyzeExternalSignals(supplier, signals);
return SupplierRiskAnalysis.builder()
.supplierId(supplier.getId())
.supplierName(supplier.getName())
.riskScore(Math.min(riskScore, 1.0))
.riskLevel(getRiskLevel(riskScore))
.riskFactors(riskFactors)
.aiExternalAnalysis(aiAnalysis)
.recommendation(buildRecommendation(riskScore, riskFactors))
.build();
}
private String analyzeExternalSignals(
Supplier supplier,
ExternalSignals signals) {
if (signals == null || signals.getNewsItems().isEmpty()) {
return "无外部风险信号";
}
String prompt = String.format("""
分析以下供应商的外部风险信号:
供应商:%s,主要供应物料:%s
近期相关信息:
%s
请评估:这些信息是否对供应链有实质影响?影响程度如何?
如有影响,建议采购团队采取什么行动?
回答简洁(100字以内)。
""",
supplier.getName(),
supplier.getMainMaterials(),
signals.getNewsItems().stream()
.limit(5)
.map(n -> "- " + n.getTitle() + "(" + n.getDate() + ")")
.collect(java.util.stream.Collectors.joining("\n"))
);
return chatClient.prompt().user(prompt).call().content();
}
private RiskLevel getRiskLevel(double score) {
if (score >= 0.70) return RiskLevel.CRITICAL;
if (score >= 0.50) return RiskLevel.HIGH;
if (score >= 0.30) return RiskLevel.MEDIUM;
return RiskLevel.LOW;
}
private String buildRecommendation(double score, List<String> factors) {
if (score >= 0.70) return "建议立即启动备选供应商评估,增加安全库存";
if (score >= 0.50) return "建议与供应商沟通改善计划,启动备选供应商筛选";
if (score >= 0.30) return "建议密切关注,下次采购时适当增加库存";
return "风险可控,按常规管理";
}
private void notifyProcurementTeam(Supplier supplier, SupplierRiskAnalysis risk) {
log.warn("供应商高风险告警: {} - {}", supplier.getName(), risk.getRiskLevel());
// 发送钉钉/企业微信通知
}
public enum RiskLevel { LOW, MEDIUM, HIGH, CRITICAL }
}七、实施路径:制造业AI项目从PoC到规模化
7.1 PoC阶段关键检查清单
/**
* 制造业AI项目PoC阶段检查清单
* 在开始编码之前,确认以下问题都有答案
*/
public class ManufacturingAIPocChecklist {
/*
* ===== 数据准备 =====
* [ ] 历史质检数据量:是否有至少1000条带标注的数据
* [ ] 数据质量:标注准确率是否 > 95%
* [ ] 传感器数据:采样频率是否足够(振动至少10Hz)
* [ ] 数据存储:实时数据如何传输到AI系统(OPC-UA/Modbus/Kafka)
*
* ===== 现场条件 =====
* [ ] 工业相机:分辨率/帧率/焦距是否符合检测需求
* [ ] 网络:产线到AI服务器的网络延迟 < 100ms
* [ ] 边缘部署:AI推理是否需要在本地(断网也能工作)
* [ ] 环境因素:光线变化/温度/振动对图像质量的影响
*
* ===== 业务确认 =====
* [ ] 缺陷定义:所有缺陷类型都有明确标准(书面)
* [ ] 误报率容忍度:AI误判为不良品的最大比例(影响产量)
* [ ] 漏检率要求:客户或法规要求的最大漏检率
* [ ] 告警响应:高优先级告警发出后,多长时间内有人处理
*
* ===== 集成与运维 =====
* [ ] MES集成:AI结果如何写入MES系统
* [ ] 人工复核流程:AI不确定的情况如何转到人工
* [ ] 模型更新机制:产品更换时如何更新AI模型
* [ ] 故障降级:AI服务不可用时,生产线如何运行
*/
}八、效果数据
| 指标 | 改造前 | 改造后 | 改善幅度 |
|---|---|---|---|
| 视觉质检漏检率 | 5.0% | 0.3% | -94% |
| 质检人员数量 | 8名 | 2名(处理复核) | -75% |
| 设备意外停机次数/月 | 4.2次 | 1.1次 | -74% |
| 设备手册查询时间 | 45分钟 | 3分钟 | -93% |
| 供应链风险响应时间 | 发现后2-3天 | 提前5-7天预警 | - |
| 工艺调试时间(新产品) | 平均2周 | 平均5天 | -64% |
FAQ
Q:我们公司没有GPU服务器,能做AI质检吗?
可以。视觉质检用云视觉API(阿里云、百度、腾讯都有工业缺陷检测API),按调用量付费,不需要自建GPU。Java层做图像采集、预处理、API调用、结果入库。如果对延迟有极高要求(<50ms),再考虑边缘推理设备(如英伟达Jetson)。
Q:传感器数据很杂乱,异常检测准确率很低怎么办?
传感器数据的信噪比问题是制造业AI最大的挑战之一。三个处理策略:(1) 先做滑动平均/中值滤波;(2) 建立设备的正常运行基线(不是全局阈值,而是每台设备自己的基线);(3) 避免单传感器判断,多传感器联合判断准确率高得多。
Q:工人不信任AI质检结果,怎么推广?
让工人参与建立"AI的边界":告诉他们AI只是辅助,最终判决权还在人工。同时把AI置信度可视化展示给工人看——让他们知道AI什么时候有把握,什么时候不确定。工人会逐渐建立信任。
Q:设备手册有几百本,导入向量数据库的成本高吗?
按每本手册200页、每页500字估算,300本手册约3000万字,向量化费用用text-embedding-3-small约1.5美元,一次性成本很低。检索时每次查询的Embedding费用更低(<0.001美元)。整体来看远比人工翻手册便宜。
