AI Services
到目前为止,我们一直在介绍底层组件,如 ChatLanguageModel、ChatMessage、ChatMemory 等。 在这个层面上工作非常灵活,给予您完全的自由,但也迫使您编写大量的样板代码。 由于 LLM 驱动的应用程序通常不仅需要单个组件,还需要多个组件协同工作 (例如,提示模板、聊天记忆、LLM、输出解析器、RAG 组件:嵌入模型和存储) 并且经常涉及多次交互,协调所有这些组件变得更加繁琐。
我们希望您专注于业务逻辑,而不是低级实现细节。 因此,LangChain4j 中目前有两个高级概念可以帮助您:AI 服务和链。
Chains (legacy)
链的概念源自 Python 的 LangChain(在引入 LCEL 之前)。 其思想是为每个常见用例(如聊天机器人、RAG 等)提供一个 Chain。 链组合多个低级组件并协调它们之间的交互。 它们的主要问题是,如果您需要自定义某些内容,它们过于僵化。 LangChain4j 只实现了两个链(ConversationalChain 和 ConversationalRetrievalChain), 目前我们不打算添加更多。
AI Services
我们提出了另一种为 Java 量身定制的解决方案,称为 AI 服务。 其思想是将与 LLM 和其他组件交互的复杂性隐藏在简单的 API 后面。
这种方法与 Spring Data JPA 或 Retrofit 非常相似:您以声明方式定义具有所需 API 的接口, 然后 LangChain4j 提供实现该接口的对象(代理)。 您可以将 AI 服务视为应用程序服务层中的组件。 它提供 AI 服务。因此得名。
AI 服务处理最常见的操作:
- 为 LLM 格式化输入
- 解析 LLM 的输出
它们还支持更高级的功能:
- 聊天记忆
- 工具
- RAG
AI 服务可用于构建有状态的聊天机器人,促进来回交互, 也可用于自动化每次调用 LLM 都是独立的流程。
让我们看一下最简单的 AI 服务。之后,我们将探索更复杂的示例。
最简单的 AI 服务
首先,我们定义一个带有单个方法 chat 的接口,该方法接受 String 作为输入并返回 String。
1 | interface Assistant { |
然后,我们创建低级组件。这些组件将在我们的 AI 服务底层使用。 在这种情况下,我们只需要 ChatLanguageModel:
1 | ChatLanguageModel model = OpenAiChatModel.builder() |
最后,我们可以使用 AiServices 类创建我们的 AI 服务实例:
1 | Assistant assistant = AiServices.create(Assistant.class, model); |
备注
在 Spring Boot 应用程序中, 自动配置会处理创建 Assistant bean。 这意味着您不需要调用 AiServices.create(...),只需在需要的地方注入/自动装配 Assistant 即可。
现在我们可以使用 Assistant:
1 | String answer = assistant.chat("Hello"); |
案例:使用高阶API
1 | public interface Assistant { |
它是如何工作的?
您将接口的 Class 与低级组件一起提供给 AiServices, 然后 AiServices 创建一个实现该接口的代理对象。 目前,它使用反射,但我们也在考虑其他替代方案。 这个代理对象处理所有输入和输出的转换。 在这种情况下,输入是单个 String,但我们使用的是 ChatLanguageModel,它接受 ChatMessage 作为输入。 因此,AiService 会自动将其转换为 UserMessage 并调用 ChatLanguageModel。 由于 chat 方法的输出类型是 String,在 ChatLanguageModel 返回 AiMessage 后, 它将在从 chat 方法返回之前转换为 String。
@SystemMessage
现在,让我们看一个更复杂的例子。 我们将强制 LLM 使用俚语回复 😉
这通常通过在 SystemMessage 中提供指令来实现。
1 | public interface Assistant { |
在这个例子中,我们添加了 @SystemMessage 注解,其中包含我们想要使用的系统提示模板。 这将在幕后转换为 SystemMessage 并与 UserMessage 一起发送给 LLM。
系统消息提供者
1 | Assistant assistant = AiServices.builder(Assistant.class) |
如您所见,您可以根据聊天记忆 ID(用户或对话)提供不同的系统消息。
@UserMessage
现在,假设我们使用的模型不支持系统消息, 或者我们只想为此目的使用 UserMessage。
1 | public interface Assistant { |
我们将 @SystemMessage 注解替换为 @UserMessage, 并指定了一个包含变量 it 的提示模板,该变量指的是唯一的方法参数。
也可以用 @V 注解 String userMessage, 并为提示模板变量分配自定义名称:
1 | interface Friend { |
备注
请注意,在使用 Spring Boot 的 LangChain4j 时,不需要使用 @V。 只有在 Java 编译期间未启用 -parameters 选项时,才需要此注解。
有效的 AI 服务方法示例
以下是一些有效的 AI 服务方法示例。
1 | String chat(String userMessage); |
1 |
|
返回类型
AI 服务方法可以返回以下类型之一:
String- 在这种情况下,LLM 生成的输出将不经任何处理/解析直接返回- 结构化输出支持的任何类型 - 在这种情况下, AI 服务将在返回之前将 LLM 生成的输出解析为所需类型
任何类型都可以额外包装在 Result<T> 中,以获取有关 AI 服务调用的额外元数据:
TokenUsage- AI 服务调用期间使用的令牌总数。如果 AI 服务对 LLM 进行了多次调用 (例如,因为执行了工具),它将汇总所有调用的令牌使用情况。- Sources - 在 RAG 检索期间检索到的
Content - 已执行的工具
FinishReason
示例:
1 | interface Assistant { |
结构化输出
如果您想从 LLM 接收结构化输出(例如,复杂的 Java 对象,而不是 String 中的非结构化文本), 您可以将 AI 服务方法的返回类型从 String 更改为其他类型。
有关结构化输出的更多信息可以在这里找到。
返回类型为 boolean
1 | interface SentimentAnalyzer { |
返回类型为 Enum
1 | enum Priority { |
返回类型为 POJO
1 | class Person { |
流式处理
AI 服务可以使用 TokenStream 返回类型逐个令牌流式处理响应:
1 | interface Assistant { |
Flux
您也可以使用 Flux<String> 代替 TokenStream。 为此,请导入 langchain4j-reactor 模块:
1 | <dependency> |
案例:高阶API + Flux 实现流式输出
1 | public interface StreamingAssistant { |
聊天记忆
AI 服务可以使用聊天记忆来”记住”之前的交互:
1 | Assistant assistant = AiServices.builder(Assistant.class) |
在这种情况下,所有 AI 服务调用都将使用相同的 ChatMemory 实例。 然而,如果您有多个用户,这种方法将不起作用, 因为每个用户都需要自己的 ChatMemory 实例来维护各自的对话。
解决这个问题的方法是使用 ChatMemoryProvider:
1 | interface Assistant { |
在这种情况下,ChatMemoryProvider 将提供两个不同的 ChatMemory 实例,每个记忆 ID 一个。
以这种方式使用 ChatMemory 时,重要的是要清除不再需要的对话记忆,以避免内存泄漏。要使 AI 服务内部使用的聊天记忆可访问,只需让定义它的接口扩展 ChatMemoryAccess 接口即可。
1 | interface Assistant extends ChatMemoryAccess { |
这使得可以访问单个对话的 ChatMemory 实例,并在对话终止时删除它。
1 | String answerToKlaus = assistant.chat(1, "Hello, my name is Klaus"); |
备注
请注意,如果 AI 服务方法没有用 @MemoryId 注解的参数, ChatMemoryProvider 中 memoryId 的值将默认为字符串 "default"。
案例:高阶API实现多用户聊天记忆
1 | public interface StreamingAssistant extends ChatMemoryAccess { |
用户1:


用户2:
