摘要:人工智能(AI)正变得越来越普遍。作为一名企业 Java 开发人员,你可能想知道 AI 能为业务应用程序增添什么价值,Java 提供了哪些工具可以轻松实现这一目标,以及你可能需要学习哪些技能和知识。在本文中,我们将为你提供必要的基础知识和技能,帮助你开始探索
作者 | Daniel Dominguez
译者 | 刘雅梦
策划 | 丁晓昀
人工智能(AI)正变得越来越普遍。作为一名企业 Java 开发人员,你可能想知道 AI 能为业务应用程序增添什么价值,Java 提供了哪些工具可以轻松实现这一目标,以及你可能需要学习哪些技能和知识。在本文中,我们将为你提供必要的基础知识和技能,帮助你开始探索 AI 的功能,以构建智能且响应迅速的企业 Java 应用程序。
在本文中,当我们谈论 AI 时,我们指的是基于 Java 应用程序向大语言模型(LLM)发送的请求,并从 LLM 获取响应。在本文的示例中,我们创建了一个简单的聊天机器人,客户可以向其询问星际旅游目的地推荐,然后使用它预订前往这些目的地的宇宙飞船。我们展示了如何使用 LangChain4j 和 Quarkus 等 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类中。或者,你可以直接将行星信息放置在系统消息中。@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); }}接口使用构造函数注入自动提供对 AI 服务的引用。当最终用户在聊天机器人中输入消息时,onTextMessage方法将消息传递给 AI 服务的chat方法。例如,如果用户问:“如果我想去看火山,哪个星球适合我?”应用程序会给出一个建议,并告诉用户作为一个火山迷,他可能想去那里的理由:
宇宙飞船租赁应用聊天机器人
提供记忆的错觉
随着你与聊天机器人对话的继续,它似乎意识到之前交换的消息,即对话的上下文。当你与另一个人交谈时,你理所当然地认为他们记得你(和他们)最后说了什么。然而,对 LLM 的请求是无状态的,因此每个响应仅基于请求提示中所包含的信息生成。
为了在对话中保持上下文,AI 服务使用聊天记忆,通过 LangChain4j 存储之前用户的消息和聊天机器人的响应。默认情况下,Quarkus LangChain4j 扩展将聊天存储在内存中,AI 服务根据需要管理聊天记忆(例如,通过丢弃或汇总最老的消息)以保持在内存的限制内。单独使用 LangChain4j 需要你首先配置一个记忆提供者,但使用 Quarkus LangChain4j 扩展时则不需要。这为最终用户提供了实际的记忆错觉,并改善了用户体验,使他们可以在不需要重复之前所说的一切的情况下输入后续消息。通过流式处理来自 LLM 的响应,也可以改善用户聊天机器人体验。
流式响应带来更灵敏的用户体验
你可能会注意到,对聊天消息窗口的响应需要一些时间来生成,然后一次性全部出现。为了提高聊天机器人的感知响应性,我们可以修改代码,使其在生成响应时返回每个 token。这种方法称为流式传输,允许用户在完整响应可用之前开始阅读部分响应。有关完整应用程序代码,请参阅 GitHub “spaceship rental step-02”目录。
更改我们的应用程序以实现流式传输聊天机器人响应很容易。首先,我们将更新接口,添加一个返回 SmallRye Mutiny@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}将@SystemMessage注解移到接口上意味着无需为接口中的每个方法都添加该注解。streamChat方法每次返回一个 token 作为 LLM 对聊天窗口的响应,而不是等待一次性显示全部响应。我们还需要从 WebSocket 端点调用新的streamChat方法。为了同时保留批处理和流处理功能,我们创建了一个新的ChatWebSocketStream类,该类公开了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 MultiString> 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”目录。
同样,为了表示用户对我们舰队中宇宙飞船的查询,我们根据用户在聊天中提供的信息创建了一个SpaceshipQuery@Description("A request for a compatible spaceship")public record SpaceshipQuery(int passengers, boolean hasCargo, List}Fleet类填充了多个Spaceship对象,并提供了一种方法来过滤掉那些与用户请求不匹配的对象。接下来,我们更新接口,以接收用户的消息(非结构化文本),并生成SpaceshipQuery记录格式的结构化输出。为了实现这一目标,我们只需将 AI 服务中新方法extractSpaceshipAttributes的返回类型设置为在底层,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);我们对接口的最后添加是,使代理能够根据我们的舰队和用户请求(无论是否包含流式传输),提供宇宙飞船的建议:@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@UserMessage(""" """)Multi}我们的最后一步是更新和类,以便首先检查用户的查询是否是关于我们舰队中的宇宙飞船的。如果是,客户支持代理会从用户消息中提取信息,创建一个SpaceshipQuery记录,然后回复与用户请求兼容的建议宇宙飞船。ChatWebSocket和类更新的后代码是相似的,因此这里仅展示ChatWebSocket类:@OnTextMessagepublic String onTextMessage(String message) { boolean isSpaceshipQuery = customerSupportAgent.isSpaceshipQuery(message); if (isSpaceshipQuery) { SpaceshipQuery userQuery = customerSupportAgent.extractSpaceshipAttributes(message); ListSpaceship> spaceships = Fleet.findCompatibleSpaceships(userQuery); return customerSupportAgent.suggestSpaceships(message, spaceships); } else return customerSupportAgent.chat(message);}通过这些更新,客户支持代理已经准备好使用结构化输出为用户提供飞船建议了:
该应用程序根据结构化输出为用户提供宇宙飞船建议
至此,我们已经完成了一个融合了 AI 的 Java 聊天机器人应用程序,该应用程序提供星际旅游推荐和宇宙飞船租赁服务。
为了继续学习,请结合 Quarkus 和 LangChain4j 文档,尝试运行我们 示例应用程序的完整代码。
更多关于这些 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 Liberty、Spring Boot 和 Micronaut。MicroProfile 和 Jakarta 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 文档,尝试运行我们 示例应用程序的完整代码。
感谢红帽公司的 Clement Escoffier、Markus Eisele 和 Georgios Andrianakis 提供的宝贵审阅意见。
来源:极客邦科技