System Prompt(系统提示词) 的用法

一、最直接的方式:.system()

在每次调用时传入系统提示词:

java 复制代码
@Service
@RequiredArgsConstructor
public class ChatService {

    private final ChatClient chatClient;
    private final WeatherTools weatherTools;

    public String chat(String message) {
        return chatClient.prompt()
                // ← 每次请求都执行
                .system("你是一位专业的气象助手,说话简洁,只回答与天气相关的问题。")
                .user(message)
                .tools(weatherTools)
                .call()
                .content();
    }
}

特点:

  • 每次调用都要写一遍,适合不同请求用不同角色的场景。

  • 可以和 .user().tools() 自由组合。

  • 系统提示词会作为 system 角色的消息发给模型,模型会据此调整回答风格。


二、全局默认方式:defaultSystem()

如果你希望这个 ChatClient 的所有调用都用同一个角色,在构建时设置:

java 复制代码
@Configuration
public class ChatClientConfig {

    @Bean
    public ChatClient chatClient(ChatClient.Builder builder) {
        return builder
                // ← 只执行一次,全局共享
                .defaultSystem("你是一位专业的气象助手,说话简洁,只回答与天气相关的问题。")
                .build();
    }
}

之后在 ChatService 里就可以省略 .system()

java 复制代码
public String chat(String message) {
    return chatClient.prompt()
            .user(message)
            .tools(weatherTools)
            .call()
            .content();
}

特点:

  • 适合"这个 ChatClient 就是干这个的"场景。

  • 如果某次调用想覆盖,仍然可以在 .prompt() 后再次调用 .system()就近原则,后者覆盖前者


两种方式的区别:

.system() 可以传变量,defaultSystem() 不行

场景:根据用户身份切换角色

假设你的系统有普通用户和 VIP 用户,VIP 用户希望模型回答更详细:

java 复制代码
public String chat(String message, boolean isVip) {
    String role = isVip
            ? "你是一位资深气象专家,回答要专业、详细,包含数据来源"
            : "你是一位简洁的气象助手,回答不超过两句话";

    return chatClient.prompt()
            .system(role)        // ← 每次请求根据参数决定角色
            .user(message)
            .call()
            .content();
}

这就是 .system() 的意义:角色内容由代码在运行时决定,而不是写死在配置里。


场景:多语言

java 复制代码
public String chat(String message, String lang) {
    String role = switch (lang) {
        case "en" -> "You are a professional weather assistant.";
        case "ja" -> "あなたはプロの天気アシスタントです。";
        default   -> "你是一位专业的气象助手。";
    };

    return chatClient.prompt()
            .system(role)
            .user(message)
            .call()
            .content();
}

场景:从数据库读取角色配置

java 复制代码
public String chat(String message, Long userId) {
    User user = userService.findById(userId);
    String role = user.getAiRole();   // 从数据库读,每个用户可能不同

    return chatClient.prompt()
            .system(role)
            .user(message)
            .call()
            .content();
}

这三种场景,defaultSystem() 都做不到,因为它只在构建时执行一次,拿不到运行时的请求参数。


defaultSystem() 什么时候用?

当你确定这个 ChatClient 的角色永远不变时!!!

因为:

  1. 业务代码更干净,不用每次都写 .system()

  2. 角色定义集中在一处,好维护


三、用模板参数:defaultSystem(Consumer<SystemSpec>)

如果系统提示词里有动态部分,可以用占位符 + 参数:

java 复制代码
@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
    return builder
            .defaultSystem(s -> s
                    .text("你是一位专业的{role}助手,服务对象是{audience},说话风格:{style}。")
                    .param("role", "气象")
                    .param("audience", "普通用户")
                    .param("style", "简洁"))
            .build();
}

之后每次调用时可以覆盖参数:

java 复制代码
chatClient.prompt()
        .system(s -> s
                .text("你是一位专业的{role}助手,服务对象是{audience},说话风格:{style}。")
                .param("role", "气象")
                .param("audience", "农民")
                .param("style", "详细"))
        .user(message)
        .call()
        .content();

特点:

  • 适合提示词模板化的场景,比如多语言、多角色切换。

  • 参数用 {name} 占位,.param() 填值。


四、从文件加载提示词(推荐生产环境用)

系统提示词往往很长(几百字),写在 Java 代码里不优雅。Spring AI 支持从 classpath 加载:

1. 在 src/main/resources/prompts/ 下建文件 weather-system.st

java 复制代码
你是一位专业的气象助手。

要求:
1. 只回答与天气相关的问题
2. 回答简洁,不超过 3 句话
3. 如果用户问与天气无关的问题,礼貌拒绝

当前日期:{currentDate}

2. 在代码中引用:

java 复制代码
@Value("classpath:/prompts/weather-system.st")
private Resource systemPrompt;

public String chat(String message) {
    return chatClient.prompt()
            .system(s -> s
                    .text(systemPrompt)
                    .param("currentDate", LocalDate.now().toString()))
            .user(message)
            .call()
            .content();
}

特点:

  • 提示词和代码分离,改提示词不用重新编译。

  • 适合长提示词、需要版本管理的场景。

  • .st 是 Spring AI 推荐的扩展名(String Template),其实 .txt 也行。


五、system 消息的底层长什么样?

无论用哪种方式,最终发给模型的消息列表大致是:

java 复制代码
[
  { "role": "system", "content": "你是一位专业的气象助手..." },
  { "role": "user",   "content": "北京天气怎么样?" }
]

模型看到 system 消息后,会把它当作最高优先级的行为准则

注意:不同模型的"听话程度"不一样。Qwen2.5 对 system prompt 的遵循度不错,但如果发现模型不按角色说话,可以试试:

  • 把关键约束放在 system prompt 的开头或结尾(中间容易被忽略)

  • 用更强的措辞,比如"必须""严禁"

  • 减少 system prompt 长度,越短越聚焦


六、多种方式对比

方式 作用范围 适用场景
.system() 单次调用 不同请求不同角色
defaultSystem() 整个 ChatClient 固定角色
defaultSystem(Consumer) 整个 ChatClient,带模板 模板化提示词
Resource 文件 单次或全局 长提示词、版本管理
system + user 组合 单次调用 复杂对话

七、实战:完整示例

java 复制代码
@Configuration
public class ChatClientConfig {

    @Bean
    public ChatClient weatherChatClient(
            ChatClient.Builder builder,
            @Value("classpath:/prompts/weather-system.st") Resource systemPrompt) {
        return builder
                .defaultSystem(s -> s
                        .text(systemPrompt)
                        .param("currentDate", LocalDate.now().toString()))
                .build();
    }
}
java 复制代码
@Service
@RequiredArgsConstructor
public class ChatService {

    private final ChatClient weatherChatClient;
    private final WeatherTools weatherTools;

    public String chat(String message) {
        return weatherChatClient.prompt()
                .user(message)
                .tools(weatherTools)
                .call()
                .content();
    }
}

八、一个容易踩的坑

system prompt 和 tool description 要协调,不能冲突。

比如你的 system prompt 写"你只回答天气问题",但用户问"帮我订机票",模型可能会因为角色限制而拒绝调用 bookFlight 工具。

正确做法是让 system prompt 描述角色和风格 ,工具 description 描述能力,两者不重叠:

java 复制代码
// system prompt
你是一位专业的旅行助手,说话简洁礼貌。

// weather tool description
查询指定城市的当前天气情况

// flight tool description
根据出发地、目的地和日期预订航班

模型会自然地把"助手角色"和"可用工具"结合起来。


总结

  • 单次调用用 .system()

  • 全局固定用 defaultSystem()

  • 长提示词放 Resource 文件,用 .param() 填动态值

  • system prompt 管"角色和风格",tool description 管"能力"

相关推荐
user_admin_god1 小时前
第 03 篇:Java HttpClient 手写第一个 Chat 请求
java·人工智能·spring boot·语言模型
西峰u1 小时前
Java多线程从入门到线程安全
java·开发语言·jvm
AI直播技术杂谈1 小时前
多路数字人直播的算力调度方案对比:单卡、多卡与分布式
ai·音视频
EterNity_TiMe_1 小时前
Pascal Editor 本地部署实战:Bun 启动 WebGPU 3D 编辑器,再解决公网访问报错
人工智能·docker·ai·容器·cpolar
gis开发之家1 小时前
Spring Boot 4 定时任务与异步线程池:从 @Scheduled 到高并发任务编排(生产级实战)
java·spring boot·后端·wpf·spring boot4
是Guava不是瓜娃2 小时前
开源 AI Agent 中台 AgentOne(灵一)---私有部署、数据不出域的企业级 AI 助手
ai·agent·ai agent·skill·agentscope·agent 中台
郑州光合科技余经理2 小时前
本地生活系统:多业务订单字段怎么分账本导出
java·开发语言·前端·数据库·uni-app·php·ai编程
YangYang9YangYan4 小时前
2026 校招审计风控岗位 JD 拆解,工具、专业能力与面试考点
java·大数据·人工智能·数据分析
wuminyu9 小时前
Kafka中sendfile与mmap实现机制解析
java·linux·c语言·jvm·c++