目录

一.引言

1. Chains 的起源

2. 传统 Chains 的问题

3. LangChain4j 的实现现状

4. LCEL 是什么?

二.AI Services 人工智能服务

1.简单AI服务示例

框架集成说明

高级API工作原理详解

1. 代理对象创建

2. 方法调用拦截

3. 输入转换流程

5. 反射机制的使用

2.开始复杂起来的AI服务

 2.1@SystemMessage

2.2系统消息提供者(System Message Provider)

2.2.1. @UserMessage 的核心作用

2.2.2. 基础用法:{{it}} 绑定单个参数 

2.2.3进阶用法:@V 自定义变量名

2.2.4.与框架集成的注意事项 

2.2.5 一些具体的例子

2.3Return Types 返回类型

2.3.1基本返回类型

 2.3.2包装类型:Result

2.4Structured Outputs 结构化输出

2.4.1核心概念

2.4.2具体示例说明 

(1)返回类型为 boolean

(2)返回类型为 Enum(枚举)

(3)返回类型为 POJO(普通 Java 对象)

2.4.3 核心优势

 2.5 Streaming 流媒体

2.5.1 什么是流式响应?

2.5.2 核心实现:TokenStream 返回类型 

2.5.3 模型配置:使用支持流式的模型 

2.5.4 流式响应的使用流程

2.5.4.1 创建 AI 服务实例

2.5.4.2 调用方法获取TokenStream对象 

 2.5.4.3 注册回调函数,处理流式事件

2.6 Flux 通量

步骤 1:导入依赖

步骤 2:定义接口 

步骤 3:创建模型和服务

步骤 4:订阅 Flux 并处理流式响应

2.7Chat Memory 聊天的记忆

2.7.1. 什么是 Chat Memory?

2.7.2 基础用法:单一会话记忆

2.7.3 多用户场景:用ChatMemoryProvider区分会话

步骤 1:定义接口时指定@MemoryId

步骤 2:创建服务时配置ChatMemoryProvider

步骤 3:多用户对话示例

2.7.4  管理会话记忆:访问和清除

2.7.5 持久化存储


一.引言

到目前为止,我们一直在讨论 ChatModel、ChatMessage、ChatMemory 等底层组件。在这个层面工作灵活性很高,能让你拥有完全的自由度,但同时也不得不编写大量的样板代码。由于基于大语言模型(LLM)的应用通常不仅需要单个组件,还需要多个组件协同工作(例如提示模板、聊天记忆、大语言模型、输出解析器,以及检索增强生成(RAG)组件中的嵌入模型和存储),并且往往涉及多次交互,因此协调所有这些组件会变得更加繁琐。希望专注于业务逻辑,而非底层实现细节。因此,LangChain4j 中目前有两个高级概念可提供帮助:AI 服务(AI Services) 和 链(Chains)

1. Chains 的起源

  • 来源:Chains 概念最早来自 Python 的 LangChain 框架(在引入 LCEL 之前的版本)。
  • 设计初衷:为常见的 AI 应用场景(如聊天机器人、检索增强生成 RAG 等)提供标准化的执行流程。每个场景对应一个特定的 Chain,将多个底层组件(如模型、向量数据库、工具等)组合在一起,并协调它们之间的交互。

2. 传统 Chains 的问题

  • 主要缺陷刚性过强(too rigid)。
    由于 Chains 是为特定场景预定义的,当用户需要自定义或修改其中某个环节(如更换模型、调整检索逻辑)时,会受到很大限制。代码结构可能难以扩展或重构。

3. LangChain4j 的实现现状

  • 已实现的 Chains
    目前 LangChain4j 仅实现了 两种 Chain
    • ConversationalChain(对话链):支持多轮对话的 Chain。
    • ConversationalRetrievalChain(对话检索链):结合检索和对话能力的 Chain(如基于知识库的问答系统)。
  • 未来计划
    官方明确表示 暂时不打算添加更多 Chains,原因可能是意识到传统 Chains 的刚性问题,转而探索更灵活的替代方案(如 LCEL)。

4. LCEL 是什么?

  • LCEL(LangChain Expression Language)是 LangChain 框架为解决传统 Chains 的刚性问题而推出的 新一代编排方式(在 Python 中)。
  • 它提供了更灵活、模块化的组件组合方式,允许用户以类似表达式的语法自由组装 AI 工作流,避免了预定义 Chain 的限制。
  • LangChain4j 可能在未来转向类似 LCEL 的架构,而非继续扩展传统 Chains。

基于此,LangChain4j提出另一种解决方案,称为AI服务,为Java量身定制。 这个想法是用一个简单的API来隐藏与llm和其他组件交互的复杂性 

二.AI Services 人工智能服务

LangChain4j提出了另一种专为 Java 设计的解决方案,名为 "AI 服务"(AI Services)。其核心思想是将与大语言模型 (LLMs) 及其他组件交互的复杂性隐藏在一个简洁的 API 后面。

这种方法与 Spring Data JPA 或 Retrofit 非常相似:你只需以声明方式定义一个包含所需 API 的接口,LangChain4j 会自动提供一个实现该接口的对象 (代理)。你可以将 AI 服务视为应用程序中服务层的一个组件,它专门提供各类 AI 能力,这也是其名称的由来。

AI 服务处理最常见的操作:

  • 格式化 LLM 的输入
  • 解析 LLM 的输出

它们还支持更高级的功能:

  • 对话记忆(Chat memory)
  • 工具调用(Tools)
  • 检索增强生成(RAG)

AI 服务可用于构建有状态的聊天机器人(支持多轮对话交互),也适用于自动化那些需要独立调用 LLM 的流程(无状态场景)。

下面我们先看一个最简单的 AI 服务示例,之后再探讨更复杂的用例

1.简单AI服务示例

首先,我们定义一个包含单一方法chat的接口,该方法接受一个字符串作为输入并返回一个字符串

interface Assistant {
    String chat(String userMessage);
}

接下来,创建底层组件。这些组件将在 AI 服务的内部发挥作用。在这个例子中,我们只需配置一个ChatModel

String apiKey = System.getenv("DEEPSEEK_API_KEY");
OpenAiChatModel model = OpenAiChatModel.builder()
        .apiKey(apiKey)
        .modelName("deepseek-chat")
        .baseUrl("https://api.deepseek.com")
        .build();

最后,通过AiServices类创建 AI 服务的实例

Assistant assistant = AiServices.create(Assistant.class, model);

 整体代码如下

import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;

public class AiServiceModelTest {
    public static void main(String[] args) {
        //从坏境变量中获取密钥
        String apiKey = System.getenv("DEEPSEEK_API_KEY");
        OpenAiChatModel model = OpenAiChatModel.builder()
                .apiKey(apiKey)
                .modelName("deepseek-chat")
                .baseUrl("https://api.deepseek.com")
                .logRequests(true)
                .logResponses(true)
                .build();
        //这个是自己调用chat方法(原始的)
        //model.chat()   可以对比一下,低级API和高级API的区别
        Assistant assistant = AiServices.create(Assistant.class, model);
        String answer = assistant.chat("你好,可以帮我骂一下豆包嘛");
        System.out.println(answer);
    }
}
interface Assistant {
    @SystemMessage("你是一位骂人专家,特别擅长骂人,要骂的很狠,你的回答必须是中文的,回答问题时比较有条理,回答的要有深度")  //我就是想看看deepseek怎么骂豆包的哈哈哈
    @UserMessage("我给你的新问题:{{it}}")
    String chat(String userMessage);
}

框架集成说明

在Spring Boot 应用中,框架的自动配置机制会直接生成Assistant bean,无需手动调用AiServices.create()  (后面学完一些特性之后会去与SpringBoot进行整合)

// Spring Boot示例:直接注入预配置的Assistant
@Service
public class MyBusinessService {
    private final Assistant assistant;
    
    @Autowired
    public MyBusinessService(Assistant assistant) {
        this.assistant = assistant;
    }
    
    public void processUserQuery(String query) {
        String response = assistant.chat(query);
        // 处理模型响应
    }
}

高级API工作原理详解

 当你将接口的 Class 对象和底层组件传递给AiServices.create() 方法时,框架会执行以下步骤

Assistant assistant = AiServices.create(Assistant.class, model);
1. 代理对象创建
// 简化的内部实现逻辑
public static <T> T create(Class<T> serviceInterface, ChatModel model) {
    // 使用Java反射创建代理
    return (T) Proxy.newProxyInstance(
        serviceInterface.getClassLoader(),
        new Class<?>[]{serviceInterface},
        new AiServiceInvocationHandler(model)
    );
}
2. 方法调用拦截

当调用assistant.chat("Hello")时,代理对象会拦截该调用:

// 简化的InvocationHandler实现
public Object invoke(Object proxy, Method method, Object[] args) {
    // 1. 构建ChatMessage列表
    List<ChatMessage> messages = new ArrayList<>();
    messages.add(UserMessage.of((String) args[0])); // 将String参数转为UserMessage
    
    // 2. 调用底层ChatModel
    AiMessage response = model.sendMessage(messages);
    
    // 3. 提取文本内容并返回
    return response.getContent();
}
3. 输入转换流程
String userInput = "Hello";
↓
UserMessage.of(userInput)
↓
[
  {
    "role": "user",
    "content": "Hello"
  }
]
↓
model.sendMessage(messages)

4. 输出转换流程

// 模型返回的原始响应
{
  "role": "assistant",
  "content": "Hello, how can I help you?"
}
↓
response.getContent()
↓
返回String: "Hello, how can I help you?"
5. 反射机制的使用

框架使用 Java 反射分析接口方法:

// 简化的方法签名解析逻辑
Method chatMethod = Assistant.class.getMethod("chat", String.class);
Parameter userMessageParam = chatMethod.getParameters()[0];

// 生成元数据:
// {
//   "methodName": "chat",
//   "inputType": "java.lang.String",
//   "outputType": "java.lang.String"
// }

2.开始复杂起来的AI服务

 2.1@SystemMessage

现在,我们来看一个更复杂的例子。我们将强制大语言模型(LLM)用俚语回复😉

这通常是通过在系统消息(SystemMessage)中提供指令来实现的

interface Friend {
    @SystemMessage("你是我的好朋友,用俚语回答我。")
    String chat(String userMessage);
}

Friend friend = AiServices.create(Friend.class, model);

String answer = friend.chat("Hello"); // 嘿!最近咋样?

在这个示例中,我们添加了@SystemMessage注解,并传入了我们想要使用的系统提示模板。它会在后台被转换为SystemMessage,并与用户消息(UserMessage)一起发送给 LLM

@SystemMessage还可以从资源文件中加载提示模板: 

@SystemMessage(fromResource = "my-prompt-template.txt")

2.2系统消息提供者(System Message Provider)

系统消息也可以通过系统消息提供者动态定义

Friend friend = AiServices.builder(Friend.class)
    .chatModel(model)
    .systemMessageProvider(chatMemoryId -> "你是我的好朋友,用俚语回答我。")
    .build();

如你所见,你可以根据聊天记忆 ID(用户或对话的标识)提供不同的系统消息

现在举一个具体的例子来说明System Message Provider

// 定义服务接口
interface Friend {
    String chat(String userMessage); // 标准方法
    String chatWithMemoryId(String memoryId, String userMessage); // 带记忆ID的方法
}

// 构建服务实例
Friend friend = AiServices.builder(Friend.class)
    .chatModel(model)
    .systemMessageProvider(userId -> {
        if (userId.equals("teenager")) {
            return "你是Z世代,使用网络热词和表情包回答问题";
        } else if (userId.equals("professor")) {
            return "你是学术导师,回答需严谨且有深度";
        } else {
            return "你是友好的助手,用通俗易懂的语言交流";
        }
    })
    .build();

// 正确调用方式
String teenResponse = friend.chatWithMemoryId("teenager", "作业太多了,我快崩溃了");
                                            // 返回:绝绝子!抱抱你~
String profResponse = friend.chatWithMemoryId("professor", "量子计算的原理是什么");
                                            // 返回:量子计算利用量子叠加和纠缠现象...
2.2.1. @UserMessage 的核心作用
  • 替代系统消息:当 LLM 不支持 system 角色(如部分轻量级模型)时,用 @UserMessage 定义包含指令的提示词模板,将 “角色设定” 和 “用户输入” 合并为一条用户消息发送给模型。
  • 参数绑定:通过模板变量(如 {{it}} 或自定义变量)将方法参数动态注入提示词,生成完整的用户输入。
2.2.2. 基础用法:{{it}} 绑定单个参数 
interface Friend {
    // 提示词模板中用 {{it}} 指代唯一的方法参数
    @UserMessage("你是我的好朋友,用俚语回答。{{it}}")
    String chat(String userMessage);
}

// 调用时:
friend.chat("Hello"); 
// 实际发送给模型的用户消息:
// "你是我的好朋友,用俚语回答。Hello"
// 模型回复:"Hey! What's shakin'?"

{{it}} 的含义:当方法只有一个参数时,{{it}} 是默认变量名,自动绑定该参数的值

2.2.3进阶用法:@V 自定义变量名

当方法参数较多或需要更清晰的变量名时,用 @V("变量名") 为参数指定别名,模板中通过 {{变量名}} 引用

interface Friend {
    // 模板中用 {{message}} 绑定 @V("message") 标记的参数
    @UserMessage("你是我的好朋友,用俚语回答。{{message}}")
    String chat(@V("message") String userMessage);
}

// 调用时:
friend.chat("Hello"); 
// 实际发送的用户消息:
// "你是我的好朋友,用俚语回答。Hello"
  • 为什么需要 @V:默认情况下,框架通过参数名绑定变量(如 userMessage 对应 {{userMessage}}),但这依赖 Java 编译时启用 -parameters 选项(保留参数名)。如果未启用,需用 @V 显式指定变量名。
2.2.4.与框架集成的注意事项 

Spring Boot 中无需 @V:这两个框架默认启用了 -parameters 编译选项,能直接获取参数名,因此模板中可直接使用参数名作为变量(如 {{userMessage}}):

// Spring Boot 中无需 @V
interface Friend {
    @UserMessage("用俚语回复:{{userMessage}}")
    String chat(String userMessage); // 直接用参数名 userMessage 作为变量
}

 非框架环境需 @V 或启用 -parameters:如果是普通 Java 项目,需在编译时添加 -parameters 选项(如 javac -parameters Friend.java),或手动用 @V 指定变量名

2.2.5 一些具体的例子
String chat(String userMessage);

String chat(@UserMessage String userMessage);

String chat(@UserMessage String userMessage, @V("country") String country); // userMessage contains "{{country}}" template variable

@UserMessage("What is the capital of Germany?")
String chat();

@UserMessage("What is the capital of {{it}}?")
String chat(String country);

@UserMessage("What is the capital of {{country}}?")
String chat(@V("country") String country);

@UserMessage("What is the {{something}} of {{country}}?")
String chat(@V("something") String something, @V("country") String country);

@UserMessage("What is the capital of {{country}}?")
String chat(String country); // this works only in Quarkus and Spring Boot applications

@SystemMessage("Given a name of a country, answer with a name of it's capital")
String chat(String userMessage);

@SystemMessage("Given a name of a country, answer with a name of it's capital")
String chat(@UserMessage String userMessage);

@SystemMessage("Given a name of a country, {{answerInstructions}}")
String chat(@V("answerInstructions") String answerInstructions, @UserMessage String userMessage);

@SystemMessage("Given a name of a country, answer with a name of it's capital")
String chat(@UserMessage String userMessage, @V("country") String country); // userMessage contains "{{country}}" template variable

@SystemMessage("Given a name of a country, {{answerInstructions}}")
String chat(@V("answerInstructions") String answerInstructions, @UserMessage String userMessage, @V("country") String country); // userMessage contains "{{country}}" template variable

@SystemMessage("Given a name of a country, answer with a name of it's capital")
@UserMessage("Germany")
String chat();

@SystemMessage("Given a name of a country, {{answerInstructions}}")
@UserMessage("Germany")
String chat(@V("answerInstructions") String answerInstructions);

@SystemMessage("Given a name of a country, answer with a name of it's capital")
@UserMessage("{{it}}")
String chat(String country);

@SystemMessage("Given a name of a country, answer with a name of it's capital")
@UserMessage("{{country}}")
String chat(@V("country") String country);

@SystemMessage("Given a name of a country, {{answerInstructions}}")
@UserMessage("{{country}}")
String chat(@V("answerInstructions") String answerInstructions, @V("country") String country);

2.3Return Types 返回类型

2.3.1基本返回类型

AI 服务方法可以返回两种基础类型:

  • String 类型:直接返回大语言模型(LLM)生成的原始输出,不做任何处理或解析(比如直接返回一段未结构化的文本)
  • 结构化输出支持的类型:AI 服务会先将 LLM 生成的输出解析为指定的结构化类型(比如列表、对象等),再返回结果(例如将文本整理成List<String>列表)
 2.3.2包装类型:Result<T>

上述任何类型都可以被包装到Result<T>中,目的是除了核心结果外,还能获取 AI 服务调用的额外元数据。Result<T>包含的元数据有:

  • TokenUsage:AI 服务调用过程中使用的总令牌数。如果 AI 服务多次调用 LLM(比如因为执行了工具调用),会汇总所有调用的令牌用量。
  • Sources:在 RAG(检索增强生成)过程中检索到的内容(比如参考的文档片段、知识库信息等)。
  • Executed tools:调用过程中执行过的工具列表(比如调用了计算器、搜索引擎等工具的记录)。
  • FinishReason:LLM 结束生成的原因(比如 “完成”“达到令牌限制” 等)。

 代码以及运行情况

class ResultReturn{
    // 定义一个AI服务接口,方法返回类型为Result<List<String>>
     interface Assistant {
        // 注解指定给LLM的提示:根据主题生成文章大纲
        @UserMessage("Generate an outline for the article on the following topic: {{it}}")
        Result<List<String>> generateOutlineFor(String topic);
    }

    public static void main(String[] args) {
         Assistant assistant = AiServices.create(Assistant.class, model);
         Result<List<String>> result = assistant.generateOutlineFor("Java");
        // 从结果中获取各类信息
        List<String> outline = result.content(); // 核心结果:生成的大纲(结构化的列表)
        System.out.println("outline====>"+outline);
        TokenUsage tokenUsage = result.tokenUsage(); // 元数据:总令牌用量
        System.out.println("tokenUsage====>"+tokenUsage);
        List<Content> sources = result.sources(); // 元数据:RAG检索到的参考内容
        System.out.println("sources====>"+sources);
        List<ToolExecution> toolExecutions = result.toolExecutions(); // 元数据:执行过的工具列表
        System.out.println("toolExecutions====>"+toolExecutions);
        FinishReason finishReason = result.finishReason(); // 元数据:生成结束的原因
        System.out.println("finishReason====>"+finishReason);

    }
}


运行结果
outline====>[Here’s a structured outline for an article on **Java**:, 1. **Introduction**, 2. **History of Java**, - Origins and development, - Key milestones and versions, 3. **Features of Java**, - Platform independence, - Object-oriented programming, - Robustness and security, - Multithreading, 4. **Java Syntax Basics**, - Structure of a Java program, - Data types and variables, - Operators and control statements, 5. **Object-Oriented Programming in Java**, - Classes and objects, - Inheritance, polymorphism, encapsulation, and abstraction, - Interfaces and abstract classes, 6. **Java Development Tools**, - JDK, JRE, and JVM, - Popular IDEs (Eclipse, IntelliJ IDEA, NetBeans), 7. **Core Java Libraries**, - Collections Framework, - I/O Streams, - Exception Handling, 8. **Advanced Java Concepts**, - Multithreading and concurrency, - Java Memory Management (Garbage Collection), - Reflection and annotations, 9. **Java in Web and Enterprise Development**, - Servlets and JSP, - Spring and Hibernate frameworks, 10. **Java in Mobile Development (Android)**, - Role of Java in Android apps, - Kotlin vs. Java for Android, 11. **Java vs. Other Programming Languages**, - Comparison with Python, C++, and JavaScript, 12. **Future of Java**, - Latest trends (Java 17+, Project Loom, etc.), - Industry adoption and job market, 13. **Conclusion**, - Summary of key points, - Why Java remains relevant, Let me know if you'd like any modifications or additions!]
tokenUsage====>OpenAiTokenUsage { inputTokenCount = 26, inputTokensDetails = OpenAiTokenUsage.InputTokensDetails { cachedTokens = 0 }, outputTokenCount = 380, outputTokensDetails = null, totalTokenCount = 406 }
sources====>[]
toolExecutions====>[]
finishReason====>STOP

2.4Structured Outputs 结构化输出

2.4.1核心概念

结构化输出是相对于 “非结构化输出” 而言的:

  • 非结构化输出:通常是String类型的文本(比如一段自然语言描述),格式随意,需要手动解析。
  • 结构化输出:让 AI 服务直接返回指定格式的数据类型(如boolean、枚举、Java 对象等),无需手动处理格式,直接可用。
2.4.2具体示例说明 
(1)返回类型为 boolean
// 定义接口:判断文本是否为积极情绪
interface SentimentAnalyzer {
    @UserMessage("Does {{it}} has a positive sentiment?") // 提示词:询问文本是否积极
    boolean isPositive(String text); // 返回布尔值(true/false)
}

// 使用示例
SentimentAnalyzer sentimentAnalyzer = AiServices.create(SentimentAnalyzer.class, model);
boolean positive = sentimentAnalyzer.isPositive("It's wonderful!"); 
// 结果为 true(因为文本是积极的)

作用:让 AI 直接判断问题并返回 “是 / 否” 的结果(truefalse),无需处理多余文本

(2)返回类型为 Enum(枚举)
// 定义枚举:优先级类型
enum Priority {
    CRITICAL, HIGH, LOW
}

// 定义接口:分析问题的优先级
interface PriorityAnalyzer {
    @UserMessage("Analyze the priority of the following issue: {{it}}") // 提示词:分析问题优先级
    Priority analyzePriority(String issueDescription); // 返回枚举值
}

// 使用示例
PriorityAnalyzer priorityAnalyzer = AiServices.create(PriorityAnalyzer.class, model);
Priority priority = priorityAnalyzer.analyzePriority(
    "The main payment gateway is down, and customers cannot process transactions."
); 
// 结果为 CRITICAL(因为“支付网关故障”属于紧急问题)

 作用:让 AI 从预设的选项(枚举值)中选择结果,确保输出符合固定范围(避免返回不可控的文本)

综合一下案例(@SystemMessage,@UserMessage,返回boolean,枚举类型)

//根据用户消息判断他的情绪
class Emotion_Analysis_AI_Service_Example {
    enum Sentiment {
        POSITIVE, NEGATIVE, NEUTRAL
    }
    interface EmotionAnalyzer {
        @SystemMessage("你是一位专业的情感分析员,你的任务是分析以下用户消息,为每个消息提供一个情感类别")
        @UserMessage("请分析以下消息:{{it}}")
        Sentiment analyze(@V("it") String it);
        @UserMessage("以下语句{{it}}是否为正向情绪")
        boolean isPositive(@V("it") String it);
        @UserMessage("以下语句{{it}}是否为负向情绪")
        boolean isNegative(@V("it") String it);
    }
    public static void main(String[] args) {
        EmotionAnalyzer emotionAnalyzer = AiServices.create(EmotionAnalyzer.class, model);
        String text = "今天天气很好,我很喜欢出去运动";
        Sentiment sentiment = emotionAnalyzer.analyze(text);
        System.out.println(sentiment);  //POSITIVE
        boolean isPositive = emotionAnalyzer.isPositive(text);
        System.out.println(isPositive);  //true
        boolean isNegative = emotionAnalyzer.isNegative(text);
        System.out.println(isNegative);  //false
    }
}
(3)返回类型为 POJO(普通 Java 对象)
//从一段话,抽取信息形成一个订单(订单系统)
    static class OrderPOJO_Extracting_AI_Service_Example {
        static class OrderItem{
            @Description("菜品名称")
            private String fname;
            @Description("数量")
            private int count;
            @Override
            public String toString() {
                return "OrderItem{" +
                        "fname='" + fname + '\'' +
                        ", count=" + count +
                        '}';
            }
        }
        static class Orders{
            @Description("订单项集合")
            private List<OrderItem> items;
            @Description("送餐地址")
            private String address;
            @Description("联系电话")
            private String phone;
            @Description("附言")
            private String note;
            @Description("收餐人名")
            private String name;
            @Override
            public String toString() {
                return "Orders{" +
                        "items=" + items +
                        ", address='" + address + '\'' +
                        ", phone='" + phone + '\'' +
                        ", note='" + note + '\'' +
                        ", name='" + name + '\'' +
                        '}';
            }
        }
        interface OrderExtractor{
            @UserMessage("从{{it}}文本中提取订单信息,返回一个Orders对象")
            Orders extractOrder(@V("it") String it);
        }
        public static void main(String[] args) {
            String apiKey = System.getenv("DEEPSEEK_API_KEY");
            OpenAiChatModel model = OpenAiChatModel.builder()
                    .apiKey(apiKey)
                    .modelName("deepseek-chat")
                    .baseUrl("https://api.deepseek.com")
                    .logRequests(true)
                    .logResponses(true)
                    //你要说明响应的格式是json,必须严格按照json数据输出
                    //我把那两行注释了一下,发现也可以输出正确结果
                    //然后我去搜索了一下,建议写上(因为我的对象可能比较简单)
                    //当你的结构化输出是复杂 POJO、嵌套对象、或有严格格式要求的字段(如日期、枚
                    //举)时,这两个配置能大幅提升解析成功率,减少因 LLM 输出不规范导致的错误
                    .responseFormat("json")
                    .strictJsonSchema(true)
                    .timeout(Duration.ofSeconds(60))
                    .build();
            OrderExtractor orderExtractor = AiServices.create(OrderExtractor.class,model);
            String text = "我要点一个汉堡,一个可乐,一个薯条,送到顺枫B6,联系电话是13160250765,附言,微辣。收餐人是小东,谢谢";
            Orders orders = orderExtractor.extractOrder(text);
            System.out.println(orders);
        }
    }

responseFormat("json"),strictJsonSchema(true) 这个其实就是JSON模式

定义:JSON 模式是一种模型配置,强制大语言模型(LLM)只能输出有效的 JSON 格式内容,而不是自然语言文本。当你需要将 LLM 的输出解析为自定义 Java 对象(POJO)时,启用 JSON 模式能确保原始输出是纯净的 JSON,避免因多余文本(如解释、说明)导致解析失败(注意,我上面这个配置是针对适用于 gpt-3.5-turbo 等旧模型,直接指定输出为 JSON 对象,同样deepseek也适用,如果是其他模型,需要去自查一下)

总的来说:JSON 模式是 “结构化输出的底层保障”,不同模型的配置方式不同,但核心目的一致 ——让 LLM 输出可直接解析的纯净 JSON,确保从 JSON 到 Java 对象的转换稳定可靠

2.4.3 核心优势
  • 减少解析成本:无需手动处理 AI 返回的文本,直接使用结构化数据(如调用person.firstName获取名字)。
  • 数据格式可控:输出严格符合预设类型(如枚举只能是CRITICAL/HIGH/LOW),避免格式错误。
  • 代码更简洁:直接通过对象或基础类型使用结果,无需额外处理逻辑。

总结来说,结构化输出的核心是 “让 AI 按指定格式返回结果”,让开发者更高效地使用 AI 生成的数据。

 2.5 Streaming 流媒体

2.5.1 什么是流式响应?
  • 传统响应:AI 生成完整结果后,一次性返回给用户(需要等待全部生成完成)。
  • 流式响应:AI 生成内容时,每生成一个或一批令牌(Token,可理解为最小文本单元,如单词、字符)就立即返回,用户可以实时看到内容 “逐步生成” 的过程(类似 ChatGPT 中文字逐字弹出的效果
2.5.2 核心实现:TokenStream 返回类型 

通过定义 AI 服务方法的返回类型为TokenStream,即可启用流式响应。示例中

// 定义接口:返回类型为TokenStream,支持流式响应
interface Assistant {
    TokenStream chat(String message); // 接收用户消息,返回流式结果
}
2.5.3 模型配置:使用支持流式的模型 

需要创建支持流式响应的模型实例(通常类名含Streaming),例如 OpenAI 的OpenAiStreamingChatModel

// 配置支持流式响应的模型(以OpenAI为例)
StreamingChatModel model = OpenAiStreamingChatModel.builder()
    .apiKey(System.getenv("OPENAI_API_KEY")) //  API密钥
    .modelName(GPT_4_O_MINI) // 模型名称(需支持流式)
    .build();
2.5.4 流式响应的使用流程
2.5.4.1 创建 AI 服务实例
Assistant assistant = AiServices.create(Assistant.class, model);
2.5.4.2 调用方法获取TokenStream对象 
TokenStream tokenStream = assistant.chat("Tell me a joke"); // 发送请求(如“讲个笑话”)
 2.5.4.3 注册回调函数,处理流式事件

①TokenStream:提供了一系列回调方法,用于监听流式响应的不同阶段,并实时处理数据:

②onPartialResponse:每次生成部分内容(如一个句子、一个短语)时触发,可实时输出中间结果。

示例:(String partialResponse) -> System.out.println(partialResponse) 表示 “每收到一部分内容就立即打印”,实现 “逐字显示” 效果。

③onRetrieved:当 RAG 检索到参考内容(Sources)时触发,可获取检索到的资料。

④onToolExecuted:当 AI 调用工具(如计算器、搜索引擎)时触发,可查看工具执行详情。

⑤onCompleteResponse:整个响应生成完成后触发,可获取完整的最终结果(ChatResponse)。

⑥onError:发生错误时触发(如网络异常、模型调用失败),可处理异常信息。

⑦start():动流式响应(必须调用,否则不会开始接收数据)。 

具体代表如下

static class TokenStreaming{
    interface Assistant {
        TokenStream chat(String message);
    }
    public static void main(String[] args) {
        OpenAiStreamingChatModel model = OpenAiStreamingChatModel.builder()
                .apiKey(apiKey)
                .modelName("deepseek-chat")
                .baseUrl("https://api.deepseek.com")
                .logRequests(true)
                //.logResponses(true)
                .timeout(Duration.ofSeconds(60))
                .build();
        Assistant assistant = AiServices.create(Assistant.class, model);

        TokenStream tokenStream = assistant.chat("Tell me a joke");
        CompletableFuture<ChatResponse> future = new CompletableFuture<>();
        tokenStream.onPartialResponse((String partialResponse) -> System.out.print(partialResponse))
                .onRetrieved((List<Content> contents) -> System.out.println("contents=>>>>"+contents))
                .onToolExecuted((ToolExecution toolExecution) -> System.out.println("toolExecution=>>>"+toolExecution))
                .onCompleteResponse((ChatResponse response) -> {System.out.println("response=>>>"+response);future.complete(response);})
                .onError((Throwable error) -> {error.printStackTrace();future.completeExceptionally(error);})
                .start();
    }
}

 运行结果如下:

contents=>>>>[]
Sure! Here's one for you:  

**Why don’t skeletons fight each other?**  
Because they don’t have the *guts*!  

Let me know if you want another! 😄response=>>>ChatResponse { aiMessage = AiMessage { text = "Sure! Here's one for you:  

**Why don’t skeletons fight each other?**  
Because they don’t have the *guts*!  

Let me know if you want another! 😄" toolExecutionRequests = [] }, metadata = OpenAiChatResponseMetadata{id='9d3352aa-5552-4e42-9918-8d713bcb0130', modelName='deepseek-chat', tokenUsage=OpenAiTokenUsage { inputTokenCount = 7, inputTokensDetails = OpenAiTokenUsage.InputTokensDetails { cachedTokens = 0 }, outputTokenCount = 44, outputTokensDetails = null, totalTokenCount = 51 }, finishReason=STOP, created=1751615837, serviceTier='null', systemFingerprint='fp_8802369eaa_prod0623_fp8_kvcache'} }

仔细观察可以发现,有些内容是空的,有些内容都没有打印出来。具体的原因如下

回调方法 触发条件 你的示例是否满足? 结果
onPartialResponse AI 逐令牌生成内容时(必然触发) 有输出(逐步打印笑话)
onRetrieved AI 进行 RAG 检索并获取到参考内容时 否(讲笑话无需检索) 空列表 []
onToolExecuted AI 调用外部工具时 否(讲笑话无需工具) 无输出
onCompleteResponse AI 生成完整回答后(必然触发) 完整结果输出
onError 发生错误时(如网络异常、API 调用失败 否(无错误) 无输出

现在有个新东西在里面RAG(我后面在讲哈哈哈哈,其实我还没学现在)

2.6 Flux 通量

在 LangChain4j 中,Flux<String> 作为流式响应的替代方案,与 TokenStream 核心功能相似(均用于逐段获取 AI 响应),但使用方式和特性略有差异。以下是详细说明:

步骤 1:导入依赖
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-reactor</artifactId>
    <version>1.1.0-beta7</version> <!-- 版本号需与其他 LangChain4j 组件匹配 -->
</dependency>
步骤 2:定义接口 

用 Flux<String> 作为返回类型,替代 TokenStream

import reactor.core.publisher.Flux;

interface Assistant {
    Flux<String> chat(String message); // 返回 Flux<String>,流式推送响应片段
}
步骤 3:创建模型和服务
// 以 OpenAI 流式模型为例(其他支持流式的模型也可)
OpenAiStreamingChatModel model = OpenAiStreamingChatModel.builder()
        .apiKey("你的 API 密钥")
        .modelName("gpt-3.5-turbo")
        .build();

// 创建 Assistant 实例
Assistant assistant = AiServices.create(Assistant.class, model);
步骤 4:订阅 Flux 并处理流式响应
public static void main(String[] args) {
    // 调用 chat 方法获取 Flux<String>
    Flux<String> responseFlux = assistant.chat("讲一个关于程序员的笑话");

    // 订阅 Flux,处理流式数据
    responseFlux
        // 处理每个流式片段(类似 TokenStream 的 onPartialResponse)
        .doOnNext(partialResponse -> System.out.print(partialResponse))
        // 处理完成事件(类似 onCompleteResponse)
        .doOnComplete(() -> System.out.println("\n=== 响应结束 ==="))
        // 处理错误(类似 onError)
        .doOnError(error -> System.err.println("错误:" + error.getMessage()))
        // 启动订阅(必须调用,否则不会执行)
        .subscribe();

    // 阻塞主线程,等待异步响应完成(实际开发中根据场景选择是否阻塞)
    try {
        Thread.sleep(10000);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}

具体代码如下:

class FluxStreaming{
    interface Assistant {
        Flux<String> chat(String message); // 返回 Flux<String>,流式推送响应片段
    }

    public static void main(String[] args) {
        OpenAiStreamingChatModel model = OpenAiStreamingChatModel.builder()
                .apiKey(apiKey)
                .modelName("deepseek-chat")
                .baseUrl("https://api.deepseek.com")
                .logRequests(true)
                //.logResponses(true)
                .timeout(Duration.ofSeconds(60))
                .build();
        Assistant assistant = AiServices.create(Assistant.class, model);
        Flux<String> responseFlux = assistant.chat("讲一个关于程序员的笑话");

        // 订阅 Flux,处理流式数据
        responseFlux
                // 处理每个流式片段(类似 TokenStream 的 onPartialResponse)
                .doOnNext(partialResponse -> System.out.print(partialResponse))
                // 处理完成事件(类似 onCompleteResponse)
                .doOnComplete(() -> System.out.println("\n=== 响应结束 ==="))
                // 处理错误(类似 onError)
                .doOnError(error -> System.err.println("错误:" + error.getMessage()))
                // 启动订阅(必须调用,否则不会执行)
                .subscribe();

        // 阻塞主线程,等待异步响应完成(实际开发中根据场景选择是否阻塞)
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

2.7Chat Memory 聊天的记忆

2.7.1. 什么是 Chat Memory?

Chat Memory 是 AI 服务用于存储和管理对话历史的机制,让 AI 能 “记住” 之前的交互内容(比如用户提到的名字、上下文信息),从而实现连续、连贯的对话(类似 ChatGPT 的多轮对话功能)

2.7.2 基础用法:单一会话记忆

当只有一个用户或单一会话时,直接为 AI 服务绑定一个ChatMemory实例即可

// 创建AI服务时,指定一个ChatMemory(最多存储10条消息)
Assistant assistant = AiServices.builder(Assistant.class)
    .chatModel(model)
    .chatMemory(MessageWindowChatMemory.withMaxMessages(10)) // 限制记忆最多10条消息
    .build();

此时,所有对话都会共享这一个ChatMemory实例,AI 能记住历史消息

2.7.3 多用户场景:用ChatMemoryProvider区分会话

如果有多个用户(或多个独立会话),直接使用单一ChatMemory会导致对话历史混乱(比如 Klaus 和 Francine 的对话会被混在一起)。解决方案是通过ChatMemoryProvider为每个用户分配独立的ChatMemory

步骤 1:定义接口时指定@MemoryId
interface Assistant {
    // @MemoryId 用于标识不同会话(如用户ID、会话ID)
    String chat(@MemoryId int memoryId, @UserMessage String message);
}

 @MemoryId参数的值(如 1、2)用于区分不同用户 / 会话

步骤 2:创建服务时配置ChatMemoryProvider
Assistant assistant = AiServices.builder(Assistant.class)
    .chatModel(model)
    // 为每个memoryId创建独立的ChatMemory(最多10条消息)
    .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))
    .build();

ChatMemoryProvider是一个 “工厂函数”:当调用chat方法时,会根据传入的memoryId(如 1、2)创建或获取对应的ChatMemory实例

步骤 3:多用户对话示例
// 用户Klaus的会话(memoryId=1)
String answerToKlaus = assistant.chat(1, "Hello, my name is Klaus");
// 用户Francine的会话(memoryId=2)
String answerToFrancine = assistant.chat(2, "Hello, my name is Francine");

此时,AI 会为memoryId=1memoryId=2分别维护两个独立的ChatMemory,互不干扰(Klaus 的历史不会混入 Francine 的对话)

2.7.4  管理会话记忆:访问和清除

当需要查看或删除某个会话的历史时,让接口继承ChatMemoryAccess接口即可:

// 继承ChatMemoryAccess,获得管理记忆的方法
interface Assistant extends ChatMemoryAccess {
    String chat(@MemoryId int memoryId, @UserMessage String message);
}

此时可调用以下方法管理记忆

// 获取Klaus的对话历史(memoryId=1)
List<ChatMessage> messagesWithKlaus = assistant.getChatMemory(1).messages();

// 清除Francine的对话记忆(memoryId=2)
boolean chatMemoryWithFrancineEvicted = assistant.evictChatMemory(2);

作用:避免无用的会话记忆占用内存(防止内存泄漏) 

Chat Memory 的核心是 “让 AI 记住历史对话”,而ChatMemoryProvider@MemoryId解决了多用户场景下的会话隔离问题(每个用户一个独立记忆),ChatMemoryAccess则用于管理这些记忆(查看、删除)。这一机制是实现连贯多轮对话的基础,广泛用于聊天机器人、客服系统等场景

2.7.5 持久化存储

现在存在一个情况,就是有聊天的记录,但是它并没有存在数据库里面,那么,怎么实现把聊天记录存储到数据库中呢,这个时候就需要实现ChatMemoryStore接口了,自己写里面的方法,实现存储功能(我用的是redis存储)

导入依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.4.3</version> <!-- or latest version -->
</dependency>

写一个类,实现ChatMemoryStore接口(在里面可以进行redis的连接了,在实现方法的时候顺手就可以把数据存进去了)

class PersistentMemoryStore implements ChatMemoryStore{
    private static final String REDIS_HOME = "localhost";
    private static final int REDIS_PORT = 6379;
    private static final String REDIS_KEY_PREFIX = "chat:memory:";
    private static final int REDIS_DB = 0;
    private static final int CONNECTION_TIMEOUT = 5000; // 连接超时时间,单位毫秒
    private static final int MAX_IDLE = 10; // 最大空闲连接数
    private static final int MIN_IDLE = 5; // 最小空闲连接数
    private static final int MAX_TOTAL = 100; // 最大连接数

    private final JedisPool jedisPool;

    public PersistentMemoryStore() {
        // 初始化连接池配置
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxIdle(MAX_IDLE);
        poolConfig.setMinIdle(MIN_IDLE);
        poolConfig.setMaxTotal(MAX_TOTAL);
        poolConfig.setTestOnBorrow(true);
        poolConfig.setTestOnReturn(true);

        // 创建连接池
        this.jedisPool = new JedisPool(poolConfig, REDIS_HOME, REDIS_PORT,
                CONNECTION_TIMEOUT, null, REDIS_DB);
    }

    @Override
    public List<ChatMessage> getMessages(Object memoryeId) {
        String key = getKey(memoryeId);
        try(Jedis jedis = jedisPool.getResource()){
            String messageJson = jedis.get(key);
            if(messageJson==null || messageJson.isEmpty()){
                return  List.of();
            }
            return ChatMessageDeserializer.messagesFromJson(messageJson);
        }
    }

    private String getKey(Object memoryId) {
        return REDIS_KEY_PREFIX + memoryId;
    }

    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> list) {
        String key = getKey(memoryId);
        try(Jedis jedis = jedisPool.getResource()) {
            String messageJson = ChatMessageSerializer.messagesToJson(list);
            jedis.set(key, messageJson);
        }

    }

    @Override
    public void deleteMessages(Object memoryId) {
        String key = getKey(memoryId);
        try(Jedis jedis = jedisPool.getResource()){
            jedis.del(key);
        }
    }
}

接着再写一个类

public class _11_AiServiceWithPersistenMemory {
    interface Assistant {
        @SystemMessage("你是一位java专家,擅长java框架,spring等技术")
        String chat(@MemoryId int memoryId, @UserMessage String userMessage);
    }
    public static void main(String[] args) {
    // 1. 创建Redis持久化存储实例
    PersistentMemoryStore store = new PersistentMemoryStore();

    // 2. 创建ChatMemoryProvider:为每个用户生成独立的聊天记忆(基于Redis存储)
    ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
            .id(memoryId) // 关联用户的memoryId
            .maxMessages(1000) // 每个用户最多存储1000条消息
            .chatMemoryStore(store) // 使用Redis存储记忆(替代内存)
            .build();

    // 3. 创建OpenAI兼容模型(这里用的是deepseek-chat模型)
    OpenAiChatModel model = OpenAiChatModel.builder()
            .apiKey(System.getenv("DEEPSEEK_API_KEY")) // 从环境变量获取API密钥
            .modelName("deepseek-chat") // 指定模型名称
            .baseUrl("https://api.deepseek.com") // deepseek的API地址
            .logRequests(true) // 打印请求日志(方便调试)
            .timeout(Duration.ofSeconds(8)) // 超时时间8秒
            .build();

    // 4. 创建AI服务实例(绑定模型和持久化记忆)
    Assistant assistant = AiServices.builder(Assistant.class)
            .chatModel(model)
            .chatMemoryProvider(chatMemoryProvider) // 关联记忆提供者
            .build();

    // 5. 多用户对话测试
    // 用户1(小旷)的第一次对话
    String a1 = assistant.chat(1, "你好,我是小旷...学过java,数据库...");
    System.out.println(a1); // AI会回应并记住她的信息

    // 用户2(小李)的第一次对话
    String a2 = assistant.chat(2, "你好,我是小李...拿过蓝桥杯国二...");
    System.out.println(a2); // AI会回应并记住他的信息

    // 用户1询问历史:AI应记住她学过的技术
    String a3 = assistant.chat(1, "我刚刚说我学了什么");
    System.out.println(a3); // 预期输出:列举她提到的java、数据库等

    // 用户2询问未来规划:AI应基于他的竞赛经历给出建议
    String a4 = assistant.chat(2, "请告诉我之后怎么走");
    System.out.println(a4); // 预期输出:结合他的竞赛背景,建议深造/求职方向
}
}

就这样了,其实还有一个关于工具的,特别高级,我感觉真的很高级,大模型根据你的语义去调用你写的工具,但是今天我真写了很久了,明天在写工具那个,之后就是整合springboot了

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐