LangChain4j 面试题:结构化输出怎么做?JSON Schema、POJO 解析、异常兜底讲透

LangChain4j 的结构化输出很适合 Java 开发者,因为它可以把模型结果直接落到 POJO、record、enum 这些熟悉的类型里。
但真正上线时,难点不只是“能不能解析”,而是字段约束、模型能力边界和失败兜底这三件事要一起想清楚。

🦅个人主页
🐼GitHub主页


先看真实问题:为什么很多模型回答看起来正常,系统一接就开始出问题

在真实业务里,很多 AI 能力最后都不是给人直接读的,而是要进数据库、进规则引擎、进审批流。如果模型只是回了一段自然语言,系统后面往往根本接不住。
LangChain4j 的结构化输出价值就在这里:它可以把模型结果约束成 Java 对象,让后端的代码、表结构和流程节点真正接得起来。

  • 字段名和枚举值不稳定,后面做规则判断很容易出错
  • 结构化结果如果没有失败兜底,模型一旦格式异常,整条链路就会卡住
  • 不是所有模型和模式都支持同样强的 JSON Schema 约束,设计时必须知道边界

一张表先看懂:结构化输出在项目里最关心的四件事

维度 怎么做 为什么
Schema 约束 把输出定义成 POJO / enum / 集合 让模型回答更像系统结果而不是聊天内容
字段描述 用 @Description 和 @JsonProperty 补约束 尽量减少歧义和枚举飘移
模型能力 确认当前模型是否支持 JSON Schema 不是所有提供商都一样稳
失败兜底 解析失败落异常表或走人工复核 保证主链路不要被一次格式异常卡死

举个具体例子:工单智能分流:把用户投诉直接提取成结构化对象

  1. 客服系统收到一段自然语言投诉后,不是直接给人看,而是先让模型抽取分类、优先级、是否转人工。
  2. LangChain4j 把返回结果映射成 TicketDecision,后面的规则引擎只认这个对象。
  3. 如果分类结果是 QUALITY 且优先级是 HIGH,系统直接走加急售后流程。
  4. 如果模型返回不完整或者解析失败,就写入异常表并打上待人工复核状态。

企业里的典型应用场景

  • 售后工单智能分流:模型输出分类、优先级、转人工标记,后面直接驱动流程引擎和客服工作台。
  • 合同审阅和法务摘要:从长文本里提取风险条款、责任方、截止时间,结果直接入库并挂到审批单。
  • 风控审核场景:把用户申诉内容先转成结构化对象,再交给规则引擎和人工复核平台做后续判定。

如果按企业项目落地,我会这样走完整闭环

  1. 入口层先接收业务请求,做幂等校验、租户校验和基础字段补全,避免同一业务单重复调用模型。
  2. 编排层根据场景选择结构化 DTO 和模板,把系统提示词、字段枚举、兜底策略一起收敛在应用服务里。
  3. AI 调用层只负责拿到结构化对象,不直接写业务表,防止模型异常结果污染主数据。
  4. 持久化层把成功结果、失败结果、原始业务主键、调用版本一起落库,后面方便重放和做效果评估。
  5. 规则层基于结构化对象继续流转,例如是否自动派单、是否走高优先级审核、是否直接升级人工。
  6. 治理层补上审计日志、失败补偿、人工复核任务和指标监控,形成从调用到落地的完整闭环。

代码示例:AI Service 返回 POJO + JSON Schema 约束

Maven 依赖

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-bom</artifactId>
            <version>1.11.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j</artifactId>
</dependency>
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-open-ai</artifactId>
</dependency>

返回结构定义

@Description("工单分类结果")
public record TicketDecision(
        @JsonProperty(required = true)
        @Description("工单分类,只能是 REFUND、LOGISTICS、QUALITY、OTHER")
        TicketCategory category,

        @JsonProperty(required = true)
        @Description("优先级,只能是 LOW、MEDIUM、HIGH")
        TicketPriority priority,

        @Description("是否需要转人工")
        boolean needManualReview,

        @Description("判断原因")
        String reason
) {
}

enum TicketCategory {
    REFUND, LOGISTICS, QUALITY, OTHER
}

enum TicketPriority {
    LOW, MEDIUM, HIGH
}

AI Service 接口和装配

public interface TicketClassifier {

    @SystemMessage("""
            你是售后工单分流助手。
            只能输出结构化结果,不要输出额外解释。
            """)
    TicketDecision classify(@UserMessage String ticketText);
}

@Configuration
public class TicketClassifierConfig {

    @Bean
    public TicketClassifier ticketClassifier() {
        ChatModel chatModel = OpenAiChatModel.builder()
                .apiKey(System.getenv("OPENAI_API_KEY"))
                .modelName("gpt-4o-mini")
                .supportedCapabilities(RESPONSE_FORMAT_JSON_SCHEMA)
                .strictJsonSchema(true)
                .build();

        return AiServices.builder(TicketClassifier.class)
                .chatModel(chatModel)
                .build();
    }
}

应用服务里的兜底处理

@Service
@RequiredArgsConstructor
public class TicketDecisionService {

    private final TicketClassifier ticketClassifier;
    private final TicketAiResultRepository ticketAiResultRepository;

    public TicketDecision decide(Long ticketId, String ticketText) {
        try {
            TicketDecision decision = ticketClassifier.classify(ticketText);
            ticketAiResultRepository.saveSuccess(ticketId, JsonUtils.toJson(decision));
            return decision;
        } catch (Exception ex) {
            ticketAiResultRepository.saveFail(ticketId, ex.getMessage());
            return new TicketDecision(
                    TicketCategory.OTHER,
                    TicketPriority.MEDIUM,
                    true,
                    "模型解析失败,已转人工复核"
            );
        }
    }
}

企业级代码示例:企业里更常见的是 Facade + Orchestrator + ReviewTask 的闭环写法

结构化提取编排服务

@Slf4j
@Service
@RequiredArgsConstructor
public class TicketDecisionOrchestrator {

    private final IdempotentGuard idempotentGuard;
    private final TicketClassifier ticketClassifier;
    private final TicketAiResultRepository ticketAiResultRepository;
    private final TicketFlowDecisionService ticketFlowDecisionService;
    private final ManualReviewTaskService manualReviewTaskService;
    private final AiInvocationAuditService aiInvocationAuditService;

    @Transactional(rollbackFor = Exception.class)
    public TicketDecisionResult handle(TicketDecisionCommand command) {
        idempotentGuard.check(
                "AI:TICKET:DECISION:" + command.ticketId(),
                Duration.ofMinutes(10)
        );

        long start = System.currentTimeMillis();
        try {
            TicketDecision decision = ticketClassifier.classify(command.ticketContent());

            ticketAiResultRepository.save(TicketAiResultEntity.success(
                    command.ticketId(),
                    command.tenantId(),
                    JsonUtils.toJson(decision),
                    command.promptVersion()
            ));

            TicketFlowAction flowAction = ticketFlowDecisionService.decide(command.ticketId(), decision);
            if (decision.needManualReview()) {
                manualReviewTaskService.create(
                        command.ticketId(),
                        "AI_STRUCTURE_REVIEW",
                        decision.reason()
                );
            }

            aiInvocationAuditService.success(
                    command.ticketId(),
                    "TICKET_DECISION",
                    System.currentTimeMillis() - start
            );
            return new TicketDecisionResult(decision, flowAction);
        } catch (Exception ex) {
            log.error("ticket decision fail, ticketId={}", command.ticketId(), ex);
            ticketAiResultRepository.save(TicketAiResultEntity.fail(
                    command.ticketId(),
                    command.tenantId(),
                    ex.getMessage(),
                    command.promptVersion()
            ));
            manualReviewTaskService.create(
                    command.ticketId(),
                    "AI_STRUCTURE_FALLBACK",
                    "结构化提取失败,自动转人工"
            );
            aiInvocationAuditService.fail(
                    command.ticketId(),
                    "TICKET_DECISION",
                    ex.getMessage(),
                    System.currentTimeMillis() - start
            );
            return new TicketDecisionResult(
                    new TicketDecision(TicketCategory.OTHER, TicketPriority.MEDIUM, true, "模型异常,已走人工"),
                    TicketFlowAction.MANUAL_REVIEW
            );
        }
    }
}

SQL 示例:结构化结果与异常表

create table ai_ticket_decision_log (
    id bigint primary key auto_increment,
    ticket_id bigint not null,
    result_json json null,
    parse_status varchar(32) not null comment 'SUCCESS/FAIL',
    error_message varchar(500) null,
    created_time datetime not null default current_timestamp
);

系统设计时我会优先拆哪几层

输出定义层

  • 先定义 Java 类型,再倒推 prompt 和 JSON Schema,而不是反过来
  • 复杂字段尽量收敛成 enum、boolean、嵌套对象,后端接起来更稳

AI Service 约束层

  • 优先用模型原生支持的 JSON Schema,尽量少靠纯提示词约束格式
  • 类和字段上补 @Description,能显著减少字段语义漂移

落库与补偿层

  • 成功和失败都要落库,后面做回放、评测和误判分析时特别有用
  • 核心链路不要把一次格式异常当成致命错误,必须有人工兜底入口

真正上线时最容易卡住的点

  1. 所有字段都设成可选,短期看成功率高,长期看后端会处理一堆‘看起来像成功其实缺字段’的脏数据。
  2. 只会写一个大 Prompt,不给类型和字段补描述,模型对边界字段会非常不稳定。
  3. 结构化输出失败后直接抛错,没有补偿记录,线上一旦出问题很难复盘。

监控和指标建议盯哪些

  • 结构化解析成功率
  • 字段缺失率、枚举越界率
  • 人工复核触发率
  • 不同模型的结构化稳定性和成本对比

如果面试官问我这块怎么设计,我会这样答

如果面试官问 LangChain4j 的结构化输出怎么落地,我会先讲这不是‘让模型回 JSON’这么简单,而是 Java 类型建模、模型能力判断、异常兜底三件事一起做。项目里我会先定义返回 POJO,再用 JSON Schema 和字段描述约束模型,最后把失败结果落库并转人工复核。这样这块能力才是真正可上线的。

结语

结构化输出的核心,不是让大模型看起来更规范,而是让它的结果真的能被 Java 系统稳定消费。

如果你们项目里已经做了 AI 提取,最容易出问题的是字段不稳定,还是解析失败后的补偿?

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐