在构建基于大模型的 Agent 时,系统行为通常由四类要素共同决定:提示词、代码逻辑、模型参数和外部工具定义。这四者中,代码可以被单元测试覆盖,模型参数可以固化成配置,但提示词经常被写在代码字符串里或者直接在线修改,导致某个版本出现异常时无法还原现场。一个可维护的 Agent 项目应当像管理源代码一样管理提示词,把它们放进同一个 Git 仓库,用提交历史、分支和标签记录每一次行为变化。

为什么 Agent 必须把提示词当成代码管理
提示词是 Agent 的行为入口。代码决定控制流,提示词决定策略。模型对提示词极其敏感,改变一个词就可能改变拒答率、工具调用频率或输出格式。若提示词只存在后台配置中心,代码仓库的提交记录就只剩逻辑改动,无法看到同一时间提示词发生了什么变化。代码回滚之后,行为仍然对不上,因为提示词还停留在线上的旧版或新版。
多人协作时,这个问题会被进一步放大。运营或产品人员调整话术,开发人员修改函数签名,彼此都不知道对方的变更会影响最终输出。把提示词纳入 Git 管理后,任何改动都需要经过提交记录,可以在 pull request 里看到上下文,也可以把某次投诉定位到具体的提示词差异。提示词从此不再是散落在管理后台里的不可见状态,而是和代码一样具备版本身份。
agent-repo/ ├── src/ │ ├── agent.py │ └── tools.py ├── prompts/ │ ├── system.md │ ├── tools.yaml │ └── fewshot/ │ ├── order_query.md │ └── refund.md ├── evals/ │ ├── cases.jsonl │ └── metrics.py └── prompts.lock
如何组织提示词文件与目录结构
最需要避免的做法是在 agent.py 里写一大段字符串拼接,例如把系统提示词、工具说明和示例全部塞进一个 SYSTEM_PROMPT 常量。这样不仅难以阅读,diff 也会显示一整行变化。推荐把提示词拆成独立文件,按职责划分:系统提示词使用 Markdown,工具说明使用 YAML,少量示例放在 fewshot 目录下。代码启动时读取这些文件,而不是硬编码。
这种结构还可以让非开发人员直接编辑 Markdown 文件,不需要接触代码逻辑。提示词中常见需要动态填充的部分,例如用户名称、订单状态、当前时间,可以使用模板占位符,由代码在运行时替换。这样既能保持提示词文件的静态可读性,又能保留动态信息。下面是一个简单的 YAML 配置示例,用来声明模型参数和提示词文件位置。
model: name: gpt-4o-mini temperature: 0.2 prompt_files: system: prompts/system.md tools: prompts/tools.yaml fewshot_dir: prompts/fewshot runtime: max_tool_calls: 6 fallback_message: 暂时无法处理,请转人工。
把配置放进版本库之后,环境差异也能被记录。例如生产环境使用 temperature: 0.1,测试环境使用 temperature: 0.4,可以通过分支或配置文件区分,但所有变化都有据可查。
原子化提交、标签与提示词代码配对
如果要让历史版本真正可复现,提交粒度必须清晰。一次提交最好只表达一个意图:要么是修改提示词,要么是修改代码。若提示词变化必须配合代码调整才能生效,就把它们放进同一个提交,但提交信息里要说明两者关系。否则应拆成两个提交,避免后续 git revert 时把不相关的内容一起回滚。
对于需要对外发布的 Agent,建议使用 Git 标签记录代码和提示词的组合。代码可以沿用语义化版本,提示词可以单独维护一个大版本,最终标签把二者绑定。下面的命令演示了一次包含提示词和代码的原子提交,以及标签记录方式。
git add prompts/system.md src/agent.py git commit -m "feat(agent): 调整退款场景的判断条件并同步工具描述" git tag -a agent-v1.3.0 -m "release: code v1.3 + prompt v4" git push origin main --tags
为了让回滚时不遗漏模型配置,可以增加一个锁文件,例如 prompts.lock,记录本次发布实际使用的模型名称、参数和评估指标。这个文件同样由 Git 管理,每次发布时更新。
{
"agent_version": "1.3.0",
"code_commit": "a3f9c21",
"prompt_commit": "b7d2e10",
"model": "gpt-4o-mini",
"temperature": 0.2,
"eval_score": 0.91
}
提示词 Diff 与评审难点处理
长文本提示词最麻烦的是 Git diff 可读性差。如果把整个 system prompt 写成一行,改动一个词也会显示整行被替换。解决方式之一是在 Markdown 中让每句话独立成行,或者按逻辑段落换行。这样 Git 的 diff 会精确到句子,评审人员不用在整段文本里找变化。
另一类问题是提示词评审不能只看语法,必须看模型输出。可以在提交提示词变更时,附带运行示例输出或评估结果。比如在 PR 描述里粘贴改动前后同一批测试问题的回答差异。对于想要自动提取差异的场景,可以使用 Python 的 difflib 模块生成简洁的文本对比。
import difflib
from pathlib import Path
old = Path("prompts/system.md").read_text(encoding="utf-8").splitlines()
new = Path("prompts/system.new.md").read_text(encoding="utf-8").splitlines()
diff = difflib.unified_diff(old, new, lineterm="")
print("\n".join(diff))
这样做还有一个好处:当团队同时对提示词提出多个修改意见时,可以像处理代码冲突一样解决提示词冲突。因为文件已经拆分,冲突范围更小,合并时也能看出是哪部分语义发生了变化。
用 CI 评估守住版本底线
只做版本记录还不够,理想情况下每次提示词或代码变更都要经过自动化评估。把评估集放在仓库里,用 CI 流水线在合并前运行。评估脚本读取当前版本的提示词和代码,跑一批固定用例,输出通过率、工具调用准确率或输出格式错误率。如果指标相比基线下降超过阈值,就阻断合并。
python evals/run.py --prompt prompts/system.md --cases evals/cases.jsonl --output result.json python evals/compare.py --baseline result_baseline.json --current result.json --threshold 0.02 if [ $? -ne 0 ]; then echo "评估得分下降超过阈值,禁止合并" exit 1 fi
回滚时,git revert 可以同时撤销代码和提示词,但要注意模型自身的随机性。为了让回滚后行为尽量接近历史版本,还应在锁文件中记录 seed、temperature 等采样参数。即便如此,大模型服务端更新也可能导致细微差异,因此最好保留一份旧版输出作为快照,用于人工对比。
把提示词和代码纳入同一个 Git 工作流,核心目的不是增加流程负担,而是让 Agent 的行为变化从黑盒变成可追踪的工程对象。目录结构、原子提交、标签和 CI 评估四者配合起来,可以大幅降低事故定位和多人协作的成本。