
别把 Prompt 硬编码进代码Spring AI PromptTemplate 与提示词管理最佳实践系列Spring AI 深入浅出第 5 篇版本Spring AI 2.0.1 / Spring Boot 4.1.x / JDK 21标签#SpringAI #PromptTemplate #提示词工程 #工程化前置阅读第 2 篇《ChatClient 全拆解》预计字数6000 字可复制源码 模板示例 踩坑 FAQ本文将解决把 prompt 写成代码里的字符串改一句提示就要重新发版。本文用PromptTemplate把提示词做成可配置、可版本、可灰度的工程资产。你将学到为什么提示词不能硬编码PromptTemplate/SystemPromptTemplate的变量、渲染、消息生成条件片段与多模板组装提示词外置到配置/数据库的方案版本化与灰度的思路一、版本标注本文基于 Spring AI 2.0.1 / Boot 4.1 / JDK 21。依赖沿用第 1 篇 pom。示例里 prompt 模板类属核心模块1.x/2.0 用法稳定。二、为什么提示词不能硬编码硬编码 prompt 的典型代码StringreplychatClient.prompt().system(你是一个严谨的 Java 技术助手回答必须给出代码示例。).user(解释一下q).call().content();问题在哪提示词和业务逻辑耦合带来四个痛点变更要重新发版调一句人设要走编译、测试、上线。无法 A/B想对比两套提示词哪个效果好得改代码部署两遍。无法复用同一段提示词散落在多个类里改一处漏一处。无法审计提示词是 AI 应用的软代码散在代码里没法版本管理。架构师视角把提示词当代码管理——版本化、可配置、可灰度、可回滚。三、PromptTemplate 基础3.1 变量占位与渲染importorg.springframework.ai.chat.prompt.PromptTemplate;PromptTemplatetemplatenewPromptTemplate(请用 {language} 实现一个 {algorithm} 排序算法);// 用 Map 填充变量生成 PromptPromptprompttemplate.create(Map.of(language,Java,algorithm,快速排序));StringreplychatClient.prompt(prompt).call().content();{language}、{algorithm}就是变量.create(Map)负责填充并渲染成完整 prompt。3.2 SystemPromptTemplate / UserPromptTemplate按角色拆分模板职责更清晰importorg.springframework.ai.chat.prompt.SystemPromptTemplate;importorg.springframework.ai.chat.prompt.UserPromptTemplate;SystemPromptTemplatesystemTemplatenewSystemPromptTemplate(你是 {role}回答风格要 {style}。);UserPromptTemplateuserTemplatenewUserPromptTemplate(帮我看看这段代码有什么问题\n{code});SystemMessagesystemMsgsystemTemplate.createMessage(Map.of(role,资深 Java 架构师,style,严谨、给出修改建议));UserMessageuserMsguserTemplate.createMessage(Map.of(code,public void f(){ ... }));StringreplychatClient.prompt().messages(systemMsg,userMsg).call().content();四、条件片段与多模板组装真实场景里一段 prompt 常由固定骨架 可选片段组成比如有没有上下文、要不要约束输出格式。不要用一堆if-else拼字符串而是按场景选模板Stringbase根据以下资料回答用户问题。\n;StringwithContextbase资料\n{context}\n\n用户问题{question};StringwithoutContext直接回答用户问题{question};// 有检索上下文时用带 context 的模板否则用不带PromptTemplatetemplatecontext!null?newPromptTemplate(withContext):newPromptTemplate(withoutContext);MapString,ObjectvarsnewHashMap();vars.put(question,question);if(context!null){vars.put(context,context);}Promptprompttemplate.create(vars);条件片段更高级的玩法是配合模板引擎如 StringTemplate做真正的条件渲染但多数项目用多模板 场景路由就够了够用且好维护。五、提示词外置配置 / 数据库5.1 外置到 yml配合 ConfigurationProperties把提示词放进配置文件改提示不用重新编译app:prompts:system:你是 {role}回答要 {style}。summarize:用一句话总结{text}importorg.springframework.boot.context.properties.ConfigurationProperties;importorg.springframework.stereotype.Component;ComponentConfigurationProperties(prefixapp.prompts)publicclassPromptProperties{privateStringsystem;privateStringsummarize;// getter/setter 省略}RestControllerpublicclassPromptController{privatefinalPromptPropertiesprops;privatefinalChatClientchatClient;publicPromptController(PromptPropertiesprops,ChatClient.Builderbuilder){this.propsprops;this.chatClientbuilder.build();}GetMapping(/summarize)publicStringsummarize(RequestParamStringtext){PromptpromptnewPromptTemplate(props.getSummarize()).create(Map.of(text,text));returnchatClient.prompt(prompt).call().content();}}5.2 外置到数据库动态管理提示词放数据库配合缓存可实现不改代码、不重启地调整提示词甚至运营同学也能改。核心是一个按 key 取模板的仓储 本地缓存这里给出骨架思路ServicepublicclassPromptService{// 从数据库按 key 加载模板本地缓存 失效刷新publicStringgetTemplate(Stringkey){/* 查询 缓存 */}}提示外置后要理解配置绑定的时机——yml 里的值在启动时绑定改文件要重启数据库方案配合缓存才能热更新。六、版本化与灰度进阶把提示词当软代码就需要版本和灰度版本号模板带版本如summarize:v2改提示词是发新版可对比、可回滚。多版本并存数据库里同时存 v1/v2。灰度路由按租户/按比例把部分流量打到 v2观察效果再全量呼应本系列第 22 篇的灰度架构。骨架publicStringrender(StringpromptKey,Stringversion,MapString,Objectvars){StringtemplatepromptRepo.get(promptKey,version);// 按 key版本取模板returnnewPromptTemplate(template).create(vars).getContents();}七、原理图解提示词生命周期90%10%效果好效果差编写模板配置/入库版本化 v1/v2灰度路由v1v2观察效果全量 v2回滚 v1八、踩坑 FAQ坑 1变量没传全报模板变量缺失现象运行报Template variable xxx not found之类异常。原因模板里有{xxx}但create(Map)里没给这个 key。解决核对模板里所有{...}变量Map 里一一对应。坑 2字面量花括号没转义现象prompt 里本来要输出{或}结果被当成变量解析报错。原因{}是占位符语法字面量花括号要特殊处理。解决确认模板引擎的转义规则确实要输出花括号时用转义或避免在模板里写。坑 3外置到 yml 后改了不生效现象改了 yml 里的提示词运行没变化。原因ConfigurationProperties在启动时绑定改文件要重启且可能有缓存。解决明确外置不等于热更新要热更新用数据库 缓存失效方案。坑 4模板从 classpath 读取路径写错现象从classpath:/prompts/xx.st读模板报找不到。原因文件路径或资源目录不对。解决确认模板文件在src/main/resources下路径用classpath:前缀文件名大小写一致。坑 5多套提示词散落各处难维护现象同一段提示词在多个类里重复改一处漏一处。原因没有统一管理。解决用PromptProperties/PromptService集中管理按 key 引用杜绝硬编码。九、小结与合集导航本篇把提示词从代码里的字符串升级为可管理的工程资产PromptTemplate变量渲染 SystemPromptTemplate/UserPromptTemplate按角色拆分多模板按场景组装替代 if-else 拼字符串外置到配置/数据库实现不改代码调提示版本化 灰度让提示词可对比、可回滚下一篇《Advisor 责任链原理》——本系列的灵魂篇讲清 ChatClient 背后所有横切能力的统一机制。十、互动钩子投票你的 prompt 现在放哪代码里硬编码配置文件数据库专门的提示词管理平台评论区聊聊你是怎么管提示词的源码见置顶评论。