Java 中的 AI:使用 Spring Boot 和 LangChain 构建 ChatGPT 克隆
zhezhongyun 2025-06-15 20:33 30 浏览
学习在 Java 中使用 Spring Boot、LangChain 和 Hilla 构建 ChatGPT 克隆。涵盖同步聊天完成和高级流式处理完成。
许多用于 AI 应用程序开发的库主要是用 Python 或 JavaScript 编写的。好消息是,其中一些库也具有 Java API。在本教程中,我将向您展示如何使用 Spring Boot、LangChain 和 Hilla 构建 ChatGPT 克隆。
本教程将介绍简单的同步聊天完成和更高级的流式处理完成,以获得更好的用户体验。
已完成的源代码
您可以在我的 GitHub 存储库中找到该示例的源代码。
要求
- Java 17+
- Node 18+
- An OpenAI API key in an environment variableOPENAI_API_KEY
创建一个 Spring Boot 和 React 项目,添加 LangChain
首先,使用 Hilla CLI 创建一个新的 Hilla 项目。这将创建一个带有 React 前端的 Spring Boot 项目。
npx @hilla/cli init ai-assistant在 IDE 中打开生成的项目。然后,将 LangChain4j 依赖项添加到文件中:pom.xml
.XML
<dependency>br <groupId>dev.langchain4j</groupId>br <artifactId>langchain4j</artifactId>br <version>0.22.0</version> <!-- TODO: use latest version -->br</dependency>使用 LangChain 使用内存完成简单的 OpenAI 聊天
我们将通过简单的同步聊天完成开始探索 LangChain4j。在本例中,我们希望调用 OpenAI 聊天完成 API 并获得单个响应。我们还希望跟踪多达 1,000 个聊天记录的令牌。
在包中,创建一个包含以下内容的类:
com.example.application.serviceChatService.java
@BrowserCallablebr@AnonymousAllowedbrpublic class ChatService {brbr @Value("${openai.api.key}")br private String OPENAI_API_KEY;brbr private Assistant assistant;brbr interface Assistant {br String chat(String message);br }brbr @PostConstructbr public void init() {br var memory = TokenWindowChatMemory.withMaxTokens(1000, new OpenAiTokenizer("gpt-3.5-turbo"));br assistant = AiServices.builder(Assistant.class)br .chatLanguageModel(OpenAiChatModel.withApiKey(OPENAI_API_KEY))br .chatMemory(memory)br .build();br }brbr public String chat(String message) {br return assistant.chat(message);br }br}- @BrowserCallable使该类可用于前端。
- @AnonymousAllowed允许匿名用户调用这些方法。
- @Value从环境变量中注入 OpenAI API 密钥。OPENAI_API_KEY
- Assistant是我们将用于调用聊天 API 的接口。
- init()使用 1,000 个令牌的内存和模型初始化助手。gpt-3.5-turbo
- chat()是我们将从前端调用的方法。
通过在 IDE 中运行或使用默认的 Maven 目标来启动应用程序:Application.java
mvnThis will generate TypeScript types and service methods for the front end.
Next, open in the folder and update it with the following content:App.tsxfrontend
TypeScript-JSX
export default function App() {br const [messages, setMessages] = useState<MessageListItem[]>([]);brbr async function sendMessage(message: string) {br setMessages((messages) => [br ...messages,br {br text: message,br userName: "You",br },br ]);brbr const response = await ChatService.chat(message);br setMessages((messages) => [br ...messages,br {br text: response,br userName: "Assistant",br },br ]);br }brbr return (br <div className="p-m flex flex-col h-full box-border">br <MessageList items={messages} className="flex-grow" />br <MessageInput onSubmit={(e) => sendMessage(e.detail.value)} />br </div>br );br}- 我们使用 Hilla UI 组件库中的 和 组件。MessageListMessageInput
- sendMessage()将消息添加到消息列表中,并调用该类的方法。收到响应后,该响应将添加到消息列表中。chat()ChatService
您现在有一个使用 OpenAI 聊天 API 并跟踪聊天历史记录的工作聊天应用程序。它非常适合短消息,但对于长答案来说很慢。为了改善用户体验,我们可以改用流式处理完成,在收到响应时显示响应。
使用 LangChain 将 OpenAI 聊天完成与内存进行流式处理
让我们更新类以改用流式处理完成:ChatService
@BrowserCallablebr@AnonymousAllowedbrpublic class ChatService {brbr @Value("${openai.api.key}")br private String OPENAI_API_KEY;br private Assistant assistant;brbr interface Assistant {br TokenStream chat(String message);br }brbr @PostConstructbr public void init() {br var memory = TokenWindowChatMemory.withMaxTokens(1000, new OpenAiTokenizer("gpt-3.5-turbo"));brbr assistant = AiServices.builder(Assistant.class)br .streamingChatLanguageModel(OpenAiStreamingChatModel.withApiKey(OPENAI_API_KEY))br .chatMemory(memory)br .build();br }brbr public Flux<String> chatStream(String message) {br Sinks.Many<String> sink = Sinks.many().unicast().onBackpressureBuffer();brbr assistant.chat(message)br .onNext(sink::tryEmitNext)br .onComplete(sink::tryEmitComplete)br .onError(sink::tryEmitError)br .start();brbr return sink.asFlux();br }br}代码与以前基本相同,但有一些重要区别:
- Assistant现在返回 a 而不是 .TokenStreamString
- init()用代替 .streamingChatLanguageModel()chatLanguageModel()
- chatStream()返回 a 而不是 .Flux<String>String
使用以下内容进行更新:App.tsx
打字稿-JSX
export default function App() {br const [messages, setMessages] = useState<MessageListItem[]>([]);brbr function addMessage(message: MessageListItem) {br setMessages((messages) => [...messages, message]);br }brbr function appendToLastMessage(chunk: string) {br setMessages((messages) => {br const lastMessage = messages[messages.length - 1];br lastMessage.text += chunk;br return [...messages.slice(0, -1), lastMessage];br });br }brbr async function sendMessage(message: string) {br addMessage({br text: message,br userName: "You",br });brbr let first = true;br ChatService.chatStream(message).onNext((chunk) => {br if (first && chunk) {br addMessage({br text: chunk,br userName: "Assistant",br });br first = false;br } else {br appendToLastMessage(chunk);br }br });br }brbr return (br <div className="p-m flex flex-col h-full box-border">br <MessageList items={messages} className="flex-grow" />br <MessageInput onSubmit={(e) => sendMessage(e.detail.value)} />br </div>br );br}模板与以前相同,但我们处理响应的方式不同。我们不是等待收到响应,而是开始侦听响应的块。当收到第一个块时,我们将其添加为新消息。当收到后续块时,我们将它们附加到最后一条消息中。
重新运行应用程序,您应该会看到响应在收到时显示。
结论
正如你所看到的,LangChain使得在Java和Spring Boot中构建LLM驱动的AI应用程序变得容易。
完成基本设置后,您可以按照本文前面链接的 LangChain4j GitHub 页面上的示例,通过链接操作、添加外部工具等来扩展功能。在 Hilla 文档中了解有关 Hilla 的更多信息。
原文标题:AI in Java: Building a ChatGPT Clone With Spring Boot and LangChain
原文链接:
https://dzone.com/articles/ai-in-java-building-a-chatgpt-clone-with-spring-bo
作者:Marcus Hellberg
编译:LCR
相关推荐
- Python入门学习记录之一:变量_python怎么用变量
-
写这个,主要是对自己学习python知识的一个总结,也是加深自己的印象。变量(英文:variable),也叫标识符。在python中,变量的命名规则有以下三点:>变量名只能包含字母、数字和下划线...
- python变量命名规则——来自小白的总结
-
python是一个动态编译类编程语言,所以程序在运行前不需要如C语言的先行编译动作,因此也只有在程序运行过程中才能发现程序的问题。基于此,python的变量就有一定的命名规范。python作为当前热门...
- Python入门学习教程:第 2 章 变量与数据类型
-
2.1什么是变量?在编程中,变量就像一个存放数据的容器,它可以存储各种信息,并且这些信息可以被读取和修改。想象一下,变量就如同我们生活中的盒子,你可以把东西放进去,也可以随时拿出来看看,甚至可以换成...
- 绘制学术论文中的“三线表”具体指导
-
在科研过程中,大家用到最多的可能就是“三线表”。“三线表”,一般主要由三条横线构成,当然在变量名栏里也可以拆分单元格,出现更多的线。更重要的是,“三线表”也是一种数据记录规范,以“三线表”形式记录的数...
- Python基础语法知识--变量和数据类型
-
学习Python中的变量和数据类型至关重要,因为它们构成了Python编程的基石。以下是帮助您了解Python中的变量和数据类型的分步指南:1.变量:变量在Python中用于存储数据值。它们充...
- 一文搞懂 Python 中的所有标点符号
-
反引号`无任何作用。传说Python3中它被移除是因为和单引号字符'太相似。波浪号~(按位取反符号)~被称为取反或补码运算符。它放在我们想要取反的对象前面。如果放在一个整数n...
- Python变量类型和运算符_python中变量的含义
-
别再被小名词坑哭了:Python新手常犯的那些隐蔽错误,我用同事的真实bug拆给你看我记得有一次和同事张姐一起追查一个看似随机崩溃的脚本,最后发现罪魁祸首竟然是她把变量命名成了list。说实话...
- 从零开始:深入剖析 Spring Boot3 中配置文件的加载顺序
-
在当今的互联网软件开发领域,SpringBoot无疑是最为热门和广泛应用的框架之一。它以其强大的功能、便捷的开发体验,极大地提升了开发效率,成为众多开发者构建Web应用程序的首选。而在Spr...
- Python中下划线 ‘_’ 的用法,你知道几种
-
Python中下划线()是一个有特殊含义和用途的符号,它可以用来表示以下几种情况:1在解释器中,下划线(_)表示上一个表达式的值,可以用来进行快速计算或测试。例如:>>>2+...
- 解锁Shell编程:变量_shell $变量
-
引言:开启Shell编程大门Shell作为用户与Linux内核之间的桥梁,为我们提供了强大的命令行交互方式。它不仅能执行简单的文件操作、进程管理,还能通过编写脚本实现复杂的自动化任务。无论是...
- 一文学会Python的变量命名规则!_python的变量命名有哪些要求
-
目录1.变量的命名原则3.内置函数尽量不要做变量4.删除变量和垃圾回收机制5.结语1.变量的命名原则①由英文字母、_(下划线)、或中文开头②变量名称只能由英文字母、数字、下画线或中文字所组成。③英文字...
- 更可靠的Rust-语法篇-区分语句/表达式,略览if/loop/while/for
-
src/main.rs://函数定义fnadd(a:i32,b:i32)->i32{a+b//末尾表达式}fnmain(){leta:i3...
- C++第五课:变量的命名规则_c++中变量的命名规则
-
变量的命名不是想怎么起就怎么起的,而是有一套固定的规则的。具体规则:1.名字要合法:变量名必须是由字母、数字或下划线组成。例如:a,a1,a_1。2.开头不能是数字。例如:可以a1,但不能起1a。3....
- Rust编程-核心篇-不安全编程_rust安全性
-
Unsafe的必要性Rust的所有权系统和类型系统为我们提供了强大的安全保障,但在某些情况下,我们需要突破这些限制来:与C代码交互实现底层系统编程优化性能关键代码实现某些编译器无法验证的安全操作Rus...
- 探秘 Python 内存管理:背后的神奇机制
-
在编程的世界里,内存管理就如同幕后的精密操控者,确保程序的高效运行。Python作为一种广泛使用的编程语言,其内存管理机制既巧妙又复杂,为开发者们提供了便利的同时,也展现了强大的底层控制能力。一、P...
- 一周热门
- 最近发表
- 标签列表
-
- HTML 教程 (33)
- HTML 简介 (35)
- HTML 实例/测验 (32)
- HTML 测验 (32)
- JavaScript 和 HTML DOM 参考手册 (32)
- HTML 拓展阅读 (30)
- HTML文本框样式 (31)
- HTML滚动条样式 (34)
- HTML5 浏览器支持 (33)
- HTML5 新元素 (33)
- HTML5 WebSocket (30)
- HTML5 代码规范 (32)
- HTML5 标签 (717)
- HTML5 标签 (已废弃) (75)
- HTML5电子书 (32)
- HTML5开发工具 (34)
- HTML5小游戏源码 (34)
- HTML5模板下载 (30)
- HTTP 状态消息 (33)
- HTTP 方法:GET 对比 POST (33)
- 键盘快捷键 (35)
- 标签 (226)
- opacity 属性 (32)
- transition 属性 (33)
- 1-1. 变量声明 (31)
