聊天与语言模型
注
本页面描述的是一个低层级的 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
是一个接口,具有以下实现:
TextContent
ImageContent
AudioContent
VideoContent
PdfFileContent
你可以在 这里 的对比表中查看哪些 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
进行异步执行。
这种方式既简洁,又能通过协程提供非阻塞行为。