跳到主要内容

聊天与语言模型

备注

本页描述的是低级 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

LangChain4j 将不再扩展对 LanguageModel 的支持, 因此在所有新功能中,我们将使用 ChatModel API。

ChatModel 是 LangChain4j 中与 LLM 交互的低级 API,提供最大的能力和灵活性。 还有一个高级 API(AI Services),我们将在介绍完基础知识后再讨论。

除了 ChatModelLanguageModel 之外,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、工具、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(...) // 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 实例。 这类似于接受 String 作为输入的第一个版本的 chat 方法。 这里的主要区别在于,它现在返回的不是 String,而是 ChatResponse。 除了 AiMessage 之外,ChatResponse 还包含 ChatResponseMetadataChatResponseMetadata 包含 TokenUsage,其中包含有关输入 (你提供给 generate 方法的所有 ChatMessage)包含多少 token、 作为输出生成了多少 token(在 AiMessage 中)以及总计(输入 + 输出)的统计信息。 你需要这些信息来计算对 LLM 的某次调用的成本。 然后,ChatResponseMetadata 还包含 FinishReason, 这是一个枚举,包含生成停止的各种原因。 通常,如果 LLM 自行决定停止生成,它将是 FinishReason.STOP

创建 UserMessage 有多种方式,具体取决于内容。 最简单的是 new UserMessage("Hi")UserMessage.from("Hi")

多个 ChatMessage

那么,为什么需要提供多个 ChatMessage 作为输入,而不是只有一个? 这是因为 LLM 本质上是无状态的,意味着它们不会维护对话状态。 因此,如果你想支持多轮对话,就应该自己负责管理对话状态。

假设你想构建一个聊天机器人。想象用户与聊天机器人(AI)之间的简单多轮对话:

  • 用户: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> contentsContent 是一个接口,具有以下实现:

  • 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);

文本内容

TextContent 是最简单的 Content 形式,表示纯文本并包装单个 StringUserMessage.from(TextContent.from("Hello!")) 等价于 UserMessage.from("Hello!")

可以在 UserMessage 中提供一个或多个 TextContent

UserMessage userMessage = UserMessage.from(
TextContent.from("Hello!"),
TextContent.from("How are you?")
);

图像内容

根据 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)来控制模型如何处理图像。 更多详情请参阅这里

音频内容

AudioContent 类似于 ImageContent,但表示音频内容。

视频内容

VideoContent 类似于 ImageContent,但表示视频内容。

PDF 文件内容

PdfFileContent 类似于 ImageContent,但表示 PDF 文件的二进制内容。

Kotlin 扩展

ChatModelKotlin 扩展 提供了用于处理与语言模型聊天交互的异步方法,利用了 Kotlin 的 协程 能力。chatAsync 方法允许对 ChatRequestChatRequest.Builder 配置进行非阻塞处理,并返回带有模型回复的 ChatResponse。类似地,generateAsync 处理来自聊天消息的异步响应生成。这些扩展简化了在 Kotlin 应用程序中构建聊天请求和高效处理对话的过程。请注意,这些方法被标记为实验性,可能会随时间演变。

ChatModel.chatAsync(request: ChatRequest):专为 Kotlin 协程设计,此异步扩展函数在使用 Dispatchers.IO 的协程作用域中包装同步的 chat 方法。这使得非阻塞操作成为可能,对于保持应用程序响应性至关重要。它特意命名为 chatAsync,以避免与现有的同步 chat 冲突。其函数签名为:suspend fun ChatModel.chatAsync(request: ChatRequest): ChatResponse。关键字 suspend 将其指定为协程函数。

ChatModel.chat(block: ChatRequestBuilder.() -> Unit):此 chat 变体通过使用 Kotlin 的类型安全构建器 DSL,提供了更精简的方法。它简化了 ChatRequest 对象的构建,同时在内部使用 chatAsync 进行异步执行。此版本通过协程同时提供了简洁性和非阻塞行为。