LangChain4j 学习笔记 03:添加学习计划助手接口,让 AI 返回结构化结果

前两篇已经完成了两个基础步骤。

第一篇是直接通过 ChatModel 调用 DeepSeek,先把聊天接口跑通。

第二篇是加入 Assistant 接口,通过 AiServices 把 AI 能力封装成一个服务对象。

这一篇继续往前走一步,开始做一个更具体的接口:

根据用户输入的学习目标,生成一个学习计划。

这次不再只是让 AI 返回一段普通文本,而是让它返回一个 Java 对象。


一、这一章做了什么

当前这一章主要新增了几个内容:

1. 添加 Lombok 依赖
2. 新增学习计划相关模型类
3. 新增 StudyPlanAssistant 接口
4. 在 LangChain4jConfig 中注册 StudyPlanAssistant
5. 新增 StudyPlanController,对外提供学习计划生成接口

这一章的接口地址是:

POST http://localhost:8080/study-plan/generate

请求参数是一个学习目标,例如:

{
  "goal": "我想用 7 天入门 LangChain4j"
}

接口会返回一个结构化对象,里面包含当天主题、任务清单、产出物和验收标准。


在这里插入图片描述

二、添加 Lombok 依赖

这一章开始增加了一些模型类,如果每个类都手写 gettersetter,代码会比较啰嗦。

所以先在 pom.xml 中加入 Lombok:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

在这里插入图片描述

后面就可以在实体类上直接使用:

@Data
@NoArgsConstructor

这样代码会清爽很多。


三、新增请求对象

这一章新增了一个请求对象 StudyPlanRequest

package com.example.langchain4jstudy.model;

import lombok.Data;

/**
 * 学习计划请求
 */
@Data
public class StudyPlanRequest {

    /**
     * 学习目标
     */
    private String goal;

    public StudyPlanRequest() {
    }
}

这个类目前只有一个字段:

private String goal;

也就是用户想学什么。

比如用户可以传:

{
  "goal": "我想学习 LangChain4j,并能做一个简单的 AI 对话接口"
}

Controller 接收到这个参数后,会把 goal 传给 AI 助手。


四、新增每日学习计划对象

接着新增了 StudyPlanDayItem

package com.example.langchain4jstudy.model;

import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * 学习计划中的每日学习安排。
 */
@Data
@NoArgsConstructor
public class StudyPlanDayItem {

    /**
     * 第几天。
     */
    private Integer dayIndex;

    /**
     * 当天学习主题。
     */
    private String topic;

    /**
     * 当天任务清单。
     */
    private List<String> tasks;

    /**
     * 当天产出物。
     */
    private String deliverable;

    /**
     * 当天验收标准。
     */
    private String acceptanceCriteria;
}

这个对象用来描述某一天的学习安排。

字段也比较直观:

dayIndex:第几天
topic:当天主题
tasks:当天任务清单
deliverable:当天产出物
acceptanceCriteria:验收标准

比如 AI 返回的内容可能类似这样:

{
  "dayIndex": 1,
  "topic": "跑通 LangChain4j 基础对话接口",
  "tasks": [
    "创建 Spring Boot 项目",
    "添加 LangChain4j 依赖",
    "配置 DeepSeek API Key",
    "编写一个简单的聊天接口"
  ],
  "deliverable": "一个可以正常调用 DeepSeek 的 Spring Boot 接口",
  "acceptanceCriteria": "使用 Postman 或 curl 调用接口,能够正常返回 AI 回答"
}

这比直接返回一大段文字更适合前端展示,也更适合后续扩展。


五、新增学习计划难度枚举

这一章还加了一个枚举类 StudyPlanLevelEnum

package com.example.langchain4jstudy.model.enums;

/**
 * 学习计划难度等级枚举。
 */
public enum StudyPlanLevelEnum {

    /**
     * 入门级。
     */
    BEGINNER,

    /**
     * 进阶级。
     */
    INTERMEDIATE,

    /**
     * 高级。
     */
    ADVANCED
}

这个枚举用来表示学习计划难度:

BEGINNER:入门级
INTERMEDIATE:进阶级
ADVANCED:高级

这里有一个细节:枚举值使用英文大写。

这样做是为了让大模型输出时更稳定,也方便 Java 反序列化。

如果让模型返回中文,比如“入门级”“进阶级”,后面转成枚举时就容易出问题。


六、新增完整学习计划响应对象

这一章还定义了一个 StudyPlanResponse

package com.example.langchain4jstudy.model;

import com.example.langchain4jstudy.model.enums.StudyPlanLevelEnum;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * 学习计划生成结果。
 */
@Data
@NoArgsConstructor
public class StudyPlanResponse {

    /**
     * 学习计划标题。
     */
    private String title;

    /**
     * 学习难度等级。
     */
    private StudyPlanLevelEnum level;

    /**
     * 学习计划摘要。
     */
    private String summary;

    /**
     * 每日学习安排列表。
     */
    private List<StudyPlanDayItem> days;

    /**
     * 风险提醒列表。
     */
    private List<String> warnings;
}

这个对象看起来更完整,包含:

title:学习计划标题
level:学习难度
summary:学习计划摘要
days:每日学习安排
warnings:风险提醒

不过需要注意一下:当前这一章接口里实际返回的是 StudyPlanDayItem,不是 StudyPlanResponse

也就是说,StudyPlanResponse 目前更像是提前准备好的完整响应结构,后面可以继续用。

当前真实接口先返回单个每日计划对象。


七、新增 StudyPlanAssistant

这一章的核心是新增了 StudyPlanAssistant

package com.example.langchain4jstudy.ai;

import com.example.langchain4jstudy.model.StudyPlanDayItem;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;

public interface StudyPlanAssistant {

    @SystemMessage("""
            你是一个非常严格的技术学习教练,擅长给 Java 工程师制定实战学习计划。

            你的任务:
            根据用户目标,生成一个结构化学习计划。

            规则:
            1. 必须偏实战,不要堆理论
            2. 每一天都必须有明确任务
            3. 每一天都必须有明确产出物
            4. 每一天都必须有验收标准
            5. 不要编造不存在的软件版本
            6. 如果用户目标过大,要在 warnings 中提醒风险
            7. 内容必须适合 Java / Spring Boot 工程师
            8. 回答必须使用中文
            """)
    StudyPlanDayItem generate(@UserMessage String userGoal);
}

这里和第二章的 Assistant 思路一样。

还是通过接口定义 AI 能力。

只不过这一次的方法返回值不再是 String,而是:

StudyPlanDayItem

也就是说,我们希望 AI 返回的内容可以被 LangChain4j 解析成 Java 对象。

这就是这一章最关键的变化。


八、SystemMessage 的作用

这段 @SystemMessage 很重要。

它不是随便写几句话,而是在约束 AI 的输出方向:

你是一个严格的技术学习教练
要给 Java 工程师制定实战学习计划
必须偏实战
每天都要有任务、产出物、验收标准
不要编造不存在的软件版本
目标过大时要提醒风险
必须使用中文

如果没有这些规则,模型很可能会返回一段比较散的文字。

有了这些约束以后,返回内容会更接近我们想要的结构。

当前方法定义是:

StudyPlanDayItem generate(@UserMessage String userGoal);

用户传入一个学习目标,AI 返回一个学习计划对象。


九、在配置类中注册 StudyPlanAssistant

接口写好以后,还需要在 LangChain4jConfig 里注册 Bean:

@Bean
public StudyPlanAssistant studyPlanAssistant(ChatModel chatModel) {
    return AiServices.builder(StudyPlanAssistant.class)
            .chatModel(chatModel)
            .build();
}

完整配置里,现在有三个 Bean:

@Bean
public ChatModel chatModel(...) {
    // 创建 DeepSeek 模型
}

@Bean
public Assistant assistant(ChatModel chatModel) {
    return AiServices.builder(Assistant.class)
            .chatModel(chatModel)
            .build();
}

@Bean
public StudyPlanAssistant studyPlanAssistant(ChatModel chatModel) {
    return AiServices.builder(StudyPlanAssistant.class)
            .chatModel(chatModel)
            .build();
}

这里可以简单理解成:

ChatModel:负责连接 DeepSeek
Assistant:普通聊天助手
StudyPlanAssistant:学习计划助手

它们底层用的还是同一个 ChatModel

只是不同的 Assistant 接口,有不同的系统提示词和方法返回值。


十、新增 StudyPlanController

最后新增一个控制器 StudyPlanController

package com.example.langchain4jstudy.controller;

import com.example.langchain4jstudy.ai.StudyPlanAssistant;
import com.example.langchain4jstudy.model.StudyPlanDayItem;
import com.example.langchain4jstudy.model.StudyPlanRequest;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/study-plan")
public class StudyPlanController {

    private final StudyPlanAssistant studyPlanAssistant;

    public StudyPlanController(StudyPlanAssistant studyPlanAssistant) {
        this.studyPlanAssistant = studyPlanAssistant;
    }

    @PostMapping("/generate")
    public StudyPlanDayItem generate(@RequestBody StudyPlanRequest request) {
        return studyPlanAssistant.generate(request.getGoal());
    }
}

这个接口地址是:

POST http://localhost:8080/study-plan/generate

请求体是:

{
  "goal": "我想用 7 天入门 LangChain4j"
}

Controller 做的事情很简单:

接收用户学习目标
调用 StudyPlanAssistant.generate()
返回 AI 生成的 StudyPlanDayItem

核心代码就是这一行:

return studyPlanAssistant.generate(request.getGoal());

十一、当前调用链路

这一章的调用链路可以这样理解:

用户请求
  ↓
StudyPlanController
  ↓
StudyPlanAssistant
  ↓
AiServices 生成代理对象
  ↓
ChatModel
  ↓
DeepSeek
  ↓
返回 StudyPlanDayItem

和第二章相比,这一章最大的变化是:

第二章:AI 返回 String
第三章:AI 返回 Java 对象

这个变化很关键。

因为真实项目里,很多时候我们不希望 AI 只返回一段自然语言,而是希望它返回结构化结果。

比如:

学习计划
文章评分结果
SQL 生成结果
商品推荐结果
风险诊断结果
任务拆解结果

这些都更适合用 Java 对象承接。


十二、启动测试

先设置环境变量:

$env:DEEPSEEK_API_KEY="你的 DeepSeek API Key"

启动项目:

mvn spring-boot:run

然后请求接口:

curl -X POST http://localhost:8080/study-plan/generate ^
  -H "Content-Type: application/json" ^
  -d "{\"goal\":\"我想用 7 天入门 LangChain4j\"}"

如果接口正常,会返回类似这样的结构:

{
  "dayIndex": 1,
  "topic": "LangChain4j 基础环境搭建与第一个对话接口",
  "tasks": [
    "创建 Spring Boot 项目",
    "添加 LangChain4j 相关依赖",
    "配置 DeepSeek API Key",
    "编写一个简单的 Controller 接口",
    "使用 curl 或 Postman 完成接口测试"
  ],
  "deliverable": "一个可以正常调用 DeepSeek 的 Spring Boot 对话接口",
  "acceptanceCriteria": "调用接口后能够正常返回大模型回答,并且控制台没有明显异常"
}

在这里插入图片描述

这一篇先做到这里。

第三章的重点不是做一个完整学习系统,而是先把这件事跑通:

让 AI 根据用户目标,返回一个 Java 对象

后面再继续在这个基础上,把单天计划扩展成完整学习计划。

Logo

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

更多推荐