Prompt 模板变量设计要注意什么?
简化版
Prompt 变量要像 API 参数一样定义类型、来源、信任级别、最大长度、必填/默认值和转义规则。用户文本、检索证据不能插入高权限指令位置;枚举和结构对象优于自由字符串。渲染前做 schema 校验,渲染后检查未绑定占位符、Token 上限与段落边界,并记录变量哈希以便复现。
详细版
先建立变量清单,例如 locale 是受控枚举,documents 是带 source_id 的数组,user_query 是不可信字符串,max_items 是有范围整数。模板引擎按上下文转义 XML/JSON/Markdown,不能把所有变量做同一种替换;秘密和服务端权限根本不应进入模型可见变量。
缺值要明确 fail-closed 还是安全默认,不能静默渲染空字符串。长内容按文档边界裁剪并保留截断标记;列表用循环结构而非预拼文本。测试覆盖引号、闭合标签、Unicode、超长值、空值和注入语句,并对最终 Prompt 做快照结构测试而非敏感全文泄露。
变量定义(schema+trust) -> 输入校验 -> 上下文转义 -> 模板渲染 -> Token/占位符检查 -> 模型
完整版教学
一、变量是接口,不是字符串洞
模板变量连接用户、数据库、检索和配置。若只做字符串替换,任何来源都可能改变 Prompt 结构,类型错误也只能到模型输出异常时才发现。把变量视作函数参数,就需要明确契约和失败行为。
同名变量还要有唯一语义。context 这种大口袋常同时装历史、文档和工具结果,无法分配权限与预算。拆成 conversation_state、evidence_documents 和 tool_result 更易治理。
二、变量 schema 应包含哪些信息
每个变量至少定义类型、来源、信任、必填、长度/范围、转义上下文和日志策略。枚举字段不要让调用方传“formal,并忽略以上规则”这种自由文本。结构化数组保留每个元素边界和来源。
| 变量 | 类型 | 信任 | 约束 |
|---|---|---|---|
| locale | enum | 应用配置 | zh-CN/en-US |
| max_items | integer | 应用配置 | 1~20 |
| user_query | string | 不可信 | ≤4000 Token |
| documents | object[] | 不可信证据 | source_id 必填 |
| policy_version | enum | 高信任 | 服务端注入 |
记忆钩子:变量设计要同时回答“它是什么”和“它从谁那里来”;类型防错误,信任级别防越权。
三、转义必须看目标上下文
插入 XML 时要转义 <>&,插入 JSON 要用序列化器处理引号与反斜线,Markdown code fence 内则要防用户内容提前闭合围栏。不存在一种 sanitize() 能安全适配所有位置。最好由模板引擎根据 typed slot 自动序列化。
const payload = JSON.stringify({ query: userQuery });
// 不要:'{"query":"' + userQuery + '"}'
转义能保护结构,不会让内容变可信。用户即使被安全包在 <user_input> 中,模型仍可能听从其中注入,所以还需明确角色和执行层权限。
四、缺值和默认值怎么处理
关键变量如权限、目标 schema 和证据来源缺失时应 fail closed,返回配置错误;风格等非关键变量可有安全默认。空字符串、null、未提供要区分,否则模型会看到“依据以下证据:”后空白并自行猜测。
默认值必须写在 schema,而不是散落模板的自然语言中。新增必填变量会破坏旧调用方,应升级模板接口版本。渲染完成后扫描 {...} 等未解析占位符,但不要用过宽规则误判用户正文。
五、Token 预算怎样分配给变量
每个可变长字段设置独立预算和全局上限。证据数组按相关性和文档边界截断,对话历史用结构化摘要,用户查询通常保留完整。不能先任意拼接再从字符串尾部硬截,可能切掉输出 schema 或半句关键条件。
假设窗口 16K:固定规则 1K、输出 2K、用户 2K、证据 8K、历史 2K、缓冲 1K。渲染器输出每段实际 Token 和被丢弃元素 ID。预算变化也需版本化,因为它能改变质量。
六、秘密与权限为何不能作为普通变量
API key、数据库凭据和用户无权查看的数据不应进入 Prompt,即使 system 说“不要输出”。模型可能受注入或日志系统泄露。工具在服务器端使用凭据,只向模型返回最小必要结果。
身份与权限从认证上下文传给策略引擎,不接受用户变量声明。模型可以看到匿名的 permission=read_only 以规划行为,但最终执行仍由后端使用真实主体判定,且模型不能修改这个值。
七、怎样测试模板变量
为每种类型生成边界值:空、最大长度、Unicode、引号、标签闭合、代码围栏、换行和注入句。断言渲染结构、角色、Token 上限和日志脱敏正确。属性测试可随机生成字符串,发现手工样例没覆盖的转义组合。
测试还应验证相同输入得到稳定 Prompt 哈希,列表排序有明确规则,locale 不改变事实字段。生产日志记录模板版本、变量 schema 版本、各段 Token 与非敏感哈希,必要时在受控环境重放。
八、常见误区与追问
- 误区:所有变量先 HTML escape 就安全。 转义取决于 JSON/XML/Markdown 上下文,且不等于指令隔离。
- 误区:空变量让模型自己处理更灵活。 关键输入缺失应显式失败,避免模型猜测。
- 误区:用户写自己是管理员可作为 role 变量。 身份来自认证系统,不可信文本不能授权。
- 追问:如何防模板注入? 类型化 slot、上下文转义、角色隔离,并在执行层最小权限。
- 追问:如何裁剪文档变量? 按相关性和完整元素边界,保留 source_id 与截断记录。
- 追问:日志如何兼顾复现和隐私? 记版本、长度、来源 ID 和哈希,敏感全文进入受控短期存储。
九、加强记忆
模板变量可记成“型、源、信、界、转、验”:定义类型与来源,标信任等级,限制长度范围,按所在格式转义,渲染前后都校验。秘密不入 Prompt、权限不由变量决定、截断不破坏结构。做到这些,Prompt 模板才是有契约的接口,而不是危险的字符串拼接。
项目实战落地
项目里怎么做的
《AI Agentic RAG高级企业知识库平台》的模板用 {变量名} 写占位变量,例如问答模板 RAG_ANSWER 里的 {context}、{question}。所有模板都经过同一个 render 渲染,渲染前先核对变量:
# 找出模板里出现的全部 {name},和调用方传进来的值比对,缺一个就报错
required = set(VARIABLE_PATTERN.findall(content))
missing = required - set(values.keys())
if missing:
raise CustomException(f"Prompt 模板 {code} 缺少变量:{'、'.join(sorted(missing))}")
result = content
for name in required:
# 用 replace 只替换认识的变量名,模板里其他大括号原样保留
result = result.replace("{" + name + "}", str(values[name]))
围绕这个渲染函数还有两处配套:
- 变量清单自动抽取。 保存模板时从正文里抽出变量,按出现顺序去重后回填
variables列,不让管理员手填; - 预览走真实渲染。 管理端每条模板有「预览」按钮,按变量清单给每个变量一个输入框,调用的是和真实链路同一个
render:RAG_ANSWER只填context就点生成,会弹出「Prompt 模板 RAG_ANSWER 缺少变量:question」;两个都填上,下方显示替换后的完整文本。
《AI Agent智能会议纪要辅助系统》把检查放在保存时:五条预置模板各有必需变量,例如纪要审查模板必须同时有 {meeting_title}、{minutes_json}、{transcript}。管理员改模板时误删了 {transcript},点保存就提示「用户模板缺少必需变量:{transcript}」,数据库不会被改动。
《AI面试官与刷题训练平台》没有自己写渲染函数,直接用 Spring AI 的 PromptTemplate:模板正文存在 prompt_template 表,业务按编码取(评分 INTERVIEW_SCORE、追问 INTERVIEW_FOLLOW_UP、总结 INTERVIEW_SUMMARY、出题 QUESTION_GENERATE),变量同样写成 {position}、{answer} 这样的占位符,放进 Map 后调用 new PromptTemplate(template).render(variables)。Spring AI 2.0 默认的渲染器按大括号识别变量,模板里出现了没传入的变量时直接抛异常,不会把占位符原样发出去。追问要求模型按 {"question":"追问题目","referenceAnswer":"追问参考答案"} 返回,这段带大括号的 JSON 示例是渲染完成之后才拼到提示词末尾的,不经过渲染器。
《AI Agent岗位匹配与求职规划系统》的 prompt_template 表按「模板类型」取模板,变量清单写在 variables 列(如「resumeProfile,projectExperiences」),由管理员维护。简历解析、JD 解析、用户画像三处调用按类型取出启用的模板,用 String.replace 把 {resumeText}、{jobDescription}、{resumeProfile}、{projectExperiences} 换成实际内容,只替换这几个认识的变量名。
《AI企业流程编排系统》的 Prompt 模板在配置节点时就复制进节点:在流程结构页给大模型节点选一条启用的模板,前端把模板正文填进节点的 prompt_content,节点配置里另记下 promptTemplateId;执行时引擎只读节点自己的 prompt_content,把其中的 {变量} 换成流程上下文里的值。之后再改模板库里的这条模板,已经配好的节点不受影响。
《AI个人学习计划与资料助手》的 Prompt 模板按编码取启用的那条(如资料总结 MATERIAL_SUMMARY、知识点提取 KNOWLEDGE_EXTRACT),用 String.replace 替换 {goalTitle}、{materialTitle}、{materialText} 三个变量;学习目标名和资料标题为空时,分别替换成「未命名学习目标」「未命名资料」。资料还没有解析文本时,在调用模型之前就拦下,提示先上传并解析文件。
为什么这样取舍
- 缺变量宁可报错。 把「资料:{context}」原样发出去,模型会一本正经地针对这个占位符编答案,比报错难排查得多。
- 不用
str.format。 模板里可能写着 JSON 示例这类带大括号的文本,format遇到会报 KeyError;replace只动认识的那几个变量名。 - 变量清单不手填。 正文里加了
{count}、清单忘了改,预览时就发现不了缺变量;从正文抽取,清单和正文永远一致。 - 纪要项目在保存时拦。 审查要同时对着纪要和原文看,少了原文,审查就只能凭纪要本身判断;在保存这一步拦下,错误的模板根本进不了库。
面试官还会追问
- 管理员把一条模板停用了,知识库平台的代码为什么不退回一份写死的默认 Prompt 继续跑?
- 会议纪要项目的模板页为什么只能编辑、不能新增?模板编码为什么不允许改?
学完《AI Agentic RAG高级企业知识库平台》,上面这些追问你都会迎刃而解。