摘要
Spring AI 通过消息(Message)抽象统一了与大模型的对话载体,将系统指令、用户输入、助手回复和工具调用结果分别封装为四个独立的消息类。本文深入解析 SystemMessage、UserMessage、AssistantMessage 和 ToolResponseMessage 的底层设计、作用边界与配合方式,并结合 Spring AI Alibaba DashScope 给出从环境配置到多轮对话、Function Calling 完整闭环的代码实践,助你彻底吃透 Spring AI 的提示词工程核心。
1. 四大角色整体设计
Spring AI 将与模型的一次完整交互抽象为 Message 接口,并针对对话中不同来源和用途的内容设计了四个具体的实现类。每条消息都携带一个专属的 Role 标识,大模型通过这一角色区分语义边界,从而彻底告别混乱的字符串拼接。
| 角色类 | 枚举类型 | 核心作用 | 使用位置与特性 |
|---|---|---|---|
SystemMessage |
SYSTEM |
全局系统指令,定义 AI 身份、输出规范、行为约束 | 消息列表首位,全局永久生效,不被用户覆盖 |
UserMessage |
USER |
用户原始提问、业务输入、多模态内容 | 对话输入源,动态可变业务数据存放处 |
AssistantMessage |
ASSISTANT |
大模型历史回答、工具调用请求载体 | 多轮上下文核心,可携带 ToolCall 指令 |
ToolResponseMessage |
TOOL |
外部工具执行结果 | Function Calling 闭环必备,补齐数据后回传 |
这四个角色的有序组合构成了 Prompt(提示词),最终交给模型推理。下面逐一拆解每个角色的设计意图和正确使用方式。
2. 角色详解与使用规范
2.1 SystemMessage ------ 全局指挥官
SystemMessage 是对话中最高优先级的指令,用于固定 AI 的人设、输出格式、领域约束和安全边界。在一轮完整对话的消息列表中,通常只在最前面放置一条。
⚠️ 使用禁忌
不要把动态变化的业务数据(如用户 ID、订单详情)放入
SystemMessage,因为每次变更都会导致提示词整体发生变化,白白消耗 token。系统消息应该保持稳定。
SystemMessage systemMsg = new SystemMessage("""
你是资深Java后端技术专家,基于Spring AI Alibaba框架解答问题;
1. 回答必须附带可运行完整代码;
2. 代码使用Java17规范,适配DashScope通义千问;
3. 不编造不存在的框架API,解释简洁易懂。
""");
2.2 UserMessage ------ 人类输入载体
所有来自用户侧的内容都通过 UserMessage 封装。它支持纯文本、图片(多模态)等输入,是触发每一次对话的源头。在多轮对话中,每一个新的用户提问都会创建一个新的 UserMessage 追加到消息列表末尾。
// 普通文本提问
UserMessage userMsg = new UserMessage("讲解Spring AI Alibaba四大Prompt角色用法");
// 多模态示例(图片+文本)
// UserMessage multiModalMsg = new UserMessage("分析这张图片",
// List.of(new Media(MimeTypeUtils.IMAGE_PNG, imageResource)));
2.3 AssistantMessage ------ 模型双向载体
AssistantMessage 身兼两职:
- 普通对话 :存储模型上一轮的回复内容。在构建多轮对话 Prompt 时,需要按顺序将历史
AssistantMessage和后续UserMessage组合在一起,完整还原对话上下文。 - Agent 工具调用 :当模型判断需要调用外部工具时,它的回复会以
AssistantMessage的形式返回,但其中包含List<ToolCall>对象,指明要调用的函数名和参数。
多轮上下文拼接示例:
[SystemMessage, UserMessage1, AssistantMessage1, UserMessage2, AssistantMessage2, ...]
// 普通回答
AssistantMessage assistant = new AssistantMessage("ChatModel是底层标准接口,ChatClient是上层便捷封装。");
// 工具调用(框架自动生成)
// AssistantMessage 内含 toolCalls 字段
2.4 ToolResponseMessage ------ 外部数据桥梁
仅在 Function Calling 场景出现。一条完整的工具调用链路为:
UserMessage → AssistantMessage(含 ToolCall) → 本地执行工具 → ToolResponseMessage → 二次请求模型
ToolResponseMessage 将工具执行的结果封装,并将其与之前所有的消息(系统、用户、助手工具调用)合并,再次发送给大模型,由模型结合工具数据生成最终的自然语言回复。缺少该角色,就无法实现联网查询、数据库检索等 Agent 能力。
3. 项目环境配置
3.1 Maven 依赖
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>1.0.0-M6.1</version>
</dependency>
3.2 application.yml 通用配置
spring:
ai:
dashscope:
api-key: sk-你的灵积API密钥
chat:
options:
model: qwen-turbo
temperature: 0.7
3.3 两套接入方案
Spring AI Alibaba 提供自动装配 和手动配置两种方式,推荐优先使用自动装配。
方案一:Starter 自动装配(90% 场景首选)
只需引入 starter 并配置 yml,DashScopeAutoConfiguration 会自动创建:
DashScopeApi(API 客户端)DashScopeChatModel(ChatModel接口实现)- 业务代码直接注入
ChatModel或ChatClient即可。
无需任何配置类。
方案二:手动自定义配置类(需要定制网络参数时)
当需要自定义连接超时、HTTP 代理或私有化地址时,可以手动创建 Bean(此时自动装配会失效),并复用 DashScopeProperties 避免硬编码。
@Configuration
public class DashScopeLLMConfig {
@Bean
public DashScopeApi dashScopeApi(DashScopeProperties properties) {
return DashScopeApi.builder()
.apiKey(properties.getApiKey())
.connectTimeout(Duration.ofSeconds(30))
.readTimeout(Duration.ofSeconds(60))
// .baseUrl("私有化地址")
// .proxy(自定义代理)
.build();
}
@Bean
public ChatModel chatModel(DashScopeApi api, DashScopeProperties properties) {
return new DashScopeChatModel(api, properties.getChat().getOptions());
}
// 注册全局 ChatClient,统一管理系统提示词
@Bean
public ChatClient chatClient(ChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultSystem("你是Spring AI Alibaba技术专家,代码示例完整规范")
.defaultTemperature(0.7)
.build();
}
}
4. 实战一:基础多轮对话(System + User + Assistant)
本案例演示如何手动组装三种角色消息,并对比 ChatModel 底层 API 和 ChatClient 高层封装两种调用方式。
@RestController
public class PromptRoleController {
private final ChatModel chatModel;
private final ChatClient chatClient;
public PromptRoleController(ChatModel chatModel, ChatClient chatClient) {
this.chatModel = chatModel;
this.chatClient = chatClient;
}
// 方式一:原生 ChatModel 手动构建多轮对话
@GetMapping("/chat/multi/sync")
public String multiRoundSync() {
SystemMessage systemMsg = new SystemMessage("你是Java AI开发博主,回答简短精炼");
UserMessage user1 = new UserMessage("什么是ChatModel与ChatClient区别?");
AssistantMessage assistant1 = new AssistantMessage(
"ChatModel是底层标准接口,ChatClient是上层便捷封装。");
UserMessage user2 = new UserMessage("开发中该优先使用哪一个?");
Prompt prompt = new Prompt(List.of(systemMsg, user1, assistant1, user2));
return chatModel.call(prompt).getResult().getOutput().getText();
}
// 方式二:ChatClient 流式调用(推荐)
@GetMapping("/chat/client/stream")
public Flux<String> clientStreamChat(@RequestParam String query) {
return chatClient.prompt()
.system("讲解代码附带完整示例")
.user(query)
.stream()
.content();
}
}
ChatModel需要手动维护消息列表顺序,适合底层精细控制。ChatClient内部自动管理上下文并支持链式 API,更贴近业务开发。
5. 实战二:四大角色全闭环 Function Calling
下面代码完整展示了 System、User、Assistant(带 ToolCall)和 ToolResponse 四个角色的协作过程,实现"查询天气"的 Agent 功能。
@GetMapping("/chat/tool/allRole")
public String toolCallAllRole() throws Exception {
// 1. 系统消息:约束必须使用工具
SystemMessage systemMsg = new SystemMessage("""
你拥有天气查询工具getCityWeather,需要查询天气时必须调用该工具,
禁止编造数据。调用时传入参数city(城市名称)。
""");
// 2. 用户消息
UserMessage userMsg = new UserMessage("查询北京今日气温");
Prompt prompt = new Prompt(List.of(systemMsg, userMsg));
ChatResponse response = chatModel.call(prompt);
// 3. 获取助手消息(可能包含工具调用)
AssistantMessage assistantMsg = response.getResult().getOutput();
if (!assistantMsg.getToolCalls().isEmpty()) {
ToolCall toolCall = assistantMsg.getToolCalls().get(0);
// 解析参数
JsonNode args = new ObjectMapper().readTree(toolCall.arguments());
String city = args.get("city").asText();
// 执行本地工具(模拟)
String weatherData = getCityWeather(city);
// 4. 构造 ToolResponseMessage
ToolResponseMessage toolMsg = new ToolResponseMessage(
List.of(new ToolResponse(toolCall.id(), weatherData))
);
// 5. 拼接完整消息二次请求
List<Message> fullMessages = List.of(systemMsg, userMsg, assistantMsg, toolMsg);
ChatResponse finalResp = chatModel.call(new Prompt(fullMessages));
return finalResp.getResult().getOutput().getText();
}
return assistantMsg.getText();
}
private String getCityWeather(String city) {
return String.format("%s今日:晴,气温18~28℃,微风", city);
}
关键步骤解析:
- 模型返回的
AssistantMessage中getToolCalls()非空,说明需要调用外部工具; - 程序根据
ToolCall中的函数名和参数执行本地逻辑,并将结果封装为ToolResponseMessage; - 第二次请求时消息列表必须为
[System, User, Assistant(含ToolCall), ToolResponse]的顺序; - 模型结合工具数据生成最终的自然语言回答。
6. 总结与最佳实践
- 职责分离
SystemMessage定义稳态约束,UserMessage承载动态输入,AssistantMessage维护历史与工具指令,ToolResponseMessage补全外部数据。各司其职,避免混合。 - 多轮上下文维护
按顺序记录System → User → Assistant → User → Assistant ...,切勿遗漏历史助手消息,否则模型会丢失对话记忆。 - 工具调用闭环
当收到含有ToolCall的AssistantMessage时,必须执行工具并附加ToolResponseMessage重新请求,否则对话会中断或产生幻觉。 - 优先使用 ChatClient
日常开发推荐ChatClient的内置上下文管理、流式调用和默认系统提示词功能;仅在需要极端控制消息列表时下沉到ChatModel。 - 配置选择
无特殊网络需求时用自动装配;需要自定义超时、代理或私有化地址时手动创建 Bean,同时复用DashScopeProperties保持配置统一。
Spring AI 的四大角色设计不仅使得提示词构建更加模块化和类型安全,也为后续的 Agent 编排、记忆管理打下了坚实的基础。理解并善用这些角色,你将能够构建出更加稳定、智能的 AI 应用。