
一、为什么需要管理 Prompt初级做法 ┌──────────────────────────────────────────────┐ │ func askAI(question string) string { │ │ prompt : 请回答以下问题 question │ │ // 直接拼字符串调用 LLM │ │ } │ └──────────────────────────────────────────────┘ 痛点 1. Prompt 散落在代码各处改一处要搜遍全项目 2. 无法版本管理不知道哪个 Prompt 效果好 3. 没有模板化同样的逻辑写 N 遍 4. 无法 A/B 测试凭感觉改 Prompt 5. 线上出问题不知道当前用的是哪个版本的 Prompt目标像管理代码一样管理 Prompt——模板化、版本化、可测试、可追溯。二、架构设计┌──────────────────────────────────────────────┐ │ Prompt 管理系统 │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ 模板引擎 │ │ 版本管理 │ │ A/B 测试 │ │ │ │ │ │ │ │ │ │ │ │ {{var}} │ │ v1.0.0 │ │ 实验分组 │ │ │ │ 条件渲染 │ │ v1.1.0 │ │ 流量分配 │ │ │ │ 循环 │ │ 回滚 │ │ 效果统计 │ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ │ │ │ │ └──────────────┴──────────────┘ │ │ │ │ │ ┌────────▼────────┐ │ │ │ 存储后端 │ │ │ │ (文件/DB/etcd) │ │ │ └─────────────────┘ │ └────────────────────────────────────────────────┘三、模板引擎实现// prompt/engine.go package prompt import ( bytes encoding/json fmt regexp strings text/template ) // Template Prompt 模板 type Template struct { ID string json:id // 模板唯一 ID Name string json:name // 模板名称 Version string json:version // 版本号 Content string json:content // 模板内容Go template 语法 Variables []VariableDef json:variables // 变量定义 Description string json:description // 描述 Tags map[string]string json:tags // 标签 CreatedAt string json:created_at UpdatedAt string json:updated_at } // VariableDef 变量定义 type VariableDef struct { Name string json:name Type string json:type // string/number/boolean/array/object Required bool json:required Default string json:default,omitempty Description string json:description,omitempty } // RenderInput 渲染输入 type RenderInput struct { Variables map[string]interface{} json:variables UserID string json:user_id,omitempty SessionID string json:session_id,omitempty } // RenderOutput 渲染输出 type RenderOutput struct { Content string json:content UsedVars map[string]interface{} json:used_vars Warnings []string json:warnings,omitempty } // TemplateEngine 模板引擎 type TemplateEngine struct { funcMap template.FuncMap } // NewTemplateEngine 创建模板引擎 func NewTemplateEngine() *TemplateEngine { return TemplateEngine{ funcMap: template.FuncMap{ upper: strings.ToUpper, lower: strings.ToLower, title: strings.Title, trim: strings.TrimSpace, json: toJSON, join: strings.Join, now: now, date: formatDate, contains: strings.Contains, hasPrefix: strings.HasPrefix, hasSuffix: strings.HasSuffix, replace: strings.ReplaceAll, }, } } // Render 渲染模板 func (e *TemplateEngine) Render(tpl *Template, input *RenderInput) (*RenderOutput, error) { output : RenderOutput{ UsedVars: make(map[string]interface{}), } // 1. 校验必填变量 for _, v : range tpl.Variables { if v.Required { val, ok : input.Variables[v.Name] if !ok || val nil { return nil, fmt.Errorf(missing required variable: %s, v.Name) } } } // 2. 合并默认值 mergedVars : make(map[string]interface{}) for _, v : range tpl.Variables { if val, ok : input.Variables[v.Name]; ok { mergedVars[v.Name] val output.UsedVars[v.Name] val } else if v.Default ! { mergedVars[v.Name] v.Default output.UsedVars[v.Name] v.Default output.Warnings append(output.Warnings, fmt.Sprintf(variable %s using default value, v.Name)) } } // 3. 添加内置变量 mergedVars[_user_id] input.UserID mergedVars[_session_id] input.SessionID mergedVars[_now] now() // 4. 渲染 tmpl, err : template.New(tpl.ID).Funcs(e.funcMap).Parse(tpl.Content) if err ! nil { return nil, fmt.Errorf(parse template: %w, err) } var buf bytes.Buffer if err : tmpl.Execute(buf, mergedVars); err ! nil { return nil, fmt.Errorf(execute template: %w, err) } output.Content buf.String() return output, nil } // Validate 校验模板语法 func (e *TemplateEngine) Validate(content string) error { _, err : template.New(validate).Funcs(e.funcMap).Parse(content) return err } // helper functions func toJSON(v interface{}) string { b, _ : json.Marshal(v) return string(b) } func now() string { return 2026-10-27 12:00:00 } func formatDate(format string, t string) string { // 简化实现 return t } // ─── 模板示例 ─── const customerServiceTemplate 你是一个{{.role}}客服负责处理用户的{{.domain}}相关问题。 ## 用户信息 - 用户等级{{.level}} - 历史订单数{{.order_count}} ## 对话上下文 {{if .history}} {{range $i, $msg : .history}} {{$msg.role}}: {{$msg.content}} {{end}} {{else}} 暂无历史对话。 {{end}} ## 当前用户问题 {{.question}} ## 回答要求 1. 语气{{.tone}}使用{{.language}} 2. 字数控制在{{.max_words}}字以内 3. {{if .need_order_info}}需要查询订单信息后再回答{{else}}直接回答即可{{end}} 4. 如果无法解决引导用户联系人工客服 ## 请给出回答 四、版本管理// prompt/version.go package prompt import ( fmt sort sync time ) // VersionRecord 版本记录 type VersionRecord struct { Version string json:version Content string json:content Author string json:author ChangeLog string json:change_log CreatedAt time.Time json:created_at } // VersionManager 版本管理器 type VersionManager struct { templates map[string][]*VersionRecord // templateID - versions active map[string]string // templateID - active version mu sync.RWMutex } // NewVersionManager 创建版本管理器 func NewVersionManager() *VersionManager { return VersionManager{ templates: make(map[string][]*VersionRecord), active: make(map[string]string), } } // SaveVersion 保存新版本 func (vm *VersionManager) SaveVersion(templateID, content, author, changeLog string) (*VersionRecord, error) { vm.mu.Lock() defer vm.mu.Unlock() // 生成版本号 records : vm.templates[templateID] newVersion : fmt.Sprintf(v%d.%d.%d, len(records)/1001, (len(records)%100)/101, len(records)%101) record : VersionRecord{ Version: newVersion, Content: content, Author: author, ChangeLog: changeLog, CreatedAt: time.Now(), } vm.templates[templateID] append(vm.templates[templateID], record) vm.active[templateID] newVersion return record, nil } // GetActive 获取当前活跃版本 func (vm *VersionManager) GetActive(templateID string) (*VersionRecord, error) { vm.mu.RLock() defer vm.mu.RUnlock() version, ok : vm.active[templateID] if !ok { return nil, fmt.Errorf(no active version for template: %s, templateID) } return vm.GetVersion(templateID, version) } // GetVersion 获取指定版本 func (vm *VersionManager) GetVersion(templateID, version string) (*VersionRecord, error) { records, ok : vm.templates[templateID] if !ok { return nil, fmt.Errorf(template not found: %s, templateID) } for _, r : range records { if r.Version version { return r, nil } } return nil, fmt.Errorf(version %s not found for template: %s, version, templateID) } // Rollback 回滚到指定版本 func (vm *VersionManager) Rollback(templateID, version string) error { vm.mu.Lock() defer vm.mu.Unlock() // 验证版本存在 _, err : vm.GetVersion(templateID, version) if err ! nil { return err } vm.active[templateID] version return nil } // ListVersions 列出所有版本 func (vm *VersionManager) ListVersions(templateID string) []*VersionRecord { vm.mu.RLock() defer vm.mu.RUnlock() records : vm.templates[templateID] sorted : make([]*VersionRecord, len(records)) copy(sorted, records) sort.Slice(sorted, func(i, j int) bool { return sorted[i].CreatedAt.After(sorted[j].CreatedAt) }) return sorted } // Diff 比较两个版本差异 func (vm *VersionManager) Diff(templateID, versionA, versionB string) (string, error) { recA, err : vm.GetVersion(templateID, versionA) if err ! nil { return , err } recB, err : vm.GetVersion(templateID, versionB) if err ! nil { return , err } // 简单逐行对比 linesA : splitLines(recA.Content) linesB : splitLines(recB.Content) var diff strings.Builder diff.WriteString(fmt.Sprintf(--- %s (%s)\n, templateID, versionA)) diff.WriteString(fmt.Sprintf( %s (%s)\n, templateID, versionB)) maxLen : len(linesA) if len(linesB) maxLen { maxLen len(linesB) } for i : 0; i maxLen; i { if i len(linesA) { diff.WriteString(fmt.Sprintf( %s\n, linesB[i])) } else if i len(linesB) { diff.WriteString(fmt.Sprintf(- %s\n, linesA[i])) } else if linesA[i] ! linesB[i] { diff.WriteString(fmt.Sprintf(- %s\n, linesA[i])) diff.WriteString(fmt.Sprintf( %s\n, linesB[i])) } } return diff.String(), nil } func splitLines(s string) []string { return strings.Split(s, \n) }五、A/B 测试系统// prompt/abtest.go package prompt import ( crypto/md5 encoding/hex fmt hash/fnv sync time ) // ABTest A/B 测试配置 type ABTest struct { ID string json:id Name string json:name TemplateID string json:template_id Groups []TestGroup json:groups TrafficMode string json:traffic_mode // hash/user_id/random StartAt time.Time json:start_at EndAt time.Time json:end_at Status string json:status // running/paused/ended Metrics map[string]int64 json:metrics mu sync.RWMutex } // TestGroup 测试分组 type TestGroup struct { Name string json:name // control/variant_a/variant_b Version string json:version // 对应的 Prompt 版本 Weight float64 json:weight // 流量权重 0.0-1.0 IsControl bool json:is_control // 是否为对照组 } // ABTestManager A/B 测试管理器 type ABTestManager struct { tests map[string]*ABTest versionMgr *VersionManager results map[string]*TestResults mu sync.RWMutex } // TestResults 测试结果 type TestResults struct { TestID string json:test_id GroupName string json:group_name TotalCalls int64 json:total_calls SuccessRate float64 json:success_rate AvgLatency time.Duration json:avg_latency AvgTokens int json:avg_tokens UserRating float64 json:user_rating // 用户评分 1-5 Metrics map[string]float64 json:metrics } // NewABTestManager 创建 A/B 测试管理器 func NewABTestManager(versionMgr *VersionManager) *ABTestManager { return ABTestManager{ tests: make(map[string]*ABTest), versionMgr: versionMgr, results: make(map[string]*TestResults), } } // CreateTest 创建 A/B 测试 func (m *ABTestManager) CreateTest(name, templateID string, groups []TestGroup) (*ABTest, error) { // 校验权重总和为 1 totalWeight : 0.0 for _, g : range groups { totalWeight g.Weight } if totalWeight 0.999 || totalWeight 1.001 { return nil, fmt.Errorf(total weight must be 1.0, got %f, totalWeight) } test : ABTest{ ID: generateID(), Name: name, TemplateID: templateID, Groups: groups, TrafficMode: hash, StartAt: time.Now(), Status: running, Metrics: make(map[string]int64), } m.mu.Lock() m.tests[test.ID] test m.mu.Unlock() return test, nil } // AssignGroup 为用户分配测试组 func (m *ABTestManager) AssignGroup(testID, userID string) (*TestGroup, error) { m.mu.RLock() test, ok : m.tests[testID] m.mu.RUnlock() if !ok { return nil, fmt.Errorf(test not found: %s, testID) } if test.Status ! running { return nil, fmt.Errorf(test is not running: %s, test.Status) } if time.Now().After(test.EndAt) !test.EndAt.IsZero() { return nil, fmt.Errorf(test has ended) } // 根据分流模式选择分组 var groupIndex int switch test.TrafficMode { case hash: h : fnv.New32a() h.Write([]byte(testID : userID)) ratio : float64(h.Sum32()) / float64(132) groupIndex selectGroup(ratio, test.Groups) case random: groupIndex selectGroup(float64(time.Now().UnixNano()%1000)/1000, test.Groups) default: groupIndex 0 } return test.Groups[groupIndex], nil } // RecordResult 记录测试结果 func (m *ABTestManager) RecordResult(testID, groupName string, success bool, latency time.Duration, tokens int, rating float64) { key : testID : groupName m.mu.Lock() defer m.mu.Unlock() result, ok : m.results[key] if !ok { result TestResults{ TestID: testID, GroupName: groupName, Metrics: make(map[string]float64), } m.results[key] result } result.TotalCalls if success { result.SuccessRate (result.SuccessRate*float64(result.TotalCalls-1) 1) / float64(result.TotalCalls) } else { result.SuccessRate (result.SuccessRate*float64(result.TotalCalls-1)) / float64(result.TotalCalls) } result.AvgLatency (result.AvgLatency*time.Duration(result.TotalCalls-1) latency) / time.Duration(result.TotalCalls) result.AvgTokens (result.AvgTokens*int(result.TotalCalls-1) tokens) / int(result.TotalCalls) result.UserRating (result.UserRating*float64(result.TotalCalls-1) rating) / float64(result.TotalCalls) } // GetReport 获取测试报告 func (m *ABTestManager) GetReport(testID string) map[string]*TestResults { m.mu.RLock() defer m.mu.RUnlock() report : make(map[string]*TestResults) for key, result : range m.results { if strings.HasPrefix(key, testID:) { groupName : strings.TrimPrefix(key, testID:) report[groupName] result } } return report } // selectGroup 根据比例选择分组 func selectGroup(ratio float64, groups []TestGroup) int { cumulative : 0.0 for i, g : range groups { cumulative g.Weight if ratio cumulative { return i } } return len(groups) - 1 } func generateID() string { h : md5.New() h.Write([]byte(time.Now().String())) return hex.EncodeToString(h.Sum(nil))[:16] }六、完整使用示例// prompt/example.go package prompt import ( fmt log ) func Example() { // 1. 创建模板引擎 engine : NewTemplateEngine() // 2. 创建版本管理器 vm : NewVersionManager() // 3. 定义客服模板 csTemplate : Template{ ID: customer_service, Name: 客服对话模板, Version: v1.0.0, Content: customerServiceTemplate, Description: 通用客服对话 Prompt, Variables: []VariableDef{ {Name: role, Type: string, Required: true, Description: 客服角色}, {Name: domain, Type: string, Required: true, Description: 业务领域}, {Name: level, Type: string, Required: true, Description: 用户等级}, {Name: order_count, Type: number, Required: true, Description: 历史订单数}, {Name: question, Type: string, Required: true, Description: 用户问题}, {Name: tone, Type: string, Required: false, Default: 友好专业, Description: 语气}, {Name: language, Type: string, Required: false, Default: 中文, Description: 语言}, {Name: max_words, Type: number, Required: false, Default: 200, Description: 最大字数}, {Name: need_order_info, Type: boolean, Required: false, Default: false, Description: 是否需要查订单}, {Name: history, Type: array, Required: false, Description: 历史对话}, }, } // 4. 保存第一个版本 v1, _ : vm.SaveVersion( csTemplate.ID, csTemplate.Content, alice, 初始版本, ) fmt.Printf(Saved version: %s\n, v1.Version) // 5. 迭代第二个版本增加 emoji 要求 v2Content : csTemplate.Content \n6. 适当使用 emoji 表达情感\n v2, _ : vm.SaveVersion( csTemplate.ID, v2Content, bob, 增加 emoji 要求, ) fmt.Printf(Saved version: %s\n, v2.Version) // 6. 渲染模板 input : RenderInput{ Variables: map[string]interface{}{ role: 售后, domain: 电子产品退货, level: 金牌会员, order_count: 23, question: 我买的手机屏幕碎了能退货吗, tone: 温和耐心, max_words: 150, history: []map[string]string{ {role: user, content: 你好我想咨询退货}, {role: assistant, content: 您好请问有什么可以帮助您的}, }, }, UserID: U12345, SessionID: S67890, } output, err : engine.Render(csTemplate, input) if err ! nil { log.Fatal(err) } fmt.Println(\n 渲染结果 ) fmt.Println(output.Content) fmt.Println(\n 使用变量 ) for k, v : range output.UsedVars { fmt.Printf( %s: %v\n, k, v) } if len(output.Warnings) 0 { fmt.Println(\n 警告 ) for _, w : range output.Warnings { fmt.Printf( ⚠️ %s\n, w) } } // 7. A/B 测试 abMgr : NewABTestManager(vm) test, _ : abMgr.CreateTest(客服模板 Emoji 测试, csTemplate.ID, []TestGroup{ {Name: control, Version: v1.0.0, Weight: 0.5, IsControl: true}, {Name: variant_a, Version: v2.0.0, Weight: 0.5, IsControl: false}, }) // 模拟用户请求 users : []string{U001, U002, U003, U004} for _, uid : range users { group, _ : abMgr.AssignGroup(test.ID, uid) fmt.Printf(\nUser %s assigned to group: %s (version: %s), uid, group.Name, group.Version) // 模拟调用结果 abMgr.RecordResult(test.ID, group.Name, true, 350*time.Millisecond, 180, 4.5) } // 查看报告 report : abMgr.GetReport(test.ID) fmt.Println(\n\n A/B 测试报告 ) for group, result : range report { fmt.Printf(\n组: %s, group) fmt.Printf(\n 调用次数: %d, result.TotalCalls) fmt.Printf(\n 成功率: %.1f%%, result.SuccessRate*100) fmt.Printf(\n 平均延迟: %v, result.AvgLatency) fmt.Printf(\n 平均 Token: %d, result.AvgTokens) fmt.Printf(\n 用户评分: %.1f, result.UserRating) } }七、生产坑位#坑位现象解决方案1模板注入用户输入包含{{导致模板解析失败对用户输入做转义处理2版本混乱多人同时修改导致版本冲突加锁 变更通知3A/B 测试污染同用户在不同会话分配到不同组使用 user_id hash 保证一致性4模板膨胀单个模板超过 10K Token拆分子模板 组合模式5变量遗漏必填变量没传导致渲染报错编译期校验 运行时兜底6性能问题频繁渲染大型模板预编译 缓存 AST八、本讲小结┌─────────────────────────────────────────────────────────────┐ │ 第3讲 核心收获 │ │ │ │ ✅ 实现了 Go template 驱动的 Prompt 模板引擎 │ │ ✅ 建立了版本管理体系支持创建/回滚/对比 │ │ ✅ 搭建了 A/B 测试系统支持 hash/random 分流 │ │ ✅ 掌握了变量校验、默认值、内置函数等工程化手段 │ │ ✅ 了解了 6 个常见坑位及其解决方案 │ │ │ │ 核心理念Prompt 也是代码需要版本管理、测试和度量 │ └─────────────────────────────────────────────────────────────┘九、配套工具推荐 开发之余的小工具推荐在日常调试 Prompt 时经常需要对比不同版本的差异、格式化 JSON 变量、生成测试数据文本差异对比zz365.top 的文本对比工具可以快速看出两个 Prompt 版本的每一行变化JSON 格式化将复杂的变量结构展开确认模板渲染时的数据源是否正确正则表达式测试验证模板中的变量提取规则Base64 编解码处理某些场景下的编码转换同类可备站点tool.lu综合在线工具箱360站长工具zz360.cc偏 SEO/建站方向提醒所有涉及业务数据和用户信息的操作务必先在本地脱敏再使用在线工具。下一讲预告《RAG 从零实现——让大模型学会查你的数据库》你会学到Embedding 选型、向量数据库集成、文档切分策略、检索排序与重排、RAG 全链路实现。准备好了告诉我继续。