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 为空
模型 Thinkingmetadata.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));

三个必须注意的点:

  1. 每轮都要把 AssistantMessage 回填,否则模型"失忆"。
  2. 列表会不断膨胀 → 需要 窗口裁剪/摘要(ReactAgent 里对应 MessagesModelHook)。
  3. 生产环境请交给 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()

四条铁律

  1. chatModel.call(String) ≡ 单个 UserMessage,别指望拿到元数据。
  2. 多轮上下文靠自己拼 List<Message>,ChatModel 无状态。
  3. ToolResponse.id 必须和 ToolCall.id 对齐,否则模型无法关联结果。
  4. 多模态/元数据能力因厂商而异,换模型前务必查适配文档。

十一、对照本项目

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

可优化点:

  1. 接住 metadata 做成本核算:简历全文 + 长 JSON 输出 token 不低,应记录 getUsage().getTotalTokens() 并做预算告警。
  2. 回填历史实现追问:result.html 若要做"针对评估报告追问",正好用第八节的 conversationHistory 累加模式,以 resumeId 为 key 存起来。
  3. 多模态扩展:面试官"上传项目截图让 AI 点评",用 UserMessage.media(ClassPathResource/MultipartFile 转 Resource) 即可,无需改模型层。
  4. mutate 改写 SystemMessage:想做"Java / 前端 / 算法"多套面试模式时,用 systemMsg.mutate().text(...) 生成变体,避免重复写模板。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐