快速查找表
Message 类型对照
┌─────────────────────────────────┐
│ ChatMessage (顶层接口) │
└────────────┬────────────────────┘
│
┌────────┼────────┬─────────────┐
│ │ │ │
┌───▼──┐ ┌───▼──┐ ┌──▼───┐ ┌─────▼──────┐
│User │ │System│ │AI │ │ToolExecRes │
│ │ │ │ │ │ │ │
└──────┘ └──────┘ └──────┘ └────────────┘
用户输入 系统指令 AI回复 工具结果
一句话速记
| Message | 一句话定义 | 何时用 | 保存吗 |
|---|---|---|---|
| UserMessage | 用户说的话 | 用户提问时 | ✅ 是 |
| SystemMessage | 系统给LLM的指令 | 对话开始时 | ✅ 是(只1个) |
| AiMessage | LLM的回复 | 模型生成后 | ✅ 是(多轮对话) |
| ToolExecutionResultMessage | 工具执行的结果 | Agent调用工具后 | ✅ 是(Agent场景) |
代码模板速查
创建 Message
// UserMessage
UserMessage.from("用户的问题")
new UserMessage("用户的问题")
// SystemMessage
SystemMessage.from("系统指令")
new SystemMessage("系统指令")
// AiMessage(通常由模型生成)
AiMessage.from("AI的回复")
model.generate(messages).content() // ← 最常用
// ToolExecutionResultMessage(Agent中使用)
ToolExecutionResultMessage.from("工具名", "执行结果")
完整对话流程
// 1. 初始化消息列表
List<ChatMessage> messages = new ArrayList<>();
// 2. 添加系统消息(必须第一个)
messages.add(SystemMessage.from("你的角色"));
// 3. 添加用户消息
messages.add(UserMessage.from("用户问题"));
// 4. 获取AI回复
Response<AiMessage> response = model.generate(messages);
AiMessage aiMsg = response.content();
// 5. 保存AI消息(用于多轮对话)
messages.add(aiMsg);
// 6. 继续对话(重复3-5步)
messages.add(UserMessage.from("后续问题"));
// ...
使用场景决策树
需要多轮对话吗?
│
├─ 不需要(一次性问答)
│ └─ UserMessage + 模型调用 → 返回结果
│
└─ 需要(对话机器人、智能助手)
└─ 初始化
├─ SystemMessage(定义角色)
├─ UserMessage(第1个问题)
├─ AiMessage(保存回复)
│
└─ 循环
├─ UserMessage(后续问题)
├─ 模型调用(传入完整history)
├─ AiMessage(保存回复)
└─ 检查历史大小(防止过长)
常见错误和修正
❌ 错误1:SystemMessage 位置不对
// ❌ 错误
List<ChatMessage> messages = new ArrayList<>();
messages.add(UserMessage.from("..."));
messages.add(SystemMessage.from("你是...")); // ❌ 位置不对
// ✅ 正确
List<ChatMessage> messages = new ArrayList<>();
messages.add(SystemMessage.from("你是...")); // ✅ 必须第一个
messages.add(UserMessage.from("..."));
❌ 错误2:多个 SystemMessage
// ❌ 错误
messages.add(SystemMessage.from("角色A"));
messages.add(SystemMessage.from("角色B")); // ❌ 冲突
// ✅ 正确
messages.add(SystemMessage.from("""
角色A的定义
角色B的定义
""")); // ✅ 合并成一个
❌ 错误3:多轮对话时不保存 AiMessage
// ❌ 错误
messages.add(UserMessage.from("问题1"));
AiMessage response1 = model.generate(messages).content();
// 没有保存 response1!
messages.add(UserMessage.from("问题2"));
AiMessage response2 = model.generate(messages).content();
// response2 看不到问题1和回复1的context
// ✅ 正确
messages.add(UserMessage.from("问题1"));
AiMessage response1 = model.generate(messages).content();
messages.add(response1); // ✅ 保存
messages.add(UserMessage.from("问题2"));
AiMessage response2 = model.generate(messages).content();
// response2 能看到完整的对话历史
❌ 错误4:对话历史无限增长
// ❌ 错误
List<ChatMessage> history = new ArrayList<>();
// 每次都无条件添加消息
for (int i = 0; i < 1000; i++) {
history.add(UserMessage.from("问题" + i));
history.add(model.generate(history).content());
}
// 对话历史变成 2000+ 条,Token 爆炸!
// ✅ 正确
int MAX_HISTORY = 20;
// ...
if (history.size() > MAX_HISTORY) {
// 保留 SystemMessage + 最近的消息
SystemMessage sys = (SystemMessage) history.get(0);
history = history.subList(history.size() - MAX_HISTORY, history.size());
history.add(0, sys); // SystemMessage 回到最前面
}
AiMessage 详细讲解
什么是 AiMessage?
AiMessage 是 LLM 返回的回复消息,不仅包含文本,还包含:
AiMessage {
├─ text() → AI的文本回复
├─ toolCalls() → 工具调用列表(Agent场景)
└─ metadata() → 元数据(finish_reason等)
}
获取 AiMessage 的正确方式
// ✅ 标准方式
Response<AiMessage> response = model.generate(messages);
AiMessage aiMsg = response.content();
TokenUsage usage = response.tokenUsage(); // ← 一次获取!
// 获取关键信息
String text = aiMsg.text(); // 回复文本
int outputTokens = usage.outputTokens(); // 输出Token
List<ToolCall> toolCalls = aiMsg.toolCalls(); // 工具调用
// ❌ 避免重复调用
// response.tokenUsage(); // 不要多次调用
// response.content(); // 不要多次调用
AiMessage 的三个常见场景
场景1:简单问答(一次性,不保存)
// 用户问一个问题,LLM 回答,完成
Response<AiMessage> response = model.generate(
List.of(UserMessage.from("2+2等于多少?"))
);
System.out.println(response.content().text()); // 输出:4
// ❌ 不保存 AiMessage
场景2:多轮对话(必须保存)
List<ChatMessage> history = new ArrayList<>();
history.add(SystemMessage.from("你是数学家"));
// 第1轮
history.add(UserMessage.from("什么是质数?"));
AiMessage resp1 = model.generate(history).content();
history.add(resp1); // ✅ 保存!
// 第2轮(能理解第1轮的对话)
history.add(UserMessage.from("最小的质数是什么?"));
AiMessage resp2 = model.generate(history).content();
history.add(resp2); // ✅ 保存!
// → resp2 包含第1轮的上下文
场景3:Agent 中处理工具调用(完整流程)
关键步骤:
// 1️⃣ 定义工具和消息
List<Tool> tools = Arrays.asList(
Tool.function("add", "加法", ...),
Tool.function("multiply", "乘法", ...)
);
List<ChatMessage> messages = new ArrayList<>();
messages.add(SystemMessage.from("你可以使用计算工具"));
messages.add(UserMessage.from("2加3乘以4等于多少?"));
// 2️⃣ 调用模型(第1轮)
Response<AiMessage> response1 = model.generateWithTools(messages, tools);
AiMessage aiMsg1 = response1.content();
messages.add(aiMsg1); // 保存 LLM 的响应
// 3️⃣ 检查 finish_reason
if ("tool_calls".equals(getFinishReason(aiMsg1))) {
// LLM 想调用工具
// 4️⃣ 执行工具
for (ToolCall toolCall : aiMsg1.toolCalls()) {
String toolName = toolCall.id(); // "add"
String argsJson = toolCall.arguments(); // "{\"arg0\":2,\"arg1\":3}"
String result = executeTool(toolName, argsJson); // "5"
// 5️⃣ 添加工具结果
messages.add(ToolExecutionResultMessage.from(toolName, result));
}
// 6️⃣ 继续调用模型(第2轮,LLM 看到工具结果)
Response<AiMessage> response2 = model.generateWithTools(messages, tools);
AiMessage aiMsg2 = response2.content();
messages.add(aiMsg2);
// 可能继续循环,直到 finish_reason = "stop"
return aiMsg2.text();
} else {
// LLM 直接生成了答案
return aiMsg1.text();
}
API 响应示例:
{
"choices": [{
"finish_reason": "tool_calls",
"message": {
"tool_calls": [{
"function": {
"name": "add",
"arguments": "{\"arg0\":2,\"arg1\":3}"
}
}]
}
}]
}
三个关键的 finish_reason 值:
"stop"→ LLM 正常完成,生成了最终答案"tool_calls"→ LLM 想调用工具(有 toolCalls 数组)"length"→ 达到最大 token 限制
AiMessage 的最常见错误
❌ 错误:忘记保存 AiMessage
messages.add(UserMessage.from("问题1"));
AiMessage resp1 = model.generate(messages).content();
// 没有 messages.add(resp1)! ← 错误!
messages.add(UserMessage.from("问题2"));
AiMessage resp2 = model.generate(messages).content();
// resp2 看不到问题1的历史,上下文丢失
✅ 正确做法:始终保存
messages.add(UserMessage.from("问题1"));
AiMessage resp1 = model.generate(messages).content();
messages.add(resp1); // ✅ 保存
messages.add(UserMessage.from("问题2"));
AiMessage resp2 = model.generate(messages).content();
messages.add(resp2); // ✅ 保存
// → resp2 能看到完整历史
AiMessage 的 Token 成本
你问一个问题:
- 输入 Token = Prompt 的长度
- 输出 Token = AiMessage 的长度
- 总成本 = (输入Token + 输出Token) × 价格/Token
例如(GPT-4o-mini):
- 输入100字 ≈ 130 tokens × $0.15/1M = $0.00002
- 输出100字 ≈ 130 tokens × $0.60/1M = $0.00008
- 总成本 ≈ $0.0001
如何优化 AiMessage 对成本的影响
| 策略 | 说明 |
|---|---|
| 简化 SystemMessage | 少说废话,核心指令即可 |
| 定长历史 | 只保留最近10-20条消息 |
| 使用便宜模型 | gpt-4o-mini 比 gpt-4o 便宜5倍 |
| 压缩上下文 | 只发送必要的历史 |
Token 成本对比
不同的 Message 结构,Token 消耗不同
// 简单版:~50 tokens
messages.add(SystemMessage.from("你是助手"));
messages.add(UserMessage.from("2+2=?"));
// 详细版:~350 tokens
messages.add(SystemMessage.from("""
你是一位数学教师...(300字详细描述)
"""));
messages.add(UserMessage.from("2+2=?"));
// 对话版(保留完整历史):~1000+ tokens
messages.add(SystemMessage.from("你是..."));
messages.add(UserMessage.from("问题1"));
messages.add(AiMessage.from("回答1"));
messages.add(UserMessage.from("问题2"));
messages.add(AiMessage.from("回答2"));
// ... 更多对话 ...
messages.add(UserMessage.from("问题N"));
成本优化建议
优化前 → 优化后
500 tokens → 100 tokens 减少80%成本 ✅
方法:
1. 简化 SystemMessage(保留核心信息)
2. 定长历史(只保留最近10-20条)
3. 使用摘要替代完整历史(高级用法)
4. 合并简单的消息(避免碎片化)
Message 在生产系统中的最佳实践
@Service
public class ProductionMessageService {
private static final int MAX_HISTORY_SIZE = 15;
private static final Logger logger = LoggerFactory.getLogger(ProductionMessageService.class);
/**
* ✅ 生产级别的消息管理
*/
public String chat(String sessionId, String userInput) {
// 1. 从存储加载历史
List<ChatMessage> history = loadHistoryFromDatabase(sessionId);
// 2. 验证历史
if (history.isEmpty()) {
history.add(createSystemMessage());
}
// 3. 添加用户消息
history.add(UserMessage.from(userInput));
// 4. 调用模型(带超时和重试)
AiMessage aiMsg = callModelWithRetry(history);
// 5. 保存消息到历史
history.add(aiMsg);
// 6. 管理历史大小
trimHistory(history);
// 7. 持久化到数据库
saveHistoryToDatabase(sessionId, history);
logger.info("Session {} completed", sessionId);
return aiMsg.text();
}
private void trimHistory(List<ChatMessage> history) {
if (history.size() > MAX_HISTORY_SIZE) {
SystemMessage sys = (SystemMessage) history.get(0);
List<ChatMessage> trimmed = history.subList(
history.size() - MAX_HISTORY_SIZE + 1,
history.size()
);
history.clear();
history.add(sys);
history.addAll(trimmed);
}
}
}
Message vs Prompt 的关系
Prompt(字符串) Message(对象)
↓ ↓
"你是助手\n请帮我..." → SystemMessage("你是助手")
+ UserMessage("请帮我...")
优势:
- Prompt:简单直接
- Message:结构化、易管理、支持多轮对话
下一步学习
相关内容在 [[04-Prompt工程最佳实践|完整文章]] 中:
- 详细讲解:见第 "LangChain4J Message 类型深度讲解" 部分
- 完整代码:见第 "Message 在不同场景中的应用" 部分
- 常见问题:见常见问题解答 Q6-Q10
- 实战例子:见 "Message 实战:构建聊天机器人" 部分
快速版本:这份参考卡只需5分钟理解 完整学习:去读完整文章需要30分钟
推荐先快速浏览这份卡片,有问题时再去查完整文章。