
调用大模型 API 时,你是直接拼接字符串,还是用模板管理?如果只是简单调用,字符串拼接够用;但当 Prompt 越来越多、越来越复杂,散落在代码各处的字符串就成了维护噩梦。变量替换、多语言提示、版本化管理,这三个问题不解决,代码迟早变成「意大利面条」。这篇就聊用 Go 实现 Prompt 模板管理的实践。
假设你有一个翻译场景,最简单的写法:
prompt := fmt.Sprintf("请把以下文本翻译成%s:\n%s", targetLang, text)
看起来没问题。但当项目里有几十个类似的 Prompt,问题就来了:
targetLang、text 这些变量从哪来?类型安全吗?解决这些问题的核心思路是:把 Prompt 当成代码的一部分,用模板引擎管理。
Go 标准库的 text/template 就够用。先定义模板,再传入变量:
const translatePrompt = `请把以下文本翻译成{{.TargetLang}}:
{{.Text}}
翻译要求:保持原文语气`
type TranslateParams struct {
TargetLang string
Text string
}
// 渲染:params 传入结构体,返回填充后的字符串
prompt, _ := RenderPrompt(translatePrompt, params)
这样做的优点:
TranslateParams 结构体定义了所有变量,编译期就能发现拼写错误实际场景中,有些变量有默认值,有些必须填写。可以封装一个增强版:
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 方法内部会:检查必填字段 → 合并默认值 → 渲染模板。调用时只需传核心参数,减少重复代码。
复杂的 Prompt 往往有重复片段,比如「输出格式说明」「注意事项」。可以用嵌套模板复用:
const formatInstruction = `输出格式要求:使用 JSON 格式,字段包括:{{.Fields}}`
const mainPrompt = `{{template "format" .}}
任务:{{.Task}}
{{template "format" .}}`
// 渲染时先注册 helper 模板,再解析主模板
通过 {{template "name" .}} 引入公共片段,一处修改,处处生效。
假设你的产品要支持中英文,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
封装一个 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",
})
实际项目中,语言往往从请求上下文获取(如用户偏好、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,
})
}
这样业务代码不用关心语言切换,模板层自动处理。
Prompt 是 AI 应用的「代码」,修改 Prompt 就像修改代码一样,需要版本控制:
最简单的方式是在文件名中带版本号:
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"
扩展 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 开发者来说,它的价值不在于花哨,而在于得心应手。