诊断出「主轴轴承磨损」只是第一步——Agent 的闭环终点是「工单已创建,请工程师前往处理」。这篇文章讲如何用 WorkOrderTool 和 CoT 提示词打通诊断到行动的最后一段。
什么是 CoT?
CoT(Chain-of-Thought,思维链) 是一种提示词技术,让 LLM 在给出最终答案前,先一步步写出中间推理过程。
不用 CoT 时,LLM 的思考过程是隐式的——它可能同时考虑告警状态、历史数据、维修知识,但直接跳到结论。这容易出错:跳过交叉验证、漏掉关键信息、在数据不足时编造结果。
用了 CoT 后,提示词明确要求 LLM 按顺序执行:
观察数据 → 检索知识 → 分析根因 → 判断行动 → 输出结果
每一步有明确的输入和输出,LLM 的推理链条变得可追溯。对工业 Agent 来说,这点尤其重要——不是「猜对结论」就行,而是要能说清楚「为什么这样判断」。
问题:诊断完了,然后呢?
上一篇文章我们让 Agent 查时序数据、分析振动趋势、得出结论。但产线上的真实诉求是:
「CNC-001 振动异常」→ 诊断 → 创建工单 → 通知工程师 → 维修 → 闭环
诊断只是中间产物,工单才是交付物。Agent 需要从「会看病」升级到「能开药方」。
设计方案:两个改造点
1. WorkOrderTool —— 让 Agent 能「开单」
之前 DiagnosisTool 里工单只是写死的字符串:
// 之前:写死在诊断结果里
"suggestedActions":["生成维修工单","通知值班工程师","记录故障报告"]
问题是 LLM 只能「建议生成」,不能真的执行。改造后:
@Component
public class WorkOrderTool {
private final ConcurrentHashMap<String, WorkOrder> orders = new ConcurrentHashMap<>();
@Tool("创建维修工单。仅当确认设备存在硬件故障需要人工介入时使用。" +
"设备正常、无告警时不要调用。")
public String createWorkOrder(
@P("设备ID") String deviceId,
@P("工单类型:维修/检查/保养") String type,
@P("优先级:HIGH/MEDIUM/LOW") String priority,
@P("故障描述和诊断结论") String description) {
WorkOrder wo = new WorkOrder(deviceId, type, priority, description);
orders.put(wo.getWorkOrderId(), wo);
return wo.toJson(); // 返回结构化工单信息
}
public WorkOrder findById(String id) {
return orders.get(id);
}
}
关键设计决策:
- 参数化:让 LLM 传入 deviceId、type、priority、description,而不是 LLM 凭记忆编工单号
- 约束描述:
@Tool注解里写明「仅当确认硬件故障时使用,正常不调」,防止 LLM 滥用
2. CoT 提示词 —— 让 LLM 按步骤推理
之前的 @SystemMessage 只是角色描述,LLM 拿到数据后可能跳过交叉验证直接下结论。加入 Chain-of-Thought 后:
@SystemMessage("""
你是一个工业设备运维专家,服务于智能工厂的设备监控与故障诊断。
可用工具:
- queryDeviceAlarms:查询设备当前告警
- queryDeviceHistory:查询设备历史遥测数据(含统计)
- queryRealtimeData:查询设备最近5分钟原始数据
- searchKnowledgeBase:检索维修知识库
- generateDiagnosis:基于告警类型生成诊断结论
- createWorkOrder:创建维修工单(仅在确认硬件故障时使用)
思考路径(ReAct),每步先思考再行动:
1. 数据采集:先用 queryDeviceAlarms 和 queryDeviceHistory 获取实时数据
2. 知识检索:如有异常,用 searchKnowledgeBase 检索相关维修知识
3. 诊断分析:用 generateDiagnosis 分析根因
4. 工单决策:确认是硬件故障需要人工介入时,用 createWorkOrder 创建工单
如果设备状态正常、无告警,不要创建工单
5. 最终输出:按「状态→异常→诊断→建议→工单」结构呈现
""")
CoT 提示词的三个要点:
| 要素 | 说明 | 示例 |
|---|---|---|
| 工具清单 | 列出所有可用工具 + 用途 | queryDeviceAlarms 查告警 |
| 执行顺序 | 1→2→3→4→5 步序列 | 先查数据,再诊断,最后开单 |
| 约束条件 | 什么时候用、什么时候不用 | 无告警不创建工单 |
端到端验证
启动一切,灌入知识库,发送诊断请求:
curl -X POST http://localhost:8080/api/agent/chat \
-H "Content-Type: application/json" \
-d '{"message":"CNC-001 振动异常,做完整诊断,确认故障就创建工单"}'
LLM 的完整执行过程(从日志可见):
1. [TOOL] queryDeviceAlarms("CNC-001") → 2 个告警(润滑不足·HIGH,压力超标·MEDIUM)
2. [TOOL] queryDeviceHistory("CNC-001") → 振动 max 5.48,温度 max 84.9°C
3. [TOOL] searchKnowledgeBase("振动异常 润滑不足") → 主轴轴承磨损维修方案
4. [TOOL] generateDiagnosis("vibration") → 根因:轴承磨损 + 润滑失效
5. [TOOL] createWorkOrder("CNC-001","维修","HIGH","...") → WO-85AC4A6A
Agent 最终回复:
## CNC-001 完整诊断报告
### 设备状态:⚠️ 警告(2个活跃告警)
| 告警 | 级别 | 值 |
| 润滑不足 | HIGH | 142.54 |
| 压力超标 | MEDIUM | 120.73 |
### 诊断结论
根因:主轴轴承磨损/损坏 + 润滑系统失效 → 恶性循环
### 维修工单(已创建)
| 工单号 | 优先级 | 状态 | 指派人 |
| WO-85AC4A6A | HIGH | 待处理 | 值班工程师 |
> 请立即安排工程师前往现场!
两点心得
1. CoT 不复杂,但必须写清楚「不做什么」
最初 CoT 只写了步骤,LLM 每次都创建工单——不管设备有没有故障。加上约束句「设备状态正常时不调用」后,行为才收敛。负面约束比正面指导更重要。
2. Tool 的返回值是 LLM 的「眼睛」
WorkOrderTool 返回的 JSON 里带了 workOrderId,LLM 就能在回复中引用工单号,用户可以直接拿号去查。如果 Tool 只返回 "success",LLM 只能自己编一个不存在工单号,后面就断了。
代码结构
agent/tools/
├── DeviceAlarmTool.java — 告警查询
├── DeviceDataTool.java — 历史数据查询
├── DiagnosisTool.java — 故障诊断
└── WorkOrderTool.java — 工单创建(新增)
agent/
├── DeviceAgent.java — AiServices 接口 + CoT SystemMessage
└── model/
├── DiagnosticResponse.java — 诊断 POJO
└── WorkOrder.java — 工单 POJO(新增)
controller/
└── AgentController.java — 新增 /workorder/{id} 和 /diagnose-and-order
下一步
工单创建了,但还在进程内存里——重启就丢了。下一篇文章接入持久化存储(数据库 + 工单生命周期:待处理→处理中→已完成),让工单真正「留下来」。
本文由 LaoLiang 原创,首发于掘金/知乎/微信公众号。转载请联系作者。