代碼:Spring AI PromptTemplate 與提示詞管理最佳實(shí)踐)
別把 Prompt 硬編碼進(jìn)代碼Spring AI PromptTemplate 與提示詞管理最佳實(shí)踐系列Spring AI 深入淺出第 5 篇版本Spring AI 2.0.1 / Spring Boot 4.1.x / JDK 21標(biāo)簽#SpringAI #PromptTemplate #提示詞工程 #工程化前置閱讀第 2 篇《ChatClient 全拆解》預(yù)計(jì)字?jǐn)?shù)6000 字可復(fù)制源碼 模板示例 踩坑 FAQ本文將解決把 prompt 寫成代碼里的字符串改一句提示就要重新發(fā)版。本文用PromptTemplate把提示詞做成可配置、可版本、可灰度的工程資產(chǎn)。你將學(xué)到為什么提示詞不能硬編碼PromptTemplate/SystemPromptTemplate的變量、渲染、消息生成條件片段與多模板組裝提示詞外置到配置/數(shù)據(jù)庫(kù)的方案版本化與灰度的思路一、版本標(biāo)注本文基于 Spring AI 2.0.1 / Boot 4.1 / JDK 21。依賴沿用第 1 篇 pom。示例里 prompt 模板類屬核心模塊1.x/2.0 用法穩(wěn)定。二、為什么提示詞不能硬編碼硬編碼 prompt 的典型代碼StringreplychatClient.prompt().system(你是一個(gè)嚴(yán)謹(jǐn)?shù)?Java 技術(shù)助手回答必須給出代碼示例。).user(解釋一下q).call().content();問(wèn)題在哪提示詞和業(yè)務(wù)邏輯耦合帶來(lái)四個(gè)痛點(diǎn)變更要重新發(fā)版調(diào)一句人設(shè)要走編譯、測(cè)試、上線。無(wú)法 A/B想對(duì)比兩套提示詞哪個(gè)效果好得改代碼部署兩遍。無(wú)法復(fù)用同一段提示詞散落在多個(gè)類里改一處漏一處。無(wú)法審計(jì)提示詞是 AI 應(yīng)用的軟代碼散在代碼里沒(méi)法版本管理。架構(gòu)師視角把提示詞當(dāng)代碼管理——版本化、可配置、可灰度、可回滾。三、PromptTemplate 基礎(chǔ)3.1 變量占位與渲染importorg.springframework.ai.chat.prompt.PromptTemplate;PromptTemplatetemplatenewPromptTemplate(請(qǐng)用 {language} 實(shí)現(xiàn)一個(gè) {algorithm} 排序算法);// 用 Map 填充變量生成 PromptPromptprompttemplate.create(Map.of(language,Java,algorithm,快速排序));StringreplychatClient.prompt(prompt).call().content();{language}、{algorithm}就是變量.create(Map)負(fù)責(zé)填充并渲染成完整 prompt。3.2 SystemPromptTemplate / UserPromptTemplate按角色拆分模板職責(zé)更清晰importorg.springframework.ai.chat.prompt.SystemPromptTemplate;importorg.springframework.ai.chat.prompt.UserPromptTemplate;SystemPromptTemplatesystemTemplatenewSystemPromptTemplate(你是 {role}回答風(fēng)格要 {style}。);UserPromptTemplateuserTemplatenewUserPromptTemplate(幫我看看這段代碼有什么問(wèn)題\n{code});SystemMessagesystemMsgsystemTemplate.createMessage(Map.of(role,資深 Java 架構(gòu)師,style,嚴(yán)謹(jǐn)、給出修改建議));UserMessageuserMsguserTemplate.createMessage(Map.of(code,public void f(){ ... }));StringreplychatClient.prompt().messages(systemMsg,userMsg).call().content();四、條件片段與多模板組裝真實(shí)場(chǎng)景里一段 prompt 常由固定骨架 可選片段組成比如有沒(méi)有上下文、要不要約束輸出格式。不要用一堆if-else拼字符串而是按場(chǎng)景選模板Stringbase根據(jù)以下資料回答用戶問(wèn)題。\n;StringwithContextbase資料\n{context}\n\n用戶問(wèn)題{question};StringwithoutContext直接回答用戶問(wèn)題{question};// 有檢索上下文時(shí)用帶 context 的模板否則用不帶PromptTemplatetemplatecontext!null?newPromptTemplate(withContext):newPromptTemplate(withoutContext);MapString,ObjectvarsnewHashMap();vars.put(question,question);if(context!null){vars.put(context,context);}Promptprompttemplate.create(vars);條件片段更高級(jí)的玩法是配合模板引擎如 StringTemplate做真正的條件渲染但多數(shù)項(xiàng)目用多模板 場(chǎng)景路由就夠了夠用且好維護(hù)。五、提示詞外置配置 / 數(shù)據(jù)庫(kù)5.1 外置到 yml配合 ConfigurationProperties把提示詞放進(jìn)配置文件改提示不用重新編譯app:prompts:system:你是 {role}回答要 {style}。summarize:用一句話總結(jié){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 外置到數(shù)據(jù)庫(kù)動(dòng)態(tài)管理提示詞放數(shù)據(jù)庫(kù)配合緩存可實(shí)現(xiàn)不改代碼、不重啟地調(diào)整提示詞甚至運(yùn)營(yíng)同學(xué)也能改。核心是一個(gè)按 key 取模板的倉(cāng)儲(chǔ) 本地緩存這里給出骨架思路ServicepublicclassPromptService{// 從數(shù)據(jù)庫(kù)按 key 加載模板本地緩存 失效刷新publicStringgetTemplate(Stringkey){/* 查詢 緩存 */}}提示外置后要理解配置綁定的時(shí)機(jī)——yml 里的值在啟動(dòng)時(shí)綁定改文件要重啟數(shù)據(jù)庫(kù)方案配合緩存才能熱更新。六、版本化與灰度進(jìn)階把提示詞當(dāng)軟代碼就需要版本和灰度版本號(hào)模板帶版本如summarize:v2改提示詞是發(fā)新版可對(duì)比、可回滾。多版本并存數(shù)據(jù)庫(kù)里同時(shí)存 v1/v2?;叶嚷酚砂醋鈶?按比例把部分流量打到 v2觀察效果再全量呼應(yīng)本系列第 22 篇的灰度架構(gòu)。骨架publicStringrender(StringpromptKey,Stringversion,MapString,Objectvars){StringtemplatepromptRepo.get(promptKey,version);// 按 key版本取模板returnnewPromptTemplate(template).create(vars).getContents();}七、原理圖解提示詞生命周期90%10%效果好效果差編寫模板配置/入庫(kù)版本化 v1/v2灰度路由v1v2觀察效果全量 v2回滾 v1八、踩坑 FAQ坑 1變量沒(méi)傳全報(bào)模板變量缺失現(xiàn)象運(yùn)行報(bào)Template variable xxx not found之類異常。原因模板里有{xxx}但create(Map)里沒(méi)給這個(gè) key。解決核對(duì)模板里所有{...}變量Map 里一一對(duì)應(yīng)???2字面量花括號(hào)沒(méi)轉(zhuǎn)義現(xiàn)象prompt 里本來(lái)要輸出{或}結(jié)果被當(dāng)成變量解析報(bào)錯(cuò)。原因{}是占位符語(yǔ)法字面量花括號(hào)要特殊處理。解決確認(rèn)模板引擎的轉(zhuǎn)義規(guī)則確實(shí)要輸出花括號(hào)時(shí)用轉(zhuǎn)義或避免在模板里寫???3外置到 yml 后改了不生效現(xiàn)象改了 yml 里的提示詞運(yùn)行沒(méi)變化。原因ConfigurationProperties在啟動(dòng)時(shí)綁定改文件要重啟且可能有緩存。解決明確外置不等于熱更新要熱更新用數(shù)據(jù)庫(kù) 緩存失效方案???4模板從 classpath 讀取路徑寫錯(cuò)現(xiàn)象從classpath:/prompts/xx.st讀模板報(bào)找不到。原因文件路徑或資源目錄不對(duì)。解決確認(rèn)模板文件在src/main/resources下路徑用classpath:前綴文件名大小寫一致???5多套提示詞散落各處難維護(hù)現(xiàn)象同一段提示詞在多個(gè)類里重復(fù)改一處漏一處。原因沒(méi)有統(tǒng)一管理。解決用PromptProperties/PromptService集中管理按 key 引用杜絕硬編碼。九、小結(jié)與合集導(dǎo)航本篇把提示詞從代碼里的字符串升級(jí)為可管理的工程資產(chǎn)PromptTemplate變量渲染 SystemPromptTemplate/UserPromptTemplate按角色拆分多模板按場(chǎng)景組裝替代 if-else 拼字符串外置到配置/數(shù)據(jù)庫(kù)實(shí)現(xiàn)不改代碼調(diào)提示版本化 灰度讓提示詞可對(duì)比、可回滾下一篇《Advisor 責(zé)任鏈原理》——本系列的靈魂篇講清 ChatClient 背后所有橫切能力的統(tǒng)一機(jī)制。十、互動(dòng)鉤子投票你的 prompt 現(xiàn)在放哪代碼里硬編碼配置文件數(shù)據(jù)庫(kù)專門的提示詞管理平臺(tái)評(píng)論區(qū)聊聊你是怎么管提示詞的源碼見(jiàn)置頂評(píng)論。