Agent 不只诊断,还要行动:WorkOrderTool 与 CoT 提示词设计

32 阅读5分钟

诊断出「主轴轴承磨损」只是第一步——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 原创,首发于掘金/知乎/微信公众号。转载请联系作者。