白歌画了一个类图给小崔看:"SystemMessage 定规则,UserMessage 带问题,AssistantMessage 存历史,ToolResponseMessage 传结果。四种消息各司其职,Prompt 就是一个装消息的容器。"
Prompt 与 Message 体系
定义与作用
在 Spring AI 中,每次与 AI 模型的交互都通过 Prompt 对象表示。Prompt 是一个容器,装载:
- 消息列表(
List<Message>):对话的完整上下文 - ChatOptions(可选):模型调用参数(temperature、model 等)
Message 是 Prompt 的基本构建单元。Spring AI 定义了四种消息类型,对应 LLM 的四种标准角色。
Message 四种类型详解
| 类型 | 角色 | 接口/类 | 典型用途 | 在对话中的位置 |
|---|---|---|---|---|
| SystemMessage | system | SystemMessage | 设定 AI 全局行为和角色 | 对话第一条,贯穿全程 |
| UserMessage | user | UserMessage | 用户输入,可附加图片/音频 | 每轮对话的用户侧 |
| AssistantMessage | assistant | AssistantMessage | AI 的历史回复,维持上下文 | 每轮对话的 AI 侧 |
| ToolResponseMessage | tool | ToolResponseMessage | 工具函数执行结果 | Tool Calling 循环中 |
类继承体系
图释:四种 Message 共享 AbstractMessage 的 content + messageType 基础结构,各自扩展特有构造器。UserMessage 额外支持 Media 多模态参数。
Prompt 的三种构造方式
// 方式一:简单字符串(最简,适合单轮问答)
Prompt prompt1 = new Prompt("今天天气怎么样?");
// 方式二:消息列表(多角色,适合有角色设定场景)
Prompt prompt2 = new Prompt(List.of(
new SystemMessage("你是学生的课程顾问。"),
new UserMessage("离散数学的前置课程是什么?")
));
// 方式三:消息列表 + ChatOptions(精确控制参数)
Prompt prompt3 = new Prompt(
List.of(
new SystemMessage("你是飞翔科技大学的课程顾问。"),
new UserMessage("推荐一门选修课")
),
ChatOptions.builder()
.temperature(0.3) // 低温度 → 更准确的课程信息
.model("gpt-4o")
.build()
);
完整示例一:构建带角色设定的课程问答
场景说明
孔蓝要求课程咨询的 AI 回复风格专业且结构化。
关键代码
@RestController
public class CourseQAController {
private final ChatClient chatClient;
public CourseQAController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@GetMapping("/course-qa")
public String ask(@RequestParam String question) {
return chatClient.prompt()
.system("""
你是飞翔科技大学的课程顾问。回答时遵循以下规则:
1. 先给出直接答案
2. 然后列出相关的课程编号和学分
3. 最后补充选课建议
即使不确定,也要给出参考信息而非泛泛而谈。
""")
.user(question)
.options(ChatOptions.builder()
.temperature(0.3) // 课程信息需要准确
.build())
.call()
.content();
}
}
对比效果
普通版本(无 SystemMessage):
Q: 离散数学的前置课程
A: 离散数学的常见前置课程包括高等数学、线性代数等基础数学课。
具体以学校培养方案为准。
专业版本(有 SystemMessage):
Q: 离散数学的前置课程
A: 根据培养方案,离散数学(CS201)需先修:
1. MATH101 高等数学A — 5学分(侧重逻辑推理基础)
2. MATH102 线性代数 — 4学分(侧重矩阵与组合基础)
选课建议:建议大二上学期修读,与数据结构 CS301 搭配学习效果最佳。
完整示例二:UserMessage 的多模态能力
场景说明
孙鹤提出"学生拍照上传课程表图片,AI 自动识别课程安排"。
关键代码
@PostMapping("/read-schedule")
public String readSchedule(@RequestParam("image") MultipartFile image)
throws IOException {
Media media = new Media(
MimeTypeUtils.IMAGE_PNG,
new InputStreamResource(image.getInputStream())
);
return chatClient.prompt()
.system("你是 OCR 专家。从图片中提取所有课程信息,用表格格式输出。")
.user(userSpec -> userSpec
.text("请提取这张课程表中的课程名称、时间和教室")
.media(media))
.call()
.content();
}
易错场景与面试考点
易错场景一:SystemMessage 和 UserMessage 混用
// ❌ 错误:把角色设定放在 UserMessage 中
Prompt prompt = new Prompt(List.of(
new UserMessage("你是一个课程顾问。" +
"离散数学的前置课程是什么?")
));
// AI 可能把前半句当成普通聊天内容
问题分析
LLM 对 SystemMessage 和 UserMessage 的处理优先级不同。SystemMessage 被视为不可反驳的指令,UserMessage 视为可讨论的内容。
// ✅ 正确:角色设定放在 SystemMessage
Prompt prompt = new Prompt(List.of(
new SystemMessage("你是飞翔科技大学的课程顾问。"),
new UserMessage("离散数学的前置课程是什么?")
));
易错场景二:多模态请求忘记设置 MIME Type
// ❌ 错误:未指定 MIME type
Media media = new Media(resource); // 编译错误或默认值不明确
问题分析
Media 的构造器需要明确的 MimeType,AI 模型据此判断如何处理附件。
// ✅ 正确:明确指定 MIME type
Media media = new Media(MimeTypeUtils.IMAGE_JPEG, resource);
面试高频题
Q1:SystemMessage 和 UserMessage 的 Token 分配优先级是什么?
大部分 LLM 给 SystemMessage 更高的"指令优先级"——AI 更倾向于遵守 System 的约束,即使与 User 消息冲突。建议在 SystemMessage 中放恒定的角色约束("你是课程顾问"),在 UserMessage 中放可变的具体问题。
Q2:Prompt 中四种消息的典型排列顺序是怎样的?
通常顺序:SystemMessage(角色)→ 历史 User/Assistant 交替 → 最新 UserMessage → 可能有 ToolResponseMessage。SystemMessage 必须放在最前面以确立全局约束。
Q3:什么时候需要手动构造 Prompt 而不是用 ChatClient 的 Fluent API?
当需要精确控制消息列表中每条消息的类型、顺序和参数时(如手动注入 ToolResponseMessage 做 Tool Calling 循环、动态裁剪历史消息),直接构造 Prompt 对象更灵活。
本章小结
- Prompt 是消息列表 + ChatOptions 的容器
- Message 有四种类型:System(规则)、User(问题)、Assistant(回复历史)、Tool(工具结果)
- SystemMessage 在最前面设定角色,优先级高于 UserMessage
- Prompt 有三种构造方式:简单字符串、消息列表、消息列表 + 选项
- UserMessage 支持多模态(Media),可附加图片/音频