03-Message类型快速参考

0 阅读6分钟

快速查找表

Message 类型对照

┌─────────────────────────────────┐
│ ChatMessage (顶层接口)           │
└────────────┬────────────────────┘
             │
    ┌────────┼────────┬─────────────┐
    │        │        │             │
┌───▼──┐ ┌───▼──┐ ┌──▼───┐   ┌─────▼──────┐
│User  │ │System│ │AI    │   │ToolExecRes │
│      │ │      │ │      │   │            │
└──────┘ └──────┘ └──────┘   └────────────┘
 用户输入  系统指令  AI回复     工具结果

一句话速记

Message一句话定义何时用保存吗
UserMessage用户说的话用户提问时✅ 是
SystemMessage系统给LLM的指令对话开始时✅ 是(只1个)
AiMessageLLM的回复模型生成后✅ 是(多轮对话)
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分钟

推荐先快速浏览这份卡片,有问题时再去查完整文章。