Agent 返回 "CNC-001 温度有点高,建议检查一下"------前端怎么解析?用 POJO 替代 String,让 Agent 返回类型安全的 Java 对象。
问题:String 返回值的困境
默认情况下,Agent 返回的是 String------自然语言,LLM 自由发挥:
java
interface IndustrialAssistant {
String chat(String message);
}
调用结果:
arduino
"根据查询结果,CNC-001 设备当前振动值为 4.8mm/s,超出正常范围 0-2.8mm/s,
轴承温度 72°C 也高于正常上限 65°C。初步判断为轴承磨损导致的振动异常,
建议立即安排检修,优先级 HIGH。"
前端要从中提取「设备 ID」「异常指标」「建议操作」「优先级」------全靠正则或二次 LLM 调用。不可靠,也不优雅。
方案:让 Agent 返回 POJO
LangChain4j 支持方法的返回类型直接定义为 Java POJO。框架会把 LLM 的输出自动映射到 POJO 字段。
Step 1:定义输出模型
java
@Data
public class DiagnosticResponse {
@Description("设备ID")
private String deviceId;
@Description("设备当前状态:normal/warning/critical")
private String status;
@Description("诊断分析结论")
private String analysis;
@Description("可能的故障原因,按可能性从高到低排列")
private List<String> possibleCauses;
@Description("建议的维修或处理措施")
private List<String> suggestedActions;
@Description("优先级:HIGH/MEDIUM/LOW")
private String priority;
@Description("是否需要立即处理")
private Boolean requiresImmediateAction;
@Description("诊断置信度,0.0-1.0")
private Double confidence;
}
@Description 注解是核心。 它告诉 LLM 每个字段的含义和取值范围,LLM 会根据这些描述决定填什么值。
Step 2:定义接口
java
interface DiagnosticAssistant {
@SystemMessage("""
你是一个工业设备故障诊断专家。你必须使用提供的工具查询设备数据,
然后基于工具返回的结构化数据进行诊断分析,不要凭猜测回答。
""")
@UserMessage("""
请对设备 {{deviceId}} 进行全面的故障诊断分析:
1. 查询设备告警信息
2. 查询设备历史遥测数据
3. 基于以上数据生成诊断报告
""")
DiagnosticResponse diagnose(@V("deviceId") String deviceId);
}
注意返回类型从 String 变成了 DiagnosticResponse。@UserMessage 里的 {{deviceId}} 是模板占位符,@V("deviceId") 绑定方法参数。
Step 3:调用
java
DiagnosticResponse result = deviceAgent.diagnose("CNC-001");
// result.getDeviceId() → "CNC-001"
// result.getStatus() → "warning"
// result.getPriority() → "HIGH"
// result.getPossibleCauses() → ["轴承磨损", "转子不平衡"]
前端直接反序列化,不需要做任何字段提取。API 契约由 POJO 定义------字段类型(String/Boolean/Double/List)本身就是文档。
String vs POJO 对比
| 维度 | String 返回 | POJO 返回 |
|---|---|---|
| 前端解析 | 正则/手工提取 | 直接反序列化 |
| 类型安全 | 无,全靠约定 | 编译期校验 |
| API 文档 | 注释靠人维护 | POJO 即文档 |
| 灵活性 | 高,LLM 自由发挥 | 受字段约束 |
| LLM 填充质量 | --- | 依赖 @Description 质量 |
| 适用场景 | 聊天式交互、调试 | 业务接口、结构化诊断 |
实际效果对比
同一个诊断请求「CNC-001 振动异常是什么原因?」:
String 返回:
erlang
根据查询结果,CNC-001 设备当前振动值为 4.8mm/s,超出正常范围...
初步判断为轴承磨损导致的振动异常,建议立即安排检修...
前端要从中提取结构化信息------几乎不可能可靠地做到。
POJO 返回(JSON 序列化后):
json
{
"deviceId": "CNC-001",
"status": "warning",
"analysis": "振动值4.8mm/s超出正常范围0-2.8mm/s,轴承温度72°C高于正常上限65°C,判断为轴承磨损导致的振动异常",
"possibleCauses": ["轴承磨损", "转子不平衡"],
"suggestedActions": ["立即安排轴承检查", "校验联轴器对中参数", "生成维修工单"],
"priority": "HIGH",
"requiresImmediateAction": true,
"confidence": 0.85
}
前端可以直接用 result.priority 决定 UI 上的告警颜色,用 result.suggestedActions 渲染操作按钮列表。

@Description 的写法技巧
@Description 是 LLM 理解字段含义的唯一依据。写好它直接影响 POJO 的填充质量:
java
// 差 --- LLM 不知道取值范围
@Description("状态")
private String status;
// 好 --- 明确取值范围
@Description("设备当前状态:normal/warning/critical")
private String status;
// 差 --- 太笼统
@Description("数据列表")
private List<String> data;
// 好 --- 说明内容和排序
@Description("可能的故障原因,按可能性从高到低排列")
private List<String> possibleCauses;
// 差 --- 没有数值范围
@Description("置信度")
private Double confidence;
// 好 --- 有范围
@Description("诊断置信度,0.0-1.0")
private Double confidence;
经验规则 :@Description 里包含三个信息------字段含义、值类型(枚举/范围)、排序规则(如有)。
什么时候用 POJO,什么时候用 String?
| 场景 | 推荐 | 原因 |
|---|---|---|
| 工单生成 | POJO | 工单号、设备ID、优先级、操作步骤------全是结构化字段 |
| 告警诊断 | POJO | 诊断结论 + 建议操作 + 置信度,下游系统需要消费 |
| 报表查询 | POJO | 字段固定,前端需要一个确定的 JSON 结构 |
| 日常闲聊 | String | 不需要结构化,让 LLM 自由发挥 |
| 调试/探索 | String | POJO 字段设计还没定型,先跑通流程再建模 |
| 多模态输出 | String | 如果需要混合文字+图片+表格,String 更灵活 |
一个实用策略:同时保留两个接口。
java
interface IndustrialAssistant {
String chat(String message); // 自由对话
DiagnosticResponse diagnose(...); // 结构化诊断
}
同一个 Agent 可以同时暴露多个方法------每个方法对应一种输出模式。其他开发团队按需调用。
一句话总结
让 Agent 返回 POJO,不是"限制 LLM 的自由",而是把 LLM 的输出变成下游系统可消费的数据。String 是人看的,POJO 是代码读的。工业 Agent 的输出,大部分应该被代码消费。
本文由 LaoLiang 原创,首发于掘金/知乎/微信公众号。转载请联系作者。