Spring AI 源码解读 - 第 3 篇:Prompt 与 Message 体系
消息的构建、模板化与角色映射
📖 开篇引言
在第 2 篇中,我们看到 PromptBuilder.user() 和 system() 方法添加消息。但这些消息是如何被构建的?不同 AI 模型对消息格式的要求不同,Spring AI 如何统一处理?
本篇将深入 Prompt、Message 和 PromptTemplate 的源码,理解消息体系的设计与实现。
一、Message 接口体系
1.1 Message 顶层接口
// org.springframework.ai.chat.messages.Message
public interface Message extends Node<String> {
// 获取消息内容
String getContent();
// 获取消息类型(角色)
MessageType getMessageType();
// 获取元数据
Map<String, Object> getMetadata();
// 获取消息名称(可选)
default String getName() {
return null;
}
}
// 消息类型枚举
public enum MessageType {
USER("user"), // 用户消息
ASSISTANT("assistant"), // AI 助手消息
SYSTEM("system"), // 系统提示词
TOOL("tool"); // 工具调用结果
}
1.2 四种消息实现类
1. UserMessage - 用户消息
public class UserMessage implements Message {
private final String content;
private final Map<String, Object> metadata;
public UserMessage(String content) {
this(content, Map.of());
}
public UserMessage(String content, Map<String, Object> metadata) {
this.content = content;
this.metadata = metadata;
}
@Override
public MessageType getMessageType() {
return MessageType.USER;
}
@Override
public String getContent() {
return this.content;
}
}
2. SystemMessage - 系统提示词
public class SystemMessage implements Message {
private final String content;
private final Map<String, Object> metadata;
public SystemMessage(String content) {
this(content, Map.of());
}
@Override
public MessageType getMessageType() {
return MessageType.SYSTEM;
}
}
3. AssistantMessage - AI 回复消息
public class AssistantMessage implements Message {
private final String content;
private final List<ToolCall> toolCalls; // 工具调用信息
private final Map<String, Object> metadata;
public AssistantMessage(String content) {
this(content, List.of(), Map.of());
}
public AssistantMessage(String content, List<ToolCall> toolCalls) {
this(content, toolCalls, Map.of());
}
@Override
public MessageType getMessageType() {
return MessageType.ASSISTANT;
}
// 获取工具调用信息(用于 Tool Calling)
public List<ToolCall> getToolCalls() {
return this.toolCalls;
}
}
4. ToolResponseMessage - 工具调用结果
public class ToolResponseMessage implements Message {
private final List<ToolResponse> responses;
private final Map<String, Object> metadata;
public ToolResponseMessage(List<ToolResponse> responses) {
this(responses, Map.of());
}
@Override
public MessageType getMessageType() {
return MessageType.TOOL;
}
public List<ToolResponse> getResponses() {
return this.responses;
}
}
1.3 消息在多轮对话中的角色
第 1 轮
┌─────────────────────────────────────────┐
│ SystemMessage("你是一个 Java 专家") │
│ UserMessage("什么是 Spring AI?") │
│ AssistantMessage("Spring AI 是...") │
└─────────────────────────────────────────┘
第 2 轮(历史消息 + 当前消息)
┌─────────────────────────────────────────┐
│ SystemMessage("你是一个 Java 专家") │
│ UserMessage("什么是 Spring AI?") │ ← 历史
│ AssistantMessage("Spring AI 是...") │ ← 历史
│ UserMessage("它有哪些核心模块?") │ ← 当前
└─────────────────────────────────────────┘
二、Prompt 数据模型
2.1 Prompt 类结构
// org.springframework.ai.chat.prompt.Prompt
public class Prompt implements ModelRequest<List<Message>> {
private final List<Message> messages; // 消息列表(核心)
private final ChatOptions chatOptions; // 可选参数
// 构造方法
public Prompt(String contents) {
this(new UserMessage(contents));
}
public Prompt(Message message) {
this(List.of(message));
}
public Prompt(List<Message> messages) {
this(messages, null);
}
public Prompt(List<Message> messages, ChatOptions chatOptions) {
this.messages = messages;
this.chatOptions = chatOptions;
}
// 获取消息列表
public List<Message> getInstructions() {
return this.messages;
}
// 获取选项
public ChatOptions getOptions() {
return this.chatOptions;
}
}
2.2 Prompt 的构建方式
// 方式 1:单条消息
Prompt prompt1 = new Prompt("你好");
// 等价于 new Prompt(new UserMessage("你好"))
// 方式 2:消息列表
Prompt prompt2 = new Prompt(List.of(
new SystemMessage("你是一个助手"),
new UserMessage("你好")
));
// 方式 3:带选项
Prompt prompt3 = new Prompt(
List.of(new UserMessage("你好")),
OllamaOptions.builder()
.temperature(0.3)
.build()
);
// 方式 4:通过 Builder(推荐)
Prompt prompt4 = Prompt.builder()
.system("你是一个助手")
.user("你好")
.build();
三、PromptTemplate 模板引擎
3.1 PromptTemplate 接口
// org.springframework.ai.chat.prompt.PromptTemplate
public interface PromptTemplate {
// 根据变量值渲染模板
Prompt create(Map<String, Object> variables);
// 便捷方法:单个变量
default Prompt create(String variable) {
return create(Map.of("input", variable));
}
}
3.2 DefaultPromptTemplate 实现
public class DefaultPromptTemplate implements PromptTemplate {
private final String template; // 模板字符串
private final ChatOptions options; // 可选参数
public DefaultPromptTemplate(String template) {
this(template, null);
}
public DefaultPromptTemplate(String template, ChatOptions options) {
this.template = template;
this.options = options;
}
@Override
public Prompt create(Map<String, Object> variables) {
// 1. 替换模板中的变量
String renderedTemplate = renderTemplate(this.template, variables);
// 2. 构建 UserMessage
UserMessage userMessage = new UserMessage(renderedTemplate);
// 3. 构建 Prompt
return new Prompt(List.of(userMessage), this.options);
}
// 变量替换实现
private String renderTemplate(String template, Map<String, Object> variables) {
String result = template;
// 替换 ${key} 格式的变量
for (Map.Entry<String, Object> entry : variables.entrySet()) {
String placeholder = "${" + entry.getKey() + "}";
String value = String.valueOf(entry.getValue());
result = result.replace(placeholder, value);
}
return result;
}
}
3.3 SystemPromptTemplate - 系统提示词模板
public class SystemPromptTemplate implements PromptTemplate {
private final String template; // 系统提示词模板
public SystemPromptTemplate(String template) {
this.template = template;
}
@Override
public Prompt create(Map<String, Object> variables) {
// 1. 替换系统提示词中的变量
String renderedSystemPrompt = renderTemplate(this.template, variables);
// 2. 构建 SystemMessage
SystemMessage systemMessage = new SystemMessage(renderedSystemPrompt);
// 3. 构建 Prompt(只包含系统消息)
return new Prompt(List.of(systemMessage));
}
}
3.4 模板使用示例
// 示例 1:简单模板
PromptTemplate template1 = new DefaultPromptTemplate(
"请用 ${language} 语言写一个 ${topic} 的例子"
);
Prompt prompt1 = template1.create(Map.of(
"language", "Java",
"topic", "快速排序"
));
// 渲染结果:
// "请用 Java 语言写一个 快速排序 的例子"
// 示例 2:系统提示词模板
SystemPromptTemplate systemTemplate = new SystemPromptTemplate(
"你是一个 ${role} 专家,擅长 ${expertise}"
);
Prompt prompt2 = systemTemplate.create(Map.of(
"role", "Java",
"expertise", "Spring Framework"
));
// 渲染结果:
// SystemMessage("你是一个 Java 专家,擅长 Spring Framework")
四、消息角色映射(Role Mapping)
4.1 为什么需要角色映射?
不同 AI 模型对消息角色的定义略有不同:
Spring AI 标准 Ollama API OpenAI API
───────────────── ───────────── ─────────────
USER "user" "user"
ASSISTANT "assistant" "assistant"
SYSTEM "system" "system"
TOOL "tool" "tool"
虽然大多数模型一致,但有些模型可能有特殊要求。
4.2 角色映射的实现
// org.springframework.ai.chat.messages.MessageType
public enum MessageType {
USER("user"),
ASSISTANT("assistant"),
SYSTEM("system"),
TOOL("tool");
private final String value;
MessageType(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
}
4.3 OllamaChatModel 中的角色映射
// OllamaChatModel 将 Spring AI Message 转换为 Ollama Message
private ChatRequest.Message toOllamaMessage(Message message) {
// 获取消息角色
String role = switch (message.getMessageType()) {
case USER -> "user";
case ASSISTANT -> "assistant";
case SYSTEM -> "system";
case TOOL -> "tool";
};
// 构建 Ollama Message
return new ChatRequest.Message(
role,
message.getContent()
);
}
4.4 OpenAIChatModel 中的角色映射
// OpenAIChatModel 的角色映射(基本相同)
private OpenAiApi.ChatCompletionRequest.ChatCompletionMessage
toOpenAiMessage(Message message) {
String role = switch (message.getMessageType()) {
case USER -> "user";
case ASSISTANT -> "assistant";
case SYSTEM -> "system";
case TOOL -> "tool";
};
return new OpenAiApi.ChatCompletionRequest.ChatCompletionMessage(
role,
message.getContent()
);
}
五、Prompt 构建的完整流程
5.1 从 PromptBuilder 到 Prompt
// 用户代码
chatClient.prompt()
.system("你是一个助手")
.user("你好")
.call();
// 内部流程
// 1. prompt() 返回 PromptUserMessageBuilder
PromptUserMessageBuilder builder = chatClient.prompt();
// 2. system() 设置系统提示词
builder.system("你是一个助手");
// builder.systemPromptTemplate = new SystemPromptTemplate("你是一个助手")
// 3. user() 添加用户消息
builder.user("你好");
// builder.messages = [UserMessage("你好")]
// 4. call() 触发 buildPrompt()
Prompt prompt = builder.buildPrompt();
// buildPrompt() 的实现
private Prompt buildPrompt() {
List<Message> allMessages = new ArrayList<>();
// 1. 如果有系统提示词,先添加 SystemMessage
if (this.systemPromptTemplate != null) {
Prompt systemPrompt = this.systemPromptTemplate.create(Map.of());
allMessages.addAll(systemPrompt.getInstructions());
}
// 2. 添加用户消息
allMessages.addAll(this.messages);
// 3. 构建最终 Prompt
return new Prompt(allMessages, this.options);
}
// 最终 Prompt 的消息列表
// [
// SystemMessage("你是一个助手"),
// UserMessage("你好")
// ]
5.2 Prompt 的完整数据结构
Prompt
├── messages: List<Message>
│ ├── [0] SystemMessage
│ │ ├── content: "你是一个助手"
│ │ ├── messageType: SYSTEM
│ │ └── metadata: {}
│ │
│ ├── [1] UserMessage
│ │ ├── content: "你好"
│ │ ├── messageType: USER
│ │ └── metadata: {}
│ │
│ └── [2] AssistantMessage (历史消息)
│ ├── content: "你好,有什么可以帮助你的吗?"
│ ├── messageType: ASSISTANT
│ └── metadata: {}
│
└── chatOptions: ChatOptions
├── model: "qwen2.5:14b"
├── temperature: 0.3
└── maxTokens: 2048
六、消息内容的多种形式
6.1 文本消息
// 最常见的形式
UserMessage message = new UserMessage("你好");
6.2 带元数据的消息
// 消息可以携带额外的元数据
UserMessage message = new UserMessage(
"你好",
Map.of(
"userId", "user-123",
"timestamp", System.currentTimeMillis(),
"source", "web"
)
);
// 获取元数据
Map<String, Object> metadata = message.getMetadata();
String userId = (String) metadata.get("userId");
6.3 带工具调用的 AssistantMessage
// AI 可能决定调用工具
List<ToolCall> toolCalls = List.of(
new ToolCall(
"get_weather", // 工具名称
Map.of("city", "北京") // 工具参数
)
);
AssistantMessage message = new AssistantMessage(
"我来帮你查询天气",
toolCalls
);
七、消息序列化与反序列化
7.1 为什么需要序列化?
当消息需要存储到数据库或通过网络传输时,需要序列化。
7.2 JSON 序列化示例
// 消息对象
UserMessage message = new UserMessage("你好");
// 序列化为 JSON
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(message);
// {"content":"你好","messageType":"USER","metadata":{}}
// 反序列化
UserMessage deserialized = mapper.readValue(json, UserMessage.class);
7.3 ChatMemory 中的序列化
// MessageWindowChatMemory 将消息存储到内存
public class MessageWindowChatMemory implements ChatMemory {
private final Map<ConversationId, List<Message>> conversationHistory;
@Override
public void add(ConversationId conversationId, Message message) {
// 直接存储 Message 对象
conversationHistory
.computeIfAbsent(conversationId, k -> new ArrayList<>())
.add(message);
}
@Override
public List<Message> get(ConversationId conversationId) {
// 直接返回 Message 对象列表
return conversationHistory.getOrDefault(conversationId, List.of());
}
}
八、Prompt 与 ChatOptions 的关系
8.1 Prompt 包含 ChatOptions
// Prompt 可以携带 ChatOptions
Prompt prompt = new Prompt(
List.of(new UserMessage("你好")),
OllamaOptions.builder()
.model("qwen2.5:14b")
.temperature(0.3)
.build()
);
// 获取选项
ChatOptions options = prompt.getOptions();
8.2 Options 的优先级
Prompt 中的 Options(请求级)
↓ 覆盖
ChatClient 默认 Options
↓ 覆盖
application.properties 配置
8.3 Options 合并示例
// application.properties
spring.ai.ollama.chat.options.temperature=0.8
// ChatClient 构建时
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultOptions(OllamaOptions.builder()
.temperature(0.5)
.maxTokens(1000)
.build())
.build();
// 请求时
Prompt prompt = new Prompt(
List.of(new UserMessage("你好")),
OllamaOptions.builder()
.temperature(0.3) // 请求级
.build()
);
// 最终合并结果
// temperature = 0.3(请求级覆盖)
// maxTokens = 1000(使用 ChatClient 默认)
九、小结
9.1 本篇要点
| 主题 | 核心要点 |
|---|---|
| Message 体系 | 四种消息类型:USER、ASSISTANT、SYSTEM、TOOL |
| Prompt 数据模型 | 消息列表 + ChatOptions 的容器 |
| PromptTemplate | 模板引擎,支持 ${variable} 变量替换 |
| SystemPromptTemplate | 系统提示词模板,用于动态生成系统消息 |
| 角色映射 | 不同模型的消息角色转换 |
| Prompt 构建流程 | system() → user() → buildPrompt() |
| 消息元数据 | 消息可携带额外的上下文信息 |
9.2 关键类清单
| 类 / 接口 | 职责 |
|---|---|
Message | 消息接口 |
UserMessage | 用户消息 |
SystemMessage | 系统提示词 |
AssistantMessage | AI 回复消息 |
ToolResponseMessage | 工具调用结果 |
Prompt | 请求封装(消息 + 选项) |
PromptTemplate | 模板接口 |
DefaultPromptTemplate | 默认模板实现 |
SystemPromptTemplate | 系统提示词模板 |
MessageType | 消息类型枚举 |
系列目录:
- 第 1 篇:整体架构与核心抽象
- 第 2 篇:ChatClient 调用链路
- 第 3 篇:Prompt 与 Message 体系(本篇)
需要Spring AI系列学习代码的同学 欢迎关注公众号「AI日撰」,点击菜单「获取源码」获取完整代码(Gitee 仓库)。