当前位置: 首页 > news >正文

Spring AI 提示词模板实战:告别硬编码,实现提示词工程化管理

Spring AI 提示词模板实战:告别硬编码,实现提示词工程化管理

Posted on 2026-06-01 21:32  work hard work smart  阅读(0)  评论(0)    收藏  举报

摘要:在实际项目中,提示词(Prompt)往往冗长复杂且频繁调整。本文基于真实项目代码,深入讲解 Spring AI 的 PromptTemplate 机制,从基础变量替换到外部文件加载,全面掌握提示词模板的最佳实践。

一、为什么需要提示词模板?

硬编码提示词的痛点

在实际开发中,我们经常看到这样的代码:

// ❌ 错误示范:提示词硬编码在业务逻辑中
@GetMapping("/recommend")
public String recommend(String topic) {String prompt = """你是一个专业的技术顾问,请推荐 5 个关于%s的开源项目。要求:1. 项目必须是活跃的(最近 3 个月有更新)2. GitHub Star 数大于 10003. 提供项目简介和适用场景4. 用 Markdown 表格形式输出""".formatted(topic);return chatClient.prompt(prompt).call().content();
}

问题

  • 🔴 提示词与业务代码耦合,难以维护
  • 🔴 调整提示词需要修改 Java 代码并重新编译
  • 🔴 无法复用相似的提示词结构
  • 🔴 长提示词影响代码可读性

提示词模板的价值

关注点分离:提示词与业务逻辑解耦
动态替换:通过变量实现提示词复用
外部管理:支持从文件加载,便于版本控制
团队协作:非技术人员也能参与提示词优化


二、PromptTemplate 核心概念

什么是 PromptTemplate?

PromptTemplate 是 Spring AI 提供的提示词模板类,类似于 Java 的 String.format() 或前端模板引擎,支持:

  1. 变量占位符:使用 {variable} 语法
  2. 动态赋值:运行时替换变量内容
  3. 模板复用:同一模板适配不同场景

核心 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 {@Autowiredprivate 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();}@Overridepublic 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}的开源项目""";// 一行代码:创建模板 + 替换变量 + 生成 Promptreturn 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 {@Autowiredprivate 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();}@Overridepublic 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 {@Autowiredprivate 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();}@Overridepublic 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 提供了从简单到复杂的完整提示词管理方案:

三种方式的选择指南

graph TDA[提示词复杂度?] -->|简单,1-2个变量| B[内联模板 + create Map]A -->|中等,需要调试| C[内联模板 + add 分步]A -->|复杂,超过50行| D[外部文件 + Builder]B --> E[一行代码搞定]C --> F[逻辑清晰,便于调试]D --> G[团队协作,独立管理]

最佳实践清单

  • ✅ 简单场景用内联模板,复杂场景用外部文件
  • ✅ 变量名使用常量定义,避免拼写错误
  • ✅ 模板文件按功能分类,语义化命名
  • ✅ 使用 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,让你的提示词管理更加专业、高效、可维护!


参考资源

  • Spring AI 官方文档 - Prompt Templates
  • [项目源码 - PromptTemplateController](file:///D:/springaiworkspace/LLMentor/springai/src/main/java/cn/hollis/llm/llmentor/controller/PromptTemplateController.java)
  • [提示词工程实战指南](file:///D:/springaiworkspace/LLMentor/springai/docs/提示词工程实战指南.md)