Prompt & Messages
结构化提示词工程体系是 Spring AI 的核心设计之一,理解消息类型和 Prompt 构建是写好 AI 应用的基础。
消息类型体系
Spring AI 将 LLM 的对话结构抽象为一套完整的消息类型系统:
Message (接口)
├── SystemMessage // 系统指令,定义 AI 角色和行为规范
├── UserMessage // 用户输入,支持文本 + 媒体内容
├── AssistantMessage // 模型回复,可包含 ToolCall 请求
└── ToolResponseMessage // 工具执行结果,返回给模型SystemMessage
系统消息定义 AI 的角色、能力边界和行为规范,通常在对话开始时设置一次:
java
// 简单系统消息
SystemMessage system = new SystemMessage("你是一个专业的 Java 架构师,擅长微服务设计。");
// 复杂系统消息(多行)
String systemPrompt = """
你是一个代码审查助手,职责如下:
1. 检查代码规范和最佳实践
2. 识别潜在的性能问题
3. 发现安全漏洞
4. 提供具体的改进建议
回答格式:
- 问题描述
- 严重程度(高/中/低)
- 改进建议
""";
SystemMessage system = new SystemMessage(systemPrompt);UserMessage
用户消息支持纯文本和多模态内容:
java
// 纯文本
UserMessage textMsg = new UserMessage("解释 Java 虚拟线程的工作原理");
// 多模态:文本 + 图片
UserMessage multimodalMsg = new UserMessage(
"分析这张架构图,指出潜在问题",
List.of(new Media(MimeTypeUtils.IMAGE_PNG, imageResource))
);
// 多模态:文本 + 多张图片
UserMessage multiImageMsg = new UserMessage(
"比较这两张 UML 图的差异",
List.of(
new Media(MimeTypeUtils.IMAGE_PNG, image1),
new Media(MimeTypeUtils.IMAGE_PNG, image2)
)
);AssistantMessage
模型的回复消息,通常由框架自动创建,但在构建历史对话时需要手动创建:
java
// 构建对话历史
List<Message> history = new ArrayList<>();
history.add(new UserMessage("什么是 Spring Boot?"));
history.add(new AssistantMessage("Spring Boot 是一个简化 Spring 应用开发的框架..."));
history.add(new UserMessage("它和 Spring MVC 有什么区别?")); // 当前问题
// AssistantMessage 也可以包含 ToolCall(工具调用请求)
AssistantMessage withToolCall = new AssistantMessage(
"", // 文本内容为空
Map.of(),
List.of(new AssistantMessage.ToolCall(
"call_001", "function", "getWeather",
"{\"city\": \"北京\"}"
))
);ToolResponseMessage
工具执行结果消息:
java
// 工具执行后,将结果封装为 ToolResponseMessage
ToolResponseMessage toolResult = new ToolResponseMessage(
List.of(new ToolResponseMessage.ToolResponse(
"call_001", // 对应 ToolCall 的 id
"getWeather", // 工具名称
"北京:晴,25°C" // 工具执行结果
))
);Prompt 构建
Prompt 是 ChatModel.call() 的入参,封装了消息列表和调用选项:
java
// 单条消息
Prompt simple = new Prompt("你好");
// 多条消息(完整对话)
Prompt conversation = new Prompt(List.of(
new SystemMessage("你是一个 Java 专家"),
new UserMessage("解释泛型擦除"),
new AssistantMessage("泛型擦除是指..."),
new UserMessage("那运行时如何获取泛型信息?")
));
// 带选项的 Prompt
Prompt withOptions = new Prompt(
"写一首诗",
DashScopeChatOptions.builder()
.withTemperature(0.9f)
.withModel("qwen-max")
.build()
);PromptTemplate:动态提示词
PromptTemplate 支持变量替换,是构建可复用提示词的最佳方式:
java
// 基础用法
PromptTemplate template = new PromptTemplate(
"请用 {language} 语言解释 {concept},面向 {audience} 读者。"
);
Prompt prompt = template.create(Map.of(
"language", "中文",
"concept", "依赖注入",
"audience", "初学者"
));
// 从文件加载模板(推荐用于复杂提示词)
// resources/prompts/code-review.st
PromptTemplate fileTemplate = new PromptTemplate(
new ClassPathResource("prompts/code-review.st")
);
Prompt reviewPrompt = fileTemplate.create(Map.of(
"code", sourceCode,
"language", "Java",
"standards", "Google Java Style Guide"
));模板文件示例
# resources/prompts/code-review.st
你是一个资深 {language} 代码审查专家,遵循 {standards}。
请审查以下代码:
```{language}
{code}请从以下维度给出反馈:
- 代码规范
- 性能优化
- 安全问题
- 可维护性
每个问题请注明严重程度(🔴高 / 🟡中 / 🟢低)。
## ChatClient 中的消息构建
`ChatClient` 提供了更简洁的消息构建方式:
```java
// 方式一:字符串
chatClient.prompt()
.system("你是专家")
.user("问题")
.call().content();
// 方式二:Lambda 构建(支持变量替换)
chatClient.prompt()
.system(s -> s.text("你是 {role} 专家").param("role", "Java"))
.user(u -> u.text("解释 {topic}").param("topic", "虚拟线程"))
.call().content();
// 方式三:传入 Prompt 对象
Prompt myPrompt = new Prompt(List.of(
new SystemMessage("..."),
new UserMessage("...")
));
chatClient.prompt(myPrompt).call().content();结构化输出
Spring AI 支持将模型输出自动解析为 Java 对象:
java
// 定义输出结构
record CodeReview(
List<Issue> issues,
int overallScore,
String summary
) {}
record Issue(
String description,
String severity, // HIGH / MEDIUM / LOW
String suggestion,
int lineNumber
) {}
// 自动解析
CodeReview review = chatClient.prompt()
.system("你是代码审查专家,以 JSON 格式输出结果")
.user("审查这段代码:\n" + code)
.call()
.entity(CodeReview.class);
// 框架自动:
// 1. 生成 JSON Schema 并注入到 Prompt
// 2. 调用模型
// 3. 解析 JSON 响应为 CodeReview 对象提示词工程最佳实践
1. 角色设定(Role Prompting)
java
String systemPrompt = """
你是一个拥有 10 年经验的 Java 架构师,专注于:
- 微服务架构设计
- 性能优化
- 代码质量
你的回答风格:
- 简洁直接,避免废话
- 给出具体可执行的建议
- 必要时提供代码示例
""";2. 少样本学习(Few-Shot)
java
String fewShotPrompt = """
将以下 Java 代码转换为 Kotlin:
示例1:
Java: public String getName() { return name; }
Kotlin: fun getName(): String = name
示例2:
Java: if (obj != null) { obj.doSomething(); }
Kotlin: obj?.doSomething()
现在转换:
Java: {code}
Kotlin:
""";3. 思维链(Chain of Thought)
java
String cotPrompt = """
分析以下系统设计问题,请按步骤思考:
问题:{problem}
请按以下步骤分析:
1. 理解需求:明确功能和非功能需求
2. 识别挑战:找出主要技术难点
3. 方案设计:提出 2-3 个可行方案
4. 方案对比:分析各方案的优缺点
5. 最终建议:给出推荐方案及理由
""";4. 输出格式控制
java
String formatPrompt = """
分析 {topic},以 Markdown 格式输出:
## 概述
(2-3 句话概括)
## 核心原理
(技术细节)
## 代码示例
```java
// 示例代码
```
## 最佳实践
- 实践1
- 实践2
## 常见陷阱
| 陷阱 | 原因 | 解决方案 |
|------|------|----------|
""";消息 Token 计算
了解 Token 消耗对控制成本至关重要:
java
ChatResponse response = chatModel.call(prompt);
Usage usage = response.getMetadata().getUsage();
System.out.printf("""
Token 使用情况:
- 输入 Token:%d
- 输出 Token:%d
- 总计:%d
""",
usage.getPromptTokens(),
usage.getGenerationTokens(),
usage.getTotalTokens()
);Token 优化建议
- 系统提示词尽量简洁,避免冗余描述
- 对话历史使用滑动窗口,不要无限累积
- 结构化输出的 Schema 注入会增加 Token 消耗
- 使用
qwen-long处理超长文档,成本更低