Spring AI PromptTemplate插入JSON时花括号冲突怎么办?三种可靠解决方案
文章摘要
Spring AI默认使用花括号识别Prompt模板变量。当Prompt中包含JSON Schema、示例对象或代码时,JSON自身的 {} 可能被误当成模板变量,导致渲染报错、变量缺失或内容被错误替换。本文通过错误示例,介绍自定义 `` 分隔符、NoOpTemplateRenderer和外部Resource模板三种解决方式,并说明ChatClient模板与Advisor内部模板的作用范围差异。
一、典型问题
Prompt:
String template = """
请根据用户输入返回JSON:
{
"name": "{name}",
"age": 18
}
""";
这里存在两类花括号:
JSON对象花括号
模板变量{name}
Spring AI默认模板渲染器使用 {variable} 识别变量。
复杂JSON中可能出现:
- 把JSON字段误当变量;
- 提示缺少变量;
- Schema渲染失败;
- 示例对象被修改;
- Advisor模板报错。
二、方案一:修改模板变量分隔符
这是最推荐的方法。
使用:
作为变量语法。
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.template.st.StTemplateRenderer;
PromptTemplate promptTemplate =
PromptTemplate.builder()
.renderer(
StTemplateRenderer.builder()
.startDelimiterToken('')
.build()
)
.template("""
请根据用户信息返回JSON:
{
"name": "",
"department": "",
"enabled": true
}
""")
.build();
String result = promptTemplate.render(
Map.of(
"name", "张三",
"department", "产品部"
)
);
JSON花括号保持原样,只有 和 会被替换。
三、在ChatClient中配置自定义Renderer
@Bean
ChatClient jsonFriendlyChatClient(
ChatModel chatModel
) {
return ChatClient.builder(chatModel)
.templateRenderer(
StTemplateRenderer.builder()
.startDelimiterToken('')
.build()
)
.build();
}
调用:
String response = chatClient.prompt()
.user(user -> user
.text("""
把以下信息转换为JSON:
{
"customer": "",
"requirement": ""
}
""")
.param(
"customer",
"某白酒企业"
)
.param(
"requirement",
"渠道动销分析"
)
)
.call()
.content();
注意:
ChatClient配置的TemplateRenderer只影响直接在ChatClient中定义的user和system模板。
它不会自动修改Advisor内部使用的模板。
四、方案二:不需要模板时使用NoOpTemplateRenderer
如果Prompt已经完整,不需要变量替换:
String prompt = """
请严格返回以下JSON结构:
{
"status": "SUCCESS",
"data": {
"value": 100
}
}
""";
可以关闭模板渲染:
@Bean
ChatClient noTemplateChatClient(
ChatModel chatModel
) {
return ChatClient.builder(chatModel)
.templateRenderer(
NoOpTemplateRenderer.INSTANCE
)
.build();
}
适合:
- 动态字符串已经提前生成;
- Prompt中大量JSON;
- Prompt由外部系统渲染;
- 不需要Spring AI变量替换。
缺点是不能再使用 .param(...)。
因此,是否使用NoOp应按客户端用途拆分,不要全局关闭后又期待模板变量生效。
五、方案三:外部Resource模板
将Prompt放到:
src/main/resources/prompts/customer-analysis.st
模板:
你是企业经营分析助手。
客户:
分析周期:
请返回:
{
"summary": "分析结论",
"risks": [
{
"name": "风险名称",
"level": "HIGH"
}
]
}
加载:
@Value(
"classpath:/prompts/customer-analysis.st"
)
private Resource promptResource;
构建:
PromptTemplate template =
PromptTemplate.builder()
.resource(promptResource)
.renderer(
StTemplateRenderer.builder()
.startDelimiterToken('')
.build()
)
.build();
String prompt = template.render(
Map.of(
"customer",
"某渠道客户",
"period",
"2026年7月"
)
);
外部文件便于:
- 版本控制;
- 代码审查;
- 单独测试;
- 多语言;
- Prompt复用;
- 减少Java字符串噪声。
六、为什么不推荐手工转义所有花括号
问题是:
- 不同模板引擎转义规则不同;
- JSON Schema非常长;
- 可读性差;
- 容易漏掉嵌套对象;
- 模板升级后可能失效;
- 开发者难以区分JSON与变量。
改变变量分隔符通常更清晰。
七、JSON Schema场景
复杂Schema中花括号密集,推荐:
模板变量使用
JSON继续使用{ }
例如:
String template = """
任务:
输出必须符合以下Schema:
{
"type": "object",
"properties": {
"result": {
"type": "string"
}
}
}
""";
八、Advisor内部模板要单独配置
假设你配置了:
ChatClient.builder(chatModel)
.templateRenderer(customRenderer)
QuestionAnswerAdvisor仍然可能使用自己的模板。
原因是:
ChatClient模板
Advisor内部模板
属于不同作用范围。
自定义RAG模板时,需要在Advisor Builder中设置对应PromptTemplate。
排查方法:
普通user模板是否正常
RAG开启后是否报错
错误是否来自QuestionAnswerAdvisor
Advisor是否有独立模板配置
九、模板变量缺失如何提前发现
不要等到运行时才发现。
可以写单元测试:
class PromptTemplateTest {
@Test
void shouldRenderJsonTemplate() {
PromptTemplate template =
createTemplate();
String result = template.render(
Map.of(
"name",
"张三"
)
);
assertThat(result)
.contains("\"name\": \"张三\"");
assertThat(result)
.contains("\"enabled\": true");
}
}
测试:
- 所有变量可以渲染;
- JSON格式保留;
- 没有未替换变量;
- 中文和换行正确;
- 示例代码没有被破坏。
十、检测未替换变量
private static final Pattern UNRESOLVED =
Pattern.compile("");
public static void validateRenderedPrompt(
String prompt
) {
Matcher matcher = UNRESOLVED.matcher(prompt);
if (matcher.find()) {
throw new IllegalArgumentException(
"存在未替换变量:"
+ matcher.group()
);
}
}
推荐变量统一命名:
避免与普通XML标签混淆。
十一、用户输入中包含花括号怎么办
变量值可能是:
请解释Java中的Map和JSON { }。
正常模板替换不会再次递归解析变量值。
不要对渲染结果重复执行模板渲染,否则用户输入中的花括号可能被第二次解释。
十二、模板安全
PromptTemplate不是安全过滤器。
用户变量仍可能包含:
忽略之前的指令
需要独立处理:
- Prompt Injection;
- 输入长度;
- 敏感信息;
- HTML;
- 日志脱敏;
- 数据权限。
模板渲染只负责变量替换,不负责内容可信度。
十三、选择建议
Prompt包含JSON并需要变量
使用自定义分隔符
Prompt不需要变量
使用NoOpTemplateRenderer
Prompt较长、需要版本控制
使用Resource外部模板
+自定义分隔符
Advisor内部Prompt
在Advisor中单独配置模板
总结
Spring AI PromptTemplate与JSON冲突的根因是:
模板变量和JSON共用花括号
最可靠的解决方式是将变量改为:
并通过单元测试确认JSON结构和变量替换都正确。
延伸阅读
如果你正在关注企业级 AI 应用、Agent、RAG、MCP 与大模型工程化落地,欢迎访问 智元界:
https://www.zyentor.com/
智元界将持续分享可运行的技术实战、架构设计、问题排查与企业应用案例。