记得我第一次接触大模型开发时,最头疼的就是如何构建有效的Prompt。那时候的做法简单粗暴——直接字符串拼接。比如要查询用户订单,代码大概是这样的:
java复制String orderId = "O12345";
String prompt = "查询订单" + orderId + "的物流信息,要求返回快递公司和预计送达时间";
这种写法在demo阶段还能凑合,但当我接手企业级知识库系统时,问题就暴露无遗了。某次需求变更要求在所有查询Prompt末尾添加免责声明,我不得不全局搜索拼接字符串的位置,整整改了28处代码。更糟的是,当需要根据不同用户角色动态调整Prompt内容时,代码里到处都是if-else嵌套的字符串拼接,活脱脱一个"代码屎山"。
Spring AI的PromptTemplate就像及时雨,它用模板引擎的思路解决了这些问题。来看看重构后的版本:
java复制PromptTemplate template = new PromptTemplate(
"{{#if isVIP}}尊敬的VIP用户{{/if}}您的订单{{orderId}}物流信息:\n" +
"- 快递公司:{{company}}\n" +
"- 预计送达:{{estimateTime}}\n" +
"{{> disclaimer}}"
);
Map<String, Object> params = new HashMap<>();
params.put("orderId", "O12345");
params.put("isVIP", true);
params.put("company", "顺丰速运");
params.put("estimateTime", "2023-12-25");
这种写法有三大优势:首先是可维护性,模板与业务逻辑解耦;其次是灵活性,支持条件判断等逻辑;最重要的是安全性,内置的参数转义机制能有效防止Prompt注入攻击。
在开发知识库问答系统时,我设计了三层模板结构:
finance.st存放金融相关Promptloan-query.st处理贷款查询文件目录结构示例:
code复制templates/
├── base/
│ ├── header.st
│ └── footer.st
├── domain/
│ ├── finance.st
│ └── legal.st
└── scenario/
├── loan-query.st
└── contract-review.st
知识库系统需要根据用户身份动态调整回答详略程度。我们通过参数处理器实现:
java复制public class UserLevelParameterProcessor implements ParameterProcessor {
@Override
public void process(Map<String, Object> params) {
User user = (User) params.get("user");
if (user.isExpert()) {
params.put("detailLevel", "专业版");
params.put("maxLength", 500);
} else {
params.put("detailLevel", "简明版");
params.put("maxLength", 200);
}
}
}
// 使用示例
PromptTemplate template = new PromptTemplate("{{> header}} {{content}} {{> footer}}");
template.addParameterProcessor(new UserLevelParameterProcessor());
为了支持灰度发布,我们给模板加上版本号:
java复制@GetMapping("/query")
public String handleQuery(@RequestParam String q) {
String templateName = "v2.1/knowledge-query"; // 带版本号的模板
PromptTemplate template = templateLoader.load(templateName);
// ...
}
配合配置中心,可以实现动态切换模板版本,方便进行A/B测试。
在客服场景中,我们使用条件判断实现差异化响应:
java复制String template =
"{{#if user.isPremium}}尊敬的VIP用户{{/if}}您的问题:{{question}}\n" +
"{{#if queryType=='urgent'}}【加急处理】{{/if}}\n" +
"历史记录:{{#each history}}{{role}}:{{content}}{{/each}}";
更复杂的场景可以用嵌套条件:
java复制String template =
"{{#if user.age < 18}}"
+ "根据青少年保护条例,{{#if queryType=='finance'}}金融问题需监护人确认{{else}}我们将简化回复{{/if}}"
+ "{{else}}正常回复流程{{/if}}";
处理商品列表时,循环语法特别有用:
java复制String template =
"为您推荐以下商品:\n" +
"{{#each products as product}}" +
"{{@index}}. {{product.name}}(评分:{{product.rating}}/5)\n" +
"{{/each}}";
还可以结合条件过滤:
java复制String template =
"{{#each products as product}}" +
"{{#if product.stock > 0}}" +
"现货:{{product.name}} {{product.price}}元\n" +
"{{else}}" +
"预售:{{product.name}}({{product.restockDate}}到货)\n" +
"{{/if}}" +
"{{/each}}";
大型项目推荐使用模块化设计:
java复制// base-template.st
"{{> header}}" +
"{{content}}" +
"{{> footer}}"
// header.st
"【{{systemName}}】{{time}}"
// query-template.st
"{{< base-template}}" +
"{{#content}}查询条件:{{query}}{{/content}}"
这种架构让模板更易维护,修改全局样式只需调整base-template即可。
通过CacheManager实现模板缓存:
java复制@Configuration
public class TemplateCacheConfig {
@Bean
public CacheManager templateCache() {
return new CaffeineCacheManager("templates") {
@Override
protected Cache<Object, Object> createNativeCache(String name) {
return Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterAccess(1, TimeUnit.HOURS)
.build();
}
};
}
}
我们实现了参数过滤拦截器:
java复制public class InjectionFilter implements PromptTemplatePostProcessor {
private static final Set<String> BLACKLIST = Set.of("system", "sudo", "rm");
@Override
public Prompt postProcess(Prompt prompt) {
Map<String, Object> params = prompt.getParameters();
params.replaceAll((k, v) ->
v instanceof String ? sanitize((String) v) : v);
return prompt;
}
private String sanitize(String input) {
for (String word : BLACKLIST) {
if (input.contains(word)) {
throw new SecurityException("检测到危险参数: " + word);
}
}
return input.replace("{{", "").replace("}}", "");
}
}
通过AOP监控模板渲染性能:
java复制@Aspect
@Component
public class TemplateMonitor {
@Around("execution(* org.springframework.ai.PromptTemplate.create(..))")
public Object monitor(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
try {
return pjp.proceed();
} finally {
long cost = System.currentTimeMillis() - start;
Metrics.counter("template.render.time").record(cost);
if (cost > 1000) {
log.warn("模板渲染超时: {}", pjp.getArgs());
}
}
}
}
结合ChatHistory实现多轮对话:
java复制public class KnowledgeService {
private final ChatHistory chatHistory;
public String answer(String question) {
// 添加上下文
chatHistory.add(new UserMessage(question));
// 构建Prompt
String template = "历史对话:\n{{#each history}}{{role}}:{{content}}\n{{/each}}" +
"当前问题:{{question}}\n" +
"请根据知识库回答";
Prompt prompt = new PromptTemplate(template)
.create(Map.of(
"history", chatHistory.getMessages(),
"question", question
));
// 调用模型并保存响应
AiMessage response = aiClient.generate(prompt);
chatHistory.add(response);
return response.getText();
}
}
从数据库和API获取模板参数:
java复制public Prompt buildProductPrompt(String productId) {
Product product = productService.getById(productId);
List<Review> reviews = reviewService.getTopReviews(productId);
Map<String, Object> params = new HashMap<>();
params.put("product", product);
params.put("reviews", reviews);
params.put("timestamp", Instant.now());
return productPromptTemplate.create(params);
}
基于用户画像动态生成Prompt:
java复制String template =
"根据{{user.name}}的浏览历史({{#each user.history}}{{this}}{{/each}})\n" +
"和{{user.interests}}兴趣标签\n" +
"推荐3个相关知识点,用{{user.preferredLanguage}}回答";
Prompt prompt = new PromptTemplate(template)
.create(Map.of(
"user", userProfile,
"timestamp", LocalDate.now()
));
启用调试输出:
java复制@Configuration
public class TemplateConfig {
@Bean
public PromptTemplate promptTemplate() {
return new PromptTemplate()
.setDebug(true) // 开启调试
.setStrictMode(true); // 严格校验参数
}
}
调试输出示例:
code复制[DEBUG] 渲染模板: product-detail
参数: {product=Product(id=123), user=User(level=VIP)}
渲染耗时: 45ms
结果: 【VIP专享】商品123...
我们的基准测试结果(1000次渲染):
| 模板复杂度 | 平均耗时 | 内存占用 |
|---|---|---|
| 简单变量 | 12ms | 5MB |
| 条件逻辑 | 28ms | 8MB |
| 嵌套循环 | 75ms | 15MB |
优化建议:
实现模板热加载:
java复制@Scheduled(fixedRate = 300000) // 每5分钟检查
public void reloadTemplates() {
templateCache.forEach((name, template) -> {
if (template.isModified()) {
template.reload();
log.info("模板{}热更新完成", name);
}
});
}
独立部署模板服务,提供API接口:
java复制@RestController
@RequestMapping("/templates")
public class TemplateController {
@GetMapping("/{name}")
public String getTemplate(@PathVariable String name,
@RequestParam Map<String, Object> params) {
PromptTemplate template = templateService.load(name);
return template.render(params);
}
}
使用ETag实现缓存协商:
java复制@Bean
public WebClient webClient() {
return WebClient.builder()
.baseUrl("http://template-service")
.defaultHeader(HttpHeaders.ACCEPT, "application/json")
.filter((request, next) -> {
String etag = templateCache.getEtag(request.url());
if (etag != null) {
request.header(HttpHeaders.IF_NONE_MATCH, etag);
}
return next.exchange(request);
})
.build();
}
集成Sleuth实现链路追踪:
java复制public Prompt renderWithTrace(String templateName, Map<String, Object> params) {
Span span = tracer.nextSpan().name("template-render");
try (var ws = tracer.withSpan(span.start())) {
span.tag("template", templateName);
return templateService.render(templateName, params);
} finally {
span.end();
}
}
自动简化复杂模板:
java复制public String simplifyTemplate(String template) {
// 分析模板结构
TemplateAnalysis analysis = analyzer.analyze(template);
// 应用优化规则
if (analysis.getConditionDepth() > 3) {
return templateOptimizer.flattenConditions(template);
}
if (analysis.getLoopCount() > 2) {
return templateOptimizer.splitLoops(template);
}
return template;
}
用大模型优化Prompt:
java复制public String optimizeWithAI(String template) {
String prompt = "请优化以下Prompt模板:\n" + template + "\n" +
"要求:\n" +
"1. 保持语义不变\n" +
"2. 减少token使用\n" +
"3. 提高指令清晰度";
return aiClient.generate(new Prompt(prompt)).getText();
}
根据模型反馈动态调整:
java复制public class AdaptiveTemplate {
private final Map<String, TemplateVariant> variants;
public String selectBestVariant(String input) {
return variants.values().stream()
.max(Comparator.comparingDouble(v -> v.getScore(input)))
.map(v -> v.getTemplate())
.orElseGet(this::getDefaultTemplate);
}
}