本文通过 Spring AI 的 System Prompt 实战,介绍 System Message 与 User Message 的职责区别,以及如何使用 ChatClient 的 defaultSystem()、system() 和模板变量,为 AI 助手设置身份、回答规则和行为边界,并分析 System Prompt 的适用场景、常见问题与安全边界。
一、为什么要设置 System Prompt
上一篇中,我们只是把用户输入直接发送给模型:
java
chatClient.prompt()
.user(message)
.call()
.content();
这种调用可以回答问题,但模型没有稳定的身份和输出规范。同一个问题可能得到不同风格的答案,也无法体现具体业务场景。
System Prompt 用来告诉模型:
- 你是谁;
- 你要服务什么场景;
- 你应该遵守哪些规则;
- 你应当使用什么表达方式;
- 哪些内容不能做。
例如,一个 Java 学习助手的 System Prompt 可以写成:
text
你是一名专业的 Java 技术导师,擅长使用循序渐进的方式解释问题。
回答时请先给出结论,再解释原理,最后提供一个最小示例。
如果问题中的信息不足,请明确指出,不要编造不存在的 API。
二、System Message 与 User Message 的区别
| 对比项 | System Message | User Message |
|---|---|---|
| 来源 | 应用开发者 | 最终用户 |
| 主要作用 | 定义身份、规则和边界 | 表达当前问题或任务 |
| 生命周期 | 可以作为客户端默认配置复用 | 通常每次请求都不同 |
| 示例 | "你是一名 Java 技术导师" | "请解释 Spring Bean" |
二者会共同组成发送给模型的 Prompt:
text
System Message:你是一名 Java 技术导师
User Message:请解释依赖注入
↓
Prompt
↓
ChatModel
我的理解:System Prompt 负责建立回答的"坐标系",User Prompt 负责给出本次要解决的具体问题。
三、配置默认 System Prompt
我们可以在创建 ChatClient 时使用 defaultSystem() 配置默认角色:
java
package org.xingyue.ai.config;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AiConfig {
@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultSystem("""
你是一名专业的 Java 技术导师。
回答要求:
1. 先给出简洁结论,再解释底层原理;
2. 涉及代码时,提供可以理解的最小示例;
3. 如果不确定答案,请明确说明,不要编造 API;
4. 默认使用中文回答,专业术语可保留英文。
""")
.build();
}
}
使用 Java 文本块的好处是:多行提示词更容易阅读,也便于后续补充规则。
四、代码实现
在 Controller 中注入 ChatClient
java
package org.xingyue.ai.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/ai")
public class ChatController {
@Autowired
private ChatService chatService;
@GetMapping("/chat/prompt")
public String chatPrompt(@RequestParam(defaultValue = "请介绍一下你自己")
String message) {
return chatService.chatPrompt(message);
}
@GetMapping("/prompt")
public String prompt(@RequestParam("language")
String language,
@RequestParam("level")
String level) {
return chatService.prompt(language, level);
}
}
java
/**
* AI 对话业务的默认实现。
*
* <p>封装 Spring AI 模型调用细节,为 Controller 提供统一的业务入口。</p>
*/
@Service
public class ChatServiceImpl implements ChatService {
private static final Logger log = LoggerFactory.getLogger(ChatServiceImpl.class);
@Autowired
private ChatClient chatClient;
@Override
public String chatPrompt(String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
}
由于 ChatClient 创建时已经设置默认 System Prompt,Controller 不需要在每次请求中重复写角色信息。
此时调用流程为:
text
默认 System Prompt
+
本次 User Message
↓
Prompt
↓
调用 DeepSeek
↓
按照 Java 技术导师角色生成回答
五、测试角色预设
可以访问:
text
http://localhost:9999/chat/prompt?message=请介绍一下你自己

defaultSystem 设置了全局角色

然后继续测试:
text
http://localhost:9999/chat/prompt?message=请解释什么是Spring依赖注入
测试结果示例:

测试时不要只看"有没有返回文字",还要验证以下几点:
- 模型是否使用了设定的身份;
- 是否先给结论再解释;
- 是否提供了最小代码示例;
- 是否默认使用中文;
- 面对不确定问题时是否会承认信息不足。
这实际上是最简单的一组提示词评测用例。
defaultSystem 是指System Prompt 会作为该 ChatClient 的默认配置重复使用。.system()则是构建某一次 Prompt 时,仅当前这一次请求有效。
六、默认 System Prompt 与单次 System Prompt
默认 System Prompt 适合整个客户端都要遵守的规则:
java
builder.defaultSystem("你是一名 Java 技术导师")
如果某条请求需要临时角色,可以在本次调用中设置 System Message:
java
@Override
public String prompt(String language, String level) {
return chatClient.prompt()
.system(system -> system
.text("""
你是一名 {language} 技术导师。
回答难度应适合 {level} 开发者。
默认使用中文回答。
""")
.param("language", language)
.param("level", level))
.user("请解释什么是依赖注入")
.call()
.content();
}
可以按照下面的原则选择:
| 场景 | 推荐方式 |
|---|---|
| 整个应用共享同一角色 | defaultSystem() |
| 某个业务模块共享角色 | 为该模块创建独立 ChatClient |
| 只有一次请求需要临时角色 | 在本次 prompt() 中调用 system() |
七、使用变量构建动态 System Prompt
如果角色信息需要动态变化,可以使用模板参数,而不是手动拼接字符串:
java
@Override
public String prompt(String language, String level) {
return chatClient.prompt()
.system(system -> system
.text("""
你是一名 {language} 技术导师。
回答难度应适合 {level} 开发者。
默认使用中文回答。
""")
.param("language", language)
.param("level", level))
.user("请解释什么是依赖注入")
.call()
.content();
}
模板参数:
language:技术语言;level:用户水平;
java
@Override
public String prompt(String language, String level) {
return chatClient.prompt()
.system(system -> system
.text("""
你是一名 {language} 技术导师。
回答难度应适合 {level} 开发者。
默认使用中文回答。
""")
.param("language", language)
.param("level", level))
.user("请解释什么是依赖注入")
.call()
.content();
}
@Override
public String promptByChatModel(String language, String level) {
String systemText = """
你是一名 %s 技术导师。
回答难度应适合 %s 开发者。
默认使用中文回答。
""".formatted(language, level);
Prompt prompt = new Prompt(List.of(
new SystemMessage(systemText),
new UserMessage("请解释什么是依赖注入")
));
ChatResponse response = chatModel.call(prompt);
return response.getResult().getOutput().getText();
}
@Override
public String promptByChatModelTemplate(String language, String level) {
String systemText = """
你是一名 {language} 技术导师。
回答难度应适合 {level} 开发者。
默认使用中文回答。
""";
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemText);
Message systemMessage = systemPromptTemplate.createMessage(Map.of(
"language", language,
"level", level
));
UserMessage userMessage = new UserMessage("请解释什么是依赖注入");
Prompt prompt = new Prompt(List.of(systemMessage, userMessage));
ChatResponse response = chatModel.call(prompt);
return response.getResult().getOutput().getText();
}

八、如何写出更稳定的 System Prompt
一个实用的 System Prompt 通常包含以下部分:
8.1 身份
text
你是一名专业的 Java 技术导师。
8.2 目标
text
你的目标是帮助初学者理解 Spring 和 Spring AI 的核心概念。
8.3 输出规则
text
回答时先给出结论,再解释原理,最后提供最小示例。
8.4 行为边界
text
如果信息不足或不确定,请明确说明,不要编造不存在的类和方法。
8.5 输出格式
text
代码使用 Markdown 代码块;多个方案使用有序列表;关键结论控制在五条以内。
可以整理成通用结构:
text
角色 + 目标 + 背景 + 约束 + 输出格式 + 异常处理
九、System Prompt 的边界
System Prompt 很重要,但不能被当作绝对可靠的安全控制。
真实项目还需要:
- 对用户身份和权限进行校验;
- 对敏感数据进行脱敏;
- 限制模型可以调用的工具;
- 对输入和输出进行内容检查;
- 记录必要的审计信息;
- 防范 Prompt Injection 等攻击。
例如,"请不要泄露系统提示词"只是一条模型指令,并不能替代真正的数据隔离。
十、常见问题
10.1 配置了 System Prompt,为什么回答仍然不完全一致
大模型生成具有一定随机性,System Prompt 是引导而不是普通程序中的强制分支。可以通过更明确的规则、降低随机性、增加输出格式约束和编写评测用例来提高稳定性。
10.2 System Prompt 是否越长越好
不是。过长、重复或互相冲突的规则会增加理解难度和 Token 消耗。应该优先保证结构清晰、约束明确、没有冲突。
10.3 为什么仍然不是多轮对话
System Prompt 只是为每次调用提供默认角色。当前代码仍然没有保存不同 HTTP 请求之间的历史消息,因此不具备跨请求记忆。
10.4 System、User、Assistant 的顺序重要吗
构造对话历史时,应按照实际会话顺序组织消息。一般先放系统规则,再依次放置用户和助手的历史消息,最后加入本次用户问题。
十一、本文总结
本篇完成了 AI 助手的角色预设,并建立了以下认识:
- System Message 用于定义身份、目标、规则和回答边界。
- User Message 表示用户本次提出的具体问题。
defaultSystem()适合配置可复用的默认角色。- 单次
.system()适合临时覆盖或补充本次任务规则。 - 模板变量名称必须与实际含义一致。
- System Prompt 不是安全边界,生产系统仍需实现权限和内容治理。
下一篇将讲解 Spring AI 的流式响应,并区分 Flux、普通分块文本和 Server-Sent Events。