Agent 返回 JSON 而不是闲聊:LangChain4j 结构化输出实战

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 原创,首发于掘金/知乎/微信公众号。转载请联系作者。

相关推荐
小林ixn1 小时前
大模型随机说话的秘密:Temperature 和 Top K 深度解析,LangChain 实战调优
人工智能·langchain
CCPC不拿奖不改名3 小时前
大模型推理架构与开源生态知识整理
数据库·windows·python·架构·langchain·开源·github
Darling噜啦啦5 小时前
揭秘 LLM 的随机性黑盒:从 Temperature + Top-K 到 LangChain Chain 工作流实战
langchain·llm
迷途呀8 小时前
Python:函数中的参数类型
开发语言·笔记·python·langchain·nlp
玩转4G物联网10 小时前
FS800DTU 新品上线|免费使用 DMP 设备管理平台,支持远程批量配置 & OTA 升级
物联网·网络协议·tcp/ip·云平台·核心板·fs800dtu·iot管理平台
鉴生Eric11 小时前
IoT基础设施产品商的未来:回归本质的三层能力
物联网
MetrixAeroCore11 小时前
畜牧牛羊定位耳标物联网卡纽扣电池供电与休眠唤醒策略优化
物联网
智圣新创0112 小时前
存量团学系统效能跃升:智圣新创“青春科大”智慧团学平台二期升级的全周期建设参考
大数据·人工智能·物联网
liguojun202512 小时前
智慧文体旅系统选哪家?豆米跳跳十年技术沉淀支持源码交付
java·大数据·人工智能·物联网·1024程序员节
python在学ing12 小时前
LangChain完全指南:从核心组件到RAG与Agent实战
langchain