聊天与语言模型
注
本页面描述的是一个低层级的 LLM API。
关于更高层级的 LLM API,请参见 AI Services。
注
所有支持的 LLM 模型可以在 这里 找到。
LLM 目前提供两种 API 类型:
LanguageModel。它们的 API 非常简单 —— 接收一个String作为输入,并返回一个String作为输出。
这种 API 现在逐渐被聊天 API(第二种 API 类型)取代。ChatModel。它们接收多个ChatMessage作为输入,并返回一个单一的AiMessage作为输出。
ChatMessage 通常包含文本,但一些 LLM 还支持其他模态(例如图像、音频等)。
此类聊天模型的示例包括 OpenAI 的 gpt-4o-mini 和 Google 的 gemini-1.5-pro。
对 LanguageModel 的支持在 LangChain4j 中将不再扩展,
因此在所有新功能中,我们将使用 ChatModel API。
ChatModel 是与 LLM 交互的低层级 API,提供了最大的功能与灵活性。
此外还有一个高层级 API(AI Services),我们将在学习完基础内容后再介绍。
除了 ChatModel 和 LanguageModel,LangChain4j 还支持以下几种模型类型:
EmbeddingModel—— 该模型可以将文本转换为Embedding。ImageModel—— 该模型可以生成和编辑Image。ModerationModel—— 该模型可以检查文本是否包含有害内容。ScoringModel—— 该模型可以针对查询对多段文本进行打分(或排序),
本质上是判断每段文本与查询的相关性。这在 RAG 中非常有用。
这些内容我们将在后续进行讲解。
现在,让我们更深入地看看 ChatModel API。
public interface ChatModel {
String chat(String userMessage);
...
}如你所见,这里有一个简单的 chat 方法,它接收一个 String 作为输入并返回一个 String 作为输出,类似于 LanguageModel。
这是一个便捷方法,让你可以快速、轻松地进行实验,而不需要手动将 String 封装进 UserMessage。
以下是其他的聊天 API 方法:
...
ChatResponse chat(ChatMessage... messages);
ChatResponse chat(List<ChatMessage> messages);
...这些版本的 chat 方法接收一个或多个 ChatMessage 作为输入。ChatMessage 是一个基础接口,表示一条聊天消息。
下一节将会介绍更多关于聊天消息的细节。
如果你想自定义请求(例如指定模型名称、temperature、tools、JSON schema 等),
可以使用 chat(ChatRequest) 方法:
...
ChatResponse chat(ChatRequest chatRequest);
...ChatRequest chatRequest = ChatRequest.builder()
.messages(...)
.modelName(...)
.temperature(...)
.topP(...)
.topK(...)
.frequencyPenalty(...)
.presencePenalty(...)
.maxOutputTokens(...)
.stopSequences(...)
.toolSpecifications(...)
.toolChoice(...)
.responseFormat(...)
.parameters(...) // 你也可以一次性设置通用或提供商特定的参数
.build();
ChatResponse chatResponse = chatModel.chat(chatRequest);ChatMessage 的类型
目前有四种聊天消息类型,每种对应不同的消息来源:
UserMessage:这是来自用户的消息。
用户可以是应用程序的终端用户(人类),也可以是你的应用程序本身。
根据 LLM 所支持的模态,UserMessage可以只包含文本(String),
也可以包含 其他模态。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,它统计输入中包含多少 token(你传递给生成方法的所有 ChatMessage),
输出(在 AiMessage 中)生成了多少 token,以及总数(输入 + 输出)。
你需要这些信息来计算一次调用 LLM 的成本。
此外,ChatResponseMetadata 还包含 FinishReason,
这是一个枚举,表示生成为什么停止。
通常,它会是 FinishReason.STOP,表示 LLM 自行决定停止生成。
创建 UserMessage 有多种方式,最简单的是 new UserMessage("Hi") 或 UserMessage.from("Hi")。
多个 ChatMessage
那么,为什么需要提供多个 ChatMessage 作为输入,而不是只提供一个呢?
这是因为 LLM 本质上是无状态的,它们不会维护对话的上下文状态。
因此,如果你想支持多轮对话,就需要自己维护对话的状态。
假设你想构建一个聊天机器人。下面是一个简单的多轮对话示例:
- 用户:Hello, my name is Klaus
- AI:Hi Klaus, how can I help you?
- 用户:What is my name?
- AI:Klaus
在 ChatModel 中,这种交互如下所示:
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 提供商支持哪些模态。
以下是一个同时发送文本和图像给 LLM 的示例:
UserMessage userMessage = UserMessage.from(
TextContent.from("Describe the following image"),
ImageContent.from("https://example.com/cat.jpg")
);
ChatResponse response = model.chat(userMessage);文本内容(Text Content)
TextContent 是最简单的 Content 形式,表示纯文本并包装一个 String。UserMessage.from(TextContent.from("Hello!")) 等价于 UserMessage.from("Hello!")。
你可以在 UserMessage 中提供一个或多个 TextContent:
UserMessage userMessage = UserMessage.from(
TextContent.from("Hello!"),
TextContent.from("How are you?")
);图像内容(Image Content)
根据 LLM 提供商的不同,ImageContent 可以通过远程图像的 URL 创建(如上例所示),也可以通过 Base64 编码的二进制数据创建:
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 选项),用于控制模型如何处理图像。
详情参见 这里。
音频内容(Audio Content)
AudioContent 与 ImageContent 类似,但表示音频内容。
视频内容(Video Content)
VideoContent 与 ImageContent 类似,但表示视频内容。
PDF 文件内容(PDF File Content)
PdfFileContent 与 ImageContent 类似,但表示 PDF 文件的二进制内容。
Kotlin 扩展
ChatModel 的 Kotlin 扩展 提供了异步方法,用于在 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 的类型安全 builder DSL 提供了一种更简洁的构建方式。
它简化了 ChatRequest 对象的构建,同时内部使用 chatAsync 进行异步执行。
这种方式既简洁,又能通过协程提供非阻塞行为。