用 text/template + 自定义模板注册表管理 Prompt 最轻量、可测试、易维护;避免 fmt.Sprintf 硬编码,支持结构体传参、嵌套复用、文件加载、运行时校验与多语言扩展。

如何用 Go 语言结构化管理 Prompt 模板
直接说结论:别用字符串拼接硬编码 Prompt,用 text/template + 自定义模板注册表是最轻量、可测试、易维护的方案。硬塞 fmt.Sprintf 或 strings.ReplaceAll 在中大型项目里很快会失控——变量漏传、转义出错、多语言支持卡死。
核心思路是把 Prompt 当作“带逻辑的配置”,而非“一次性字符串”。模板文件可独立存放,渲染时传入结构体,还能做基础校验。
为什么不用 fmt.Sprintf 管理 Prompt
看似简单,实际踩坑密集:
-
fmt.Sprintf不检查参数个数和类型,少传一个字段就 panic,且错误堆栈不指向 Prompt 本身 - 无法复用片段(比如通用 system message),复制粘贴导致后续修改不同步
- 没法做运行时模板热加载或 A/B 测试(比如切换不同 instruction 格式)
- JSON 输出类 Prompt 中的双引号、换行需手动转义,极易遗漏
示例问题:
立即学习“go语言免费学习笔记(深入)”;
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
prompt := fmt.Sprintf("你是一个%s助手,请回答%s相关问题", role, topic)如果 role 是空字符串或含换行符,输出就不可控;更别说要加 JSON schema 约束时,fmt.Sprintf 完全无能为力。
用 text/template 实现可注册的 Prompt 模板
关键不是“怎么渲染”,而是“怎么组织和查找”模板。推荐封装一个 PromptRegistry 类型:
- 所有模板通过
registry.MustRegister("qa", <code>{{.System}}\n{{.Question}}) 注册,名字即 key - 模板内容支持嵌套:
{{template "system_default" .}}复用公共块 - 渲染时强制传入结构体(如
struct{ System, Question string }),字段缺失会报明确错误 - 支持从文件加载(
ParseFiles("prompts/*.tmpl")),便于前端或运营人员协作修改
最小可用示例:
type QAPromptData struct {
System string
Question string
}
<p>t := template.Must(template.New("qa").Parse("{{.System}}\n\nQ: {{.Question}}\nA:"))
var buf strings.Builder
err := t.Execute(&buf, QAPromptData{
System: "你是一个严谨的技术文档助手",
Question: "Go 如何安全地关闭 HTTP server?",
})
// buf.String() 即最终 Prompt模板渲染时必须处理的三个细节
哪怕用了 text/template,仍容易忽略这些点:
-
template默认会转义 HTML 字符(如<→),Prompt 给 LLM 用时必须用 <code>{{.Field | safe}}或自定义safe函数取消转义 - 多行缩进不一致会导致 LLM 理解偏差,建议在模板中用
{{- if}}和{{- end}}去除前后空白,或统一用strings.TrimSpace包一层 - 某些模型(如 Claude)对末尾空行敏感,渲染后建议用
strings.TrimRight(prompt, "\r\n")清理尾部空白
复杂 Prompt 的真正难点不在语法,而在语义一致性——比如 “角色设定” 字段在多个模板中含义是否统一、是否允许为空、是否需要长度限制。这些得靠结构体字段 tag(如 json:"system" validate:"required,max=200")+ 运行时校验来兜底,而不是依赖模板层。

















