Skip to content

第 19 章 从散落文件到工程化资产 ​

先来看一个很多团队正在经历,或者即将遇到的问题。

AI 项目大获成功,几个月前还只有三五个 Prompt,转眼就膨胀到了几十上百个。有负责打招呼和意图分类的入口 Prompt,有十几个各管一块业务的领域 Prompt,还有一整条 RAG 用的 Prompt 链。这些 Prompt 散落在各个地方:有的硬编码在 Python 服务的 f-string 里,有的躺在各个项目的 .txt 文件里,还有的放在共享的飞书文档里,大家靠心照不宣的复制粘贴来“同步”。

团队只有三五个人的时候,这种没有版本、没有流程的管理方式还勉强撑得住,靠钉钉群里吼一嗓子和人与人之间的信任维持协同。人多了、业务复杂了,事故就开始接连出现。

周五下午五点,产品经理紧急要求,为了配合周末的营销活动,把核心推荐 Prompt 里的“为您推荐”改成“猜您喜欢”。负责的工程师小钱想着只是改几个字,急匆匆改了硬编码的字符串,跳过所有测试,走了紧急发布通道。不料修改时顺手删掉了一个约束输出格式的 JSON 示例,发布之后,推荐服务开始大量输出夹杂自然语言的非 JSON 文本,前端解析失败,页面大面积崩溃。等大家手忙脚乱想回滚时才发现,上一个稳定版本的 Prompt 长什么样,已经没人记得了,只能到 Git 提交历史里,从一堆代码变更中翻找那段改错的文本。

发布事故只是最显眼的一类,静悄悄的损失同样在发生。新来的工程师小赵花两天从头写了一个“解析非结构化日期”的 Prompt,却不知道隔壁团队的老张早维护着一个经过上千个用例验证的同类组件,连他踩的坑都是人家一年前填平的;还有人修复了公共组件的 bug,在群里红色加粗 @所有人 通知升级 V1.2,一个月后才发现至少三个应用还在跑带 bug 的旧版本。

这几起事故的根子是同一个,那就是 Prompt 改起来太容易了。改代码要过编译、测试、评审,每一道关卡都在拦错;改 Prompt 只要十秒钟加一次回车,什么都不会拦。可十秒钟的修改,后果一点也不轻。

更麻烦的是,代码改错了会当场崩溃,反馈来得又快又疼;Prompt 改错了往往表面正常,质量在悄悄下降,等察觉时常常已经不知道下降了多久。这就是 Prompt 这类资产独有的特点:易改,难测。

所以这一章的全部内容,说白了就是一件事,给“十秒钟的修改”补上它本该有的验证和管控。让每一次修改留下痕迹,这是版本;让修改走得起流程,这是评审和平台。让修改先通过评估再上线,出问题还能快速退回,这就要靠评估门禁,加上灰度与回滚。从粗放管理走到工程化管理,差的从来不是更努力的工程师,而是一套让“随手一改”变得安全的体系。

19.1 Prompt-as-Code,不是魔法字符串 ​

所有管理工作的起点,是一个思想上的根本性转变。必须不再把 Prompt 看作代码里的“魔法字符串”,而是把它们当作和系统里任何一段 Java 或 Python 代码一样重要的、需要严肃对待的“一等公民”。这个思想,就叫 Prompt-as-Code。

它不是一个具体的工具,而是一种开发哲学。落到地上,Prompt 应该存进 Git 这样的版本控制系统,而不是飞书文档或某个人的本地文件里。它的每一次修改,都应该有清晰的 Commit Message,用人话说明白改了什么、为什么这么改。

重要的修改要走 Pull Request (PR),接受团队内部的代码评审(Code Review),同事需要像评审代码一样,来评审这些 Prompt。它还应该有自己的版本号。版本这件事比想象中复杂,下一节专门说。

实现 Prompt-as-Code 的第一步,也是见效最直接的一步,就是把所有硬编码在应用程序里的 Prompt 字符串,都剥离出来,变成外部的、可加载的配置文件。

硬编码很方便,项目初期一个 f-string 就能搞定一切。一两个 Prompt 时这种写法没问题,几十个 Prompt 时就难以收拾。Prompt 逻辑和业务代码混在一起难以分离,改任何一个 Prompt,都意味着一次代码的重新发布。

正确的做法是把它们分开。可以把 Prompt 存放在单独的 .txt、.md、.yaml 或 .json 文件里,比较推荐 YAML,它在结构化和可读性之间平衡得最好。比如建一个 prompts 目录,为“评论总结”功能创建一个 YAML 文件,写清这个 Prompt 的用途,定义版本(比如 summarize_reviews_v1_0_0)和依赖的模型,把 System Prompt 和 User Prompt 模板放进去,用 {variable} 作占位符。

在应用程序代码里,则不再是拼接字符串,而是加载并渲染这个配置文件。可以封装一个加载 Prompt 的函数,用 Jinja2 这样的模板引擎渲染最终 Prompt,比简单的 .format 更强大也更安全。有人可能会想:“这不就是把一个简单的事情搞复杂了吗?”初期确实多了一点“仪式感”,但换来的是逻辑(Code)和内容(Prompt)的分离,长期回报巨大。

最直接的变化,是非工程师也能安全协作了。产品经理、运营、文案专家想改措辞,直接在 YAML 文件里改,提交一个 PR 就行,不用去读懂 Python 代码。所有 Prompt 集中在专门目录里,想找哪个一目了然,不再需要全局搜索。更重要的是,每一次修改都在 Git 里留下了可追溯、可回滚的历史记录。

19.2 递增编号为主,语义化版本是特例 ​

外部化只是第一步,还缺一套机制来管理这些 Prompt 的版本和兼容性,不然开头那起“通知升级 V1.2、三个应用还在跑旧版”的事故还会重演。

说到版本,很多工程师的第一反应是语义化版本(Semantic Versioning, SemVer),就是常见的 MAJOR.MINOR.PATCH 格式,比如 v1.2.0。这套规范用在 Prompt 上是成立的,它不仅仅是一个编号,更是一种“契约”,一种关于“变更影响范围”的公开承诺。

在 Prompt 的世界里,可以这么定义这三个数字。修一个不影响核心逻辑和输出格式的小毛病,比如拼写错误,升 PATCH;增加了新指令但没破坏原有输出格式,比如 JSON Prompt 里多了一个可选字段,升 MINOR;做了破坏性修改,比如输出格式从 JSON 整个改成 XML,或者删掉一个下游服务正依赖的字段,就必须升 MAJOR,没有商量余地。

不过,这里得先说一句实话,免得日后拿着语义化版本去对照现实时产生怀疑。19.3 节要讲的那几家主流 Prompt 管理平台,没有一家原生用它。它们记版本的方式朴素得很,就是递增编号,v3、v4、v5 地往上走,每一版保存之后不可修改,另外配一个可以移动指向的“标签”(Langfuse 叫 label,MLflow 叫 alias)。

为什么不搞语义化版本?因为它承诺的是兼容性契约,这对几十个下游依赖的公共库是刚需,可对大多数跟着应用走的 Prompt 来说,“这次修改算 MINOR 还是 PATCH”本身就说不清。改一个词,可能修好十个用例,又弄坏另外十个,兼容不兼容,人眼判断不了。

所以业界的实际分工是,版本号只回答“这是哪一版”,好不好、兼容不兼容,交给评估流水线去回答。第 17 章搭的那套评估体系,到 19.4 节就会作为发布门禁再次用上。

语义化版本真正的用武之地,是那些会跨团队复用、像发布一个库一样发布的公共 Prompt。当一个工程师看到他依赖的“日期解析”组件从 v1.2.5 升到 v1.2.6,可以放心地自动更新;看到它一下子跳到 v2.0.0,就该警觉,去读更新日志、改自己的适配代码。这种变更契约,只有存在真实消费方的时候才值得维护。多数团队的多数 Prompt 始终到不了这个阶段,递增编号加评估门禁,已经够用。

19.3 用注册中心集中管理 Prompt ​

到这一步,Prompt 都代码化、版本化了。但当团队拥有几十上百个 Prompt,散落在几十个仓库里时,新的难题来了:发现和管理。新来的小赵还是不知道那个好用的“日期解析”Prompt 在哪个仓库的哪个文件里;想知道哪些 Prompt 依赖了即将下线的模型,该怎么查?某个 Prompt 最新的稳定版本是哪个?每个版本的评估报告在哪?

面对这些难题,一个顺理成章的方案浮出来,那就是建一个 Prompt 注册中心(Prompt Registry)。但动手之前,得先回答一个尖锐的问题。真的有必要自建吗?

把实话放在前面,绝大多数团队不需要自建,主流实践也不是自建。真实发生的事情是,注册中心作为一类功能,已经内置进了 LLMOps 平台。开源的 MLflow 专门做了一块叫 Prompt Registry 的能力;Langfuse、LangChain 家的 LangSmith、商业产品 PromptLayer,也都把 Prompt 的集中管理、版本、标签做成了标配。

所以真正值得纠结的问题从来不是“要不要建集中式注册中心”,而是“Prompt 到底放在哪里管理”。在这个问题上,业界分成了旗帜鲜明的两个阵营。

一个阵营认为,Prompt 就是代码,应该待在 Git 仓库里,跟着代码一起评审、一起测试、一起部署。promptfoo 给这个阵营准备了现成的工具。它的 GitHub Action 会在 PR 一改动 prompts/ 目录下的文件时,自动跑一轮”改前 vs 改后”的对比评估,把结果作为评论贴回 PR。

另一个阵营认为,Prompt 是配置。Langfuse 在 Prompt Management 文档里把这条动机放在显眼位置,强调在多数 LLM 应用中迭代 Prompt 的角色与管理部署的角色往往不是同一批人,Prompt 跟代码绑在一起,意味着产品经理改一句措辞也要走完整的发版流程,两分钟的修改拖成一两天的等待。这个阵营的解法是把 Prompt 放进平台,应用在运行时按名字和标签去取,改 Prompt 不动代码,更不用重新部署。

有意思的是,两个阵营正在互相靠近。平台阵营承认 Git 的评审能力不可替代,Langfuse、LangSmith 都提供 Git 集成与 webhook,可将 Prompt 版本变更镜像回仓库或触发 CI。Git 阵营也承认,评估门禁、按版本观察线上指标这些事,平台做起来顺手得多。所以眼下看到的成熟形态是,部署的事实源选一边,另一边做镜像。两条路各有一整套工具链支撑,不存在唯一正确的答案。

走进一个真实的注册中心,会发现它比想象中朴素。MLflow 的 Prompt Registry 里,一个 Prompt 对象包含名字、模板正文、递增的版本号、一条类似 Git 提交说明的变更信息、若干用于检索的标签,还有一个可移动的别名,比如 production。版本一旦保存就不可修改,两个版本之间可以并排对比差异。它甚至能连同 Prompt 一起记录推荐的模型参数,比如模型名和温度值,让“这条 Prompt 配什么模型、什么参数”不再靠口口相传。

Langfuse 那边对应的概念是版本加标签。标签可以指环境,比如 staging 和 production,也可以指灰度实验,比如 prod-a 和 prod-b,回滚就是把标签挪回旧版本。

那什么时候该从 Git 仓库升级到平台?判断标准不是 Prompt 的数量,而是协作模式。如果 Prompt 都由工程师维护,变更不频繁,跟着应用的发版节奏走,那么一个专门的 Git 仓库,或者干脆放在应用仓库的 prompts/ 目录里,加上 CI 里的评估,就已经足够。这不是什么丢人的过渡态,很多团队的终态就是它。

平台开始回本有几个表现。改 Prompt 的人里出现了非工程师,而且改得很勤;线上出了事需要秒级回滚,重新走一遍发版流程来不及;想按 Prompt 的版本来拆解第 18 章讲的那些成本、延迟、质量指标。至于“哪些 Prompt 依赖了即将下线的模型”这类全局查询,两种方案都能解决,Git 侧靠代码搜索,平台侧靠元数据过滤,MLflow 的 search_prompts 就支持按标签筛选。

一套好的 Prompt 管理,能让团队从各自为战的散乱状态,走向有组织的协同,把沉淀在个人电脑里的“隐性知识”,变成整个公司可以复用的“显性资产”。

再补一笔学术界怎么看这件事。Prompt 的生命周期管理,如今在 LLMOps 这个方向里有了一席之地,成了有正式综述的研究领域,比如 Pahune 和 Akhtar 在 2025 年发表的从 MLOps 到 LLMOps 的综述,梳理了大模型应用的运维与传统机器学习运维的差异。学术界还有一条更激进的路线,干脆不手工管理 Prompt 文本。斯坦福的 DSPy 框架把提示词当成编译器的产物,工程师写声明式的程序,编译器对着评估指标自动优化出提示词。这条路线眼下还不是工业主流,但它是个提醒,这一章管理的,是“手写 Prompt”这个当下主流的范式,范式本身未必永恒。

19.4 评估门禁、灰度发布与快速回滚 ​

现在,有了版本化的 Prompt,也有了中心化的管理,终于来到了“最后一公里”,也是风险最高的一环,如何安全地、可控地,把一个新版本的 Prompt,发布到生产环境。

一个核心 Prompt 的变更,其潜在的风险,堪比一次数据库的 Schema 变更,甚至更高。一次错误的发布,可能会导致大面积的服务不可用,或者更糟,输出大量错误的、带有偏见的、甚至有害的内容,给公司带来严重的声誉和法律风险。

Langfuse 的工程团队有一句话说得很透彻,一次 Prompt 修改的影响半径不亚于一次代码部署,但它往往绕过了代码部署享有的全部保障,没有评审、没有测试、没有回滚预案。所以,绝不能再沿用那种“一键发布,听天由命”的粗放做法。落到 Prompt 发布上,业界沉淀出的保障有三道,而且顺序很关键,第一道根本不在发布环节,而在发布之前。

第一道保障,是评估门禁。代码发布有编译器和单元测试把门,Prompt 发布没法照搬这一套,因为模型的输出本来就不是精确相等的,没法写一条“输出必须等于某某字符串”的断言。把门的只能是评估,在一组固定的数据集上,给新旧两个版本分别打分,分数过线,才允许放行。

这件事已经有现成的工具链。走 Git 路线的团队用 promptfoo 的 GitHub Action,PR 一改 Prompt,CI 自动跑“改前 vs 改后”的对比,结果贴回 PR 评论;走平台路线的团队用 Langfuse 的 experiment-action,它把候选版本在指定数据集上跑一遍,把分数作为评论发到 PR 里,一旦指标回归,直接让 CI 失败,合并按钮想按都按不下去。第 17 章辛辛苦苦搭起来的评估体系,在这里就是发布门禁本身。

评估门禁过了,是不是就能放心全量上线了?还不行。门禁是在一组固定的数据集上做的测试,真实用户的问题千奇百怪,数据集没覆盖到的问法、没预料到的场景,都可能让一个通过了门禁的版本在线上出问题。门禁管不到的这一段,就得靠第二道保障,灰度发布(Canary Release)。这个名字来源于以前的煤矿工人下井时会带上一只对有毒气体非常敏感的金丝雀的故事,在发布流程中,那一小部分最先用上新版本的用户,就是“金丝雀”。核心思想是,不要一次性地把 100% 的用户流量都切换到新版本上。

具体到 Prompt,灰度是怎么落地的?靠的是“标签加路由”。给候选版本打上一个 canary 标签,稳定版本继续挂着 production 标签,网关按用户 ID 把 1% 的流量路由到 canary 上,其余流量走老版本。然后死死盯住监控大盘,观察这 1% “金丝雀”流量上的错误率、延迟和业务指标,一切正常就逐步放大比例,从 1% 到 10%,再到 50%,最后全量;任何一个阶段出问题,就把流量全部切回老版本。

这里也有句实话要交代,Langfuse 文档里没有把流量切分和审批流做成内置功能,需要用标签、受保护标签加上自家 CI 组合实现。平台提供了“版本不可变、标签可移动”这两个基础能力,灰度这一段,各家得自己实现。在这三道保障里,灰度是最考验定力的一道,它能把一次潜在的、可能影响所有用户的大事故,控制在一个小范围的、影响可控的事件之内。

第三道保障,是快速回滚加发布日志。在标签体系下,回滚退化成一个非常简单的操作,把 production 标签从新版本挪回旧版本,一次界面点击或一条 API 调用就能完成,不用重新部署任何东西,因为应用代码引用的是标签,不是某个具体版本。这正是 MLflow 用别名、LangSmith 用环境标签、Langfuse 用 production 标签共同收敛到的同一个设计,具体的晋升历史保留与回滚界面,则以各家文档为准。

发布日志则负责把“谁在什么时候、把哪个标签指向了哪个版本、依据的是哪份评估报告”记下来。线上出了问题,把故障时间和最近一次标签变更对齐,排查范围立刻缩小一大截。

19.5 设计文档与评估数据集 ​

前面做的所有事情,都是在“技术”层面,对 Prompt 进行管理。但还有一个同样重要的、埋头写代码的工程师常常忽略的“软”层面,即知识管理(Knowledge Management)。

一个 Prompt 文件,它本身,只是一段文本。它说的是“做什么”,但它无法说明“为什么这么做”。它无法说明当初为什么要把这条规则设计成这个样子,背后有哪些业务考量和技术权衡;为了让这个 Prompt 达到现在的效果,都踩过哪些“坑”,尝试过哪些失败的方案;这个 Prompt 的 Few-shot 示例,当初是从哪些经典的 BAD Case 里提炼出来的。

这些隐藏在代码背后的“智慧”、“思考过程”和“历史背景”,如果不把它们记录下来,就会随着时间的流逝和人员的变动而永远地丢失。所以,必须为 Prompt 资产,建立配套的设计文档。

对于每一个核心的 Prompt,都应该为它编写一份简单的设计文档,这份文档应该和 Prompt 文件一起存放在 Git 仓库里。这份文档至少应该包含背景与目标、设计思路与权衡、关键指令解读、示例选择逻辑、迭代历史和已知问题与未来方向这几个部分。这份文档能让任何一个后来者都快速地理解当初的设计意图,在前人的智慧基础上继续迭代,而不是每次都从零开始。

和这份文档配套的,还有第 17 章的评估数据集。Langfuse 的工程实践里有一条特别值得抄,生产环境里的每一条调用记录,都可以一键加进数据集,让每一次线上事故都变成一个永久的测试用例。BAD Case 不应该只留在复盘文档里,它应该直接变成下一轮评估的测试用例,用在发布门禁上。文档记录“为什么”,数据集记录“证据”,两样都攒下来,人员流动就不会带走这些积累。

19.6 用 Prompt 协同设计加载状态与错误文案 ​

这一节,来看一个后端工程师常常忽略、却直接决定用户体验的话题。Prompt 不仅仅决定了 AI “说什么”,很多时候,它也间接决定了产品在“等待 AI 说话”和“AI 说错话”的时候应该做什么。别忘了,开头那场发布事故里,崩掉的可是前端页面,Prompt 变更的影响半径,本来就延伸到了产品体验层。所以后端工程师需要和前端工程师、产品经理坐在一起,进行协同设计。

比如,加载状态的设计。一个复杂的 Prompt 链可能需要好几秒才能完成。在这段时间里,前端界面是显示一个简单的加载动画,还是可以根据 Prompt 链的执行进度,给用户一些更友好的反馈?当 RAG 系统正在执行“检索文档”时,可以在界面上显示:“正在知识库中为您查找相关资料...”。当它进入“生成答案”时,再更新为:“已找到相关资料,正在为您总结答案...”。这种“分步”的加载状态,能极大地缓解用户的等待焦虑。

再比如,错误文案的 Prompt 策略。当 AI 系统遇到无法处理的问题时,它应该如何回应用户?是只简单返回一句“对不起,我不知道”,还是可以更智能、更人性化一点?可以设计一个专门的“兜底 Prompt”,当主流程失败时,就调用这个 Prompt,让它根据失败的类型,生成一句更友好、更有帮助的错误文案。比如:“抱歉,关于‘XXX’这个问题,我暂时还没能在我们的知识库里找到足够的信息来回答。不过,我已经将您的问题记录下来,并通知了我们的专家团队。”

AI 应用的用户体验,是一个完整的、端到端的链路。它不仅仅取决于那个最终的、正确的答案,也取决于在通往那个答案的过程中,以及在无法给出答案时,系统给用户的每一种反馈。而这些反馈,很多时候,也需要用 Prompt 去设计。


本章总结 让十秒钟的修改安全上线 ​

这一章聊的,重心都在“管理”和“流程”上,没有多深的算法细节,但它决定了一个团队能不能走得稳、走得远。

回头看,这些做法看着五花八门,其实都在回答同一个问题。怎么给一件十秒钟就能改完、后果却不容易看见的东西,补上完整的验证和管控。版本号让每次修改留下痕迹,注册中心让修改走得起流程、让资产可发现可复用。评估门禁负责让修改先证明自己,灰度和回滚兜住证明错了的情况,设计文档和评估数据集,则把每次修改背后的理由和证据攒下来。把 Prompt 从一串“魔法字符串”变成一份有版本、有评审、有门禁、有退路的资产,这一步走完,Prompt 的管理才算真正从粗放走到了工程化。

当这套体系在团队里真正地建立起来之后,工程师的角色也变了。不再是一个每天忙于处理 Prompt 线上问题的人,而是转身去做那个设计体系的人,把版本管理、评估门禁、发布流程这些机制建起来,让团队里的每个人都能在清晰的规则下,高效、安全地协作。

改一个 Prompt,依然只要十秒钟。区别在于,这十秒钟的背后,是版本、评审、评估、灰度和回滚。这套机制立没立起来,就是粗放管理和工程化管理的差别。

参考资料 ​

  1. Preston-Werner T. Semantic Versioning 2.0.0(规范官方页面). https://semver.org/
  2. Pallets Projects. Jinja Documentation(Jinja2 官方文档). https://jinja.palletsprojects.com/
  3. MLflow. Prompt Registry(官方文档:Prompt 对象含模板、递增版本号、提交信息、标签、别名与模型参数,版本不可变,支持搜索与对比). https://mlflow.org/docs/latest/genai/prompt-registry/
  4. Langfuse. Prompt Management(官方文档:Prompt 更新与代码部署解耦的动机,迭代与部署由不同角色负责). https://langfuse.com/docs/prompt-management
  5. Langfuse. Version Control & Labels(官方文档:版本、staging/production 标签、回滚即移动标签、受保护标签权限). https://langfuse.com/docs/prompt-management/features/prompt-version-control
  6. Langfuse. Prompt CI/CD: Version, Gate, and Roll Out Prompts Like Code(工程实践文:六阶段发布流水线、experiment-action 回归即失败、webhook 镜像到 GitHub、流量切分与审批流需自行组合). https://langfuse.com/resources/engineering/prompt-cicd
  7. LangChain. Manage Prompts(LangSmith 官方文档:commits、staging/production 环境晋升与回滚历史、prompt owners 权限、webhook 触发 CI/CD 与 GitHub 同步). https://docs.langchain.com/langsmith/manage-prompts
  8. Promptfoo. Testing Prompts with GitHub Actions(官方文档:PR 改动 Prompt 文件时自动跑改前改后对比评估并评论到 PR). https://www.promptfoo.dev/docs/integrations/github-action/
  9. PromptLayer. Prompt Management, Evals & Observability(官网). https://www.promptlayer.com/
  10. Fowler M. Canary Release(bliki 文章). https://martinfowler.com/bliki/CanaryRelease.html
  11. Khattab O, Singhvi A, Maheshwari P, et al. DSPy: Compiling Declarative Language Model Calls into Self-Improving Pipelines. arXiv:2310.03714. https://arxiv.org/abs/2310.03714
  12. Pahune S, Akhtar Z. Transitioning from MLOps to LLMOps: Navigating the Unique Challenges of Large Language Models. Information, 2025, 16(2): 87. DOI: 10.3390/info16020087. https://www.mdpi.com/2078-2489/16/2/87