LangChain4j从入门到精通-3-聊天与语言模型
本文深入解析了LangChain4j框架中与大型语言模型(LLM)交互的核心底层API------ChatModel。作为渐趋淘汰的LanguageModel的现代化替代方案,ChatModel支持更复杂的多轮对话场景,通过接受多个ChatMessage作为输入并返回AiMessage,为开发者提供了更强大的交互能力。文章系统介绍了五种核心ChatMessage类型(UserMessage、AiMessage、SystemMessage等)及其应用场景,并通过丰富Java代码示例演示了如何进行多轮对话、支持多模态内容(如图片、音频、PDF文件)。此外,详细阐述了如何利用ChatRequest进行深度参数定制以精细控制模型行为,并特别介绍了为Kotlin开发者提供的协程异步扩展(chatAsync),有效提升应用响应能力。掌握ChatModel这一基础而关键的API,是开发者高效、灵活集成AI能力,构建智能对话应用的坚实第一步。
#Java #人工智能 #LangChain4j #大模型应用开发 #AI集成
目前有两种类型的LLM API可用:
LanguageModel语言模型。它们的API非常简单------接收一个字符串作为输入,并返回一个字符串作为输出。 这个API现在正逐渐被聊天API(第二种API类型)所取代ChatModel. 这些功能接受多个ChatMessage作为输入,并返回一个AiMessage作为输出。 ChatMessage通常包含文本,但一些大语言模型也支持其他模态(如图像、音频等)。
这类聊天模型的例子包括OpenAI的gpt-4o-mini和谷歌的gemini-1.5-pro。
LangChain4j将不再扩展对LanguageModel的支持, 因此在所有新功能中,我们将使用ChatModelAPI。
ChatModel是 LangChain4j 中与大型语言模型交互的低级 API,提供最强大的功能和灵活性。 还有一个高级 API(AI 服务),我们将在介绍完基础知识后详细讲解。
除了 ChatModel和 LanguageModel之外,LangChain4j 还支持以下类型的模型:
EmbeddingModel- 该模型可将文本转换为嵌入向量.ImageModel- 该模型可生成和编辑图像.ModerationModel- 该模型可检测文本是否包含有害内容.ScoringModel-该模型可根据查询对多段文本进行评分(或排序)
本质上决定了每段文本与查询的相关性。这对于RAG非常有用。这些内容将在后面进行介绍。
现在,让我们更详细地了解一下 ChatModelAPI。
java
public interface ChatModel {
String chat(String userMessage);
...
}
如你所见,这里有一个简单的chat方法,它接收一个String作为输入并返回一个String作为输出,类似于LanguageModel。 这只是一个便捷方法,让你可以快速轻松地进行尝试,而无需将String包装在UserMessage中。
以下是其他聊天API方法:
java
...
ChatResponse chat(ChatMessage... messages);
ChatResponse chat(List<ChatMessage> messages);
...
这些版本的 chat方法接收一个或多个 ChatMessage作为输入。 ChatMessage是一个表示聊天消息的基础接口。 下一节将详细介绍聊天消息的相关内容。
如果你想自定义请求(例如指定模型名称、温度参数、工具、JSON模式等),可以使用chat(ChatRequest)方法:
java
...
ChatResponse chat(ChatRequest chatRequest);
...
java
ChatRequest chatRequest = ChatRequest.builder()
.messages(...)
.modelName(...)
.temperature(...)
.topP(...)
.topK(...)
.frequencyPenalty(...)
.presencePenalty(...)
.maxOutputTokens(...)
.stopSequences(...)
.toolSpecifications(...)
.toolChoice(...)
.responseFormat(...)
.parameters(...) // you can also set common or provider-specific parameters all at once
.build();
ChatResponse chatResponse = chatModel.chat(chatRequest);
ChatMessage的类型
目前有五种聊天消息类型,每种对应消息的一个"来源":
-
UserMessage: 这是来自用户的一条消息。 用户可以是您应用程序的最终用户(人类),也可以是应用程序本身。 它可能包含:contents(): 消息的内容。根据LLM支持的模态,它可以仅包含单一文本 (String),name(): 用户名。并非所有模型提供商都支持此功能。attributes(): 附加属性:这些属性不会发送给模型,但它们会存储在 ChatMemory中。
-
AiMessage: 这是一条由AI生成的消息,用于回复已发送的消息。 它可能包含:text(): 文本内容thinking(): 思考/推理内容toolExecutionRequests(): 请求执行工具。我们将在另一章节中attributes(): 附加属性,通常是特定于供应商的
-
ToolExecutionResultMessage: 这个是ToolExecutionRequest的执行结果. -
SystemMessage: 这是来自系统的消息。 通常,作为开发者,您应该定义此消息的内容。 一般情况下,您会在这里写明关于LLM在此对话中的角色说明, 它应该如何表现,以何种风格回答等等。 LLM的训练使其对SystemMessage的关注度高于其他类型的消息, 因此请谨慎处理,最好不要让终端用户自由定义或向SystemMessage注入某些输入。 通常,它位于对话的开头。 -
CustomMessage: 这是一条自定义消息,可以包含任意属性。此消息类型仅能被支持它的ChatModel实现所使用(目前仅限Ollama)。
既然我们已经了解了所有类型的 ChatMessage,接下来看看如何在对话中组合它们。
最简单的场景是,我们可以向 chat方法提供一个 UserMessage的实例。 这与最初版本的 chat方法类似,后者接受一个 String作为输入。 这里的主要区别在于,它现在返回的不是 String,而是 ChatResponse。
除了 AiMessage外,ChatResponse还包含 ChatResponseMetadata。 ChatResponseMetadata包含 TokenUsage,其中记录了输入(即你提供给生成方法的所有 ChatMessage)包含的令牌数、 输出(在 AiMessage中)生成的令牌数以及总数(输入 + 输出)。 你需要这些信息来计算调用 LLM 的成本。 此外,ChatResponseMetadata还包含 FinishReason, 这是一个枚举,列出了生成停止的各种原因。 通常,如果 LLM 自行决定停止生成,原因会是 FinishReason.STOP。
创建 UserMessage有多种方法,具体取决于内容。 最简单的方法是 new UserMessage("Hi")或 UserMessage.from("Hi")。
多个ChatMessage
那么,为什么需要提供多个ChatMessage作为输入,而不是仅一个呢? 这是因为大语言模型本质上是无状态的,意味着它们不会维护对话的状态。 因此,如果你想支持多轮对话,就需要负责管理对话的状态。
假设你想构建一个聊天机器人。想象一下用户与聊天机器人(AI)之间的一段简单多轮对话:
- 用户: Hello, my name is Klaus
- AI: Hi Klaus, how can I help you?
- 用户: What is my name?
- AI: Klaus
与 ChatModel的交互将会是这样的:
java
UserMessage firstUserMessage = UserMessage.from("Hello, my name is Klaus");
AiMessage firstAiMessage = model.chat(firstUserMessage).aiMessage(); // Hi Klaus, how can I help you?
UserMessage secondUserMessage = UserMessage.from("What is my name?");
AiMessage secondAiMessage = model.chat(firstUserMessage, firstAiMessage, secondUserMessage).aiMessage(); // Klaus
如你所见,在第二次调用 chat方法时,我们不仅提供了单一的 secondUserMessage, 还包含了对话中的先前消息。
手动维护和管理这些消息非常繁琐。 因此,我们引入了ChatMemory的概念,这将在下一章节中详细探讨。
多模态
UserMessage 不仅可以包含文本,还可以包含其他类型的内容。
UserMessage 包含 List<Content> contents.
Content 是一个接口,具有以下实现:
TextContentImageContentAudioContentVideoContentPdfFileContent
你可以在这里的对比表中查看哪些LLM提供商支持哪些模式。
这是一个向大语言模型发送文本和图片的示例:
java
UserMessage userMessage = UserMessage.from(
TextContent.from("Describe the following image"),
ImageContent.from("https://example.com/cat.jpg")
);
ChatResponse response = model.chat(userMessage);
文本内容
TextContent 是最简单的Content形式,代表纯文本并包装单个String。
UserMessage.from(TextContent.from("Hello!")) 与 UserMessage.from("Hello!")是一样的效果.
可以在 UserMessage中提供一个或多个 TextContent:
java
UserMessage userMessage = UserMessage.from(
TextContent.from("Hello!"),
TextContent.from("How are you?")
);
图片内容
根据LLM提供商的不同,ImageContent既可以从远程图片的URL创建(参见上方示例),也可以从Base64编码的二进制数据创建:
java
byte[] imageBytes = readBytes("/home/me/cat.jpg");
String base64Data = Base64.getEncoder().encodeToString(imageBytes);
ImageContent imageContent = ImageContent.from(base64Data, "image/jpg");
UserMessage userMessage = UserMessage.from(imageContent);
还可以指定 DetailLevel枚举(包含 LOW/HIGH/AUTO选项)来控制模型处理图像的方式。 更多详情请参阅此处。
音频内容
AudioContent 类似于 ImageContent,但表示音频内容。
Video Content
VideoContent 类似于 ImageContent,但表示视频内容。
PDF File Content
PdfFileContent 类似于 ImageContent,但表示 PDF 文件的二进制内容。
Kotlin扩展
ChatModel的 Kotlin 扩展提供了异步方法来处理与语言模型的聊天交互,利用了 Kotlin 的 协程功能。chatAsync方法允许非阻塞地处理 ChatRequest或 ChatRequest.Builder配置,返回带有模型回复的 ChatResponse。类似地,generateAsync方法处理聊天消息的异步响应生成。这些扩展简化了在 Kotlin 应用程序中高效构建聊天请求和处理对话的过程。请注意,这些方法标记为实验性,可能会随时间演进。
ChatModel.chatAsync(request: ChatRequest): 专为Kotlin协程设计,这个异步扩展函数将同步的chat方法封装在Dispatchers.IO协程作用域内。这实现了非阻塞操作,对保持应用响应能力至关重要。该函数特意命名为chatAsync以避免与现有同步方法chat冲突。其函数签名为:suspend fun ChatModel.chatAsync(request: ChatRequest): ChatResponse。关键字suspend将其标记为协程函数。
ChatModel.chat(block: ChatRequestBuilder.() -> Unit) : 该版本的chat采用Kotlin类型安全构建器DSL,提供更简洁的实现方式。它在内部使用chatAsync进行异步执行的同时,简化了ChatRequest对象的构建过程。通过协程机制,该版本兼具代码简洁性和非阻塞特性。
