PromptTemplate 提示词模板
概述
PromptTemplate 是一个轻量文本模板引擎,用 {{ expression }} 从 Map 数据中取值,并支持 ?? 兜底链。 它适合把稳定的提示结构与每次请求的数据分离,例如系统指令、RAG 上下文、分类任务和结构化输出约束。
它不是完整编程语言:没有 if/else、循环或函数调用。复杂业务判断应先在 Java 中完成,再把整理好的数据传给 模板。
适用场景
- 根据用户、语言和产品配置生成系统提示;
- 把检索结果填入固定的 RAG 提示结构;
- 为分类、抽取和评测任务复用同一模板;
- 使用默认值处理可选字段;
- 把动态字符串安全嵌入 JSON 字符串字段。
一次性短字符串直接拼接更简单;涉及复杂布局、国际化资源或条件逻辑时,也可以选择成熟模板引擎。
快速开始
String source = "你好,{{ user.name ?? '访客' }}!你有 {{ count }} 条待办。";
Map<String, Object> data = Map.of(
"user", Map.of("name", "张三"),
"count", 5
);
String text = PromptTemplate.of(source).format(data);
System.out.println(text);输出:
你好,张三!你有 5 条待办。PromptTemplate.of() 会按完整模板字符串使用全局缓存;相同字符串后续复用已解析 Token 和 JSONPath。
基础语法
字段取值
姓名:{{ user.name }}
邮箱:{{ user.contact.email }}
第一项:{{ items[0] }}表达式会转换为 Fastjson2 JSONPath;没有 $ 前缀时框架自动添加 $.。
多级兜底
称呼:{{ user.nickname ?? user.name ?? '匿名用户' }}框架从左到右选取第一个非 null 值。最终结果为空且没有显式空兜底时,默认抛出 IllegalArgumentException。
允许空值
表达式以 ?? 结尾表示允许最终为空:
备注:{{ note ?? }}字符串字面量
角色:{{ '客服助手' }}
语言:{{ "简体中文" }}只支持单引号或双引号包裹的字符串字面量,不执行 Java 表达式。
缺失变量策略
默认行为是缺失时报错,适合尽早发现 Prompt 数据问题:
PromptTemplate template = PromptTemplate.of("订单:{{ order.id }}");
template.format(Map.of()); // IllegalArgumentException也可以关闭异常:
PromptTemplate template = new PromptTemplate("订单:{{ order.id }}");
template.setFailOnMissingVariable(false);
String text = template.format(Map.of()); // "订单:"或者保留原表达式:
template.setKeepExpressionOnMissingVariable(true);
String text = template.format(Map.of()); // "订单:{{order.id}}"keepExpressionOnMissingVariable 优先于 failOnMissingVariable。
PromptTemplate.of() 返回缓存中的共享可变实例。需要修改缺失变量策略时,推荐使用 new PromptTemplate(source) 创建独立实例,避免一个请求修改全局缓存对象的行为。
JSON 字符串转义
将变量嵌入 JSON 字符串值时,使用第二个参数启用转义:
PromptTemplate template = PromptTemplate.of(
"{\"query\":\"{{ input }}\"}"
);
String json = template.format(
Map.of("input", "第一行\n他说:\"你好\""),
true
);该选项会转义字符串中的反斜杠、引号和控制字符,但它不会验证最终文本一定是合法 JSON。对象、数组和整体 JSON 结构应优先使用 JSON 序列化库构建,不要依赖模板拼接复杂 JSON。
缓存机制
框架维护两个进程级 ConcurrentHashMap:
- 模板字符串到
PromptTemplate的缓存; - 完整 JSONPath 到编译结果的缓存。
PromptTemplate.clearCache();clearCache() 主要用于测试或模板热更新验证。高并发生产请求频繁清理会导致后续重新解析,也无法限制由大量 用户自定义模板造成的缓存增长。不要把无限多、用户可控的唯一模板字符串直接交给全局缓存。
与 Prompt 配合
PromptTemplate systemTemplate = PromptTemplate.of(
"你是 {{ product }} 的客服,只使用 {{ language }} 回答。"
);
String systemText = systemTemplate.format(Map.of(
"product", "Agents-Flex",
"language", "简体中文"
));
SimplePrompt prompt = new SimplePrompt(userQuestion);
prompt.setSystemMessage(SystemMessage.of(systemText));模板只负责生成文本;消息角色、历史、图片和 Tool 仍由 Prompt 管理。
生产建议
- 关键字段不提供静默空值,让缺失数据在调用模型前失败;
- 可选字段使用明确默认值或显式
??空兜底; - 不把密钥、认证 Header 或不应发送的数据放入模板上下文;
- 用户输入嵌入 JSON 字符串时启用转义,复杂 JSON 使用序列化库;
- 模板版本纳入代码或配置管理,记录调用使用的版本;
- 限制动态模板数量、长度和数据规模,避免全局缓存无界增长。
常见问题
支持 if/else 或循环吗?
不支持。先在 Java 层计算条件和列表文本,再作为字段传入。
为什么字段存在但仍得到空值?
检查 JSONPath、Map Key 大小写和对象 Getter。表达式解析异常会按“未取到值”处理,再进入兜底或缺失策略。
format(data, true) 会返回完整 JSON 字面量吗?
不会。它只对动态字符串值做 JSON 转义,不会给结果自动加引号,也不会校验整个文档。
可以修改 PromptTemplate.of() 返回对象的策略吗?
技术上可以,但对象由全局缓存共享,会影响后续相同模板。需要定制策略时使用构造器创建独立对象。