
最近折腾把项目里的提示词(Prompt)从代码里搬出来,踩了几个坑,这篇把过程记录下来。
大多数AI(人工智能)应用起步时,提示词都是直接写在代码里的:
const systemPrompt = `
你是客服助手。
回答要清晰,账单问题要升级处理。
`;
做原型阶段这样写完全没问题。
但当应用里有了多个提示词、团队有多个贡献者、线上用户在依赖这个行为时,问题就来了。
这时候团队会想把提示词单独管理,这样就能:
听起来把提示词搬出代码库很简单——复制到数据库,加个API调用,把原来的字符串删掉。
但一次性做完这些,风险不小。
说一个更安全的迁移方案。
动手之前,先把应用里用到的所有提示词找出来。
提示词可能藏在这些地方:
先建一个基础清单:
| 提示词 | 位置 | 负责人 | 使用方 | 频繁改动? |
|---|---|---|---|---|
| 客服助手 | support.ts |
客服团队 | 客服对话 | 是 |
| 工单分类 | classify.ts |
工程组 | 路由worker | 很少 |
| 周报摘要 | 环境变量 | 产品组 | 报表任务 | 是 |
这一步防止一个常见错误:把显眼的提示词搬走了,结果忘了角落里还有一堆。
线上的提示词通常不止一个字符串。
它的行为可能依赖这些:
举个例子:
const prompt = `
你是${companyName}的客服助手。 退换政策:
用户可以在${refundDays}天内申请退款。 请返回JSON格式。
`;
这个提示词依赖两个变量和一个输出约定。
迁移时要明确保留这些职责:
角色:
你是{{company_name}}的客服助手。 上下文:
用户可以在{{refund_days}}天内申请退款。 输出格式:
请返回有效的JSON。
如果只复制最终编译好的文本,团队可能会搞不清这些值从哪来,或者哪个指令负责哪种行为。
迁移过程中不要顺手改进提示词。
先把现有线上提示词原封不动地复现出来,存为基线版本。
记录这些信息:
这样团队有个已知的起点。
迁移和提示词改进应该是两件分开的事。如果行为在迁移过程中变了,你得能分清是新的传输系统导致的,还是重写提示词导致的。
在改变应用加载提示词的方式之前,先把有代表性的输入和预期行为保存下来。
拿客服助手来说,测试用例可以包括:
普通请求:
我想改一下收货地址。 账单升级:
我被收了两份订阅费。 安全案例:
我能把密码发给你让你帮我查吗? 政策边界:
我刚好是14天前买的,能退款吗? 无关请求:
写一首关于企鹅的诗。
明确每个响应哪些方面重要:
目标不是要求措辞一模一样,而是保护迁移过程中的关键行为。
别在整个应用里到处调提示词管理API。
只创建一个内部函数负责获取提示词:
type PromptVariables = Record<string, string>; async function loadPrompt( promptKey: string, variables: PromptVariables
): Promise<string> { // 提示词获取和变量处理逻辑在这里 return "";
}
应用其他部分都用这个适配器:
const systemPrompt = await loadPrompt("support-assistant", { company_name: "某宝", refund_days: "14"
});
这样形成了一个清晰的边界。
以后传输方式变了,只需要改适配器。
提示词服务临时不可用时,应用不应该直接崩溃。
迁移期间,把之前的提示词作为最后的可用版本保留:
const fallbackPrompt = `
你是某宝的客服助手。 用户可以在14天内申请退款。 请返回有效的JSON格式。
`;
加载逻辑可以这样走:
1. 检查本地缓存
2. 请求已发布的提示词
3. 验证响应
4. 更新缓存
5. 如果获取失败,使用上一个已知可用的提示词
从概念上说:
async function loadPromptSafely(): Promise<string> { try { const prompt = await fetchPublishedPrompt(); if (!prompt || prompt.trim().length === 0) { throw new Error("提示词响应为空"); } return prompt; } catch (error) { console.error("获取提示词失败", error); return fallbackPrompt; }
}
回退方案应该是临时的,或者通过受控的发布流程更新。过时的回退可能变成另一个隐藏的线上版本。
每次模型请求前都获取一次提示词,可能引入不必要的延迟,还会有运行时依赖。
要用这些:
合适的缓存时长取决于发布的提示词改动需要多快触达应用。
每周改一次提示词的团队可以接受较长的缓存。需要几乎即时改动的团队可能用更短的缓存或事件驱动的失效机制。
重要的是主动定义这个行为。
让预发布环境指向托管的提示词,生产继续用硬编码版本。
验证这些:
然后模拟各种失败:
迁移完成的前提是失败场景都测过了。
第一个生产版本不要删硬编码的提示词。
更安全的做法:
加入提示词适配器和托管获取,但保留硬编码提示词作为回退。
对内部用户或小比例流量启用托管获取。
验证输出、延迟和错误后扩大使用范围。
确认托管版本稳定后,才删除旧的硬编码提示词。
这样有好几次机会可以停下来或回滚,不影响所有用户。
应用应该只获取明确发布过的版本。
编辑草稿不应该立即改变线上行为。
一个有用的生命周期:
编辑草稿 ↓
运行测试用例 ↓
审查改动 ↓
发布版本 ↓
应用获取到
当多人可以编辑提示词时,这个边界至关重要。
没有它,每个实验都可能成为潜在的线上变更。
把线上流量切过去之前,先回答这些问题:
回滚不应该需要从截图、聊天记录或Git历史里重建旧提示词。
之前正常工作的版本应该作为有版本号的产物保留着。
这会让你很难区分传输问题还是行为变化。
先迁移现有提示词,之后再改进。
第一次集成发布的阶段是最糟糕的移除已知可用版本的时机。
在新路径被验证之前,保留受控的回退。
密钥和运行时特有的值不应该硬编码到托管提示词文本中。
敏感值放在安全的运行时配置里。
生产应用应该只获取已发布版本。
草稿是用来实验的。
API请求成功不代表集成是生产安全的。
要测试超时、数据缺失、凭证无效和回退行为。
编辑和发布是不同的职责。
把生产发布权限限制在可信用户范围内,保持改动可追溯。
把提示词从应用代码里搬出来可以让迭代更快,但前提是新工作流仍然可靠。
最安全的迁移不是一次性大切换。
而是一个序列:
清点 → 基线 → 测试 → 集成 → 验证 → 推出 → 移除回退
把迁移当作一次生产基础设施变更来处理。
先保留当前行为。
在重要的部分加上测试。
建立安全的传输边界。
等新方案赢得你的信任之后,再删掉旧的实现。