三、Spring AI Alibaba · Messages
Spring AI Alibaba · Messages 消息
整理时间:2026-10-09
适用版本:Spring AI 1.1.0 / Spring AI Alibaba 1.1.2.x
一、什么是 Message
Message 是模型交互的基本单元,代表模型的输入与输出,承载对话状态所需的内容和元数据。Spring AI Alibaba 提供跨厂商一致的标准消息类型系统。
一个 Message 由三部分组成:
| 组成 | 说明 |
|---|---|
| Role(角色) | 标识消息类型:system / user / assistant / tool |
| Content(内容) | 实际内容:文本、图像、音频、文档等 |
| Metadata(元数据) | 可选:响应信息、消息 ID、token 使用情况 |
注意:并非所有厂商都对三者一视同仁,尤其 metadata 的行为因提供商而异(有的用于用户识别,有的直接忽略),使用前需查对应厂商文档。
二、两种调用方式:文本提示 vs 消息提示
文本提示(String)
String response = chatModel.call("写一首关于春天的俳句");
适用场景:
- 单个独立请求
- 不需要对话历史
- 追求最小代码复杂度
本质是单个 UserMessage 的快捷方式,拿不到 metadata / token 用量。
消息提示(List<Message>)
List<Message> messages = List.of(
new SystemMessage("你是一个诗歌专家"),
new UserMessage("写一首关于春天的俳句"),
new AssistantMessage("樱花盛开时...")
);
Prompt prompt = new Prompt(messages);
ChatResponse response = chatModel.call(prompt);
适用场景:
- 管理多轮对话
- 处理多模态内容(图像 / 音频 / 文件)
- 需要携带系统指令
三、四种消息类型
3.1 SystemMessage —— 定规则、设人设
用于设定语气、定义角色、建立响应指南。
// 基础指令
SystemMessage systemMsg = new SystemMessage("你是一个有帮助的编程助手。");
// 详细角色设定(推荐用文本块)
SystemMessage systemMsg = new SystemMessage("""
你是一位资深的 Java 开发者,擅长 Web 框架。
始终提供代码示例并解释你的推理。
在解释中要简洁但透彻。
""");
本项目
MockInterviewService就是这一模式的典型:SystemMessage(prompt资源)+UserMessage(简历/问答)。
3.2 UserMessage —— 用户输入与多模态载体
纯文本
ChatResponse response = chatModel.call(new Prompt(List.of(new UserMessage("什么是机器学习?"))));
String shortcut = chatModel.call("什么是机器学习?"); // 等价快捷方式
带元数据
UserMessage userMsg = UserMessage.builder()
.text("你好!")
.metadata(Map.of(
"user_id", "alice", // 用户标识
"session_id", "sess_123" // 会话标识
))
.build();
多模态(Media)
UserMessage userMsg = UserMessage.builder()
.text("描述这张图片的内容。")
.media(Media.builder()
.mimeType(MimeTypeUtils.IMAGE_JPEG)
.data(new URL("https://example.com/image.jpg")) // 也支持 ClassPathResource
.build())
.build();
3.3 AssistantMessage —— 模型输出
模型调用返回的结果,包含文本内容、工具调用、媒体内容与厂商元数据。
ChatResponse response = chatModel.call(new Prompt("解释 AI"));
AssistantMessage aiMessage = response.getResult().getOutput();
System.out.println(aiMessage.getText());
四要素
| 属性 | 说明 |
|---|---|
text | 消息文本内容 |
metadata | 元数据映射(含厂商特有信息,如 reasoningContent) |
toolCalls | 模型发起的工具调用列表 |
media | 媒体内容列表(若有) |
手动构造回填历史(对话编排/纠偏常用)
AssistantMessage aiMsg = new AssistantMessage("我很乐意帮助你回答这个问题!");
List<Message> messages = List.of(
new SystemMessage("你是一个有帮助的助手"),
new UserMessage("你能帮我吗?"),
aiMsg, // 插入,就像它来自模型一样
new UserMessage("太好了!2+2 等于多少?")
);
ChatResponse response = chatModel.call(new Prompt(messages));
不同厂商对消息权重的处理方式不同,手动插入 AssistantMessage 是控制对话走向的实用技巧。
工具调用读取
AssistantMessage aiMessage = response.getResult().getOutput();
if (aiMessage.hasToolCalls()) {
for (AssistantMessage.ToolCall toolCall : aiMessage.getToolCalls()) {
System.out.println("Tool: " + toolCall.name());
System.out.println("Args: " + toolCall.arguments());
System.out.println("ID: " + toolCall.id());
}
}
3.4 ToolResponseMessage —— 工具执行结果回传
用于把单个工具执行的结果回传给模型,让 LLM 接着推理。
// 1) 模型发出工具调用
AssistantMessage aiMessage = AssistantMessage.builder()
.content("")
.toolCalls(List.of(new AssistantMessage.ToolCall(
"call_123", "tool", "get_weather", "{\"location\": \"San Francisco\"}")))
.build();
// 2) 执行工具,构造结果消息
ToolResponseMessage toolMessage = ToolResponseMessage.builder()
.responses(List.of(new ToolResponse("call_123", "get_weather", "晴朗,22°C")))
.build();
// 3) 继续对话
List<Message> messages = List.of(
new UserMessage("旧金山的天气怎么样?"),
aiMessage, // 模型的工具调用
toolMessage // 工具执行结果
);
ChatResponse response = chatModel.call(new Prompt(messages));
ToolResponse 三要素
| 字段 | 说明 |
|---|---|
id | 工具调用 ID,必须与 AssistantMessage 中的 toolCall.id 匹配 |
name | 调用的工具名称 |
responseData | 工具输出的字符串化结果 |
这就是 ReactAgent「推理 → 行动 → 观察」中 Observation 环节的底层形式。
四、Token 使用统计
ChatResponse 的 metadata 中保存 token 计数与使用信息:
ChatResponse response = chatModel.call(new Prompt("你好!"));
ChatResponseMetadata metadata = response.getMetadata();
if (metadata != null && metadata.getUsage() != null) {
System.out.println("Input tokens: " + metadata.getUsage().getPromptTokens());
System.out.println("Output tokens: " + metadata.getUsage().getCompletionTokens());
System.out.println("Total tokens: " + metadata.getUsage().getTotalTokens());
}
成本治理的第一步:别把
ChatResponse丢掉。写getResult().getOutput().getText()的链式调用会让中间的 metadata 无从获取。
五、流式与块(Chunk)
流式期间收到的每个 ChatResponse 是消息的片段,需要自己拼接:
Flux<ChatResponse> responseStream = chatModel.stream(new Prompt("你好"));
StringBuilder fullResponse = new StringBuilder();
responseStream.subscribe(chunk -> {
String content = chunk.getResult().getOutput().getText();
fullResponse.append(content);
System.out.print(content);
});
流式下的消息类型
| 场景 | 怎么取内容 |
|---|---|
| 模型普通响应 | AssistantMessage.getText(),且 metadata.reasoningContent 为空 |
| 模型 Thinking | metadata.reasoningContent 非空(如 DeepSeek / qwen 深度思考) |
| 工具调用请求 | AssistantMessage.hasToolCalls() == true |
| 工具执行结果 | ToolResponseMessage.getResponses() → responseData() |
六、多模态输入
统一通过 Media(org.springframework.ai.content.Media)+ MIME 类型承载。
// 图片 - URL
.media(Media.builder().mimeType(MimeTypeUtils.IMAGE_JPEG)
.data(new URL("https://example.com/image.jpg")).build())
// 图片 - 本地/类路径资源
.media(new Media(MimeTypeUtils.IMAGE_JPEG, new ClassPathResource("images/photo.jpg")))
// 音频
.media(new Media(MimeTypeUtils.parseMimeType("audio/wav"),
new ClassPathResource("audio/recording.wav")))
// 视频
.media(Media.builder().mimeType(MimeTypeUtils.parseMimeType("video/mp4"))
.data(new URL("https://example.com/path/to/video.mp4")).build())
警告:并非所有模型支持所有文件类型,需查厂商文档确认格式与大小限制。(对照前表:通义千问 DashScope 在官方能力矩阵中未标注多模态支持,OpenAI / Gemini / Ollama 支持。)
七、实用 API:Builder / copy / mutate
// UserMessage builder
UserMessage userMsg = UserMessage.builder()
.text("你好,我想学习 Spring AI Alibaba")
.metadata(Map.of("user_id", "user_123"))
.build();
// SystemMessage builder
SystemMessage systemMsg = SystemMessage.builder()
.text("你是一个 Spring 框架专家")
.metadata(Map.of("version", "1.0"))
.build();
// AssistantMessage builder
AssistantMessage assistantMsg = AssistantMessage.builder()
.content("我很乐意帮助你学习 Spring AI Alibaba!")
.build();
// 复制
UserMessage copy = original.copy();
// 基于副本修改(原对象不变)
UserMessage modified = original.mutate()
.text("修改后的消息")
.metadata(Map.of("modified", true))
.build();
mutate()是不可变风格的改造入口,适合在拦截器/钩子中改写消息而不污染原列表。
八、多轮对话(ChatModel 是无状态的)
ChatModel 交互天然无状态,简单对话循环 = 不断变长的消息列表:
List<Message> conversationHistory = new ArrayList<>();
conversationHistory.add(new UserMessage("你好!"));
ChatResponse response1 = chatModel.call(new Prompt(conversationHistory));
conversationHistory.add(response1.getResult().getOutput()); // 把 AI 回复回填
conversationHistory.add(new UserMessage("你能帮我学习 Java 吗?"));
ChatResponse response2 = chatModel.call(new Prompt(conversationHistory));
conversationHistory.add(response2.getResult().getOutput());
conversationHistory.add(new UserMessage("从哪里开始?"));
ChatResponse response3 = chatModel.call(new Prompt(conversationHistory));
三个必须注意的点:
- 每轮都要把
AssistantMessage回填,否则模型"失忆"。 - 列表会不断膨胀 → 需要 窗口裁剪/摘要(ReactAgent 里对应
MessagesModelHook)。 - 生产环境请交给
ChatMemory或 Agent 的Saver(MemorySaver / RedisSaver)托管。
九、在 ReactAgent 中使用 Message
ReactAgent 自动管理消息历史,但也接受直接传消息:
ReactAgent agent = ReactAgent.builder()
.name("my_agent")
.model(chatModel)
.systemPrompt("你是一个有帮助的助手")
.build();
AssistantMessage r1 = agent.call("你好"); // 字符串
AssistantMessage r2 = agent.call(new UserMessage("帮我写一首诗")); // 单条 UserMessage
List<Message> messages = List.of(
new UserMessage("我喜欢春天"),
new UserMessage("写一首关于春天的诗")
);
AssistantMessage r3 = agent.call(messages); // 消息列表
十、速查表
| 类型 | 谁产生 | 关键 API |
|---|---|---|
SystemMessage | 开发者 | .text(),多写角色+规则+输出格式 |
UserMessage | 用户/系统 | .text() .media() .metadata() |
AssistantMessage | 模型 | .getText() .hasToolCalls() .getToolCalls() .getMetadata() |
ToolResponseMessage | 工具执行侧 | .builder().responses(List.of(new ToolResponse(id, name, data))) |
// 取值链路
ChatResponse → getMetadata() // token 用量
→ getResult() // Generation
→ getOutput() // AssistantMessage
→ getText() / hasToolCalls() / getMetadata()
四条铁律
chatModel.call(String)≡ 单个 UserMessage,别指望拿到元数据。- 多轮上下文靠自己拼
List<Message>,ChatModel 无状态。 ToolResponse.id必须和ToolCall.id对齐,否则模型无法关联结果。- 多模态/元数据能力因厂商而异,换模型前务必查适配文档。
十一、对照本项目
MockInterviewService 的三次调用都是标准的「System + User → Assistant」结构:
messages.add(new SystemMessage(resumeAnalysisSystemPromptResource)); // 资源注入的系统提示
messages.add(new UserMessage(promptTemplate.render(Map.of("resumeText", resumeText))));
Prompt prompt = new Prompt(messages, DashScopeChatOptions.builder().temperature(0.7).build());
String response = chatModel.call(prompt).getResult().getOutput().getText();
可优化点:
- 接住 metadata 做成本核算:简历全文 + 长 JSON 输出 token 不低,应记录
getUsage().getTotalTokens()并做预算告警。 - 回填历史实现追问:
result.html若要做"针对评估报告追问",正好用第八节的conversationHistory累加模式,以resumeId为 key 存起来。 - 多模态扩展:面试官"上传项目截图让 AI 点评",用
UserMessage.media(ClassPathResource/MultipartFile 转 Resource)即可,无需改模型层。 - mutate 改写 SystemMessage:想做"Java / 前端 / 算法"多套面试模式时,用
systemMsg.mutate().text(...)生成变体,避免重复写模板。
更多推荐


所有评论(0)