Spring AI 提示词模板实战:变量解析、few-shot 示例与避坑指南

发布时间:2026/10/16 4:53:18
Spring AI 提示词模板实战:变量解析、few-shot 示例与避坑指南 第一次在一堆 StringBuilder 拼接的提示词里挣扎时我还没意识到 Spring AI 的提示词模板能带来多大改变。直到我把{变量}参数化配合 few-shot 示例用起来才发现原来一条提示词可以稳定复用并且根本不需要在每个业务代码里复制粘贴。这篇文章聊聊我实际的落地过程包括变量解析的几种姿势、few-shot 示例怎么组织才有效以及这条路上我踩过的一些坑。适合正在用 Spring AI 做 Java 应用集成的人尤其是提示词越来越多、已经开始觉得维护起来很痛苦的同学。1. 从手拼提示词到模板化先搞清楚要解决什么问题1.1 手拼式的痛点改一处等于改全部没用模板之前我写提示词大概是这样的String role 客服; String tone 耐心; String issue 用户反馈商品破损; String policy 支持七天无理由退货; String prompt 你是一个 role 客服请用 tone 的语气回答用户 issue 。注意平台政策 policy 。;看着不复杂但一旦这个 prompt 被用在十几个接口里问题就暴露了。某天产品说平台政策的措辞要改我第一反应不是去改一处文案而是打开代码库全局搜索所有拼过policy的地方。有的接口在 controller 层拼有的在 service 层拼还有的在调用第三方 SDK 的封装里拼。每一处都要改还要担心改了之后换行、空格、标点会不会影响模型理解。更头疼的是团队里每个人写提示词的风格不一样有人习惯用“你是一个”有人用“请扮演”导致同一个系统里模型被赋予了各种不同的人设输出风格飘忽不定。后来我看到一个同事提交的代码里面有一段提示词生成逻辑整整 80 行全是 if-else 分支往 prompt 里插入不同段落。我当时就在想这已经不是“拼接字符串”的问题了而是把业务逻辑和模型交互逻辑糊在了一起。任何改动都像在做手术得小心翼翼避开一堆无关代码。1.2 模板化解决的其实是关注点分离Spring AI 提示词模板的核心思路特别简单把提示词中“不变的骨架”和“每次变化的参数”分开。骨架就是模板参数是业务侧的数据。业务代码不再需要关心提示词长什么样只需要保证参数传对。模板文件本身可以单独维护改一行文案不需要动 Java 代码重新发布也只针对模板。我拿自己做前端时的经验来类比以前写 SQL 都是直接嵌在 Java 代码里后来换成 XML 或注解管理 SQL变化点被集中到了该变化的地方。提示词也是一样的道理——它本质上是模型交互场景里的“SQL”同样需要集中管理、参数绑定、可测试。模板化之后我可以在不重新编译代码的情况下调整人设、补充示例、修改输出格式要求这让提示词的迭代速度快了一个量级。2. 深入{变量}解析机制一条模板如何承载不同参数2.1 PromptTemplate 的基本用法回到代码层面Spring AI 中最基础的提示词模板用法其实非常简单下面是我最早在项目里用的写法String template 你是一个{role}请用{tone}的语气回复用户关于{issue}的咨询。; PromptTemplate promptTemplate new PromptTemplate(template); MapString, Object params Map.of( role, 电商客服, tone, 耐心友好, issue, 用户反馈商品破损 ); Prompt prompt promptTemplate.create(params); String response chatModel.call(prompt).getResult().getOutput().getText();模板里用{变量}做占位参数通过 Map 传入。这里需要注意Map.of只适合参数少的场景而且不允许 key 为 null。参数一多我一般会先构建一个统一的参数上下文比如把请求对象转成 Map再传给 PromptTemplate。这样做的好处是模板和参数之间是纯粹的契约关系模板列了哪些变量参数侧就必须提供哪些 key一目了然。2.2 新版 ChatClient在调用链里直接塞参数如果你用的是 Spring AI 1.x 之后推荐的新版流式 API参数注入会比上面更顺手。我最近的项目基本都在用ChatClient这种写法String answer chatClient.prompt() .system(s - s.text(你是一个{role}请严格遵循以下输出格式{outputFormat}) .param(role, 资深技术顾问) .param(outputFormat, 先给结论再解释原因)) .user(u - u.text(请分析这个问题{question}) .param(question, 为什么提示词模板比字符串拼接更利于维护)) .call() .content();s.text(...)和u.text(...)里写的就是模板内容后面的.param(...)负责填充变量。这个写法对动态拼接场景非常友好——用户每次传入的{question}都不同但模板本身保持稳定。模板只定义一次参数按请求变这就是“一条提示词反复用”的核心。2.3 参数从哪来Map、对象和资源文件变量参数化的数据来源不只是 Map 一种我实际用下来有三种常见做法直接传 Map适合参数少、一次性调用的场景。把 Java 对象转成 Map适合表单提交、批量参数场景用 Spring 的 BeanMap 或者手动反射都行。从 classpath 加载模板文件适合模板集中管理的项目也是我现在的主力方案。第 3 种值得展开说说。模板统一放到src/main/resources/prompts/目录下例如support-answer.tpl你是一个{role}请结合平台规则回复用户问题。 平台规则{policy} 用户问题{question} 回复要求{requirement}Java 侧用Value读进来Value(classpath:/prompts/support-answer.tpl) private String supportTemplate;需要生成回复时直接用这个字符串变量加参数创建 Prompt。模板文件一旦修改重启服务就生效Java 代码完全不用动。这看起来只是文件位置变了但实际影响很大产品或者运营可以自己维护模板不用再等研发发版。2.4 变量缺失时会怎样我最初踩过一个小坑模板里写了{policy}代码里 Map 少传了这个 key。结果不同版本表现不一样——有的直接抛异常有的把{policy}原样输出给模型模型就把它当普通文本处理了输出内容里还带着花括号。为了不依赖这种不确定行为我养成一个习惯在模板文件第一行用注释把必填变量列出来。# 必填变量{role}、{policy}、{question}可选变量{requirement}同时在代码里做一层参数校验缺了必填项就提前报错。后来我还给模板加了单元测试用固定参数跑一遍渲染断言输出里参数替换正确。这样每次改模板文件后跑一次测试就能挡住绝大部分低级错误。3. few-shot 示例的正确组织姿势3.1 few-shot 不是多加几个例子那么简单提示词里的示例不只是让模型“抄作业”它是在帮模型建立输出格式的锚点。同一个任务有 few-shot 和没有 few-shot 的差异经常比换一个模型还明显。我印象最深的对比是情绪分类场景只用一句话描述任务模型偶尔会输出“这句话表达了正面情绪”这种解释性文字加了 3 个示例之后模型就老老实实只输出标签格式再也没有漂过。关键点在于few-shot 示例本身要结构一致、边界清晰。如果示例一会儿输出一句话一会儿输出一段话模型反而更困惑。我一般会在每个示例里同时固定“输入是什么”“输出应包含几个字段”“字段顺序是什么”让模型从示例里学到的不是内容而是格式和判断逻辑。3.2 静态示例直接写进模板文件最简单的 few-shot 是静态的把示例固定放在模板中。以情绪分类为例你是一个文本情绪分析器。请判断输入文本的情绪类别只输出正面负面中性之一。 示例1 输入客服态度很好问题很快就解决了。 情绪正面 示例2 输入等了好久都没人理体验很差。 情绪负面 示例3 输入产品功能一般没有特别惊喜。 情绪中性 输入{comment} 情绪模板里唯一变量是{comment}示例固定不变。每次请求传入不同的评论模型都按同样的 few-shot 样板输出。这个方式的优点是稳定、省心缺点是示例无法根据输入动态调整而且如果某个类别的示例过多会白白占用 token 成本。3.3 动态示例示例本身也可以参数化有些场景下更好的做法是把示例也作为可变参数传入String template 你是内容审核助手请根据以下{count}个示例判断输入文本是否合规。 {examples} 输入文本{input} ;{examples}是在 Java 侧按业务规则拼装的。我实际做过一个电商评论审核系统需要按商品类别展示不同示例数码产品的负面评论常是“收到就坏”服装类目的负面评论则更多是“尺码不准”。如果模板里写死一组示例跨类目审核时准确率就会下滑。把示例根据商品类目动态生成同样的模板就能服务完全不同的商品线。这个“模板骨架相同、示例随业务变化”的组合方式是我认为 few-shot 配合变量的最佳实践之一。3.4 示例数量与位置的实操建议我实测下来的经验是示例一般控制在 3~5 个既能定住格式又不至于把上下文撑满。示例顺序也有讲究——把和待输入最像、最关键的示例放在最后因为模型对靠近输入的示例记忆更深刻。先让模型看一两个常规示例再在末尾放一个高度相似的示例效果往往比把相似示例放在开头更稳。如果模板文件本身很大我建议把 few-shot 部分拆成独立的子模板片段用变量方式拼进主模板。这样既方便不同场景复用同一组示例也避免一个文件里堆太多内容导致难以维护。4. 一条提示词反复用的落地套路从模板集中管理到场景组合4.1 把提示词当配置来管我现在的做法是所有提示词模板统一放在src/main/resources/prompts/下按业务域拆文件prompts/ ├── support/ │ ├── answer.tpl │ ├── class-check.tpl │ └── summary.tpl └── common/ ├── role-define.tpl └── examples/ ├── emotion-examples.tpl └── intent-examples.tpl目录结构本身就在表意谁来维护都能一目了然。模板文件内部可以写注释说明变量取值范围、常见注意事项。这样即使没有专门的提示词工程师换个人也能接手维护。相比从前“在代码里找提示词”管理成本下降得非常明显。4.2 系统提示词 用户提示词的组合复用要真正做到“一条提示词反复用”通常要把提示词拆成两层系统提示词定义角色、任务、输出格式多数场景共用。用户提示词承载当次的输入内容、具体参数。两层的变量合起来等于一个完整的组合。比如在 common 里放一个角色包装模板你是一个{domain}领域的资深顾问回答问题时必须给出结论、原因、操作建议。不同业务场景复用这个角色定义只是在用户提示词里塞不同的问题。之后想调整角色风格比如要让语气更简洁只需要改这一处系统提示词所有下游场景全部生效。这就是“集中变化点”带来的杠杆效应。4.3 同一模板在不同场景的三种组合我拿团队常用的一个“通用抽取模板”举例它长这样请从以下文本中抽取{schema}所要求的字段并按要求格式输出。 文本{content} 需要的字段{schema} 输出格式{format}这个模板本身没有任何业务含义它只定义了一个“从文本里抽取字段”的行为。在三个不同场景里我通过不同参数组合让它分别做了三件事场景传入 schema实际行为客服工单要素提取用户诉求、紧急程度、责任部门提取工单结构化数据商品评论情绪分类情绪、关键词、推荐值批量情绪分析候选简历信息抽取姓名、工作年限、技能、期望薪资简历结构化入库业务代码完全不需要改只需要调整模板中的变量值。这正是提示词模板最大的价值底层提示词保持稳定上层参数千变万化一条提示词被反复复用。5. 必须避开的坑花括号冲突、模板体积失控、缺失参数报错5.1 模板中写 JSON花括号冲突怎么处理模板变量用的是{变量}但很多结构化输出任务又要求模型返回 JSON比如{category: positive}。这就尴尬了——JSON 里到处是花括号模板引擎会把category当成变量名去解析轻则报错重则产出奇怪的替换结果。我最初在这个问题上卡了整整一个下午。最终的解法分三种把 JSON 骨架作为变量传入模板里不放字面花括号。在模板文件里使用对应的转义写法不同版本转义符略有差异建议先做个小 demo 验证。最彻底的办法不在模板里写 JSON而是用 Java 侧的结构化输出解析器让模型输出后统一做 JSON 解析。我推荐第三种稳定省心。如果确实要走第一种代码大概长这样MapString, Object params Map.of( schema, {\category\: \positive|negative|neutral\, \confidence\: 0-1}, comment, 客服态度很好 );模板里只写请根据以下 schema 输出 JSON{schema} 待处理文本{comment}这样 JSON 的花括号全部落在 Java 侧模板引擎根本不会去碰它们。5.2 模板体积与 token 消耗的关系模板里塞的示例越多、说明越长每次调用消耗的 token 就越多。成本是按次累积的而且 token 太长还会拉高延迟。我曾经把一份“超级全”的模板写到 2500 字结果单次调用 token 量直线上升接口响应速度明显变慢。优化手段我总结了两条第一把最常复用的公共说明放进系统提示词用户提示词里只保留当次变量第二few-shot 只保留对当前任务帮助最大的 3~5 条去掉那些“锦上添花”但实际用不上的示例。模板能短则短但绝不能为了压 token 牺牲输出格式的稳定性。这两者的平衡需要拿真实数据去试错而不是拍脑袋决定。5.3 缺参数、错参数名的排查姿势前面提到过缺失参数的行为这里补充我真实的排查链路。出现奇怪输出时我会按顺序做三件事看模板变量名和 Map 的 key 是否严格一致注意大小写和下划线。看模板里有没有把变量写在示例文本内部——示例内部如果也带了花括号会和模板引擎打架。看参数是否为 nullnull 会被拼进提示词同样会改变模型行为。三层查完绝大多数模板问题都能定位。把这些问题前置到单元测试里是最划算的。我后期每次提交模板文件改动都会跑一次固定输入的渲染测试确认输出格式没被意外改变。6. 模板化之后我接下来的两个扩展方向6.1 给模板加版本快照提示词模板会随着业务演进不断变化但模型侧的“效果预期”也应该有迹可循。我准备把模板文件纳入版本管理每次改动都打上标记记录它当时对应的是哪个模型、哪个参数组合下的效果。日后切换模型或者需要回滚到某个历史版本时直接比对模板内容能省去很多“凭感觉调试”的时间。具体做法也不复杂模板文件名带上版本号或者用 Git 标签管理配合一份简单的效果记录表。6.2 把提示词效果纳入回归测试模板改动最怕的不是语法错而是“改完之后个别场景悄悄变差了”。我计划把一组合法输入和期望输出整理成测试集每次模板变更后批量跑一遍对比模型输出是否仍然满足格式要求。不需要做特别复杂的评估只要能把“这次改动破坏了哪个场景”暴露出来就够了。提示词也是一种代码该有的回归测试它一样需要。这套模板化的做法帮我省下的时间已经远超当初研究它的成本。如果你也在 Spring AI 项目里遇到类似的维护困境不妨从最频繁变动的业务提示词开始抽第一个模板花一个下午把参数替换和 few-shot 跑通后续的收益会慢慢显现。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询