当前位置:Java -> Java中的AI:使用Spring Boot和LangChain构建ChatGPT克隆

Java中的AI:使用Spring Boot和LangChain构建ChatGPT克隆

许多用于AI应用程序开发的库通常是用Python或JavaScript编写的。好消息是,其中有几个库也具有Java API。在本教程中,我将向您展示如何使用Spring Boot、LangChain和Hilla构建ChatGPT克隆。

本教程将涵盖简单的同步聊天完成和更高级的流式完成,以提供更好的用户体验。

已完成的源代码

您可以在我的GitHub存储库中找到示例的源代码。

依赖

  • Java 17+
  • Node 18+
  • OpenAI API密钥,存储在OPENAI_API_KEY环境变量中

创建Spring Boot和React项目,添加LangChain

首先,使用Hilla CLI创建一个新的Hilla项目。这将创建一个带有React前端的Spring Boot项目。

npx @hilla/cli init ai-assistant


在您的IDE中打开生成的项目。然后,将LangChain4j依赖添加到pom.xml文件中:

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j</artifactId>
    <version>0.22.0</version> <!-- TODO: use latest version -->
</dependency>


使用LangChain进行简单的OpenAI聊天完成并记录

我们将从简单的同步聊天完成开始使用LangChain4j。在这种情况下,我们希望调用OpenAI聊天完成API并获得单个响应。我们还希望跟踪最多1000个聊天历史记录标记。

com.example.application.service包中,创建一个ChatService.java类,内容如下:

@BrowserCallable
@AnonymousAllowed
public class ChatService {

    @Value("${openai.api.key}")
    private String OPENAI_API_KEY;

    private Assistant assistant;

    interface Assistant {
        String chat(String message);
    }

    @PostConstruct
    public void init() {
        var memory = TokenWindowChatMemory.withMaxTokens(1000, new OpenAiTokenizer("gpt-3.5-turbo"));
        assistant = AiServices.builder(Assistant.class)
                .chatLanguageModel(OpenAiChatModel.withApiKey(OPENAI_API_KEY))
                .chatMemory(memory)
                .build();
    }

    public String chat(String message) {
        return assistant.chat(message);
    }
}


  • @BrowserCallable使该类可在前端使用。
  • @AnonymousAllowed允许匿名用户调用方法。
  • @ValueOPENAI_API_KEY环境变量中注入OpenAI API密钥。
  • Assistant是我们将用于调用聊天API的接口。
  • init()用1000标记的内存和gpt-3.5-turbo模型初始化助手。
  • chat()是我们将从前端调用的方法。

通过在IDE中运行Application.java或使用默认的Maven目标启动应用:

mvn


这将为前端生成TypeScript类型和服务方法。

接下来,打开frontend文件夹中的App.tsx并使用以下内容更新它:

export default function App() {
  const [messages, setMessages] = useState<MessageListItem[]>([]);

  async function sendMessage(message: string) {
    setMessages((messages) => [
      ...messages,
      {
        text: message,
        userName: "You",
      },
    ]);

    const response = await ChatService.chat(message);
    setMessages((messages) => [
      ...messages,
      {
        text: response,
        userName: "Assistant",
      },
    ]);
  }

  return (
    <div className="p-m flex flex-col h-full box-border">
      <MessageList items={messages} className="flex-grow" />
      <MessageInput onSubmit={(e) => sendMessage(e.detail.value)} />
    </div>
  );
}


  • 我们使用来自Hilla UI组件库的MessageListMessageInput组件。
  • sendMessage()将消息添加到消息列表,并在ChatService类上调用chat()方法。当接收到响应时,它将被添加到消息列表中。

现在,您拥有一个使用OpenAI聊天API并跟踪聊天历史记录的工作聊天应用程序。它非常适合短消息,但对于长回答来说速度较慢。为了提高用户体验,我们可以使用流式完成,以显示响应的方式为其实时显示。

一个聊天界面,显示用户和AI机器人之间的两条消息

使用LangChain实现流式OpenAI聊天完成并记录

让我们更新ChatService类,以使用流式完成:

@BrowserCallable
@AnonymousAllowed
public class ChatService {

    @Value("${openai.api.key}")
    private String OPENAI_API_KEY;
    private Assistant assistant;

    interface Assistant {
        TokenStream chat(String message);
    }

    @PostConstruct
    public void init() {
        var memory = TokenWindowChatMemory.withMaxTokens(1000, new OpenAiTokenizer("gpt-3.5-turbo"));

        assistant = AiServices.builder(Assistant.class)
                .streamingChatLanguageModel(OpenAiStreamingChatModel.withApiKey(OPENAI_API_KEY))
                .chatMemory(memory)
                .build();
    }

    public Flux<String> chatStream(String message) {
        Sinks.Many<String> sink = Sinks.many().unicast().onBackpressureBuffer();

        assistant.chat(message)
                .onNext(sink::tryEmitNext)
                .onComplete(sink::tryEmitComplete)
                .onError(sink::tryEmitError)
                .start();

        return sink.asFlux();
    }
}


代码大部分与以前相同,但存在一些重要的区别:

  • Assistant现在返回TokenStream而不是String
  • init()使用streamingChatLanguageModel()而不是  chatLanguageModel()
  • chatStream()返回Flux<String>而不是String

使用以下内容更新App.tsx

export default function App() {
  const [messages, setMessages] = useState<MessageListItem[]>([]);

  function addMessage(message: MessageListItem) {
    setMessages((messages) => [...messages, message]);
  }

  function appendToLastMessage(chunk: string) {
    setMessages((messages) => {
      const lastMessage = messages[messages.length - 1];
      lastMessage.text += chunk;
      return [...messages.slice(0, -1), lastMessage];
    });
  }

  async function sendMessage(message: string) {
    addMessage({
      text: message,
      userName: "You",
    });

    let first = true;
    ChatService.chatStream(message).onNext((chunk) => {
      if (first && chunk) {
        addMessage({
          text: chunk,
          userName: "Assistant",
        });
        first = false;
      } else {
        appendToLastMessage(chunk);
      }
    });
  }

  return (
    <div className="p-m flex flex-col h-full box-border">
      <MessageList items={messages} className="flex-grow" />
      <MessageInput onSubmit={(e) => sendMessage(e.detail.value)} />
    </div>
  );
}


模板与以前相同,但是我们处理响应的方式不同。我们不会等待接收响应,而是开始监听响应的块。当收到第一个块时,我们将其添加为新消息。当接收到后续块时,我们将其附加到上一个消息。

重新运行应用程序,您会看到响应正在实时显示。

结论

如您所见,LangChain使得在Java和Spring Boot中构建LLM动力的AI应用程序变得非常简单。

基本设置完成后,您可以通过链接本文中提到的LangChain4j GitHub页面上的示例来扩展功能,链式操作,添加外部工具等。在Hilla文档中了解更多有关Hilla的信息。

推荐阅读: 百度面经(26)

本文链接: Java中的AI:使用Spring Boot和LangChain构建ChatGPT克隆