在过往的开发实践中,我们手动实现了 HTTP 请求封装、固定格式 JSON 请求体的拼接以及对话消息列表的维护工作,包括系统提示、用户提问和AI回复等内容的组装。这种实现方式导致代码结构臃肿,维护和更新成本较高。为此,我们决定引入新的框架技术,以支持后续功能扩展和性能优化。
1.LangChain4j 和 AiServices
LangChain4j 是适配 Java 生态的大模型应用开发框架,它统一适配市面上不同大模型接口,内置对话记忆管理,支持同步和流式请求处理,同时预留工具调用和RAG 等扩展能力。因为它封装了这些底层通用逻辑,所以我们不用关心这些底层细节,只需要专注AI 人设设定、业务对话逻辑即可。
而AiServices 是 LangChain4j 提供的高阶快捷开发组件,也是我们简化编码的核心。它帮我们省去了手动编写请求封装、消息维护、流式回调这些模板代码的过程,只通过定义接口和少量注解,就能让框架自动生成可直接调用的 AI 服务实例。
我们可以具体了解一下定义的接口是什么样的。
java
// 自定义对话格式
interface ChatAssistant {
//注解:嵌入代码的编译 / 运行时配置信息
@SystemMessage("你是AI助手,请简洁回答问题")
//LangChain4j 框架定义的流式响应数据类型,用于封装大模型的流式输出结果
TokenStream streamChat(@UserMessage String userMessage);
}
ChatAssistant 是我们自定义的 AI 服务接口,作用是声明 AI 具备的能力,不需要编写任何方法实现。 AiServices 会读取这个接口,自动生成可运行的 AI 服务实例。
其中例如 @SystemMessage 的注解 是一种嵌入代码的编译或运行时配置信息 ,它不直接执行逻辑 ,仅作为标记性数据 存在,其价值会由框架实现。
@SystemMessage 是语义配置注解 ,用于注入 AI 服务的系统提示词,例如我们写的"你是AI助手,请简洁回答问题"。其作用是规范大模型的响应风格、能力边界与交互规则。
@UserMessage 属于参数语义注解 ,标记参数为用户输入的对话消息,框架会自动将这个字符串封装为 UserMessage 对象,加入对话上下文。
除了对提示词和用户消息的管理,我们还加入了流式输出功能 。之前的自定义 agent 在对话时会一口气输出回答内容,这会带来用户体验问题:如果要输出的内容较多,AI 处理的时间较长,用户会长时间得不到反馈。而 TokenStream------LangChain4j 流式输出的标准类型,能够实现逐字打印的流式对话效果,优化用户体验。
2.对话交互实现
完成接口定义后,我们通过 chat() 方法完成模型初始化、服务组装、交互逻辑的全流程实现。
2.1AI 模型初始化与服务组装
java
// 构建流式对话模型
StreamingChatModel model = OpenAiStreamingChatModel.builder()
.baseUrl(URL)
.apiKey(API_KEY)
.temperature(0.7)
.build();
// 聊天记忆:滑动窗口模式,保留最近10条对话消息
ChatMemory memory = MessageWindowChatMemory.withMaxMessages(10);
// 创建AiServices服务对象,整合模型、记忆、接口契约
ChatAssistant assistant = AiServices.builder(ChatAssistant.class)
.streamingChatModel(model)
.chatMemory(memory)
.build();
我们使用框架提供的 OpenAiStreamingChatModel 适配 DeepSeek 大模型,通过构造器配置接口地址、身份密钥、生成温度这些核心参数,得到一个支持异步流式输出的模型调用对象。
为实现多轮连贯对话,我们配置滑动窗口对话记忆组件 ,MessageWindowChatMemory 会自动保留最近十条对话记录。最后是核心装配环节,将自定义接口契约、流式模型、对话记忆三大组件注入 AiServices 构造器,框架可以通过动态代理自动生成接口实现对象,替代了原生开发中数十行的模板代码。
2.2构建交互流程
java
// 初始化控制台输入工具,开启无限循环对话
Scanner scan = new Scanner(System.in);
while (true) {
String input = scan.nextLine().trim();
// 调用AI服务,获取流式响应对象
TokenStream tokenStream = assistant.streamChat(input);
// 定义流式输出的行为逻辑
System.out.println("AI:");
tokenStream.onPartialResponse(partial -> System.out.print(partial));
tokenStream.onCompleteResponse(response -> System.out.println("\n"));
tokenStream.onError(throwable -> throwable.printStackTrace());
// 启动异步流式请求
tokenStream.start();
}
我们用 while(true) 实现永久循环对话。输入内容后,调用 AI 服务对象发起流式对话请求,框架就会自动完成消息封装、上下文拼接等底层操作,返回流式响应对象。
随后我们配置流式响应回调规则 。由于onPartialResponse onCompleteResponse onError三个方法的参数全部是函数式接口(接口里有且只有 1 个抽象方法且可以加 @FunctionalInterface 标记), 所以可以用 Lambda ((参数) -> { 执行的代码 })简写而不用写繁琐的匿名内部类。
tokenStream.onPartialResponse(partial -> System.out.print(partial));此句注册 AI 实时返回文本片段 的处理规则。AI 流式输出过程中,每生成一小段内容 ,就会自动触发一次。partial是框架自动传递的参数,代表 AI 返回的文本片段。
tokenStream.onCompleteResponse(response -> System.out.println("\n"));此句注册AI 完整回答结束 的处理规则。AI 把所有内容全部生成完毕,没有新内容返回时触发。
tokenStream.onError(throwable -> throwable.printStackTrace());此句注册程序执行出错 的处理规则。会在错误发生时触发,打印错误日志。
最后调用 start() 方法启动异步非阻塞的 API 调用,框架在后台接收响应数据并按规则输出,实现流式对话效果。