LangChain4j 核心抽象:ChatMessage、UserMessage 与模型无关设计
前言
LangChain4j 的核心设计理念是"模型无关"------通过统一的抽象层,让开发者能够轻松切换不同的 LLM 提供商(OpenAI、Ollama、Anthropic 等),而无需重写业务代码。这一设计的基石就是 ChatMessage 体系。
本文将深入剖析 LangChain4j 的核心抽象,包括消息类型体系、Content 抽象、多模态支持,以及如何实现模型提供商的无缝切换。
一、消息类型体系
1.1 ChatMessage 层级结构
LangChain4j 定义了完整的消息类型继承体系:
ChatMessage (抽象基类)
│
├── SystemMessage # 系统消息
├── UserMessage # 用户消息
├── AiMessage # AI 响应消息
├── ToolExecutionResultMessage # 工具执行结果消息
└── Content ... # 消息内容抽象
1.2 消息类型详解
1.2.1 SystemMessage(系统消息)
用于定义 AI 的角色和行为规则。
java
import dev.langchain4j.data.message.SystemMessage;
// 创建系统消息
SystemMessage systemMessage = SystemMessage.from(
"你是一个专业的Java技术顾问,擅长解答Spring Boot相关问题"
);
// 在 AI Service 中使用
@SystemMessage("你是一个专业的Java技术顾问")
public interface Assistant {
String chat(String userMessage);
}
使用场景:
- 定义 AI 角色(如"你是一个客服助手")
- 设定输出格式(如"请以JSON格式返回")
- 限制行为范围(如"只回答技术问题")
1.2.2 UserMessage(用户消息)
表示用户的输入,支持多模态内容(文本 + 图像)。
java
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.data.message.ImageContent;
import dev.langchain4j.data.message.TextContent;
// 纯文本消息
UserMessage textMessage = UserMessage.from("什么是Spring Boot?");
// 图文混合消息(1.4.0 新特性)
UserMessage multiModalMessage = UserMessage.from(
ImageContent.from("https://example.com/screenshot.png"),
TextContent.from("请解释这张截图中的错误信息")
);
使用场景:
- 用户提问
- 多模态输入(文本 + 图像/音频/视频)
- 历史上下文传递
1.2.3 AiMessage(AI 响应消息)
表示 LLM 的响应内容。
java
import dev.langchain4j.data.message.AiMessage;
// 创建 AI 消息
AiMessage aiMessage = AiMessage.from(
"Spring Boot 是基于 Spring 框架的快速开发框架..."
);
// 获取工具调用(Function Calling)
if (aiMessage.hasToolExecutionRequests()) {
List<ToolExecutionRequest> requests = aiMessage.toolExecutionRequests();
for (ToolExecutionRequest request : requests) {
System.out.println("调用工具: " + request.name());
System.out.println("参数: " + request.arguments());
}
}
使用场景:
- LLM 文本响应
- 工具调用请求
- 保存对话历史
1.2.4 ToolExecutionResultMessage(工具执行结果)
表示工具执行后的返回值。
java
import dev.langchain4j.data.message.ToolExecutionResultMessage;
// 创建工具执行结果消息
ToolExecutionResultMessage resultMessage = ToolExecutionResultMessage.from(
"tool-execution-id-123",
"查询结果:北京今天气温 25°C"
);
// 在对话中使用
List<ChatMessage> messages = new ArrayList<>();
messages.add(UserMessage.from("北京今天天气如何?"));
messages.add(AiMessage.from("tool-execution-id-123", null)); // AI 调用工具
messages.add(resultMessage); // 工具执行结果
String response = model.generate(messages);
使用场景:
- Function Calling 流程
- 多步骤工具调用
- Agent 决策循环
1.3 消息流转图
┌─────────────────────────────────────────────────────────────┐
│ ChatMessage 消息流转 │
└─────────────────────────────────────────────────────────────┘
用户 LLM 工具系统
│ │ │
│ 1. UserMessage │ │
├─────────────────────>│ │
│ "北京天气?" │ │
│ │ │
│ │ 2. 生成工具调用请求 │
│ ├────────────────────>│
│ │ ToolExecutionRequest│
│ │ │
│ │ 3. 执行工具 │
│ │<─────────────────────┤
│ │ 返回结果 │
│ │ │
│ │ 4. 生成最终响应 │
│<─────────────────────┤ │
│ 5. AiMessage │ │
│ "北京今天25°C" │ │
│ │ │
└──────────────────────┴───────────────────────┘
对话历史保存:
ChatMemory
├── MessageWindowChatMemory(窗口滑动)
└── TokenWindowChatMemory(Token 预算)
二、Content 抽象:多模态支持
2.1 Content 层级结构
LangChain4j 1.4.0 引入了统一的 Content 抽象,支持多模态输入:
Content (接口)
│
├── TextContent # 文本内容
├── ImageContent # 图像内容(1.4.0 新增)
├── AudioContent # 音频内容(预留)
└── VideoContent # 视频内容(预留)
2.2 TextContent(文本内容)
java
import dev.langchain4j.data.message.TextContent;
// 创建文本内容
TextContent textContent = TextContent.from("Hello, LangChain4j!");
// UserMessage 支持多个 TextContent
UserMessage message = UserMessage.from(
TextContent.from("问题1:什么是Spring Boot?"),
TextContent.from("问题2:如何快速开始?")
);
2.3 ImageContent(图像内容)
1.4.0 新增特性,支持图像输入,适配 GPT-4V、Claude-3V 等视觉模型。
java
import dev.langchain4j.data.message.ImageContent;
// 从 URL 创建
ImageContent fromUrl = ImageContent.from("https://example.com/image.jpg");
// 从 Base64 创建
ImageContent fromBase64 = ImageContent.from(
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
);
// 从字节数组创建
byte[] imageBytes = Files.readAllBytes(Paths.get("screenshot.png"));
ImageContent fromBytes = ImageContent.from(imageBytes);
// 图文混合输入
UserMessage visionMessage = UserMessage.from(
ImageContent.from("https://example.com/code.png"),
TextContent.from("请解释这段代码的功能")
);
// 调用支持视觉的模型
String response = gpt4VisionModel.generate(visionMessage);
2.4 多模态组合示例
java
// 示例:图文混合同步查询
UserMessage complexQuery = UserMessage.from(
TextContent.from("请分析以下内容:"),
ImageContent.from("https://example.com/diagram.png"),
TextContent.from("图表中的数据趋势如何?")
);
String analysis = model.generate(complexQuery);
// 示例:多图对比
UserMessage comparison = UserMessage.from(
TextContent.from("对比这两张图片的差异:"),
ImageContent.from("https://example.com/before.png"),
ImageContent.from("https://example.com/after.png")
);
String diff = model.generate(comparison);
三、模型提供商切换
3.1 统一接口设计
LangChain4j 提供了统一的模型接口,支持无缝切换提供商:
java
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.anthropic.AnthropicChatModel;
import dev.langchain4j.model.ollama.OllamaChatModel;
// 统一接口
ChatLanguageModel model;
// 切换到 OpenAI
model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o")
.build();
// 切换到 Anthropic Claude
model = AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName("claude-3-sonnet-20240229")
.build();
// 切换到 Ollama(本地模型)
model = OllamaChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("llama3.1")
.build();
// 使用相同的代码调用
String response = model.generate("你好");
3.2 配置差异对比
| 配置项 | OpenAI | Anthropic | Ollama |
|---|---|---|---|
| API Key | ✅ 必需 | ✅ 必需 | ❌ 不需要 |
| Base URL | 默认 | 默认 | 必需(http://localhost:11434) |
| Model Name | gpt-4o, gpt-3.5-turbo | claude-3-sonnet, claude-3-opus | llama3.1, qwen2 |
| 温度 | 支持 | 支持 | 支持 |
| 流式响应 | 支持 | 支持 | 支持 |
| 多模态 | gpt-4-vision-preview | claude-3-opus-20240229 | 依赖模型 |
| 成本 | 按用量计费 | 按用量计费 | 免费(本地) |
3.3 配置工厂模式
java
public class ModelFactory {
public static ChatLanguageModel createModel(String provider, String modelName) {
switch (provider.toLowerCase()) {
case "openai":
return OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName(modelName)
.build();
case "anthropic":
return AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName(modelName)
.build();
case "ollama":
return OllamaChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName(modelName)
.build();
default:
throw new IllegalArgumentException("Unsupported provider: " + provider);
}
}
}
// 使用配置工厂
ChatLanguageModel model = ModelFactory.createModel("openai", "gpt-4o");
String response = model.generate("你好");
3.4 模型切换流程图
┌─────────────────────────────────────────────────────────────┐
│ 模型提供商切换流程 │
└─────────────────────────────────────────────────────────────┘
┌─────────────┐ ┌──────────────┐ ┌─────────────────┐
│ 配置源 │ -> │ ModelFactory │ -> │ ChatLanguageModel │
│ 配置文件 │ │ 创建模型 │ │ 统一接口 │
│ 环境变量 │ │ │ │ │
└─────────────┘ └──────────────┘ └────────┬────────┘
│
┌──────────────────────┼──────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ OpenAI │ │ Anthropic │ │ Ollama │
│ gpt-4o │ │ claude-3 │ │ llama3.1 │
│ 云端API │ │ 云端API │ │ 本地部署 │
└───────────────┘ └───────────────┘ └───────────────┘
│ │ │
└──────────────────────┼──────────────────────┘
│
▼
┌──────────────┐
│ 统一调用接口 │
│ generate() │
└──────────────┘
│
▼
┌──────────────┐
│ 返回结果 │
│ String │
└──────────────┘
四、通用参数标准化
4.1 温度(Temperature)
控制输出的随机性,范围 0.0-2.0。
java
OpenAiChatModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o")
.temperature(0.7) // 默认值 0.7
.build();
// 温度值解释
// 0.0 - 0.2:确定性输出,适合代码生成、事实问答
// 0.3 - 0.7:平衡输出,适合大多数场景
// 0.8 - 1.5:创造性输出,适合故事创作、头脑风暴
// 1.6 - 2.0:高度随机,适合生成多样内容
4.2 Token 限制(Max Tokens)
控制输出的最大长度。
java
OpenAiChatModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o")
.maxTokens(1000) // 默认值因模型而异
.build();
// Token 计算参考
// 1 Token ≈ 4 英文字符 或 2 个汉字
// 1000 Tokens ≈ 750 英文单词 或 500 汉字
4.3 超时(Timeout)
控制请求的超时时间。
java
OpenAiChatModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o")
.timeout(Duration.ofSeconds(60)) // 默认 60 秒
.build();
// 超时处理策略
try {
String response = model.generate("长文本生成任务...");
} catch (TimeoutException e) {
System.err.println("请求超时,请重试或调整超时时间");
}
4.4 停止序列(Stop Sequences)
定义输出停止的标记。
java
OpenAiChatModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o")
.stop_sequences("\n\n", "###") // 遇到这些标记停止生成
.build();
4.5 Top-P 与 Top-K
java
OpenAiChatModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o")
.topP(0.9) // 核采样,范围 0.0-1.0,默认 1.0
.topK(40) // 选择前 K 个词,仅部分模型支持
.build();
五、实战示例:多模型问答系统
5.1 定义统一接口
java
import dev.langchain4j.service.AiService;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
@AiService
public interface QuestionAssistant {
@SystemMessage("你是一个知识渊博的助手,用简洁的语言回答问题")
String answer(@UserMessage String question);
}
5.2 构建多模型实例
java
public class MultiModelSystem {
private final Map<String, QuestionAssistant> assistants = new HashMap<>();
public void init() {
// OpenAI 实例
ChatLanguageModel openaiModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o")
.build();
QuestionAssistant openaiAssistant = AiServices.builder(QuestionAssistant.class)
.chatLanguageModel(openaiModel)
.build();
assistants.put("openai", openaiAssistant);
// Ollama 实例
ChatLanguageModel ollamaModel = OllamaChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("llama3.1")
.build();
QuestionAssistant ollamaAssistant = AiServices.builder(QuestionAssistant.class)
.chatLanguageModel(ollamaModel)
.build();
assistants.put("ollama", ollamaAssistant);
}
public String answer(String provider, String question) {
QuestionAssistant assistant = assistants.get(provider);
if (assistant == null) {
throw new IllegalArgumentException("Unknown provider: " + provider);
}
return assistant.answer(question);
}
}
5.3 使用示例
java
public class Main {
public static void main(String[] args) {
MultiModelSystem system = new MultiModelSystem();
system.init();
String question = "什么是 LangChain4j?";
// 使用 OpenAI 回答
String openaiAnswer = system.answer("openai", question);
System.out.println("OpenAI: " + openaiAnswer);
// 使用 Ollama 回答
String ollamaAnswer = system.answer("ollama", question);
System.out.println("Ollama: " + ollamaAnswer);
}
}
六、常见问题
Q1: 如何处理不同模型的 Token 限制差异?
A: 使用 maxTokens 参数或自动调整:
java
int maxTokens = switch (provider) {
case "openai" -> 4096;
case "anthropic" -> 4096;
case "ollama" -> 2048; // 本地模型通常限制较小
default -> 1024;
};
Q2: 多模态输入支持哪些模型?
A: 当前支持以下视觉模型:
- OpenAI: gpt-4-vision-preview, gpt-4o
- Anthropic: claude-3-opus-20240229, claude-3-sonnet-20240229
- Google AI: gemini-pro-vision
Q3: 如何迁移现有代码到新模型?
A: 只需修改模型创建代码,业务逻辑无需改动:
java
// 旧代码
ChatLanguageModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.build();
// 新代码:切换到 Anthropic
ChatLanguageModel model = AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.build();
// 业务逻辑无需改动
String response = model.generate("你好");
七、小结
本文深入剖析了 LangChain4j 的核心抽象,包括:
- 消息类型体系:SystemMessage、UserMessage、AiMessage、ToolExecutionResultMessage
- Content 抽象:TextContent、ImageContent 支持多模态输入
- 模型提供商切换:统一接口设计,支持无缝切换 OpenAI、Anthropic、Ollama
- 通用参数标准化:温度、Token 限制、超时、停止序列等
- 实战示例:多模型问答系统
核心思想: 通过统一的抽象层,实现"模型无关"的设计,让开发者能够灵活切换 LLM 提供商,而无需重写业务代码。
下一步学习:
- 文章 3:《LangChain4j 异常处理与测试框架:企业级健壮性实践》
参考文献:
- LangChain4j 官方文档:https://docs.langchain4j.dev/
- OpenAI API 文档:https://platform.openai.com/docs/api-reference/chat
- Anthropic Claude 文档:https://docs.anthropic.com/claude/reference/messages