【SpringAIAlibaba新手村系列】(6)PromptTemplate 提示词模板与变量替换

0 阅读6分钟

第六章 PromptTemplate 提示词模板与变量替换

版本标注

  • Spring AI: 1.1.2
  • Spring AI Alibaba: 1.1.2.0

章节定位

  • 在官方示例中,模板不仅用于普通问答,也常用于 RAG 的 Prepare 阶段、Agent 指令模板、Routing 决策与结构化输出提示词设计。

s01 > s02 > s03 > s04 > s05 > [ s06 ] s07 > s08 > s09 > s10 > s11 > s12 > s13 > s14 > s15 > s16 > s17 > s18

"把提示词参数化, 才能从演示代码走向可复用代码" -- 模板解决的是复用、维护和动态拼装。


一、为什么需要 PromptTemplate?

1.1 不使用模板的问题

在日常开发中,我们经常需要根据不同的输入去问 AI:

  • "介绍下 Java"
  • "介绍下 Python"
  • "介绍下 Go"

如果不用模板,你会这么写:

// 每个问题都要写一个新的请求
@GetMapping("/chat/java")
public String chatJava() {
    return chatClient.prompt().user("介绍下Java").call().content();
}

@GetMapping("/chat/python")
public String chatPython() {
    return chatClient.prompt().user("介绍下Python").call().content();
}

@GetMapping("/chat/go")
public String chatGo() {
    return chatClient.prompt().user("介绍下Go").call().content();
}

这种代码:

  • 大量重复
  • 难以维护
  • 无法复用

1.2 模板的价值

使用 PromptTemplate,你可以这样写:

// 定义一个模板,使用 {topic} 作为占位符
// "介绍下{topic}"
// 运行时传入不同的 topic 值,就可以生成不同的请求

// 调用1:topic = Java → "介绍下Java"
// 调用2:topic = Python → "介绍下Python"
// 调用3:topic = Go → "介绍下Go"

模板的优势

  • 一套模板,复用多个场景
  • 代码简洁,易维护
  • 动态参数替换,灵活性强

二、核心概念详解

2.1 PromptTemplate

PromptTemplate 是 Spring AI 中用于创建带变量模板的 Prompt 的类。

基本语法:

// 1. 创建模板(使用 {} 包裹占位符)
PromptTemplate template = new PromptTemplate(
    "你好,我叫{name},今年{age}岁"
);

// 2. 填充变量(Map 的 key 对应模板中的占位符)
Prompt prompt = template.create(Map.of(
    "name", "张三",
    "age", "25"
));

// 3. 调用 AI
chatClient.prompt(prompt).call().content();
// 输出:你好,我叫张三,今年25岁

2.2 SystemPromptTemplate

SystemPromptTemplate 是专门用于创建系统消息的模板,用法几乎一样:

// 创建系统消息模板
SystemPromptTemplate systemTemplate = new SystemPromptTemplate(
    "你是一个{profession}专家,擅长{skill}"
);

// 填充变量
Message systemMessage = systemTemplate.createMessage(Map.of(
    "profession", "Java",
    "skill", "后端开发"
));
// 输出:你要是一个Java专家,擅长后端开发

2.3 模板文件支持

Spring AI 还支持从外部文件加载模板:

# 在 resources/prompttemplate/atguigu-template.txt 中
# 内容如下:
讲一个关于{topic}的故事,并以{output_format}格式输出,字数在{wordCount}左右

# Java 代码中读取
@Value("classpath:/prompttemplate/atguigu-template.txt")
private Resource userTemplate;

PromptTemplate promptTemplate = new PromptTemplate(userTemplate);

三、项目代码详解

3.1 模板文件

首先在 src/main/resources/prompttemplate/ 目录下创建模板文件 atguigu-template.txt

讲一个关于{topic}的故事,并以{output_format}格式输出,字数在{wordCount}左右

3.2 控制器代码

package com.atguigu.study.controller;

import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.core.io.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.util.List;
import java.util.Map;

/**
 * PromptTemplate 演示控制器
 * 展示提示词模板的多种使用方式
 */
@RestController
public class PromptTemplateController
{
    @Resource(name = "deepseek")
    private ChatModel deepseekChatModel;

    @Resource(name = "qwen")
    private ChatModel qwenChatModel;

    @Resource(name = "deepseekChatClient")
    private ChatClient deepseekChatClient;

    @Resource(name = "qwenChatClient")
    private ChatClient qwenChatClient;

    /**
     * 方式一:代码中直接定义模板
     * 
     * 接口:http://localhost:8006/prompttemplate/chat?topic=java&output_format=html&wordCount=200
     */
    @GetMapping("/prompttemplate/chat")
    public Flux<String> chat(
        @RequestParam("topic") String topic,
        @RequestParam("output_format") String output_format,
        @RequestParam("wordCount") String wordCount)
    {
        // 1. 在代码中定义模板字符串
        PromptTemplate promptTemplate = new PromptTemplate(
            "讲一个关于{topic}的故事," +
            "并以{output_format}格式输出," +
            "字数在{wordCount}左右"
        );

        // 2. 创建 Prompt 并填充变量
        //    Map 中的 key 对应模板中的占位符 {topic}, {output_format}, {wordCount}
        Prompt prompt = promptTemplate.create(Map.of(
            "topic", topic,              // 替换 {topic}
            "output_format", output_format, // 替换 {output_format}
            "wordCount", wordCount       // 替换 {wordCount}
        ));

        // 3. 调用 AI(服务端渲染模板后发送请求)
        //    最终发送的消息是:"讲一个关于java的故事,并以html格式输出,字数在200左右"
        return deepseekChatClient.prompt(prompt).stream().content();
    }

    /**
     * 方式二:从外部模板文件加载
     * 
     * 文件位置:src/main/resources/prompttemplate/atguigu-template.txt
     * 
     * 接口:http://localhost:8006/prompttemplate/chat2?topic=java&output_format=html
     */
    @Value("classpath:/prompttemplate/atguigu-template.txt")
    private org.springframework.core.io.Resource userTemplate;

    @GetMapping("/prompttemplate/chat2")
    public String chat2(
        @RequestParam("topic") String topic,
        @RequestParam("output_format") String output_format,  
		@RequestParam("wordCount") String wordCount)
    {
        // 1. 从外部文件创建模板
        PromptTemplate promptTemplate = new PromptTemplate(userTemplate);

        // 2. 填充变量
		Prompt prompt = promptTemplate.create(Map.of(  
		    "topic", topic,  
		    "output_format", output_format,  
		    "wordCount", wordCount  
		));

        // 3. 调用 AI(非流式)
        return deepseekChatClient.prompt(prompt).call().content();
    }

    /**
     * 方式三:同时使用系统模板和用户模板
     * 
     * SystemPromptTemplate:创建系统消息模板
      * PromptTemplate:创建用户 Prompt 模板
     * 
     * 接口:http://localhost:8006/prompttemplate/chat3?sysTopic=法律&userTopic=知识产权法
     */
    @GetMapping("/prompttemplate/chat3")
    public String chat3(
        @RequestParam("sysTopic") String sysTopic,
        @RequestParam("userTopic") String userTopic)
    {
        // 1. 创建系统消息模板
        //    {systemTopic} 是占位符
        SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(
            "你是{systemTopic}助手,只回答{systemTopic}其它无可奉告,以HTML格式的结果。"
        );
        Message sysMessage = systemPromptTemplate.createMessage(
            Map.of("systemTopic", sysTopic)  // 替换占位符
        );

        // 2. 创建用户 Prompt 模板
        PromptTemplate userPromptTemplate = new PromptTemplate("解释一下{userTopic}");
        Prompt userPrompt = userPromptTemplate.create(
            Map.of("userTopic", userTopic)
        );
        Message userMessage = userPrompt.getInstructions().get(0);

        // 3. 组合多个 Message 为一个 Prompt
        //    Prompt 可以包含多个 Message(系统消息 + 用户消息 + 历史消息等)
        Prompt prompt = new Prompt(List.of(sysMessage, userMessage));

        // 4. 调用 AI
        return deepseekChatClient.prompt(prompt).call().content();
    }

    /**
     * 方式四:使用 ChatModel 直接调用(底层API)
     * 
     * 使用 SystemMessage + UserMessage + Prompt 的组合
     * 
     * 接口:http://localhost:8006/prompttemplate/chat4?question=牡丹花
     */
    @GetMapping("/prompttemplate/chat4")
    public String chat4(@RequestParam("question") String question)
    {
        // 1. 创建系统消息(角色设定)
        SystemMessage systemMessage = new SystemMessage(
            "你是一个Java编程助手,拒绝回答非技术问题。"
        );

        // 2. 创建用户消息
        UserMessage userMessage = new UserMessage(question);

        // 3. 组合成 Prompt(可以用 List.of 传入可变数量的 Message)
        Prompt prompt = new Prompt(List.of(systemMessage, userMessage));

        // 4. 调用底层 ChatModel(需要手动提取结果)
        //    getResult().getOutput().getText() 是标准的提取路径
        String result = deepseekChatModel.call(prompt)
            .getResult()
            .getOutput()
            .getText();
        
        System.out.println(result);
        return result;
    }

    /**
     * 方式五:使用 ChatClient 的简洁 API(推荐)
     * 
     * 直接用链式调用设置系统消息和用户消息
     * 
     * 接口:http://localhost:8006/prompttemplate/chat5?question=火锅
     */
    @GetMapping("/prompttemplate/chat5")
    public Flux<String> chat5(@RequestParam("question") String question)
    {
        return deepseekChatClient.prompt()
            // .system() 方法直接设置系统消息
            .system("你是一个Java编程助手,拒绝回答非技术问题。")
            // .user() 方法设置用户消息
            .user(question)
            // 流式输出
            .stream()
            .content();
    }
}

四、模板语法进阶

4.1 多变量模板

PromptTemplate template = new PromptTemplate(
    "我是{name},来自{city},从事{profession},今年{age}岁"
);

Prompt prompt = template.create(Map.of(
    "name", "李四",
    "city", "北京",
    "profession", "软件工程师",
    "age", "28"
));

4.2 条件逻辑模板

// 可用三元运算符
PromptTemplate template = new PromptTemplate(
    "用{style}风格的代码解释{concept}"
);

Prompt prompt = template.create(Map.of(
    "style", "brief".equals(style) ? "简洁" : "详细",
    "concept", "Java多线程"
));

4.3 循环模板(较少用)

// 如果需要处理列表,可以用辅助方法处理
StringBuilder sb = new StringBuilder();
for (Item item : items) {
    sb.append(item.getName()).append(",");
}
PromptTemplate template = new PromptTemplate(
    "推荐以下商品:" + sb.toString()
);

五、本章小结

5.1 核心类和方法

类/方法说明
PromptTemplatePrompt 模板,用于创建带变量的 Prompt
SystemPromptTemplate系统消息模板,用于创建带变量的系统消息
PromptTemplate.create(Map)用 Map 填充变量并生成 Prompt
SystemPromptTemplate.createMessage(Map)用 Map 填充变量并生成系统消息
Prompt(List.of(msgs))组合多个消息为一个完整的 Prompt
@Value("classpath:xxx")从外部文件加载模板

5.2 使用场景

场景推荐方式
简单替换1-2个变量PromptTemplate 代码内定义
复杂模板/团队协作外部模板文件
需要同时设置系统和用户消息SystemPromptTemplate + PromptTemplate
快速开发ChatClient.prompt().system().user()

本章重点

  1. 理解 PromptTemplate 的核心原理(占位符替换)
  2. 掌握在代码中动态创建模板的方法
  3. 学会从外部文件加载模板
  4. 理解如何组合系统消息和用户消息

下章剧透(s07):

学会了动态Prompt,接下来我们将学习结构化输出——如何让AI返回我们想要的格式(如JSON、Java对象)。


📝 编辑者:Flittly
📅 更新时间:2026年4月
🔗 相关资源Spring AI PromptTemplate API