调用大模型 API 时,你是直接拼接字符串,还是用模板管理?如果只是简单调用,字符串拼接够用;但当 Prompt 越来越多、越来越复杂,散落在代码各处的字符串就成了维护噩梦。变量替换、多语言提示、版本化管理,这三个问题不解决,代码迟早变成「意大利面条」。这篇就聊用 Go 实现 Prompt 模板管理的实践。
为什么需要 Prompt 模板?
假设你有一个翻译场景,最简单的写法:
prompt := fmt.Sprintf("请把以下文本翻译成%s:\n%s", targetLang, text)
看起来没问题。但当项目里有几十个类似的 Prompt,问题就来了:
- 变量散落:
targetLang、text这些变量从哪来?类型安全吗? - 修改困难:想改 Prompt 措辞,得在代码里搜索字符串,改完还得改测试
- 多语言难搞:要支持中英文 Prompt,怎么办?if-else 一把梭?
- 版本混乱:今天改了 Prompt,明天效果变差,想回滚却发现没保存旧版本
解决这些问题的核心思路是:把 Prompt 当成代码的一部分,用模板引擎管理。
变量替换:从字符串拼接到模板引擎
1.1 最简单的模板实现
Go 标准库的 text/template 就够用。先定义模板,再传入变量:
const translatePrompt = `请把以下文本翻译成{{.TargetLang}}:
{{.Text}}
翻译要求:保持原文语气`
type TranslateParams struct {
TargetLang string
Text string
}
// 渲染:params 传入结构体,返回填充后的字符串
prompt, _ := RenderPrompt(translatePrompt, params)
这样做的优点:
- 类型安全:
TranslateParams结构体定义了所有变量,编译期就能发现拼写错误 - 可读性好:模板和代码分离,修改 Prompt 不用动逻辑代码
- 复用性强:同一个模板可以多次渲染,参数不同即可
1.2 支持默认值和必填校验
实际场景中,有些变量有默认值,有些必须填写。可以封装一个增强版:
type PromptTemplate struct {
template *template.Template
required []string // 必填字段
defaults map[string]any // 默认值
}
// 创建模板:Question 必填,Role 和 Style 有默认值
tmpl, _ := NewPromptTemplate(
"你是一个{{.Role}},请用{{.Style}}风格回答:\n{{.Question}}",
[]string{"Question"},
map[string]any{"Role": "AI助手", "Style": "专业"},
)
prompt, _ := tmpl.Render(map[string]any{"Question": "什么是Go语言?"})
Render 方法内部会:检查必填字段 → 合并默认值 → 渲染模板。调用时只需传核心参数,减少重复代码。
1.3 嵌套模板和复用
复杂的 Prompt 往往有重复片段,比如「输出格式说明」「注意事项」。可以用嵌套模板复用:
const formatInstruction = `输出格式要求:使用 JSON 格式,字段包括:{{.Fields}}`
const mainPrompt = `{{template "format" .}}
任务:{{.Task}}
{{template "format" .}}`
// 渲染时先注册 helper 模板,再解析主模板
通过 {{template "name" .}} 引入公共片段,一处修改,处处生效。
多语言提示:一套模板,多种语言
2.1 多语言模板的结构设计
假设你的产品要支持中英文,Prompt 也得跟着变。一种方案是按语言存放模板:
prompts/
├── zh/
│ ├── translate.yaml
│ └── summarize.yaml
└── en/
├── translate.yaml
└── summarize.yaml
每个 YAML 文件定义一个 Prompt:
# prompts/zh/translate.yaml
name: translate
version: "1.0"
template: |
请把以下文本翻译成{{.TargetLang}}:
{{.Text}}
required:
- TargetLang
- Text
defaults:
TargetLang: 英语
# prompts/en/translate.yaml
name: translate
version: "1.0"
template: |
Please translate the following text into {{.TargetLang}}:
{{.Text}}
required:
- TargetLang
- Text
defaults:
TargetLang: Chinese
2.2 加载和渲染多语言模板
封装一个 Prompt 管理器,按语言加载模板:
type PromptManager struct {
templates map[string]map[string]*PromptTemplate // lang -> name -> template
baseDir string
}
// 加载:读取 YAML → 解析配置 → 创建 PromptTemplate
func (pm *PromptManager) Load(lang, name string) error { ... }
// 渲染:根据语言和模板名找到模板,调用 Render
func (pm *PromptManager) Render(lang, name string, params map[string]any) (string, error) { ... }
使用示例:
pm := NewPromptManager("./prompts")
pm.Load("zh", "translate")
pm.Load("en", "translate")
// 中文 Prompt
promptZh, _ := pm.Render("zh", "translate", map[string]any{
"TargetLang": "日语",
"Text": "你好,世界",
})
// 英文 Prompt
promptEn, _ := pm.Render("en", "translate", map[string]any{
"TargetLang": "Japanese",
"Text": "Hello, World",
})
2.3 运行时语言切换
实际项目中,语言往往从请求上下文获取(如用户偏好、HTTP Header)。可以封装一个中间层:
type PromptService struct {
manager *PromptManager
}
func (ps *PromptService) Translate(ctx context.Context, targetLang, text string) (string, error) {
lang := GetLangFromContext(ctx) // 从上下文获取语言偏好,默认 "zh"
return ps.manager.Render(lang, "translate", map[string]any{
"TargetLang": targetLang,
"Text": text,
})
}
这样业务代码不用关心语言切换,模板层自动处理。
版本化管理:追踪变更,支持回滚
3.1 为什么需要版本化?
Prompt 是 AI 应用的「代码」,修改 Prompt 就像修改代码一样,需要版本控制:
- 效果对比:改了 Prompt 后,想对比新旧效果
- 问题排查:某天输出变差了,想查是哪个 Prompt 改出的问题
- A/B 测试:同时跑多个版本,看哪个效果好
- 合规审计:某些场景需要记录 Prompt 变更历史
3.2 在文件名或配置中嵌入版本
最简单的方式是在文件名中带版本号:
prompts/
├── zh/
│ ├── translate_v1.yaml
│ ├── translate_v2.yaml
│ └── translate_v3.yaml
或者在配置中维护版本历史:
# prompts/zh/translate.yaml
current: "3"
versions:
"1":
template: "翻译成{{.TargetLang}}:{{.Text}}"
created_at: "2024-01-01"
"2":
template: "请把以下文本翻译成{{.TargetLang}}:\n{{.Text}}"
created_at: "2024-02-01"
"3":
template: "请把以下文本翻译成{{.TargetLang}}:\n{{.Text}}\n翻译要求:保持原文语气"
created_at: "2024-03-01"
3.3 版本化 Prompt 管理器
扩展 PromptManager 支持版本:
type VersionedPromptManager struct {
templates map[string]map[string]map[string]*PromptTemplate // lang -> name -> version -> template
}
// 渲染:version 为空或 "latest" 时使用最新版本
func (vpm *VersionedPromptManager) Render(lang, name, version string, params map[string]any) (string, error) {
if version == "" || version == "latest" {
version = vpm.getLatestVersion(lang, name)
}
return vpm.templates[lang][name][version].Render(params)
}
使用示例:
// 使用最新版本
prompt, _ := vpm.Render("zh", "translate", "latest", params)
// 使用指定版本(用于 A/B 测试或回滚)
prompt, _ := vpm.Render("zh", "translate", "2", params)
写在最后
Go 在 AI 应用中的作用,往往被低估。大家习惯用 Python 调模型、做原型,但 Go 并不是一无是处。text/template 不是最强大的模板引擎,但足够用,对于 Go 开发者来说,它的价值不在于花哨,而在于得心应手。