使用Jakarta EE和LangChain4j构建AI驱动的Java应用程序
人工智能正在迅速改变软件开发。如今许多开发者使用AI驱动的工具来生成代码,但下一个进步是将AI直接集成到应用程序中。现代系统越来越多地使用大型语言模型(LLM)来回答问题、自动化工作流、总结信息并增强用户体验。因此,软件工程师必须将传统企业开发实践与AI能力相结合,同时确保可靠性、可伸缩性和可维护性。
这一演进为Jakarta EE开发者提供了一个重大机遇。Jakarta EE为企业应用提供了一个成熟的平台,包含依赖注入、RESTful服务、配置、持久化和云原生开发的标准。通过将Jakarta EE与LangChain4j集成,开发者可以通过一个简单的Java API访问先进的AI模型,在不离开熟悉的Jakarta EE环境的情况下添加智能功能。
在本文中,我们将构建一个简单的"Hello World" AI应用程序,以演示如何使用LangChain4j将大型语言模型轻松集成到Jakarta EE应用程序中。
使用Jakarta EE技术配置LangChain4j
在开发你的第一个AI驱动应用程序之前,理解LangChain4j在Java生态系统中的角色及其在AI集成中的流行度非常重要。LangChain4j充当Java应用程序与AI提供商之间的编排层。它通过提供一致的编程模型(无论底层供应商是谁)简化了AI集成。如果你熟悉Spring Data或Jakarta Data,这个概念将会很熟悉。使用Spring Data和Jakarta Data,开发者定义仓库接口并使用注解来指定行为。实现细节由生成具体实现并管理数据库通信的提供者处理。这使得开发者可以专注于业务逻辑,而不是底层的数据库操作。
[LOADING...]
LangChain4j对人工智能采用了类似的方法。开发者不需要编写HTTP客户端、构建JSON负载或管理特定供应商的API,而是定义表示AI能力的Java接口。然后LangChain4j生成实现并管理与所选AI提供商的通信。LangChain4j可以被视为AI领域的Jakarta Data或Spring Data,其中AI提供商依赖的作用类似于JDBC驱动。从一个AI提供商切换到另一个,例如从OpenAI切换到其他提供商,通常只需要更新依赖和配置,而应用程序代码基本保持不变。
虽然本文为了简单起见使用了一个Java SE应用程序,但同样的方法也适用于Jakarta EE、Spring Boot、Quarkus、Helidon、Micronaut和其他Java平台。
项目依赖
第一步是创建一个Maven Quickstart项目,并添加CDI、Eclipse MicroProfile Config和LangChain4j所需的依赖:
这个示例使用langchain4j-open-ai依赖,它作为与OpenAI模型通信的特定提供商驱动。应用程序代码与提供商实现无关。
配置AI提供商
LangChain4j与Eclipse MicroProfile Config集成,允许你将所有提供商设置外部化。创建一个microprofile-config.properties文件并添加以下配置:
这个配置指定了聊天模型实现、认证API密钥以及将处理提示的模型。
这种方法的一个关键优势是灵活性。如果将来你选择其他提供商,通常只需替换提供商依赖并更新配置即可。应用程序代码通常保持不变,强化了提供商依赖类似于传统数据访问中的JDBC驱动的作用。
对于这个示例,你可以将API密钥直接放在配置文件中,或者通过环境变量提供。在生产环境中,使用环境变量、密钥管理器或保险库解决方案。切勿将API密钥提交到源代码控制中,因为暴露的凭证可能导致未经授权的使用、意外成本和安全风险。
你的第一个AI服务
项目配置完成后,我们可以构建第一个AI驱动的服务。按照软件开发的惯例,我们将从一个"Hello World"示例开始。我们不打印静态消息,而是向AI模型发送一个问题并显示其响应。
这个示例使用了最简单的契约:输入是String,输出也是String。虽然实际应用程序通常使用更复杂的领域对象,但从纯文本开始有助于我们专注于核心的LangChain4j编程模型,并理解如何创建和使用AI服务。
第一步是定义一个AI服务接口:
这个接口不包含实现。LangChain4j在运行时自动生成实现。@RegisterAIService注解指示LangChain4j为此接口创建一个AI驱动的实现。@ApplicationScoped注解使生成的实现作为CDI bean可用,可以像其他Jakarta EE组件一样被注入或访问。
方法签名定义了AI契约。当调用chat方法时,参数作为AI模型的提示,返回值包含生成的响应。在这个示例中,请求和响应都是简单的字符串。
接下来,我们需要一个客户端应用程序来消费这个服务:
应用程序使用Weld SE启动一个CDI容器,在Java SE环境中提供依赖注入。初始化容器后,我们激活请求上下文并从CDI获取一个AssistantService实例。尽管代码库中没有具体实现,但CDI返回了一个由LangChain4j生成的完全功能的服务。当调用chat方法时,LangChain4j将提示发送给配置的AI模型,等待响应,并将结果转换为Java String。
运行应用程序会产生类似于以下的输出:
Assistant response: Paris is the capital of France.
确切的表述可能会有所不同,因为大型语言模型是概率系统。与给定输入始终返回相同结果的传统方法不同,AI模型可能会产生略有不同的响应,但保持相同的意思。
虽然使用字符串对于学习基础知识很有用,但企业应用程序很少在各层之间交换原始文本。业务应用通常使用结构化数据、领域对象、命令和响应,以确保更严格的契约和更好的可维护性。
在下一节中,我们将通过用专用的输入和输出类替换原始字符串来改进这个示例,使LangChain4j能够以更类型安全和更具表现力的方式在Java对象和AI交互之间进行映射。
使用结构化输入和输出
前面的示例展示了一个基本的AI交互:字符串输入产生字符串输出。虽然这说明了基本原理,但实际应用很少单独使用非结构化文本。企业系统通常交换表示业务概念的明确定义的对象,使代码更具表现力、可维护性和类型安全性。
LangChain4j的一个关键优势是它能够将Java对象直接映射到AI交互。它自动将结构化输入转换为提示,并将AI响应转换为强类型的Java对象,消除了手动序列化和解析的需要。开发者可以处理领域概念,而不是原始文本。
为了演示这一点,我们将构建一个简单的图书推荐引擎。给定书名和作者,AI将推荐在知识学习路径中合乎逻辑地紧随其后的三本书。
首先定义输入对象:
这个记录捕获用户的输入。我们不需要手动创建文本提示,而是提供一个结构化的Java对象,包含书名和作者。
接下来,定义表示推荐图书的领域模型:
这个记录包含比简单标题更丰富的信息。它除了标题和作者外,还提供简短描述和一组关键词,以进一步描述推荐内容。
然后添加推荐原因:
最后,创建一个包装对象,表示AI服务返回的完整响应:
现在,我们有了请求和预期响应的完整领域模型。
接下来,定义AI服务:
这个示例还引入了系统消息的概念。@SystemMessage注解提供了指导模型行为的指令。与每次变化请求的用户输入不同,系统消息作为一组固定的规则用于AI响应。这里,我们指示模型推荐最多三本书,解释每项推荐,并使用我们定义的Java记录返回信息。
方法签名只使用领域对象:输入是BookRequest,输出是NextReadBooks。不需要手动处理JSON、创建提示或解析响应,LangChain4j自动管理这些任务。
应用程序代码保持简洁:
执行时,LangChain4j将BookRequest转换为提示,发送给模型,验证响应是否与目标结构匹配,并将结果映射回NextReadBooks。对于开发者来说,这种交互类似于调用一个标准的Java服务。
这种方法相比于原始字符串交互有明显的优势。代码更易于理解,IDE自动完成提高了生产率,并且重构更加安全,因为输入和输出是显式的领域模型。应用程序也能更容易地适应新的业务需求。
到目前为止,我们的示例使用了明确的用户请求和静态的系统指令。然而,现代AI应用通常需要用户输入之外的附加上下文。在下一节中,我们将探讨如何使用外部知识和上下文丰富AI交互,使模型能够产生与应用程序领域更一致且更准确的响应。
DZone贡献者的观点仅代表其个人。