Prompt 模板变量缺失怎么排查?AI API 参数校验与安全拼接指南
2026-09-14 · 约 10 分钟阅读
AI 应用偶尔输出“请提供订单信息”,并不一定是模型退化;可能只是模板里的 {{order_id}} 没有被替换。更隐蔽的情况是用户内容通过字符串拼接越过角色边界,或者数组被自动转换成逗号字符串,接口仍返回 200,却得到完全错误的答案。
Prompt 模板应像普通 API 契约一样接受 Schema 校验、类型检查和测试,而不是散落在代码中的长字符串。本文讨论常青工程方法,不依赖具体模型名称、版本、价格或新闻。
模板故障为什么难发现
HTTP 层只知道请求是否合法,不知道“客户名称为空”是否违反业务语义。很多模板引擎遇到缺失变量会渲染为空字符串;多余变量则被静默忽略。结果既不报错,也很难从最终自然语言中定位根因。
常见故障包括:必填变量缺失,变量类型错误,日期和金额格式不一致,用户输入包含模板定界符,JSON 示例中的花括号被误识别,未经限制的长文本挤占上下文,以及旧代码继续调用已升级模板。
为每个 Prompt 定义输入契约
模板应同时拥有 template_id、版本号、变量 Schema、角色消息和输出契约。不要只维护一个 Markdown 文件。下面以简化的 TypeScript 校验为例:
type TicketInput = {
subject: string;
customerMessage: string;
priority: "low" | "normal" | "high";
history: string[];
};
function validateTicketInput(value: unknown): TicketInput {
const v = value as Partial<TicketInput>;
if (!v || typeof v.subject !== "string" || !v.subject.trim()) {
throw new Error("subject_required");
}
if (typeof v.customerMessage !== "string") {
throw new Error("customer_message_invalid");
}
if (!['low', 'normal', 'high'].includes(String(v.priority))) {
throw new Error("priority_invalid");
}
if (!Array.isArray(v.history) || !v.history.every(x => typeof x === "string")) {
throw new Error("history_invalid");
}
return v as TicketInput;
}
生产项目可使用成熟 Schema 库,但关键原则不变:先验证,再渲染;错误要指出字段和原因;禁止把 undefined、null 或对象的默认字符串表示直接发给模型。
不要用字符串替换模拟模板引擎
连续调用 text.replace() 容易漏掉重复占位符,也可能让用户输入中的定界符参与第二轮替换。模板引擎应一次解析出抽象结构,只允许访问白名单变量,默认禁止执行表达式和任意属性。
渲染结束后再做两项检查:是否仍有未解析占位符;实际使用变量集合是否与 Schema 一致。多余变量可能代表调用方拼错字段名,也应该在测试或严格模式中报错。
function assertRendered(text: string, expected: Set<string>) {
if (/\{\{\s*[\w.]+\s*\}\}/.test(text)) {
throw new Error("unresolved_placeholder");
}
if (text.includes("undefined") || text.includes("[object Object]")) {
throw new Error("unsafe_coercion_detected");
}
return text;
}
不要仅靠正则解析复杂模板;示例代码只是渲染后的最后防线。条件、循环和转义仍应交给确定性的模板实现。
用户内容要进入数据边界,而非指令边界
“转义 Prompt 注入”不是像 SQL 那样加一个反斜杠就能解决。更实际的策略是把固定规则放在系统或开发者消息中,把用户文本放在独立消息或明确的数据区块中,并告诉模型该区块是待处理数据,不能覆盖外部规则。
如果中转接口只接受单段文本,可使用不可变的结构标签,并在程序侧限制用户内容不能控制标签本身。但标签不是安全沙箱;涉及发邮件、转账或查隐私数据时,仍要靠工具白名单、权限校验和人工审批形成真实边界。
长度限制要按字段设计
只限制最终 Prompt 总长度不够。客户留言、历史记录、检索片段和系统说明的重要性不同。应分别设置字符或 token 预算:保留系统规则与当前问题,历史按时间或摘要裁剪,检索材料按相关性选择,异常超长字段直接拒绝或走专门流程。
裁剪必须可观测。记录哪些字段被截断、截断前后长度和采用的策略,但不要把敏感原文写入普通日志。静默截断可能删除金额单位、否定词或 JSON 结尾,造成更难发现的错误。
模板升级要有版本与兼容期
把模板版本放入调用记录,并让业务代码显式依赖输入 Schema 版本。新增可选变量通常可以向后兼容;删除字段、改变枚举或修改语义则应创建新版本。发布时同时更新模板、Schema、测试样本和输出校验。
若模板存放在远程平台,运行时不要每次临时拉取“latest”。可以在发布阶段解析并缓存已批准版本,失败时回退到上一份完整制品。这样中转线路故障和模板平台故障不会同时放大。
应该写哪些自动化测试
单元测试至少覆盖完整输入、每个必填字段缺失、空字符串、错误类型、超长值、Unicode、包含定界符的用户文本和额外未知字段。快照测试可以发现固定指令意外变化,但不要把模型自然语言输出作为唯一快照。
集成测试则检查实际 API 请求的角色顺序、模型参数、结构化输出和日志字段。加入一个“金丝雀变量”,确认它只出现在预期数据位置,避免代理层或序列化逻辑重新拼接消息。
生产排障与日志脱敏
记录模板 ID、版本、变量名列表、各字段长度、内容哈希、渲染结果长度和请求 ID,通常足以定位问题。不要默认记录完整 Prompt;需要调试原文时,应使用采样、加密、访问审批和自动过期。
当结果异常时,按顺序核对:调用方输入是否通过 Schema,渲染是否留有占位符,角色数组是否正确,中转站是否完整透传消息,响应是否通过输出校验。这个顺序比反复调整 temperature 更有效。
上线检查清单
- 每份模板都有 ID、版本、变量 Schema 和输出契约
- 缺失、多余、错误类型变量会在调用 AI API 前失败
- 用户数据与固定指令分离,不执行模板表达式
- 各字段有独立长度预算,裁剪行为可观测
- 模板、Schema、测试和调用方可以协同升级与回滚
- 日志只保存诊断元数据,敏感原文受控且会过期
- 官方接口与中转站都经过角色顺序和字段透传测试
总结
Prompt 模板本质上是一个有输入、有编译过程、有输出约束的软件组件。Schema 校验阻止坏数据进入,安全渲染避免占位符泄露,版本化和测试保证团队升级可控。准备选择 AI API 接入线路时,可前往 openairouter.net 查看中转站对比与排行榜,再用同一套模板契约验证候选平台的消息透传能力。