AI热点 3小时前 117 阅读 0 评论

将 AI 注入 Java 应用程序

作者头像
AI中国

AI技术专栏作家 | 发布了 246 篇文章

人工智能(AI)正变得越来越普遍。作为一名企业 Java 开发人员,你可能想知道 AI 能为业务应用程序增添什么价值,Java 提供了哪些工具可以轻松实现这一目标,以及你可能需要学习哪些技能和知识。在本文中,我们将为你提供必要的基础知识和技能,帮助你开始探索 AI 的功能,以构建智能且响应迅速的企业 Java 应用程序。

 

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

你好(AI)世界:让 LLM 响应提示

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

 

<!---->

聊天机器人将客户的问题发送给应用程序,该应用程序与 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:

 

@SessionScoped@RegisterAiServicepublic 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 端点来与之交互:

 

@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() 方法。

 

例如,如果用户问:“如果我想去看火山,哪个星球适合我?”应用程序会给出一个建议,并告诉用户作为一个火山迷,他可能想去那里的理由:

 

<!---->

宇宙飞船租赁应用聊天机器人

提供记忆的错觉

随着你与聊天机器人对话的继续,它似乎意识到之前交换的消息,即对话的上下文。当你与另一个人交谈时,你理所当然地认为他们记得你(和他们)最后说了什么。然而,对 LLM 的请求是无状态的,因此每个响应仅基于请求提示中所包含的信息生成。

 

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

流式响应带来更灵敏的用户体验

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


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

 

@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 streamChat(String userMessage);}
复制代码

 

@SystemMessage 注解移到接口上意味着无需为接口中的每个方法都添加该注解。 streamChat() 方法每次返回一个 token 作为 LLM 对聊天窗口的响应,而不是等待一次性显示全部响应。

 

我们还需要从 WebSocket 端点调用新的 streamChat() 方法。为了同时保留批处理和流处理功能,我们创建了一个新的 ChatWebSocketStream 类,该类公开了 /chat/stream WebSocket 端点:

 

@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 进行了一些微调之后,我们现在可以在聊天机器人中打开和关闭流式传输功能:

 

<!---->

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

 

启用流式传输后,LLM 生成的每个 token(每个单词或词素)都会立即返回到聊天界面。

从非结构化数据中生成结构化输出

到目前为止,LLM 的输出都是针对应用程序的最终用户。但是,如果我们希望 LLM 的输出能直接被我们的应用程序使用,那该怎么办呢?当 LLM 响应请求时,负责与 LLM 交互的 AI 服务可以返回结构化输出,这些格式比 String 更结构化,如 POJO、POJO 列表和原生类型。

 

返回结构化输出可以显著简化 LLM 输出与 Java 代码的集成,因为它确保应用程序从 AI 服务接收到的输出能够映射到 Java 对象的预定义模式。让我们通过帮助最终用户从我们的舰队中选择一艘满足其需求的宇宙飞船来展示结构化输出的实用性。相关完整的应用程序代码,请参阅 GitHub 上的“spaceship rental step-03”目录。

 

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

 

record Spaceship(String name, int maxPassengers, boolean hasCargoBay, List<String> allowedDestinations) { } 
复制代码

 

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


@Description("A request for a compatible spaceship")public record SpaceshipQuery(int passengers, boolean hasCargo, List destinations) { }
复制代码

 

Fleet 类填充了多个 Spaceship 对象,并提供了一种方法来过滤掉那些与用户请求不匹配的对象。

 

接下来,我们更新 CustomerSupportAgent 接口,以接收用户的消息(非结构化文本),并生成 SpaceshipQuery 记录格式的结构化输出。为了实现这一目标,我们只需将 AI 服务中新方法 extractSpaceshipAttributes() 的返回类型设置为 SpaceshipQuery 即可:

 

SpaceshipQuery extractSpaceshipAttributes(String userMessage);
复制代码

 

在底层,LangChain4j 会自动生成一个请求到 LLM,其中包含一个由 JSON 模式表示的期望响应。LangChain4j 反序列化 LLM 返回的 JSON 格式响应,并使用它来按请求返回一个 SpaceshipQuery 记录。

 

我们还需要知道用户的输入是关于我们宇宙飞船的,还是关于其他某个话题的。这种过滤是通过使用一个更简单的结构化输出请求来实现的,该请求返回一个布尔值:

 

@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 接口的最后添加是,使代理能够根据我们的舰队和用户请求(无论是否包含流式传输),提供宇宙飞船的建议:

 

@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 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 streamSuggestSpaceships(String message, List compatibleSpaceships);}
复制代码

 

我们的最后一步是更新 ChatWebSocketChatWebSocketStream 类,以便首先检查用户的查询是否是关于我们舰队中的宇宙飞船的。如果是,客户支持代理会从用户消息中提取信息,创建一个 SpaceshipQuery 记录,然后回复与用户请求兼容的建议宇宙飞船。 ChatWebSocketChatWebSocketStream 类更新的后代码是相似的,因此这里仅展示 ChatWebSocket 类:

 

@OnTextMessagepublic 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);}
复制代码

 

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


<!---->

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

 

至此,我们已经完成了一个融合了 AI 的 Java 聊天机器人应用程序,该应用程序提供星际旅游推荐和宇宙飞船租赁服务。

 

为了继续学习,请结合Quarkus和LangChain4j文档,尝试运行我们示例应用程序的完整代码

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

在本文中,我们讨论了各种 AI 概念。如果你想了解更多相关信息,这里有一个简短的解释。

 

大语言模型(LLM)

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

 

向 LLMs 提出请求:提示、聊天记忆和词元

你向 LLM 请求信息的方式不仅会影响你从 LLM 收到的回复,还会影响最终用户的体验以及应用程序的运行成本。

 

提示

向 LLM 发送请求,无论是从应用程序代码还是作为最终用户在聊天界面中,都涉及编写提示。提示是 LLM 做出响应所需的信息(通常是文本,但并不限于文本)。如果你将与 LLM 的通信想象成与另一个人的通信,你如何措辞请求对于确保对方(或在这种情况下是 LLM)理解你想要了解的内容非常重要。例如,确保在询问特定信息之前提供请求的上下文,并且不要提供大量无关的信息来混淆视听。

 

聊天记忆

与人与人之间的交谈不同,LLM 是无状态的,不会记住之前的请求,因此你想要 LLM 考虑的所有内容都需要包含在你的请求中:提示、任何之前的请求和响应(聊天记忆),以及你提供的任何帮助 LLM 做出响应的工具。然而,在提示中向 LLM 提供过多信息可能会使请求变得复杂。也可能造成高昂费用。

 

词元

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

 

在请求中提供所有聊天记忆可以使请求既昂贵又不够清晰。对 LLM 的请求长度有限,因此管理聊天记忆以及请求中包含的信息量非常重要。你使用的 Java 框架(如本文示例应用程序中使用的带有 Quarkus 的 LangChain4j)对此大有裨益。

 

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 LangChain4j扩展在Quarkus上运行

 

结论

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

 

用 Java 编写 AI 驱动的应用程序意味着你正在 Java 强大且适用于企业的生态系统中工作,这不仅有助于你轻松与 AI 模型交互,还能使应用程序轻松受益于企业级要素,如性能、安全性、可观测性和测试。

 

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

 

如果你想了解更多,可以尝试观看以下教程,了解如何使用检索增强生成(RAG)通过 PDF 文档的内容来扩展 LLM 的知识:使用Quarkus和LangChain4j构建AI驱动的文档助手

 

感谢红帽公司的 Clement Escoffier、Markus Eisele 和 Georgios Andrianakis 提供的宝贵审阅意见。

 

原文链接:

https://www.infoq.com/articles/infusing-ai-java/


作者头像

AI前线

专注人工智能前沿技术报道,深入解析AI发展趋势与应用场景

246篇文章 1.2M阅读 56.3k粉丝

评论 (128)

用户头像

AI爱好者

2小时前

这个更新太令人期待了!视频分析功能将极大扩展AI的应用场景,特别是在教育和内容创作领域。

用户头像

开发者小明

昨天

有没有人测试过新的API响应速度?我们正在开发一个实时视频分析应用,非常关注性能表现。

作者头像

AI前线 作者

12小时前

我们测试的平均响应时间在300ms左右,比上一代快了很多,适合实时应用场景。

用户头像

科技观察家

3天前

GPT-4的视频处理能力已经接近专业级水平,这可能会对内容审核、视频编辑等行业产生颠覆性影响。期待看到更多创新应用!