Ohhnews

分类导航

$ cd ..
InfoQ Java原文

将AI融入Java应用

#人工智能#java开发#大型语言模型#langchain4j#quarkus

主要收获

  • 作为一名Java开发者,无需学习其他语言即可开始编写融入AI的应用程序。
  • Java开发者可以使用开源项目LangChain4j来管理Java应用程序与大型语言模型(LLM)之间的交互,例如存储和管理聊天记忆,以保持对LLM的请求高效、专注且成本更低。
  • 将LangChain4j与Quarkus结合使用,可以简化与LLM的交互,并且您还可以受益于Quarkus通过开发模式、开发UI和便捷的可观测性工具集成带来的开发乐趣。
  • Java经过实战检验,拥有强大、企业级的生态系统(例如性能和安全性),这将帮助您成功编写和运行生产就绪的Java AI赋能应用程序。
  • 立即开始学习使用LangChain4j和Quarkus编写Java AI赋能应用程序的基本概念。通过创建一个简单的聊天机器人应用程序亲身体验,并在快速发展的AI领域保持领先。

人工智能(AI)正变得日益普及。作为一名企业级Java开发者,您可能想知道AI能为您的业务应用程序带来什么价值,Java提供了哪些工具可以轻松实现这一点,以及您可能需要学习哪些技能和知识。在本文中,我们将为您提供所需的基本知识和技能,帮助您开始探索AI的能力,以构建智能且响应迅速的企业级Java应用程序。

在本文中,当我们谈论AI时,我们指的是从大型语言模型(LLM)获取基于Java应用程序发送给LLM的请求的响应。在我们的文章示例中,我们创建了一个简单的聊天机器人,客户可以向其咨询行星旅游目的地推荐,然后用于预订飞船前往这些地方。我们演示了如何使用LangChain4j与Quarkus等Java框架高效地与LLM交互,并为最终用户创建满意的应用程序。

Hello (AI) World:让LLM响应提示

我们的飞船租赁应用程序的第一个版本将构建一个使用自然语言与客户交互的聊天机器人。它应该能够回答客户关于他们希望访问太阳系中任何行星的问题。有关完整的应用程序代码,请参阅GitHub仓库中的spaceship rental step-01目录

[LOADING...]

聊天机器人将客户的问题发送给应用程序,应用程序与LLM交互以帮助处理自然语言问题并回复客户。

对于应用程序中与AI相关的部分,我们只创建了两个文件:

  • 一个AI服务,CustomerSupportAgent.java,它构建了一个提示,向LLM提供我们太阳系行星的信息,并指示LLM回答客户的问题。
  • 一个WebSocket端点,ChatWebSocket.java,它接收来自聊天机器人的用户消息。

AI服务是提供抽象层的Java接口。使用LangChain4j时,这些接口使LLM交互变得更容易。AI服务是一个集成点,因此在实际应用程序中,您需要考虑与LLM连接和交互的安全性、可观测性和容错性。除了处理LLM连接详情(单独存储在application.properties配置文件中)之外,AI服务还构建提示并管理其发送给LLM的请求的聊天记忆。

提示由AI服务中的两部分信息构建:系统消息和用户消息。系统消息通常由开发者用于向LLM提供上下文信息和处理请求的指令,通常包括您希望LLM在生成响应时遵循的示例。用户消息则向LLM提供应用程序用户的请求。

CustomerSupportAgent接口在应用程序中注册为AI服务。它定义了用于构建提示的消息,并将提示发送给LLM:

$ java
@SessionScoped
@RegisterAiService
public interface CustomerSupportAgent {
    @SystemMessage("""
        You are a friendly, but terse customer service agent for Rocket's
        Cosmic Cruisers, a spaceship rental shop.
        You answer questions from potential guests about the different planets
        they can visit.
        If asked about the planets, only use info from the fact sheet below.
        """
        + PlanetInfo.PLANET_FACT_SHEET)
    String chat(String userMessage);
}

让我们看看这段代码在做什么。@SessionScoped注解在Web服务连接期间维护会话,并在对话期间维护聊天记忆。@RegisterAIService注解将接口注册为AI服务。LangChain4j会自动实现该接口。@SystemMessage注解告诉LLM在响应提示时如何表现。

当最终用户在聊天机器人中输入消息时,WebSocket端点会将消息传递给AI服务中的chat()方法。我们的AI服务接口中没有指定@UserMessage注解,因此AI服务实现会自动创建一个带有chat()方法参数值(本例中为userMessage参数)的用户消息。AI服务将用户消息添加到系统消息中,以构建发送给LLM的提示,然后将LLM的响应显示在聊天机器人界面中。

请注意,为了可读性,行星信息已放置在单独的PlanetInfo类中。或者,您可以将行星信息直接放置在系统消息中。

ChatWebSocket类定义了应用程序聊天机器人UI交互的WebSocket端点:

$ java
@WebSocket(path = "/chat/batch")
public class ChatWebSocket {
 
    private final CustomerSupportAgent customerSupportAgent;
 
    public ChatWebSocket(CustomerSupportAgent customerSupportAgent) {
        this.customerSupportAgent = customerSupportAgent;
    }
 
    @OnOpen
    public String onOpen() {
        return "Welcome to Rocket's Cosmic Cruisers! How can I help you today?";
    }
 
    @OnTextMessage
    public String onTextMessage(String message) {
        return customerSupportAgent.chat(message);
    }
}

CustomerSupportAgent接口使用构造函数注入来自动提供对AI服务的引用。当最终用户在聊天机器人中输入消息时,onTextMessage()方法将消息传递给AI服务chat()方法。

例如,如果用户问:“如果我想看火山,哪个行星值得一去?”,应用程序会给出推荐,并解释为什么火山爱好者可能会喜欢去那里:

[LOADING...]

飞船租赁应用程序聊天机器人。

提供记忆的错觉

当您继续与聊天机器人对话时,它可能看起来像是意识到了之前交换过的消息,即对话的上下文。当您与另一个人交谈时,您会想当然地认为他们记得您(和他们)上次说了什么。然而,对LLM的请求是无状态的,因此每个响应都完全基于请求提示中包含的信息生成。

为了在对话中保持上下文,AI服务通过LangChain4j使用聊天记忆来存储先前的用户消息和聊天机器人的响应。默认情况下,Quarkus LangChain4j扩展将聊天存储在内存中,AI服务根据需要管理聊天记忆(例如,通过丢弃或总结最旧的消息)以保持在内存限制内。LangChain4j本身会要求您首先配置一个记忆提供程序,但使用Quarkus LangChain4j扩展时则不需要。这为最终用户提供了实用的记忆错觉,并改善了用户体验,使他们能够输入后续消息而无需重复之前说过的一切。通过流式传输LLM的响应,也可以改善用户聊天机器人体验。

流式传输响应以提供更灵敏的用户体验

您可能会注意到聊天消息窗口的响应需要时间生成,然后一次性全部出现。为了提高聊天机器人的感知响应速度,我们可以修改代码,使其在生成每个响应令牌时立即返回。这种方法称为流式传输,它允许用户在整个响应可用之前开始阅读部分响应。有关完整的应用程序代码,请参阅GitHub spaceship rental step-02目录。

更改我们的应用程序以流式传输聊天机器人响应非常容易。首先,我们将更新**CustomerSupportAgent**接口,添加一个返回SmallRye Mutiny **Multi<String>**接口实例的方法:

$ java
@SessionScoped
@RegisterAiService
@SystemMessage("""
    You are a friendly, but terse customer service agent for Rocket's Cosmic Cruisers, a spaceship rental shop. You answer questions from potential guests about the different planets they can visit. If asked about the planets, only use info from the fact sheet below.
    """
    + PlanetInfo.PLANET_FACT_SHEET)
public interface CustomerSupportAgent {
    String chat(String userMessage);

    Multi<String> streamChat(String userMessage);
}

@SystemMessage注解移动到接口意味着不必将该注解添加到接口中的每个方法中。streamChat()方法一次返回LLM响应的一个令牌到聊天窗口(而不是等待显示完整的响应)。

我们还需要从WebSocket端点调用新的streamChat()方法。为了保留批处理和流式传输功能,我们创建了一个新的ChatWebSocketStream类,它暴露了/chat/stream WebSocket端点:

$ java
@WebSocket(path = "/chat/stream")
public class ChatWebSocketStream {

    private final CustomerSupportAgent customerSupportAgent;

    public ChatWebSocketStream(CustomerSupportAgent customerSupportAgent) {
        this.customerSupportAgent = customerSupportAgent;
    }

    @OnOpen
    public String onOpen() {
        return "Welcome to Rocket's Cosmic Cruisers! How can I help you today?";
    }

    @OnTextMessage
    public Multi<String> onStreamingTextMessage(String message) {
        return customerSupportAgent.streamChat(message);
    }
}

customerSupportAgent.streamChat()调用会触发AI服务将用户消息发送到LLM。

在对UI进行一些微调后,我们现在可以在聊天机器人中开启和关闭流式传输:

[LOADING...]

启用新流式传输选项的应用程序。

启用流式传输后,LLM生成的每个令牌(每个词,或词的一部分)都会立即返回到聊天界面。## 从非结构化数据生成结构化输出 到目前为止,LLM 的输出都是面向应用程序的最终用户。但如果我们希望 LLM 的输出直接被应用程序使用,该怎么办?当 LLM 响应请求时,负责协调与 LLM 交互的 AI 服务可以返回结构化输出,这些格式比字符串更具结构性,例如 POJO、POJO 列表和原生类型。

返回结构化输出显著简化了 LLM 输出与 Java 代码的集成,因为它强制要求应用程序从 AI 服务接收到的输出映射到 Java 对象的预定义模式。让我们通过帮助最终用户从我们的舰队中选择一艘符合其需求的飞船来演示结构化输出的实用性。有关完整的应用程序代码,请参阅 GitHub spaceship rental step-03 目录。

我们首先创建一个简单的 Spaceship 记录,用于存储舰队中每艘飞船的信息:

$ java
record Spaceship(String name, int maxPassengers, boolean hasCargoBay, List<String> allowedDestinations) { 
} 

同样,为了表示用户对我们舰队中飞船的查询,我们创建了一个 SpaceshipQuery 记录,它基于用户在聊天中提供的信息:

$ java
@Description("A request for a compatible spaceship")
public record SpaceshipQuery(int passengers, boolean hasCargo, List<String> destinations) { 
}

Fleet 类填充了几个 Spaceship 对象,并提供了一种筛选掉与用户不匹配的飞船的方法。

接下来,我们更新 CustomerSupportAgent 接口,使其能够接收用户的消息(非结构化文本)并创建 SpaceshipQuery 记录形式的结构化输出。为了实现这一壮举,我们只需将 AI 服务中新的 extractSpaceshipAttributes() 方法的返回类型设置为 SpaceshipQuery

$ java
SpaceshipQuery extractSpaceshipAttributes(String userMessage);

在底层,LangChain4j 自动生成一个包含所需响应的 JSON 模式表示的 LLM 请求。LangChain4j 反序列化 LLM 返回的 JSON 格式响应,并使用它返回一个 SpaceshipQuery 记录,正如所请求的那样。

我们还需要知道用户的输入是关于我们的飞船之一,还是关于其他主题。这种筛选通过一个更简单的结构化输出请求来完成,该请求返回一个布尔值:

$ java
@SystemMessage("""
You are a friendly, but terse customer service agent for Rocket's Cosmic Cruisers, a spaceship rental shop. 
Respond with 'true' if the user message is regarding spaceships in our rental fleet, and 'false' otherwise.
""")
boolean isSpaceshipQuery(String userMessage);

我们对 CustomerSupportAgent 接口的最后一次添加使代理能够根据我们的舰队和用户的请求提供飞船建议,包括流式和非流式:

$ java
@UserMessage("""
        Given the user's query regarding available spaceships for a trip {message}, provide a well-formed, clear and concise response listing our applicable spaceships.
        Only use the spaceship fleet data from {compatibleSpaceships} for your response.
        """)
    String suggestSpaceships(String message, List<Spaceship> compatibleSpaceships);
 
@UserMessage("""
        Given the user's query regarding available spaceships for a trip {message}, provide a well-formed, clear and concise response listing our applicable spaceships.
        Only use the spaceship fleet data from {compatibleSpaceships} for your response.
        """)
Multi<String> streamSuggestSpaceships(String message, List<Spaceship> compatibleSpaceships);
}

我们的最后一步是更新 ChatWebSocketChatWebSocketStream 类,首先检查用户的查询是否与我们的舰队中的飞船有关。如果是,客户支持代理通过从用户消息中提取信息来创建一个 SpaceshipQuery 记录,然后从舰队中推荐与用户请求兼容的飞船。ChatWebSocket 和 ChatWebSocketStream 类的更新代码相似,因此这里只显示 ChatWebSocket 类:

$ java
@OnTextMessage
public String onTextMessage(String message) {
    boolean isSpaceshipQuery = customerSupportAgent.isSpaceshipQuery(message);

    if (isSpaceshipQuery) {
        SpaceshipQuery userQuery = customerSupportAgent.extractSpaceshipAttributes(message);

        List<Spaceship> spaceships = Fleet.findCompatibleSpaceships(userQuery);
        return customerSupportAgent.suggestSpaceships(message, spaceships);
    } else 
        return customerSupportAgent.chat(message);
}

通过这些更新,客户支持代理已准备好使用结构化输出为用户提供飞船建议:

[LOADING...]

应用程序根据结构化输出为用户提供飞船建议。

至此,我们已经完成了一个注入了 AI 的 Java 聊天机器人应用程序,它提供行星旅游推荐和飞船租赁服务。

要继续学习,请结合 Quarkus with LangChain4j 文档 实验我们示例应用程序的完整代码

关于这些 AI 概念的更多信息

在本文中,我们讨论了各种 AI 概念。如果您想了解更多,这里有一个快速解释。

大型语言模型 (LLM)

当我们在本文中谈论 AI 时,我们通常指的是从大型语言模型获取响应。LLM 是机器学习模型,经过训练可根据输入序列生成输出序列(通常是文本输入和输出,但一些多模态 LLM 可以处理图像、音频或视频)。LLM 可以执行各种任务,例如总结文档、在语言之间进行翻译、事实提取、编写代码等。这种从输入创建新内容的任务被称为生成式 AI (Generative AI) 或 GenAI。您可以根据需要将此类功能注入到您的应用程序中。

向 LLM 发送请求:提示、聊天记忆和令牌

您向 LLM 请求信息的方式不仅会影响您从 LLM 获得的响应,还会影响最终用户的体验和应用程序的运行成本。

提示

向 LLM 发送请求,无论是通过应用程序代码还是作为聊天界面中的最终用户,都涉及编写提示。提示是 LLM 响应的信息(通常是文本,但不总是)。如果您将与 LLM 的通信想象成与另一个人交流,那么您提出请求的方式对于确保另一个人(或本例中的 LLM)理解您想要了解的内容非常重要。例如,确保在询问特定信息之前提供请求的上下文,并且不要提供大量不相关的信息来混淆听者。

聊天记忆

与您与另一个人交谈不同,LLM 是无状态的,不记住之前的请求,因此您需要 LLM 考虑的所有信息都必须包含在您的请求中:提示、任何先前的请求和响应(聊天记忆),以及您提供的任何工具来帮助 LLM 响应。然而,在提示中向 LLM 提供过多的信息可能会使请求变得复杂。它也可能代价高昂。

令牌

LLM 将您提示中的单词转换为一系列令牌。大多数托管 LLM 根据请求和响应中的令牌数量收取使用费。一个令牌可以代表一个完整的单词或一个单词的一部分。例如,单词“unbelievable”通常被分成多个令牌:“un”、“bel”和“ievable”。您在请求中包含的令牌越多,尤其是在包含所有聊天记忆时,运行应用程序的潜在成本就越高。

在请求中包含所有聊天记忆可能会使请求既昂贵又不够清晰。对 LLM 的请求长度有限,因此管理聊天记忆以及请求中包含多少信息非常重要。这可以通过您使用的 Java 框架(例如本文示例应用程序中使用的 LangChain4j 和 Quarkus)得到很大帮助。

LangChain4j 和 Quarkus 框架

LangChain4j 是一个开源 Java 框架,用于管理 Java 应用程序与 LLM 之间的交互。例如,LangChain4j 通过 AI 服务的概念,存储并帮助您管理聊天记忆,从而使您对 LLM 的请求保持高效、专注且成本更低。

Quarkus 是一个现代的、云原生的开源 Java 框架,针对开发人员生产力、在容器化环境中运行以及快速启动和低内存使用进行了优化。LangChain4j 对 Quarkus 的扩展 简化了在注入 AI 的 Java 应用程序中连接和与 LLM 交互的配置。

LangChain4j 项目可以与其他 Java 应用程序框架一起使用,包括 Open LibertySpring BootMicronautMicroProfileJakarta EE 也在与 LangChain4j 合作,为开发 AI 应用程序提供一个基于开放标准的编程模型

示例应用程序

您可以在 GitHub 上找到我们在本文中演示的完整示例应用程序。该应用程序用 Java 编写,并在 Quarkus 上运行,使用 Quarkus LangChain4j 扩展

结论

将 AI 注入 Java 应用程序可增强应用程序的功能和最终用户的体验。借助 Quarkus 和 LangChain4j 等 Java 框架简化与 LLM 的交互,Java 开发人员可以轻松地将 AI 注入到业务应用程序中。

用 Java 编写注入 AI 的应用程序意味着您正在 Java 强大、企业就绪的生态系统中工作,这不仅有助于您轻松地与 AI 模型交互,还使应用程序能够轻松受益于性能、安全性、可观察性和测试等企业必需品。

AI 领域正在迅速发展。通过掌握本文中的概念和技术,您可以保持领先地位,并开始探索 AI 如何帮助您构建智能且引人入胜的 Java 应用程序。结合 Quarkus with LangChain4j 文档 实验我们示例应用程序的完整代码

如果您想了解更多信息,请尝试此教程,了解如何使用检索增强生成 (RAG) 扩展 LLM 的知识,并从 PDF 文档中获取内容:使用 Quarkus 和 LangChain4j 构建 AI 驱动的文档助手

感谢 Red Hat 的 Clement Escoffier、Markus Eisele 和 Georgios Andrianakis 提供宝贵的审阅意见。

关于作者

Don Bourne

显示更多显示更少

Michal Broz

显示更多显示更少

Laura Cowen

显示更多显示更少

Daniel Oh

显示更多显示更少

Kevin Dubois

显示更多显示更少