Spring AI 提示词模板实战:告别硬编码,实现提示词工程化管理
Posted on 2026-06-01 21:32 work hard work smart 阅读(111) 评论(0) 收藏 举报摘要:在实际项目中,提示词(Prompt)往往冗长复杂且频繁调整。本文基于真实项目代码,深入讲解 Spring AI 的 PromptTemplate 机制,从基础变量替换到外部文件加载,全面掌握提示词模板的最佳实践。
一、为什么需要提示词模板?
硬编码提示词的痛点
在实际开发中,我们经常看到这样的代码:
// ❌ 错误示范:提示词硬编码在业务逻辑中
@GetMapping("/recommend")
public String recommend(String topic) {
String prompt = """
你是一个专业的技术顾问,请推荐 5 个关于%s的开源项目。
要求:
1. 项目必须是活跃的(最近 3 个月有更新)
2. GitHub Star 数大于 1000
3. 提供项目简介和适用场景
4. 用 Markdown 表格形式输出
""".formatted(topic);
return chatClient.prompt(prompt).call().content();
}
问题:
- 🔴 提示词与业务代码耦合,难以维护
- 🔴 调整提示词需要修改 Java 代码并重新编译
- 🔴 无法复用相似的提示词结构
- 🔴 长提示词影响代码可读性
提示词模板的价值
✅ 关注点分离:提示词与业务逻辑解耦
✅ 动态替换:通过变量实现提示词复用
✅ 外部管理:支持从文件加载,便于版本控制
✅ 团队协作:非技术人员也能参与提示词优化
二、PromptTemplate 核心概念
什么是 PromptTemplate?
PromptTemplate 是 Spring AI 提供的提示词模板类,类似于 Java 的 String.format() 或前端模板引擎,支持:
- 变量占位符:使用
{variable}语法 - 动态赋值:运行时替换变量内容
- 模板复用:同一模板适配不同场景
核心 API
// 1. 创建模板
PromptTemplate template = new PromptTemplate("请介绍{topic}的基本概念");
// 2. 添加变量
template.add("topic", "Spring AI");
// 3. 生成 Prompt
Prompt prompt = template.create();
// 4. 或使用链式调用
Prompt prompt = PromptTemplate.builder()
.resource(templateResource)
.variables(Map.of("topic", "Spring AI"))
.build()
.create();
三、实战一:内联模板与变量替换
基础用法
@RestController
@RequestMapping("/prompt/template")
public class PromptTemplateController implements InitializingBean {
@Autowired
private ChatModel dashScopeChatModel;
private ChatClient chatClient;
@GetMapping("stream")
public Flux<String> stream(String topic, HttpServletResponse response) {
response.setCharacterEncoding("UTF-8");
// 1. 定义模板(使用 {topic} 占位符)
String template = """
请给我推荐几个关于{topic}的开源项目
""";
// 2. 创建模板对象
PromptTemplate promptTemplate = new PromptTemplate(template);
// 3. 替换变量
promptTemplate.add("topic", topic);
// 4. 发起对话(SSE 流式输出)
return chatClient.prompt(promptTemplate.create()).stream().content();
}
@Override
public void afterPropertiesSet() throws Exception {
chatClient = ChatClient.builder(dashScopeChatModel)
.defaultOptions(
DashScopeChatOptions.builder()
.temperature(0.7)
.build()
)
.build();
}
}
测试效果
# 请求 1:推荐 Java 相关项目
curl "http://localhost:8080/prompt/template/stream?topic=Java%20Web框架"
# 请求 2:推荐 Python 相关项目
curl "http://localhost:8080/prompt/template/stream?topic=Python%20机器学习"
同一模板,不同变量,输出完全不同!
简化写法
Spring AI 还支持更简洁的一行代码写法:
@GetMapping("stream1")
public Flux<String> stream1(String topic, HttpServletResponse response) {
response.setCharacterEncoding("UTF-8");
String template = """
请给我推荐几个关于{topic}的开源项目
""";
// 一行代码:创建模板 + 替换变量 + 生成 Prompt
return chatClient.prompt(
new PromptTemplate(template).create(Map.of("topic", topic))
).stream().content();
}
对比:
| 写法 | 优点 | 适用场景 |
|---|---|---|
add() 分步赋值 |
逻辑清晰,便于调试 | 变量多、需要条件判断 |
create(Map) 一步到位 |
代码简洁 | 变量少、直接传递 |
四、实战二:外部文件加载模板
为什么需要外部文件?
当提示词变得非常长时(如包含角色设定、少样本示例、输出格式要求),硬编码在代码中会严重影响可读性。
示例:一个完整的提示词可能包含:
你是一个专业的 GitHub 项目收集专家,具备以下能力:
1. 熟悉主流编程语言生态
2. 了解开源项目的评价标准
3. 能够根据技术栈推荐合适的项目
请按照以下格式输出:
| 项目名称 | GitHub 地址 | Star 数 | 简介 | 适用场景 |
|---------|------------|---------|------|---------|
参考示例:
Input:Java Web 框架
Output:| Spring Boot | github.com/spring-projects/spring-boot | 75k+ | ... | ... |
现在请推荐关于{topic}的开源项目,编程语言限定为{language}。
解决方案:使用外部模板文件
步骤 1:创建模板文件
在 resources/templates/ 目录下创建 open_source_system_prompt.st:
请给我推荐几个关于{topic}的开源项目,要求是和编程语言{language}相关的。
文件命名建议:
.st后缀(String Template 缩写)- 语义化命名:
open_source_system_prompt.st - 按功能分类存放
步骤 2:在 Controller 中加载
@RestController
@RequestMapping("/prompt/template")
public class PromptTemplateController implements InitializingBean {
@Autowired
private ChatModel dashScopeChatModel;
private ChatClient chatClient;
// 1. 使用 @Value 注解加载资源文件
@Value("classpath:/templates/open_source_system_prompt.st")
private Resource systemPrompt;
@GetMapping("/file")
public Flux<String> file(@RequestParam(value = "message") String message,
HttpServletResponse response) {
response.setCharacterEncoding("UTF-8");
// 2. 定义多个变量
HashMap variables = new HashMap();
variables.put("language", "Java");
variables.put("topic", message);
// 3. 使用 Builder 模式加载文件模板
PromptTemplate promptTemplate = PromptTemplate.builder()
.resource(systemPrompt) // 加载外部文件
.variables(variables) // 替换变量
.build();
// 4. 发起对话(可同时设置 system 角色)
return chatClient.prompt(promptTemplate.create())
.system("你是一个专业的的github项目收集人员")
.stream()
.content();
}
@Override
public void afterPropertiesSet() throws Exception {
chatClient = ChatClient.builder(dashScopeChatModel)
.defaultOptions(
DashScopeChatOptions.builder()
.temperature(0.7)
.build()
)
.build();
}
}
测试效果
# 请求:推荐 Java 相关的微服务框架
curl "http://localhost:8080/prompt/template/file?message=微服务框架"
# 输出示例
| 项目名称 | GitHub 地址 | Star 数 | 简介 | 适用场景 |
|---------|------------|---------|------|---------|
| Spring Cloud | github.com/spring-cloud/spring-cloud | 15k+ | 基于 Spring Boot 的微服务框架集合 | 企业级 Java 微服务 |
| Apache Dubbo | github.com/apache/dubbo | 40k+ | 高性能 RPC 框架 | 分布式服务治理 |
| ... | ... | ... | ... | ... |
Builder 模式的优势
// ❌ 传统方式:功能有限
PromptTemplate template = new PromptTemplate("模板内容");
template.add("key", "value");
// ✅ Builder 模式:功能强大
PromptTemplate template = PromptTemplate.builder()
.resource(systemPrompt) // 支持文件加载
.variables(Map.of("key", "value")) // 支持批量变量
.build();
Builder 模式支持:
- ✅ 从
Resource加载模板 - ✅ 批量设置变量(
Map) - ✅ 链式调用,代码优雅
五、PromptTemplate 高级用法
1. 多变量复杂模板
String template = """
你是一个{role},请用{tone}的语气回答。
请分析以下{subject}:
{content}
输出要求:
1. 字数不超过{max_words}字
2. 使用{language}语言
3. 包含{num_examples}个具体示例
""";
PromptTemplate promptTemplate = PromptTemplate.builder()
.resource(new ByteArrayResource(template.getBytes()))
.variables(Map.of(
"role", "技术专家",
"tone", "专业但通俗易懂",
"subject", "Spring AI 的优势",
"content", "用户提供的技术文档...",
"max_words", "500",
"language", "中文",
"num_examples", "3"
))
.build();
2. 条件渲染模板
// 根据条件动态选择模板
Resource template = userLevel.equals("beginner")
? new ClassPathResource("templates/beginner_prompt.st")
: new ClassPathResource("templates/advanced_prompt.st");
PromptTemplate promptTemplate = PromptTemplate.builder()
.resource(template)
.variables(Map.of("topic", topic))
.build();
3. 模板继承与组合
// 基础模板
String baseTemplate = "你是一个{role},";
// 扩展模板
String extendedTemplate = baseTemplate + """
请完成以下任务:
1. 分析{topic}
2. 提供{num}个示例
3. 输出格式:{format}
""";
PromptTemplate template = new PromptTemplate(extendedTemplate);
template.add("role", "数据分析师");
template.add("topic", "用户行为数据");
template.add("num", "5");
template.add("format", "Markdown 表格");
六、模板文件管理的最佳实践
1. 目录结构规范
src/main/resources/
└── templates/
├── system_prompts/ # 系统角色提示词
│ ├── tech_expert.st
│ ├── teacher.st
│ └── analyst.st
├── user_prompts/ # 用户任务提示词
│ ├── code_review.st
│ ├── bug_fix.st
│ └── feature_design.st
└── output_formats/ # 输出格式模板
├── json_output.st
├── markdown_table.st
└── report.st
2. 模板文件命名规范
| 命名模式 | 示例 | 说明 |
|---|---|---|
{场景}_{角色}_prompt.st |
code_review_expert_prompt.st |
语义清晰 |
{功能}_{语言}.st |
recommendation_java.st |
按功能+语言分类 |
{版本}_{描述}.st |
v2_detailed_analysis.st |
版本化管理 |
3. 模板文件版本控制
// 在文件名中体现版本
@Value("classpath:/templates/v2_recommendation_prompt.st")
private Resource promptV2;
// 或使用配置文件管理版本
@ConfigurationProperties(prefix = "prompt")
public class PromptConfig {
private String version = "v2";
private String basePath = "templates/";
}
七、常见陷阱与解决方案
❌ 陷阱 1:变量名拼写错误
// 模板中:{topc} (拼写错误)
// 代码中:template.add("topic", "Java")
// 结果:变量不会被替换,输出原始占位符
解决方案:
// 1. 使用常量定义变量名
public class PromptVariables {
public static final String TOPIC = "topic";
public static final String LANGUAGE = "language";
}
// 2. 使用 Map 批量替换(IDE 可检查拼写)
Map<String, String> variables = Map.of(
PromptVariables.TOPIC, "Java",
PromptVariables.LANGUAGE, "Java"
);
❌ 陷阱 2:模板文件未找到
// 错误路径
@Value("classpath:/template/open_source.st") // 少了一个 's'
private Resource systemPrompt;
// 启动时报错:Resource not found
解决方案:
// 1. 添加空值检查
if (!systemPrompt.exists()) {
throw new IllegalStateException("模板文件不存在:open_source_system_prompt.st");
}
// 2. 使用 @PostConstruct 验证
@PostConstruct
public void validateTemplates() {
assert systemPrompt.exists() : "系统提示词模板缺失";
}
❌ 陷阱 3:特殊字符未转义
// 模板中包含 JSON 格式
String template = """
输出格式:{"topic": "{topic}", "count": 5}
""";
// 大括号可能与变量占位符冲突
解决方案:
// 1. 使用双大括号转义(如果模板引擎支持)
String template = """
输出格式:{{"topic": "{topic}", "count": 5}}
""";
// 2. 将格式说明放在变量中
template.add("format", "{\"topic\": \"<topic>\", \"count\": 5}");
八、完整 Controller 示例
package cn.hollis.llm.llmentor.controller;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
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;
import reactor.core.publisher.Flux;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/prompt/template")
public class PromptTemplateController implements InitializingBean {
@Autowired
private ChatModel dashScopeChatModel;
private ChatClient chatClient;
/**
* 方式一:内联模板 + add() 分步赋值
*/
@GetMapping("stream")
public Flux<String> stream(String topic, HttpServletResponse response) {
response.setCharacterEncoding("UTF-8");
String template = """
请给我推荐几个关于{topic}的开源项目
""";
PromptTemplate promptTemplate = new PromptTemplate(template);
promptTemplate.add("topic", topic);
return chatClient.prompt(promptTemplate.create()).stream().content();
}
/**
* 方式二:内联模板 + create(Map) 一步到位
*/
@GetMapping("stream1")
public Flux<String> stream1(String topic, HttpServletResponse response) {
response.setCharacterEncoding("UTF-8");
String template = """
请给我推荐几个关于{topic}的开源项目
""";
return chatClient.prompt(
new PromptTemplate(template).create(Map.of("topic", topic))
).stream().content();
}
/**
* 方式三:外部文件加载模板
*/
@Value("classpath:/templates/open_source_system_prompt.st")
private Resource systemPrompt;
@GetMapping("/file")
public Flux<String> file(@RequestParam(value = "message") String message,
HttpServletResponse response) {
response.setCharacterEncoding("UTF-8");
HashMap variables = new HashMap();
variables.put("language", "Java");
variables.put("topic", message);
PromptTemplate promptTemplate = PromptTemplate.builder()
.resource(systemPrompt)
.variables(variables)
.build();
return chatClient.prompt(promptTemplate.create())
.system("你是一个专业的的github项目收集人员")
.stream()
.content();
}
@Override
public void afterPropertiesSet() throws Exception {
chatClient = ChatClient.builder(dashScopeChatModel)
.defaultOptions(
DashScopeChatOptions.builder()
.temperature(0.7)
.build()
)
.build();
}
}
九、PromptTemplate 对比传统方式
| 维度 | 硬编码 String.format() |
PromptTemplate |
外部文件 + Builder |
|---|---|---|---|
| 可读性 | ❌ 提示词与代码混杂 | ✅ 模板独立 | ✅ 完全分离 |
| 维护性 | ❌ 修改需重新编译 | ⚠️ 修改需重新编译 | ✅ 修改无需编译 |
| 复用性 | ⚠️ 需手动封装 | ✅ 原生支持 | ✅ 原生支持 |
| 团队协作 | ❌ 仅开发人员可改 | ⚠️ 需了解 Java | ✅ 非技术也可改 |
| 版本控制 | ✅ Git 管理 | ✅ Git 管理 | ✅ Git 管理 |
| 适用场景 | 简单提示词 | 中等复杂度 | 复杂提示词 |
十、总结
Spring AI 的 PromptTemplate 提供了从简单到复杂的完整提示词管理方案:
三种方式的选择指南
最佳实践清单
- ✅ 简单场景用内联模板,复杂场景用外部文件
- ✅ 变量名使用常量定义,避免拼写错误
- ✅ 模板文件按功能分类,语义化命名
- ✅ 使用 Builder 模式加载文件,代码更优雅
- ✅ 添加模板文件存在性检查,避免运行时错误
- ✅ 模板文件纳入 Git 版本控制,便于追踪变更
核心 API 速查
// 1. 内联模板 + 分步赋值
PromptTemplate template = new PromptTemplate("模板{var}");
template.add("var", "value");
// 2. 内联模板 + 一步到位
Prompt prompt = new PromptTemplate("模板{var}")
.create(Map.of("var", "value"));
// 3. 外部文件 + Builder
PromptTemplate template = PromptTemplate.builder()
.resource(resource)
.variables(Map.of("key", "value"))
.build();
掌握 PromptTemplate,让你的提示词管理更加专业、高效、可维护!
参考资源:
作者:Work Hard Work Smart
出处:http://www.cnblogs.com/linlf03/
欢迎任何形式的转载,未经作者同意,请保留此段声明!
浙公网安备 33010602011771号