- 示例工程
【免费下载链接】langchain4j-examples
导读
本指南基于langchain4j-examples仓库中的 javafx-example/README.md,完整讲解如何用 JavaFX 构建一个原生桌面聊天界面,并通过 LangChain4J 的流式(Streaming)能力把 OpenAI 的回复逐字实时渲染到界面上。文章从环境准备、项目结构到核心源码逐层拆解:你将掌握 JavaFX 属性绑定如何驱动流式 Token 渲染、LangChain4JTokenStream的三段式回调模型、AiServices+ 窗口消息记忆的配置方式,最终能以此为起点搭建自己的桌面端 AI 助手。
一、项目定位:为什么要用 JavaFX 做 LangChain4J 桌面端
javafx-example是一个独立的 Maven 模块(见 javafx-example/pom.xml),其目标非常聚焦:演示如何用 JavaFX 用户界面把 OpenAI 流式返回的答案,一边接收一边可视化。核心诉求有三点:
- 答案在收到过程中就实时渲染到右侧文本框;
- 同一答案同步呈现在左侧历史表格(Table)中;
- 每次提问的历史记录(时间戳、问题、答案、完成状态)都保留在表格里,且模型通过聊天记忆维持多轮对话上下文。
从源码结构看(javafx-example/src/main/java/),该模块把 UI、流式处理、业务服务、数据模型分层得很清晰,共 7 个 Java 类:Launcher、ChatApp、AnswerService、Assistant、CustomStreamingResponseHandler、SearchAction、ApiKeys。这种「UI 层只负责展示、服务层只负责编排模型、模型类只负责声明接口」的组织方式,是后续构建任何桌面 AI 应用都可以复用的骨架。
二、环境准备与运行方式
运行该示例的前提是使用自带 JavaFX 的 JDK。README 给出的推荐方式是使用 SDKMAN 安装 Azul Zulu 的 JavaFX 捆绑版 JDK,然后直接通过 Maven JavaFX 插件启动:
$ sdk install java 21.fx-zulu $ mvn javafx:run围绕这两条命令,结合 pom.xml 补充几个可验证的细节:
- Java 编译级别为 17:
maven.compiler.source/target均为17,而运行示例用 21 版捆绑 JDK,说明代码可在 Java 17+ 上编译、用含 JavaFX 的 JDK 运行。 - JavaFX 依赖:仅引入
javafx-controls(版本21.0.1),因为界面只用到了控件层(Application、Scene、TableView、TextArea、TextField、Button等),无需javafx-fxml。 - LangChain4J 依赖:
langchain4j(核心库)与langchain4j-open-ai(OpenAI 模型实现)均为1.17.0,流式能力由OpenAiStreamingChatModel提供。 - 日志依赖:
log4j-api+log4j-slf4j2-impl(2.22.1),用于记录启动、请求、流式完成等过程日志。 - 启动入口:
javafx-maven-plugin(版本0.0.8)配置了<mainClass>Launcher</mainClass>,Launcher只是一个转发入口,main直接调用ChatApp.main(args)启动 JavaFX 应用。
注意:模型调用需要有效的 OpenAI API Key。源码 ApiKeys.java 中默认使用
"demo"占位 Key,注释说明可前往 OpenAI 平台获取自己的 Key 后替换OPENAI_API_KEY常量。
三、整体架构:一条提问在 UI 与模型之间的流转链路
应用虽小,却构成了一条完整的「用户输入 → 线程化服务 → 流式模型 → JavaFX 主线程更新 UI」链路,可以用下面这张运行时截图直观对照:
(截图来自 javafx-example/screenshot.png,界面包含标题「JavaFX Chat Langchain4J Demo」、输入区「What is your question?」、带 Timestamp/Question/Answer/Finished 四列的表格,以及右侧完整回答文本框。)
链路各环节对应源码如下:
- UI 事件触发:ChatApp.java 中,
TextField的setOnAction(回车)与Search按钮的setOnAction都指向doSearch(input.getText()),空输入会被直接忽略。 - 新建会话记录并绑定:
doSearch创建一个新的SearchAction(question),加入ObservableList<SearchAction> data,同时执行lastAnswer.textProperty().bind(searchAction.getAnswerProperty())—— 右侧文本框直接与这条记录的答案属性绑定。 - 后台线程执行:
new Thread(() -> docsAnswerService.ask(searchAction)).start()把模型调用放到独立线程,避免阻塞 JavaFX 的 UI 线程。 - 流式回调:
AnswerService调用assistant.chat(...)得到TokenStream,分别注册onPartialResponse、onCompleteResponse、onError回调。 - 回主线程更新:CustomStreamingResponseHandler.java 中每个回调都通过
Platform.runLater(...)把 UI 更新切回 JavaFX Application Thread,这是 JavaFX 并发编程的硬性要求——模型线程不能直接操作 UI。
这个设计同时回答了 README 里强调的「如何用 JavaFX bindings 处理流式答案」:把模型返回的 Token 追加写入 JavaFX 属性,再由属性绑定自动推送到界面。
四、核心代码逐层拆解
4.1 入口与界面装配:ChatApp
ChatApp.java 是整个应用的 UI 核心,几个值得注意的装配细节:
- 全局单例数据源:
FXCollections.observableArrayList()的data作为TableView的 items,表格各列通过setCellValueFactory绑定SearchAction的 JavaFX 属性(时间戳、问题、答案、完成状态四列分别设了 250/250/300/50 的最小宽度)。 - 启动即初始化:
start()中先插入一条new SearchAction("Application started", true),再插入"Initializing search engine, please stand by..."的初始化条目,并用lastAnswer.textProperty().bind(initAction.getAnswerProperty())让右侧文本框显示初始化进度,随后在新线程中执行docsAnswerService.init(initAction)。 - 布局:
VBox外层 15px 内边距,依次放入标题 Label(25px 加粗)、输入区HBox(输入框 500px 宽 + Search 按钮)、以及new HBox(table, lastAnswer)构成的「左表右文」双栏布局;lastAnswer.setWrapText(true)保证长答案自动换行。
4.2 流式模型与记忆装配:AnswerService
AnswerService.java 负责模型生命周期管理,是 README 中「模型保持对话记忆」这一承诺的直接实现:
StreamingChatModel model = OpenAiStreamingChatModel.builder() .apiKey(ApiKeys.OPENAI_API_KEY) .modelName(GPT_4_O_MINI) // 静态导入自 OpenAiChatModelName .build(); assistant = AiServices.builder(Assistant.class) .streamingChatModel(model) .chatMemory(MessageWindowChatMemory.withMaxMessages(10)) // 最多保留 10 条消息 .build();关键点:
- 流式模型:使用
OpenAiStreamingChatModel,模型名取自OpenAiChatModelName.GPT_4_O_MINI; - AI 服务:通过
AiServices.builder(Assistant.class)把流式模型和聊天记忆注入到Assistant接口,得到可调用的代理对象; - 窗口记忆:
MessageWindowChatMemory.withMaxMessages(10)说明记忆采用「消息窗口」策略,只保留最近 10 条消息,既维持多轮上下文又控制 Token 开销; - 初始化流程:
init会先appendAnswer("Initiating..."),模型构建完成后追加"Done"并调用setFinished(),让表格中该行「Finished」列变为true。
4.3 流式回调的三种形态:CustomStreamingResponseHandler
CustomStreamingResponseHandler.java 演示了 LangChain4JTokenStream最核心的三段式回调:
| 回调 | 触发时机 | UI 行为 |
|---|---|---|
onNext(String token) | 收到一个流式分片 Token | Platform.runLater内action.appendAnswer(token),答案逐字增长 |
onComplete(ChatResponse response) | 整个响应结束 | 打印完整响应日志与答案长度,action.setFinished()置完成标志 |
onError(Throwable error) | 出错 | 日志记录错误,答案尾部追加"\nSomething went wrong: " + error.getMessage(),并置完成标志 |
这里有一个值得学习的健壮性细节:onError并不会让界面卡在「加载中」,而是把错误信息作为答案的一部分追加显示,同时把Finished置为true,保证表格状态始终一致。
4.4 AI 服务接口:Assistant
Assistant.java 是 LangChain4J 的 AI Service 声明式接口,只有一行方法签名:
public interface Assistant { TokenStream chat(String message); }返回类型TokenStream正是流式能力的入口——在 AnswerService.java 的ask方法中,对它链式注册回调后调用.start()才真正发起请求:
assistant.chat(action.getQuestion()) .onPartialResponse(responseHandler::onNext) .onCompleteResponse(responseHandler::onComplete) .onError(responseHandler::onError) .start();这种「接口声明 +AiServices动态代理」的编程模型,把记忆、模型、流式处理全部收敛到声明层,业务代码只关心回调即可。
4.5 数据模型:SearchAction 与 JavaFX 属性绑定
SearchAction.java 是整个「绑定驱动流式渲染」方案的基石。它不依赖javafx.beans之外的任何模型代码,用四个 JavaFX 属性承载一条完整会话记录:
StringProperty timestamp:LocalDateTime.now().toString()生成提问时间戳;StringProperty question:用户问题;StringProperty answer:答案,appendAnswer(String token)通过this.answer.set(this.answer.getValue() + token)实现 Token 追加;BooleanProperty finished:是否已完成。
正因为answer是StringProperty,ChatApp里的lastAnswer.textProperty().bind(searchAction.getAnswerProperty())才能做到「模型线程每追加一个 Token,右侧文本框和表格单元格就同步刷新一次」——这正是 README 强调的「用 JavaFX bindings 处理答案」的底层机制。
五、实战要点总结与扩展建议
5.1 值得直接复用的四个模式
- 后台线程 +
Platform.runLater:凡是模型/IO 操作一律放非 UI 线程,凡是要更新界面一律经Platform.runLater切回 JavaFX 主线程; - 属性绑定替代手动刷新:把流式答案写入
StringProperty,让TextArea和TableView通过绑定自动感知变化,无需手动重绘; AiServices+TokenStream声明式流式编程:接口返回TokenStream,链式注册onPartialResponse/onCompleteResponse/onError后.start();- 窗口记忆保持多轮语境:
MessageWindowChatMemory.withMaxMessages(10)是控制上下文长度最简单直接的方式。
5.2 改造为自有应用的切入点
README 明确指出该项目「可以作为构建自己 JavaFX 版 LangChain4J 实现的起点」。从当前源码出发,可以清晰看到哪些地方适合替换扩展:
- 更换模型:
AnswerService.initChat中把OpenAiStreamingChatModel换成其他StreamingChatModel实现(如 Azure OpenAI、本地 Ollama 等,可参考仓库中 ollama-examples/ 等其他模块)即可,Assistant接口与 UI 层无需改动; - 扩展服务能力:
Assistant接口可以增加带@Tool的方法或引入工具类,把桌面应用升级为可调用外部工具的智能助手; - 调整记忆策略:将
MessageWindowChatMemory换成持久化记忆,可参考仓库 other-examples/ 中的ServiceWithPersistentMemoryExamples相关实现; - 美化界面:
ChatApp中所有样式都是内联 CSS(如-fx-padding: 15px、-fx-font-size: 25px),可直接升级为 JavaFX CSS 样式表,或引入 FXML 做更复杂的布局。
5.3 运行验证路径
在javafx-example目录下执行mvn javafx:run后,可以对照 README 与截图验证三条行为:输入问题并回车或点击 Search 后,右侧文本框会逐字出现答案;左侧表格会新增一行记录,Finished列在流式结束后变为true;连续提问时,模型能结合之前的历史消息作答(由 10 条消息窗口记忆保证)。日志中可观察到"Complete response: ..."与"Answer is complete for '...', size: N"的输出,用于确认流式生命周期完整走完。
相关文件索引
- javafx-example/README.md — 本文依据的官方说明文档
- javafx-example/pom.xml — 依赖与启动插件配置
- javafx-example/src/main/java/ChatApp.java — JavaFX 界面装配与事件处理
- javafx-example/src/main/java/AnswerService.java — 流式模型与记忆装配
- javafx-example/src/main/java/Assistant.java — AI Service 声明接口
- javafx-example/src/main/java/CustomStreamingResponseHandler.java — 流式回调处理
- javafx-example/src/main/java/SearchAction.java — JavaFX 属性数据模型
- javafx-example/screenshot.png — 应用运行界面截图
- 示例工程
【免费下载链接】langchain4j-examples
相关推荐
Netty+JavaFx 实战:搭建仿桌面版微信聊天应用(CodeGuide IM 项目完整技术指南)
Netty+JavaFx 实战:搭建仿桌面版微信聊天应用(CodeGuide IM 项目完整技术指南) 本文基于 CodeGuide 仓库中 《Netty+Ja
文档教程后端Wifi-Hacking终极指南:如何使用内置Kali工具破解无线网络
Wifi Hacking终极指南:如何使用内置Kali工具破解无线网络 在当今数字化时代,无线网络安全已成为重要议题。本指南将详细介绍如何使用 Wifi Hac
网络安全渗透测试qrcode.vue:终极Vue二维码组件指南 - 同时支持Vue 2和Vue 3的完整解决方案
qrcode.vue:终极Vue二维码组件指南 同时支持Vue 2和Vue 3的完整解决方案 qrcode.vue是一款功能强大的Vue二维码组件,能够帮助开发
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考