// Package prompts 是受管系统提示词的注册表:内置默认(随代码) + 运行期覆盖(文件/控制面)。 // 让各服务散落硬编码的 system prompt 集中、可不重编译地改/回滚/对比/灰度 —— prompt 版本化的地基。 package prompts import ( "encoding/json" "log" "os" "sort" "sync" ) // 受管 prompt 的稳定键(key 即版本化/覆盖的寻址单位)。 const ( GraphExtract = "graph.extract" // mcp-go:知识图谱三元组抽取 EvalQuality = "eval.quality" // dispatcher:回答质量评审(harness) EvalRefine = "eval.refine" // dispatcher:低质回答自动修订 GuardJailbreak = "guard.jailbreak" // dispatcher:输入越狱/注入分类 CoordinatorLead = "coordinator.lead" // dispatcher:多智能体协调者(lead) MemoryExtract = "memory.extract" // dispatcher:长期记忆对账 ) // Registry 持有默认与覆盖;Get 取覆盖优先、否则默认。并发安全。 type Registry struct { mu sync.RWMutex defaults map[string]string overrides map[string]string } func New() *Registry { return &Registry{defaults: map[string]string{}, overrides: map[string]string{}} } var global = New() // SetDefault 登记某 key 的内置默认(基线版本,随代码走)。各服务启动时登记自己的 prompt。 func SetDefault(key, text string) { global.SetDefault(key, text) } func (r *Registry) SetDefault(key, text string) { r.mu.Lock() defer r.mu.Unlock() r.defaults[key] = text } // SetOverride 运行期覆盖某 key(文件/控制面下发)。空串=清覆盖、回退默认。 func SetOverride(key, text string) { global.SetOverride(key, text) } func (r *Registry) SetOverride(key, text string) { r.mu.Lock() defer r.mu.Unlock() if text == "" { delete(r.overrides, key) return } r.overrides[key] = text } // Get 取生效内容:有覆盖用覆盖,否则用默认(都没有则空串)。 func Get(key string) string { return global.Get(key) } func (r *Registry) Get(key string) string { r.mu.RLock() defer r.mu.RUnlock() if v, ok := r.overrides[key]; ok && v != "" { return v } return r.defaults[key] } // Keys 返回所有已登记默认的 key(排序,供管理面/启动日志)。 func Keys() []string { return global.Keys() } func (r *Registry) Keys() []string { r.mu.RLock() defer r.mu.RUnlock() ks := make([]string, 0, len(r.defaults)) for k := range r.defaults { ks = append(ks, k) } sort.Strings(ks) return ks } // LoadFile 从 JSON 文件({key:content})加载覆盖 —— env PROMPTS_FILE,启动时调一次。 // 不重编译即可改/回滚/对比 prompt;DB 控制面热切换是后续增量。 func LoadFile() { path := os.Getenv("PROMPTS_FILE") if path == "" { return } data, err := os.ReadFile(path) if err != nil { log.Printf("[prompts] 读 PROMPTS_FILE=%s 失败(用默认): %v", path, err) return } var m map[string]string if err := json.Unmarshal(data, &m); err != nil { log.Printf("[prompts] 解析 PROMPTS_FILE 失败(用默认): %v", err) return } n := 0 for k, v := range m { SetOverride(k, v) n++ } log.Printf("[prompts] 从 %s 加载 %d 条覆盖", path, n) }