news 2026/10/8 13:13:32

Spring AI 提示词模板:{变量} 参数化 + few-shot,一条提示词反复用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring AI 提示词模板:{变量} 参数化 + few-shot,一条提示词反复用

Spring AI 提示词模板:{变量} 参数化 + few-shot,一条提示词反复用

作者:鱼宵 | Spring AI 实战精通营 · 第 2 篇

上周拿第 1 课那个翻译接口改需求:产品说"同一句话,要能翻商务版、口语版、文艺版"。我的第一反应是再写两个接口,把system()里的提示词复制三遍、改两个字。写到第三个风格时我自己都烦了——提示词骨架一个字没动,就"风格"两个字不一样,凭什么复制三遍?

这就是第 2 课要收拾的事:把写死的提示词改造成带{变量}的模板,骨架写死一次、参数按次填;顺手再玩个 few-shot——不讲规则,直接甩 3 个"输入→输出"示例,让模型自己照着样例说话。代码全在仓库lesson-02/目录里,clone 下来照着命令重跑一遍,十分钟你手里就有两个翻译接口。

一、核心原理:提示词不是写死的字符串,是带坑的表格

1. 一次请求 = 一张对话记录表

先把底层概念掰清楚:Message(消息)是发给模型的最小单位,每条消息就两个属性——谁说的(role)+ 说了什么(content)。模型不是"读一整段话",而是捧着一本对话记录本,一行一行往下看:

角色内容
system“你是专业中英翻译助手,只输出译文” ← 员工守则
user“把【今天天气真不错】翻译成商务正式风格的英文” ← 这一次的问题
assistant“The weather is quite pleasant today.” ← 模型回话,下一轮进表

system()必须放在user()前面——顺序就是记录本上的先后,模型按行读,写反了它就先答题再读守则。

2. 参数化模板:合同为什么不用每次重写

公司的劳动合同不会来一个人重写一份,都是一份模板写死"甲方:、乙方:、岗位:____",新人入职就把三个空填上。提示词模板一个思路:

模板: "把下面这句话翻译成英文,译文风格要【{style}】:{question}" 填值: style=商务正式, question=今天天气真不错 成品: "把下面这句话翻译成英文,译文风格要【商务正式】:今天天气真不错"

对照两种写法,一眼看出差别:

写法代码问题
错误(字符串拼接)"风格是" + style + ",句子是" + question又臭又长,引号转义全靠自己擦屁股
正确(模板+填坑).user(u -> u.text("...{style}...").param("style", style))骨架复用,按名字填值

这里埋了本课最大的一个坑:直接.user("...{question}...")传字符串,占位符不会被替换——模板引擎只在 lambda 小括号模式(u -> u.text(...).param(...))里才启动。你老老实实把模板写进text()、把值交给param(),坑才会被填上。两边名字还得逐字符一致,{Question}和"question"大小写对不上,坑就原样留在句子里发给模型,模型当场懵。

3. few-shot:教新人不写 SOP,甩 3 份优秀聊天记录

few-shot(少样本提示):不写一堆规则,直接在 system 里塞 2~3 个"输入→输出"对照示例,模型自己归纳规律并模仿。给 0 个示例叫 zero-shot(第 1 课就是),给几个示例就是 few-shot。

对比zero-shot(讲规则)few-shot(给样例)
写法system 里写"你是客服翻译,语气简短客气"system 里塞 3 条"用户:xxx / 翻译:yyy"
适合规则说得清的任务风格类、格式类任务(你说不清"客服口吻"是几个字)
成本输入短示例常驻每次请求,token 略涨

类比带新人:教客服新人,与其写 500 字 SOP,不如甩 3 份优秀聊天记录让他照着聊——他自己就悟到"要短、要客气"。大模型吃这套,第四节实测给你看证据。

二、动手:十分钟跑通两个翻译接口

环境:Windows + JDK 17 + Maven 3.9+,会@RestController就行。

第 1 步:30 秒检查环境。

java-version# 期望 True[bool][Environment]::GetEnvironmentVariable('DEEPSEEK_API_KEY')# 期望 TrueGet-NetTCPConnection-LocalPort 8094-State Listen-ErrorAction SilentlyContinue# 无输出=端口空闲

第 2 步:编译 + 启动。

cd spring-ai-journey\lesson-02$env:JAVA_HOME="C:\Program Files\Java\jdk-17"# Maven 必须跑在 JDK 17 上mvn clean install-DskipTests# 结尾看到 BUILD SUCCESSmvn spring-boot:run# 看到 Tomcat started on port 8094 即成功

第 3 步:调两个接口(中文参数先 URL 编码)。

[Console]::OutputEncoding=[System.Text.Encoding]::GetEncoding(936)# 防控制台中文乱码# 接口一:参数化模板,question + style 两个变量$q=[uri]::EscapeDataString('今天天气真不错')$s=[uri]::EscapeDataString('商务正式')Invoke-RestMethod"http://localhost:8094/translate?question=$q&style=$s"# 接口二:few-shot,只传 question,风格靠 system 里的示例带出来$q2=[uri]::EscapeDataString('我想买两件衬衫')Invoke-RestMethod"http://localhost:8094/translate-fewshot?question=$q2"

浏览器直接开http://localhost:8094/translate?question=你好&style=商务正式也行,浏览器会自动编码中文。

三、关键代码:两个接口,逐行拆解

工程还是标准 Spring Boot 项目,真正要看的就两处:yml 配置和那个控制器。

第一段:application.yml——只改端口和 token 上限。

server:port:8094# 端口按课程分配表:spring-ai 系列 lesson-02 = 8094spring:ai:openai:base-url:${LLM_BASE_URL:https://api.deepseek.com}# 环境变量优先,默认 DeepSeekapi-key:${DEEPSEEK_API_KEY}# Key 只从环境变量读,文件里永远没有明文chat:options:model:${LLM_MODEL:deepseek-chat}# 模型名,默认 deepseek-chatmax-tokens:400# 翻译输出比一问一答略长,教学控成本temperature:0.7# 温度 0.7:翻译要稳但不死板

配置套路和第 1 课一模一样——这就是 yml 自动装配的好处,加新课不改配置习惯,就动了port和max-tokens两个数。

第二段:ChatClientTemplateDemo.java——本课主菜,完整可运行。

packagecom.springai.lesson02;importorg.springframework.ai.chat.client.ChatClient;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.RestController;/** * 第 2 课核心:翻译助手控制器。 * 两个接口: * /translate —— 参数化模板:模板里写 {question}/{style} 占位,运行时 .param() 填值 * /translate-fewshot —— few-shot:system 里塞 3 个"中英对照"示例,让模型模仿风格 */@RestControllerpublicclassChatClientTemplateDemo{/** ChatClient 实例:和第 1 课一样,Builder 由 starter 自动装配,零手写模型代码 */privatefinalChatClientchatClient;publicChatClientTemplateDemo(ChatClient.Builderbuilder){this.chatClient=builder.build();}/** * 接口一:参数化模板翻译。 * GET /translate?question=今天天气真不错&style=商务正式 * 重点看 .user(u -> u.text("...{question}...").param(...)) 这段: * text() 里放模板,占位符 {question}/{style} 不会原样发给模型,填完才发; * param() 按名字填坑,名字必须和模板里的占位符逐字符一致。 */@GetMapping("/translate")publicStringtranslate(@RequestParam("question")Stringquestion,@RequestParam(value="style",defaultValue="日常口语")Stringstyle){returnchatClient.prompt()// system:人设——只给译文,别啰嗦.system("你是专业中英翻译助手。只输出英文译文本身,不要解释、不要加引号、不要多余的话。")// user:带变量的模板,运行时填 question 和 style.user(u->u.text("把下面这句话翻译成英文,译文风格要【{style}】:\n{question}").param("style",style).param("question",question)).call().content();}/** * 接口二:few-shot(给示例让模型模仿)。 * GET /translate-fewshot?question=我想买两件衬衫 * system 里写死 3 个"中文→英文"对照示例(客服场景的简短客气口吻), * user 只放真正要翻的那句话,尾巴故意留个"翻译:"让模型顺着示例续写。 */@GetMapping("/translate-fewshot")publicStringtranslateFewShot(@RequestParam("question")Stringquestion){returnchatClient.prompt()// system:人设 + 3 个示例(示例就是模型的"模仿对象").system("你是外贸客服的中英翻译。下面是翻译示例,请严格模仿示例的简短客气语气,只输出英文译文:\n"+"示例1:\n用户:早上好\n翻译:Good morning!\n"+"示例2:\n用户:这件商品包邮吗?\n翻译:Is shipping free for this item?\n"+"示例3:\n用户:麻烦帮我退一下货,谢谢\n翻译:I'd like to return this, please. Thank you.")// user:只留 {question} 一个坑,尾巴的"翻译:"引导模型续写.user(u->u.text("用户:{question}\n翻译:").param("question",question)).call().content();}}

这段代码讲了三件事:u -> u.text(...)进了 user 的小括号模式(第 1 课是直接传字符串,本课要填变量所以得进 lambda);{style}、{question}是占位符不是发给模型的原文;defaultValue = "日常口语"让 style 不传也能跑。

第三段:Lesson02Application.java——启动类,三行。

packagecom.springai.lesson02;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;/** * 第 2 课启动类。启动后访问: * GET http://localhost:8094/translate?question=今天天气真不错&style=商务正式 * GET http://localhost:8094/translate-fewshot?question=我想买两件衬衫 */@SpringBootApplicationpublicclassLesson02Application{publicstaticvoidmain(String[]args){SpringApplication.run(Lesson02Application.class,args);}}

四、实测输出:同一个模板,换个填法就换个风格

以下是 2026-10-05 本机真实运行(DeepSeek,端口 8094,HTTP 200)。先调 /translate,question 不变,style 换两次:

# style=商务正式 HTTP 200 The weather is quite pleasant today. # style=古代诗人李白风格 HTTP 200 The weather today is truly fine.

模板里的{style}真的被填进去了——商务版用了 “quite pleasant” 这种书面词,李白版明显凝练。风格有影响但不会天翻地覆(temperature 0.7 下模型偏稳妥),面试时别吹成"换个词就脱胎换骨"。

再调 /translate-fewshot,只传 question,看模型怎么模仿示例:

# question=我想买两件衬衫 HTTP 200 I'd like to buy two shirts. # question=请问什么时候发货? HTTP 200 When will this be shipped?

看第一句——I'd like to buy two shirts.用了I'd like to...开头,这正是 system 里示例 3(I'd like to return this, please)的句式。你没写"要用 I’d like to 开头"这条规则,模型自己从三个示例里学去了。这就是 few-shot 的力量:风格类任务,甩样例比讲规则准。

排查提示:回答里如果残留{question}字样,说明 param 的 key 和模板占位名对不上(大小写/拼写),两边逐字符核对即可。

五、挑战题:改参数,看看会怎样

  1. ⭐不传 style:浏览器里只开http://localhost:8094/translate?question=你好,style 参数故意不给——看模型用了什么风格回答。答案就在源码ChatClientTemplateDemo.java的方法参数上,跑出来才知道默认值真的生效。
  2. ⭐⭐不用小括号模式:把/translate里的.user(u -> u.text("...").param(...))改回.user("把下面这句话翻译成英文,译文风格要【商务正式】:今天天气真不错")——再进一步,试试.user("...{question}...")带占位符但不加 param()。看{question}会不会被替换。这题的答案藏在踩坑节里,跑一遍你这辈子都忘不了。
  3. ⭐⭐示例换成法语:把 system 里 3 个示例改成"中译法"(用户:早上好 / 翻译:Bonjour! 这种),user 还是中文——验证模型会不会照猫画虎吐出法语。看你改完示例后它"学歪"还是"学对"。

六、生产环境进阶:三个加分项

1. 提示词和代码分离。模板骨架抽到application.yml或常量类里,Controller 只负责填变量。提示词改起来不用动代码、不用重新编译,产品提需求改一段 yml 就行。

2. few-shot 示例算 token 账。示例是常驻输入,每次调用都陪着你的问题一起发给模型——示例太多既加钱又可能稀释重点。2~3 个高质量示例性价比最高,别贪多。

3. 占位符命名即契约。{style}在模板里写一次、param("style", ...)在代码里填一次,两边靠字符串名字绑死。建议占位符名和业务参数名保持一致,加个常量类集中管理,别散落在 Controller 里手写。

七、面试回答模板

面试官:一次 ChatClient 请求,到底发给模型几段东西?

一句话:不是一整段字符串,是一条消息列表(Message List)。展开说:每条消息 = 角色(system/user/assistant)+ 内容;system()是员工守则放在最前,user()是这一轮问题,call()把整本记录本发给模型。顺序就是记录本上的先后,system 必须在 user 前面。(指向本课第一节)

追问:提示词模板的变量怎么填?为什么我直接 user(“…{x}…”) 不替换?

一句话:填变量必须走 lambda 小括号模式user(u -> u.text("...{x}...").param("x", v))。展开说:text()里写模板、param()按名字填坑;直接给字符串不走模板引擎,占位符原样发出去。param 的 key 和占位名逐字符一致,大小写不同就填不上。(指向本课第三节)

追问:few-shot 和 zero-shot 怎么选?

一句话:规则说得清用 zero-shot,风格/格式类说不清规则的任务用 few-shot 给 2~3 个示例。展开说:示例是模型的"模仿对象",必须和目标任务同分布;示例常驻输入会涨 token,2~3 个性价比最高。本课实测:给三个客服示例后,模型自己学会了用I'd like to...开头。(指向本课第四节)

八、总结表

坑现象解法
占位名和 param 对不上句子里残留{question}原样发给模型两边名字逐字符一致,区分大小写
直接 user(字符串带占位符)变量不替换,坑留在句子里走 lambda 小括号模式 text()+param()
few-shot 示例跑题模型学歪成别的风格示例必须和目标任务同分布
示例堆太多输入 token 涨钱、重点被稀释控制在 2~3 个高质量示例
中文 URL 参数乱码浏览器/curl 直接带中文乱码[uri]::EscapeDataString()编码
端口占用Port 8094 already in useGet-NetTCPConnection -LocalPort 8094查占用

九、关于这个系列

本文是「Java 后端实战精通营」系列第 2 篇,原则:实战驱动、由浅到深、面试向,每篇文章的结论都可以亲手验证。

👉Spring AI 实战精通营(10 课):https://gitee.com/j67mk2/spring-ai-journey

  • 本文对应源码位置:lesson-02/(内含ChatClientTemplateDemo双接口——参数化模板 + few-shot,配application.yml端口 8094)

系列文章一览(按发布顺序):

篇主题
1Spring AI 初体验:配好 yml 就能聊,ChatClient 四步链式调用
2Spring AI 提示词模板:{变量} 参数化 + few-shot,一条提示词反复用
3Spring AI 结构化输出:entity() 把模型回答解析成 JavaBean,别再手撕 JSON
4Spring AI 工具调用:@Tool 让大模型自己查订单查库存
5Spring AI 流式输出:Flux + SSE 打字机,回答不再干等三秒
6Spring AI 多模态:给大模型一双眼睛,图片它也能看懂
7Spring AI 向量检索:本地 ONNX 嵌入,文本秒变坐标,知识库零成本起步
8Spring AI RAG 问答助手:回答带引用,AI 不再睁眼说瞎话
9Spring AI Advisor 编排:记忆 + 工具 + RAG 三合一,一个接口全搞定
10Spring AI 企业智能客服:RAG + 工具 + 记忆 + 流式 + 兜底,十课收官

下一篇预告:《Spring AI 结构化输出:entity() 把模型回答解析成 JavaBean,别再手撕 JSON》——本课翻出来的是一坨英文译文,人看得懂,但程序没法拿它算钱;下一课上主菜,.entity(Order.class)一句话让模型直接吐一个 Java 对象,商品清单、数量、总价自动装进 Order。

跑完有任何报错,把终端输出发评论区,一起排查。


标签建议:SpringAI、提示词工程、few-shot
摘要建议(≤256 字):第 1 课的提示词是写死在代码里的字符串,改个风格就得复制三遍。本文把它改造成带 {变量} 的参数化模板——text() 写模板、param() 按名填值,骨架复用;再塞 3 个输入输出示例玩 few-shot,实测模型自己学会了 I’d like to 开头。逐行拆解 lambda 小括号模式这个新手坑,附 3 道挑战题与面试回答模板,源码在 gitee lesson-02 可 clone 直接跑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 13:13:32

AI反电诈怎么做?腾讯云天御风控Agent方案与落地解析

AI反电诈怎么做?腾讯云天御风控Agent方案与落地解析 金融反电诈正在从"事后堵漏"转向AI智能体驱动的主动治理。据公安部2026年1月发布的数据,2025年全国公安机关共侦破电信网络诈骗案件25.8万起,拦截诈骗电话36亿次、短信33亿条&am…

作者头像 李华
网站建设 2026/10/8 13:13:26

AI Agent 框架怎么选:从一次制度查询拆出技术边界

AI Agent 框架怎么选:从一次制度查询拆出技术边界 适用范围:应用侧模型调用、工具执行、状态编排与检索组件的选型方法。框架关系按当前 Python LangChain Agent、LangGraph、LlamaIndex 及 Spring AI 的能力边界解释,不表示所有历史版本均相…

作者头像 李华
网站建设 2026/10/8 13:13:25

B11_ContentProvider与内容访问

Android 基础补强 B11|ContentProvider:从一个 URI 读懂跨应用内容访问 发布摘要:读取用户选中文档的名称和大小,区分 ContentResolver、Cursor、内容 URI、临时访问与持久授权。标签:ContentProvider、ContentResolve…

作者头像 李华
网站建设 2026/10/8 13:11:36

高密最新招聘信息正规机构

在高密找工作,很多人会先搜“高密最新招聘信息”“正规机构”。 但招聘信息渠道多,真假混杂,求职者和招工企业都需要多留个心眼。想及时看本地岗位,可以关注高密招聘网。 它主要面向高密本地求职者和招工企业,提供岗位…

作者头像 李华