想象你是一家智能餐厅的主厨。普通餐厅的菜谱是印好的,客人点什么就给什么。但你的餐厅不一样——每道菜的菜谱都是一张“动态模板”,上面留着许多花括号空位:今日特供:{{主料}} + {{辅料}},适合{{人数}}人。每天开工前,你根据冰箱里有什么食材、今天几桌客人、有没有老主顾带忌口,把这些空位一一填上,菜谱就活了。
这份“看菜下饭”的功夫,正是 AI 应用开发中“变量插值与动态Prompt”的核心。Prompt 不再是一段写死的文字,而是一张会呼吸的模板:用户的名字、待办清单、历史对话、语言偏好……都能像食材一样被动态填入。更进一步,你还能在模板里加“如果没鲈鱼就做回锅肉”的判断、加“为每桌客人准备十份餐具”的循环、附上“三道标准菜品的照片”让新厨师照着做。这就是条件渲染、循环结构、上下文注入和 Few-shot 动态拼装。
学会这套本领,你写的 Prompt 将从“复读机”进化为“会思考的传菜员”——灵活、精准、懂人话。
对 AI 从业者而言,动态 Prompt 是构建可靠 LLM 应用的基石。掌握变量插值,意味着你能用最少的代码管理上千条 Prompt,让应用在不同场景下自动切换指令,从“能用”迈入“好用”。
可视化演示
展示TCP三次握手建立连接和四次挥手终止连接的完整流程,切换模式查看各阶段报文交互和状态转换。
本节配有交互动效(进入视口后自动加载,约 3KB)。
TCP 三次握手 蓝色为客户端→服务端,紫色为服务端→客户端 SYN→SYN+ACK→ACK 三次握手确保双方收发能力正常,建立可靠连接
变量插值基础
厨师老张每天上班第一件事,就是看冰箱。菜谱模板上写着“今日特供:{{主料}} + {{辅料}}”,他把“主料”填成“鲈鱼”、“辅料”填成“姜葱”,菜单就出来了。这份模板最妙的地方在于:架子不变,内容随时换。这就是“变量插值”——把一段文本里的固定空位,用运行时的值去填充。
在 Prompt 工程里,模板不再是打印在纸上的死文字,而是一个带占位符的“活架子”:
当用户提问时,程序把 role_name 替换成“客服小王”、question 替换成“怎么退货”,一条完整的指令就生成了。主流的 Prompt 框架(如 LangChain 的 PromptTemplate、Jinja2 模板引擎)都采用双花括号 {{var}} 语法,原因很巧妙:单花括号可能会跟其他模板语法打架,双花括号既显眼又安全。
变量还有一个容易忽略的“作用域”问题。模板内定义的临时变量只在模板内部可见,外部代码拿不到;反过来也一样,外部脚本可以注入变量,但模板不能反过来修改外部状态。这就像菜谱上写的“备菜清单”只有后厨看得懂,客人看不到,双方各管各的。
老张把填好食材的菜单递给灶台,一份菜就开始了。你也一样——写好模板、注入变量、生成 Prompt、交给 LLM,一份“动态菜单”就此完成。
- 模板函数$\text{Template}$ 是整段含占位符的模板文本,好比菜谱的骨架。它接收变量作为“食材”。
- 第 i 个变量$\text{Var}_i$ 表示第 i 个待填的值,它是运行时才确定的动态数据,比如用户ID、问题内容。
- 最终成品$\text{Prompt}_{\text{final}}$ 是变量全部替换完毕后得到的完整指令,可以安全地发给大模型。
你是{{role_name}}。用户的问题:{{question}}条件渲染技巧
有天早上,老张打开冰箱:鲈鱼卖完了。他不能取消“今日特供”,于是翻到菜谱第二页:“若无{{主料}},则改用{{替代主料}}”。菜谱还是那张菜谱,但内容会根据现实条件自动切换——这就是“条件渲染”。
Prompt 模板常常需要“看情况说话”。比如智能客服,只有用户提供了订单号才需要附上“正在查询订单”,否则就应该跳过这段,直接问“请提供订单号”。如果不管三七二十一全部注入,模型反而会被无关信息带偏。
实际工程里,条件渲染还常配“默认值兜底”:{{用户名 | default('访客')}}。这就像菜单上写着“今日例汤:{{例汤名}}”,如果今天没熬汤,就自动写“今日无例汤供应”,而不是让一个空白的占位符暴露在主厨面前。条件判断让菜谱有了“应变能力”,默认值则保证了任何情况下模板都不会出现空空洞洞的尴尬。
- 待定片段$\text{Segment}$ 是需要根据条件决定是否输出的文本块,比如“当前订单状态:已发货”。
- 分支内容$\text{内容A}$ 和 $\text{内容B}$ 是两种条件下的文案,两者互斥,只取其一。
- 判断条件$\text{条件}$ 是运行时才被求值的逻辑表达式,比如
用户提供了订单号、搜索有结果。
循环结构应用
周末餐厅接了 20 桌婚宴,每桌的饮品清单一模一样:可乐、橙汁、雪碧各一瓶。老张没有手写 20 遍,而是在菜谱模板里写“{% for 桌 in 20 %}{{桌}}号桌:可乐、橙汁、雪碧各一瓶{% endfor %}”。模板会自己展开 20 行,一行不多,一行不少。
Prompt 中也一样。当你要让 AI 处理一组数据——比如分析五条用户评价、汇总本周七个 bug、遍历三个人物设定——你不需要手工拼接,而是用循环结构让模板自动批量生成。
循环里还能自动编号:{{loop.index}}. {{item}},生成“1. 买菜 2. 写文档 3. 开会”。这就像婚宴上每桌除了饮品还放一张桌号牌——位置一样,编号不同。批量生成看似只是省了打字,但真正的好处是格式统一、不会漏项:人的手写会出错,循环不会。
- 单个条目$\text{item}_i$ 是第 i 条待展示的数据,比如一条用户评价、一个 bug 记录。
- 连接操作$\oplus$ 表示条目之间的连接符,通常是换行符
\n、逗号或分号。 - 条目总数$n$ 是运行时的列表长度,由传入的数据决定,模板本身不需要写死。
上下文动态注入
小李是这家餐厅的熟客,上周来点过红烧肉,还专门叮嘱过“少放糖”。今天他刚坐下,服务员就在传菜小票上写:熟客小李,口味偏咸,上次点过红烧肉。后厨一看就懂了——不用客人开口,菜已经往他心坎里做。
大模型也一样没有记忆。你上次问过它什么,它转头就忘。所以要在 Prompt 里“注入上下文”:把用户画像、会话历史、环境信息像传菜小票一样夹带进去。这样模型才“知道”在和谁说话、之前聊过什么、当前是什么场景。
这三者用“异或”式的拼接还提醒我们:不是所有信息都要堆上去。上下文过长反而会稀释模型注意力,让重点信息淹没在噪声里。好的“传菜小票”就三五行字,精准,不啰嗦。
- 用户画像$\text{Profile}$ 是用户的静态特征,如姓名、会员等级、偏好语言——相当于熟客档案。只注入与任务相关的字段,避免隐私泄露。
- 会话历史$\text{History}$ 是最近几轮问答记录,帮助模型接上话头,不重复提问——相当于“上次聊到哪了”。
- 环境信息$\text{Environment}$ 是当前时刻的系统状态,如时间、页面 URL、设备类型——就像小票上打印的日期和桌号。
Few-shot动态拼装
新来的帮厨小王没见过什么叫“合格的摆盘”。主厨没讲大道理,而是端出三个标准成品:“你看,客人点红烧肉,上桌是酱红色、带点油光;客人点清蒸鱼,上桌是白色、铺着葱丝;客人点炒青菜,上桌是翠绿、盘底无汁。”小王看过这三组例子,立刻明白了标准。这就是 Few-shot——用少量“输入→输出”示例,让模型领会任务模式。
动态构造 Few-shot 的关键在于“挑例子”:不是随机抽,而是从示例库里挑选与当前问题最相似的那些。
示例的“质量排序”也很重要:把与当前问题语义最接近的示例排在最前,模型的注意力会天然偏向靠前的内容。就像主厨把“红烧肉”的成品摆在小王面前,而不是把它藏在冰箱深处——距离决定影响力。
- 任务指令$\text{Instruction}$ 是告诉模型“你要做什么”的开场白,如“你是客服助手,请用简洁语气回答用户”。
- 第 i 个示例$\text{Example}_i$ 是一组“输入→输出”对,如
退货 → 请提供订单号。每个例子都在给模型示范说话格式。 - 示例个数$k$ 不宜过大,一般 2~5 个。示例太多会超 token 上限,还会让模型陷入模仿疲劳。
- 新问题$\text{Question}$ 是用户此刻的真实输入,放在所有示例之后,如同“现在轮到你了”的提示。
模板工程实践
老张的餐厅开了五年,菜谱模板改过上百次:加过新品、砍过滞销、修过配料量、调过辣度。每一版都留着编号:v1.3 加了“少油少盐”备注、v2.1 换了供应商名称。没有这些版本记录,他根本不敢随便改——改坏了怎么回滚?
Prompt 模板也是工程资产,不是写完就完事的“一次性文本”。它会被很多线上请求反复使用,改错一行就会影响所有用户。
工程实践还有几条朴素心法:一是模板独立维护,把模板存成单独文件,别硬编码在业务代码里;二是调试技巧,打印“渲染前模板”和“渲染后 Prompt”来对照,定位是模板问题还是变量问题;三是版本管理,每次改动记录版本号,评审通过再发布。菜谱需要定期清洗、更新、试菜,Prompt 模板也同样需要。
- 失败案例数$\text{FailedCases}$ 是模板渲染后模型输出不合格的测试次数,比如答非所问、格式错乱。
- 总测试数$\text{TotalCases}$ 是回归测试中所有用例的总数,涵盖正常、边界、异常三类输入。
- 百分比换算$\times 100\%$ 把比率转成百分数,方便团队设定阈值(如“错误率低于 5% 才能上线”)。
AI 应用场景
智能客服工单自动生成
阶段:Prompt 生成与优化
解决的问题:传统客服需要人工根据用户描述判断意图、手动填写工单字段,慢且容易遗漏。通过动态 Prompt 将“用户原话 + 用户画像 + 历史订单状态”注入模板,LLM 自动输出结构化工单(意图分类、紧急度、处理建议)。
具体案例:某电商平台的客服机器人收到“我要退货”时,Prompt 模板会根据变量 order_exists 判断:如果查到订单号,就注入订单详情让模型生成退款指引;查不到则走“索要订单号”分支。条件渲染让同一条 Prompt 完美处理两种场景。
代码生成助手的语言适配
阶段:上下文注入
解决的问题:不同编程语言、框架对代码格式有不同要求。通过把用户当前文件的语言、框架版本、依赖环境注入 Prompt,模型生成的代码天然贴合项目上下文。
具体案例:一个 IDE 插件在用户打开 .py 文件时注入“语言=Python 3.11”,打开 .sql 文件时注入“方言=MySQL 8.0”。同样一句“帮我写个分页查询”,生成的代码自动切换到对应语法。
营销文案的千人千面
阶段:Few-shot 动态拼装
解决的问题:统一模板生成的营销文案缺乏针对性。通过从历史高转化文案库中挑选与目标人群最相似的 3 个示例,注入 Prompt 让模型模仿其话术风格。
具体案例:某品牌针对“都市白领”和“学生党”推送不同文案。Few-shot 示例分别为两群人的历史爆款文案,模型根据注入的示例自动调整语气——对白领强调效率和品质,对学生强调性价比和潮流。
Python 演示
# -*- coding: utf-8 -*-"""动态 Prompt 构建器:变量插值实战""" class PromptBuilder: def __init__(self, template): self.template = template self.vars_map = {} self.examples_list = [] self.history_list = [] self.user_info = "" def add_var(self, key, value): self.vars_map[key] = value return self def add_example(self, inp, out): self.examples_list.append((inp, out)) return self def set_context(self, user_info, history): self.user_info = user_info self.history_list = history return self def fill(self, text): for k, v in self.vars_map.items(): text = text.replace("{{" + k + "}}", str(v)) return text def build(self, question): parts = [] parts.append(self.template) if self.user_info: parts.append("用户信息:" + self.user_info) if self.history_list: parts.append("对话历史:") parts.extend(self.history_list) if self.examples_list: parts.append("参考示例:") for inp, out in self.examples_list: parts.append("Q: " + inp + " -> A: " + out) parts.append("当前问题:" + question) prompt = "\n".join(parts) return self.fill(prompt) def main(): builder = PromptBuilder("你是智能客服助手。") builder.add_var("lang", "中文") builder.add_example("如何退货", "请提供订单号") builder.add_example("退款多久到账", "3-5个工作日") builder.set_context("VIP用户-张先生", ["用户之前咨询过退货流程"]) prompt = builder.build("我想退货") print(prompt) print("---") print("Prompt 长度:", len(prompt)) if __name__ == "__main__": main()
self.vars_map = {}变量字典,存键值对(如lang → 中文)。用_map后缀避免与add_var方法混淆。text.replace("{{" + k + "}}", str(v))核心插值逻辑:把{{key}}替换成实际值。if self.user_info:条件渲染:用户信息为空时就跳过这段。for inp, out in self.examples_list:循环拼接 Few-shot 示例。parts.extend(self.history_list)把历史记录逐条展开成多行。"".join—"\n".join(parts)用换行符把所有片段连成完整 Prompt。
常见误区
误区把用户的全部历史记录、所有可能的规则都注入模板,觉得信息越多模型就越聪明。
正解上下文过长会稀释模型的注意力,就像把菜谱写成一本书,主厨翻到第三页就忘了第一页写了什么。动态 Prompt 的核心是“精准注入”——只放当前任务必需的信息。经验法则:上下文超过一定长度后,模型对关键信息的利用率反而下降。
为何常见:大家直觉认为 AI 是“越喂越聪明”,忽略了注意力机制对长上下文的衰减效应。
误区试图在 Prompt 模板里写复杂的 if-else 嵌套、for 循环、甚至数据清洗逻辑。
正解模板是“展示层”,不是“逻辑层”。复杂的判断和计算应该在 Python 代码里完成,模板只负责把算好的结果填进去。就像菜谱上只写“加 5g 盐”,而不是“先判断盐罐有没有盐,没有就去隔壁借”——判断交给厨师,菜谱只管记录。
为何常见:模板语言(如 Jinja2)确实支持逻辑语法,但滥用会导致模板难以阅读和维护。
误区用循环展开列表时,直接把所有元素首尾相连,认为模型能自动理解每个元素的起止。
正解循环输出必须带清晰的分隔符(换行、编号、或 - 列表符号)。否则模型可能把“苹果香蕉橘子”当成一个词。就像婚宴座位表不带桌号,服务员根本分不清谁坐哪桌。
为何常见:拼接后的文本在人类眼里“好像能读”,但模型的 token 切分方式导致没有分隔符时语义边界模糊。
误区认为示例数量直接决定模型表现,于是把能收集到的例子全塞进 Prompt。
正解Few-shot 收益递减且消耗 token。示例超过 5~6 个后,效果提升微乎其微,反而可能因为示例风格混杂让模型“不知道该学谁”。应该精选 2~5 个高质量、与当前问题最相似的示例,而非贪多求全。
为何常见:机器学习里“更多数据”通常是好事,但这个直觉不适用于 token 受限的 Prompt 场景。
误区觉得 Prompt 模板是“文档”不是“代码”,改完就直接上线,不留记录。
正解模板是生产代码,必须纳入版本管理。每次改动要有版本号、变更说明和回归测试记录。否则改坏了一条规则,没法快速回滚,线上所有用户都会受影响。
为何常见:Prompt 编写看起来像写作文,不像写代码,因此容易被当成“非工程物”对待。
面试题
Q1. 变量插值的核心思想是什么?
- A.模板按变量动态生成内容← 正确答案
- B.模板固定成本不可变
- C.用复制粘贴替换所有文本
- D.在代码里写死所有内容
答案:A。变量插值让同一套模板在不同变量支持下生成不同内容,就像一张菜谱根据冰箱里有什么食材决定做什么菜。B 说反了,C 和 D 都是反模式——复制粘贴和硬编码都会让模板失去“动态”能力。
Q2. 双花括号 {{var}} 在 Prompt 模板中的作用是?
- A.表示注释文本
- B.声明一个占位符← 正确答案
- C.开启一个循环
- D.调用一个函数
答案:B。{{var}} 是占位符,运行时会被替换成变量的实际值。就像菜谱上的空格等待填入食材名。注释一般用 {# #},循环用 {% for %},函数调用不属于模板语法核心。
Q3. 条件渲染最适合解决哪类问题?
- A.有值才插入某段提示文本← 正确答案
- B.把全部内容无脑注入
- C.随机挑选一段内容展示
- D.忽略条件直接输出固定文本
答案:A。条件渲染的核心是“根据运行时条件决定文本片段是否输出”。比如用户有订单号时注入订单查询指令,没有时则跳过。B 会稀释注意力,C 是随机行为不符合业务确定性,D 直接放弃了条件能力。
Q4. 上下文动态注入通常包含哪些信息?
- A.用户画像与偏好← 正确答案
- B.最近几轮会话历史← 正确答案
- C.系统环境与当前时间← 正确答案
- D.模型内部的权重参数
答案:ABC。上下文注入把用户画像、会话历史、环境信息拼进 Prompt,让模型“知道”在跟谁说话、聊到哪了、当前是什么场景。模型权重是模型训练时的参数,不可能也不应该通过 Prompt 注入。
Q5. Few-shot 示例选择的首要标准是什么?
- A.与当前问题语义相似← 正确答案
- B.与当前问题完全不同
- C.示例数量越多越好
- D.任意随机挑选即可
答案:A。示例与当前问题越相似,模型越容易模仿出正确模式。就像给新厨师看“清蒸鲈鱼”的成品,他才知道清蒸鱼该长什么样。B 和 D 会让模型学错方向,C 忽视 token 上限和收益递减。
Q6. 循环结构在 Prompt 中的应用包括哪些?
- A.遍历列表生成编号条目← 正确答案
- B.批量格式化多条对话← 正确答案
- C.统一拼接多段参考材料← 正确答案
- D.随机采样一段文本输出
答案:ABC。循环结构用于“批量生成同构内容”:遍历待办事项自动编号、格式化历史记录、拼接多篇文档。D 的随机采样是业务逻辑,应该在代码层实现,而不是靠循环瞎猜。
Q7. 默认值兜底 {{var | default('访客')}} 的作用是?
- A.变量为空时使用备用值← 正确答案
- B.变量有值时强制清空
- C.让变量永远等于默认值
- D.删除模板中的占位符
答案:A。当变量缺失或为空时,默认值防止模板中出现空白占位符。就像菜单上写“今日例汤:无例汤供应”而不是让位置空着。B 和 C 都会破坏变量正常传值,D 在编译期就删掉占位符等于没插值。
Q8. 为什么 Few-shot 示例不是越多越好?
- A.Token 有限且超长收益递减← 正确答案
- B.模型会跳过所有示例不看
- C.示例越多模型越混乱
- D.每个示例都会降低准确率
答案:A。Few-shot 的优势是“举一反三”,但 5~6 个示例后边际收益趋近于零,而 token 消耗线性增长。给模型 20 个示例就像让新厨师看 20 道菜再动手——他反而记不住标准了。
Q9. 模板工程实践包含哪些关键环节?
- A.把模板独立成文件维护← 正确答案
- B.打印渲染前后进行对照← 正确答案
- C.每次改动记版本号← 正确答案
- D.上线前做回归测试← 正确答案
答案:ABCD。四项都是工程化的必要动作:独立文件利于维护,对照调试帮定位问题,版本号支持回滚,回归测试防止改坏线上行为。这就像餐厅的菜谱要定期更新、记录版本、试菜验收。
Q10. 上下文注入与变量插值的关系是?
- A.上下文注入是插值的应用场景← 正确答案
- B.上下文注入替代了变量插值
- C.两者完全无关互相独立
- D.插值是注入的逆向操作
答案:A。上下文注入是把用户画像、历史等信息作为变量填入模板的过程,本质上是变量插值的一种特殊应用。B 的错误在于注入依赖插值实现;C 无视了二者的实现关联;D 把方向说反了,插值不是逆向操作。