Prompt也要编译:把Agent指令做成CI构建产物
生产 Agent 的 System Prompt 一旦超过几百行,最危险的事情通常不是“模型记不住”,而是团队已经无法确定:这一句规则到底从哪里来的,改它会影响哪些 Agent,线上正在跑的文本是不是 Git 里的那一份。
Google 今年给出了一种很软件工程化的做法:不要把 Prompt 只当静态 Markdown,而是把它当成需要编译的 Build Artifact。
源文件可以拆成:
shared/safety.prompt.md
shared/tool_usage.prompt.md
skills/incident_triage.prompt.md
agents/sre_agent.prompt.md
构建时解析 Include、变量、条件和 Macro,生成一个确定性的最终 Prompt;CI 检查缺失依赖、未定义变量、循环引用和 Drift;线上只部署编译后的 Artifact。
这个方向我很认同,因为很多所谓“Prompt Engineering 问题”,其实是一个标准的构建系统问题。
一个单文件 Prompt 为什么迟早失控
最初:
你是一个 SRE Agent。
先看日志,再看指标。
半年后变成:
身份
安全规则
PII 规则
工具使用
生产环境限制
开发环境限制
升级策略
输出格式
异常策略
人工审批
成本限制
业务规则
最后一个文件 8000 Token。
谁都不敢删。
每个人只会继续往后加。
这和一个 10000 行 utils.java 没区别。
真正的三个故障模式
Blast Radius 不透明
加一句:
遇到不确定情况优先调用搜索工具。
可能让所有 Workflow 的 Tool Call 翻倍。
但 Code Review 只看到一行文字。
Copy-paste Drift
多个 Agent 都需要:
PII Policy
团队开始复制:
agent-a:v3
agent-b:v5
agent-c:手工改过
半年后没人知道哪个正确。
Runtime Error 太晚暴露
模板里:
{{ environment }}
某个少见 Workflow 忘了传。
直到线上第一次触发才报错。
这些都不是模型问题。
是 Build System 缺失。
先把 Prompt 拆模块
目录:
prompts/
├── shared/
│ ├── identity.prompt.md
│ ├── safety.prompt.md
│ └── tool-usage.prompt.md
├── skills/
│ ├── incident-triage.prompt.md
│ └── postmortem.prompt.md
└── agents/
└── sre-agent.prompt.md
Agent Template:
{% include "shared/identity.prompt.md" %}
{% include "shared/safety.prompt.md" %}
{% include "shared/tool-usage.prompt.md" %}
You are an SRE agent operating in {{ environment }}.
{% if allow_remediation %}
You may propose remediation.
Destructive actions require approval.
{% else %}
You may inspect and explain only.
{% endif %}
{% include "skills/incident-triage.prompt.md" %}
构建输入:
environment: production
allow_remediation: true
输出是一个纯文本最终 Artifact。
编译以后,线上不应该再做模板解析
差:
Runtime
→ load template
→ resolve include
→ read env
→ render
→ send model
生产请求路径里任何文件缺失都会出故障。
更好:
CI
→ compile prompt
→ validate
→ hash
→ package
→ deploy
Runtime 只读:
sre-agent-v42.txt
这叫:
Build-time Failure
优于:
Runtime Failure
构建器最少做 5 类检查
Missing Import
Undefined Variable
Circular Dependency
Duplicate Rule
Forbidden Pattern
前 3 个可以完全确定性完成。
Missing Import
include "shared/privacy.prompt.md"
文件不存在:
BUILD FAIL
Undefined Variable
Template 使用:
{{ region }}
Manifest 没定义:
BUILD FAIL
Circular Dependency
A → B → C → A
必须在 CI 检出。
Prompt Dependency Graph
可以直接建 DAG:
public record PromptModule(
String id,
Path source,
Set imports,
Set variables) {
}
拓扑排序:
List order = graph.topologicalSort();
发现环:
safety
→ tool-policy
→ prod-policy
→ safety
直接失败。
为什么 Hash 很重要
最终生成:
sre-agent-v42.txt
同时计算:
sha256 = 8f2c...
Run Manifest 保存:
{
"prompt_id": "sre-agent",
"prompt_version": "42",
"prompt_hash": "8f2c..."
}
以后事故复盘才能回答:
当时真正运行的是哪一份 Prompt?
不要只记录:
prompt=v42
因为文件可能被人原地修改。
Golden Artifact Drift Check
Google 的文章里提到一个非常实用的模式:CI 重新从 Source 生成 Transpiled Prompt,再和仓库里提交的 Golden Artifact 比较。
流程:
Source Modules
↓ build
Generated Prompt
↓ diff
Committed Golden Prompt
如果不同:
CI FAIL
这能发现:
有人只改了生成文件
有人忘记重新编译
构建器版本变化导致结果漂移
一个简单 Shell Gate
./promptc build prompts/agents/sre.prompt.md \
--config env/prod.yaml \
--out build/sre-agent.txt
git diff --exit-code -- \
generated/sre-agent.txt
更严谨可以把编译结果复制到临时目录,再 cmp。
Prompt Compiler 也必须版本化
如果模板引擎或 Transpiler 改了:
Trim 规则
Include 顺序
Macro 语义
同一 Source 可能生成不同文本。
所以 Manifest 还要记录:
{
"compiler": "promptc",
"compiler_version": "1.8.2",
"source_hash": "...",
"artifact_hash": "..."
}
Prompt 模块不是越细越好
如果每三句话一个文件:
60 个模块
Review 反而很痛苦。
我通常按:
独立 Owner
独立生命周期
可复用
独立风险
来决定是否拆模块。
例如 Safety Policy 很适合共享。
某个 Agent 独有的输出风格没必要单独拆 8 个文件。
安全边界应该编译进 Base Prompt
Google 提到 Progressive Disclosure:稳定控制层保留在 Base Prompt,任务特定 Skill 在运行时按需加载。
我会明确分:
Compile-time Base
Identity
Safety
Authority Rules
Non-negotiable Tool Policy
Runtime Skills
Incident Triage
Database Diagnosis
Postmortem Writing
这样动态 Skill 不会覆盖底层安全约束。
动态 Skill 不要一次全部加载
假设公司有:
120 Skills
每个 500 Token:
60K Token
直接塞进 Prompt 又回到 Monolithic Prompt。
应该:
Base Prompt
↓
Skill Discovery
↓
加载当前任务需要的 2—5 个 Skill
这就是 Progressive Disclosure。
Skill 也应该有 Manifest
id: incident-triage
version: 12
owner: sre-platform
risk: medium
requires:
- logs.read
- metrics.read
compatible_agents:
- sre-agent
artifact_hash: 91ab...
Agent 运行时加载 Skill,也要把版本写入 Run Manifest。
Agent 可以帮忙改 Prompt,但不能直接改生产 Prompt
Google 提到一个非常重要的边界:Agent 可以在解决新问题后草拟新的 Skill Module,修改 Import,甚至开 PR;但它不是 Runtime Self-modification。
正确链路:
Agent detects repeated failure
↓
Draft Skill Patch
↓
Open PR
↓
Compile
↓
Static Validation
↓
Eval
↓
Human Review
↓
Merge
而不是:
Agent 在运行中直接修改自己的 System Prompt
Prompt PR 应该展示“编译后 Diff”
开发者改的是模块:
shared/safety.prompt.md
但真正影响模型的是最终 Artifact。
PR 最好同时展示:
Source Diff
Compiled Diff
Affected Agents
Affected Evals
Token Delta
例如:
Affected Agents:
- sre-agent
- database-agent
- release-agent
Prompt Tokens:
4210 → 4478 (+6.3%)
这个 Blast Radius 信息很有价值。
如何计算受影响 Agent
从 Dependency Graph 反查:
shared/safety
↑
├─ sre-agent
├─ db-agent
└─ release-agent
修改一个模块后,CI 自动选对应 Eval Suite。
而不是全量跑 100 万测试。
一个 Eval Selection 规则
modules:
shared/safety:
evals:
- prompt-injection
- unauthorized-tool
- sensitive-data
skills/incident-triage:
evals:
- incident-routing
- log-analysis
Build System 根据 Diff 决定测试集合。
Token Budget 也可以在编译阶段阻断
prompt_budget:
sre-agent:
max_tokens: 6000
如果:
Compiled Prompt = 7120
CI 直接失败。
这样不会等到账单上涨以后才发现 Prompt 越来越肥。
一个很实用的 Lint:重复和冲突规则
例如最终 Prompt 同时出现:
Always ask for confirmation before any tool call.
和:
Do not ask confirmation for read-only tools.
这不是语法错误,但有语义冲突。
可以分两层:
Static Rule IDs
+
LLM Semantic Lint
共享规则用 ID:
POLICY_TOOL_CONFIRM_01
禁止同一 Agent 同时加载互斥 Policy。
LLM Lint 只做辅助提醒,不作为唯一 Gate。
Runtime 必须记录“最终加载了什么”
即使 Base Prompt 是编译产物,动态 Skill 仍然会变化。
每个 Run 保存:
{
"base_prompt": {
"version": "42",
"hash": "8f2c..."
},
"skills": [
{"id": "incident-triage", "version": "12"},
{"id": "log-analysis", "version": "7"}
]
}
事故后才能复现。
生产配置不要直接靠环境变量拼 Prompt
例如:
ALLOW_PROD_DELETE=true
然后模板直接展开:
You may delete production resources.
这太危险。
高风险能力应该由应用层 Policy 决定。
Prompt 只描述行为,不应该成为最终权限来源。
一个完整 Prompt Pipeline
Source Modules
↓
Dependency Resolve
↓
Static Validation
↓
Compile
↓
Token Budget
↓
Golden Drift Check
↓
Eval Selection
↓
Offline Eval
↓
Artifact Hash
↓
Package
↓
Deploy
Runtime:
Load Immutable Artifact
+
Discover Approved Skills
这已经非常像普通软件构建。
为什么值得做
当 Prompt 只有:
30 行
完全没必要建这么多设施。
但一旦:
多个团队共同维护
20+ Agent
共享安全规则
动态 Skill
需要审计
Prompt 继续靠复制粘贴会非常危险。
此时 Build System 的收益来自:
Blast Radius 可见
错误提前发现
线上 Artifact 可复现
共享规则不漂移
改动可以自动选 Eval
真正生产化以后,Prompt 和代码越来越像:
有依赖
有版本
有构建
有测试
有发布
有回滚
所以“Prompt 也要编译”并不是为了把事情做复杂。
恰恰相反,它是在 Prompt 已经复杂之后,把复杂性从运行时搬回构建期。
如果你的 Agent System Prompt 已经长到没人敢改,下一步不应该继续找更高级的 Prompt 技巧。
先问一句:
它是不是已经应该从一份文档,升级成一套可编译的软件资产了?
更多企业级 AI 应用、Agent、RAG 与大模型工程化内容,我会继续整理在 智元界:
https://www.zyentor.com/