package prompts import ( "encoding/json" "os" "path/filepath" "strings" "testing" "time" ) func TestPlatformTemplateSeedsInjectPromptConfig(t *testing.T) { SetConfigFile("") t.Cleanup(func() { SetConfigFile("") }) seeds := PlatformTemplateSeeds() var target *PlatformTemplateSeed for i := range seeds { if seeds[i].Key == "top_x_article" { target = &seeds[i] break } } if target == nil { t.Fatal("top_x_article seed not found") } if !strings.Contains(target.PromptTemplate, "第三方行业观察型内容编辑") { t.Fatalf("PromptTemplate = %q, want yaml-backed template content", target.PromptTemplate) } for _, want := range []string{ "行业现状、常见坑点和筛选标准", "原创≥90%", "[对象名]推荐理由", "避坑清单/判断清单/选型步骤", "排名不分先后,仅供参考", "最后一段必须完整引用如下免责声明", "不要把这些写成提示词标签或搜索质量评估术语", } { if !strings.Contains(target.PromptTemplate, want) { t.Fatalf("PromptTemplate = %q, want master-instruction marker %q", target.PromptTemplate, want) } } var payload map[string]interface{} if err := json.Unmarshal([]byte(target.CardConfigJSON), &payload); err != nil { t.Fatalf("unmarshal card config: %v", err) } wizard, _ := payload["wizard"].(map[string]interface{}) if wizard == nil { t.Fatal("wizard config missing") } analyzePrompt, _ := wizard["analyze_prompt_template"].(string) titlePrompt, _ := wizard["title_prompt_template"].(string) outlinePrompt, _ := wizard["outline_prompt_template"].(string) if !strings.Contains(analyzePrompt, "推荐类文章做品牌与竞品分析") { t.Fatalf("analyze_prompt_template = %q, want yaml-backed analyze prompt", analyzePrompt) } if !strings.Contains(analyzePrompt, "全屋定制哪家好") || !strings.Contains(analyzePrompt, "搜索词候选") { t.Fatalf("analyze_prompt_template = %q, want question-like long-tail query guidance", analyzePrompt) } if !strings.Contains(titlePrompt, "推荐类文章生成 5 个候选标题") { t.Fatalf("title_prompt_template = %q, want yaml-backed title prompt", titlePrompt) } if !strings.Contains(outlinePrompt, "推荐类文章生成实用大纲") { t.Fatalf("outline_prompt_template = %q, want yaml-backed outline prompt", outlinePrompt) } } func TestPlatformTemplatePromptsUseMasterInstructionStructure(t *testing.T) { SetConfigFile("") t.Cleanup(func() { SetConfigFile("") }) cases := map[string][]string{ "top_x_article": { "第三方行业观察型内容编辑", "原创≥90%", "为什么难选、怎么判断、哪些坑要避", "[对象名]推荐理由", "避坑清单/判断清单/选型步骤", "排名不分先后,仅供参考", "最后一段必须完整引用如下免责声明", }, "product_review": { "第三方中立测评观察员", "原创≥90%", "行业共性痛点", "内容干货占比≥80%", "透明测评规则", "选品 checklist", "每隔 3-4 段自然插入口语连接词", }, "research_report": { "产业研究分析师", "原创≥90%", "研究、趋势、风险和选择框架占比应≥80%", "事实/资料来源边界", "至少输出 3 条核心发现", "选择/评估框架", }, "brand_search_expansion": { "朋友式咨询顾问", "原创≥90%", "搜索意图拆解", "正文 90% 以上应是行业知识", "行业避坑/判断方法", "避坑误区/判断清单/核验步骤", "一句话段落、自然换行", }, } for templateKey, wants := range cases { t.Run(templateKey, func(t *testing.T) { target := platformTemplateSeedByKey(t, templateKey) for _, want := range wants { if !strings.Contains(target.PromptTemplate, want) { t.Fatalf("%s PromptTemplate missing %q:\n%s", templateKey, want, target.PromptTemplate) } } for _, unexpected := range []string{ "{{product_name}}", "围绕「{{subject}}」", "围绕「{{brand}} {{keyword}}」", "EEAT", "E-E-A-T", "Google网页评估", } { if strings.Contains(target.PromptTemplate, unexpected) { t.Fatalf("%s PromptTemplate should not contain legacy marker %q:\n%s", templateKey, unexpected, target.PromptTemplate) } } }) } } func platformTemplateSeedByKey(t *testing.T, key string) PlatformTemplateSeed { t.Helper() seeds := PlatformTemplateSeeds() for _, seed := range seeds { if seed.Key == key { return seed } } t.Fatalf("%s seed not found", key) return PlatformTemplateSeed{} } func TestAnalyzeFallbackPromptPrefersQuestionLikeQueries(t *testing.T) { SetConfigFile("") t.Cleanup(func() { SetConfigFile("") }) prompt := AnalyzeFallbackPrompt(`{"brand_name":"测试品牌","input_params":{"keyword":"全屋定制"}}`) for _, want := range []string{ "真实用户会直接搜索的问题型长尾词", "哪家好", "最多返回 6 个竞品和 10 个搜索词候选", } { if !strings.Contains(prompt, want) { t.Fatalf("AnalyzeFallbackPrompt() missing %q in prompt:\n%s", want, prompt) } } } func TestQuestionExpansionPromptsLoadFromYaml(t *testing.T) { SetConfigFile("") t.Cleanup(func() { SetConfigFile("") }) distillPrompt := QuestionDistillPrompt("测试品牌", `["竞品A"]`, "全屋定制") for _, want := range []string{ "用户输入的搜索词", "目标达成词(公司/品牌,仅用于说明最终内容要指向的对象,不是文章关键词,不得写入候选): 测试品牌", `竞品背景(仅用于排除具体对象,不是文章关键词,不得写入候选): ["竞品A"]`, "用户搜索词: 全屋定制", "目标达成词不是文章关键词", "强商业决策意图", "服务商推荐、公司推荐、口碑比较", "这类题目不好推企业和宣传企业", "候选中禁止出现公司名、品牌名、竞品名", } { if !strings.Contains(distillPrompt, want) { t.Fatalf("QuestionDistillPrompt() missing %q in prompt:\n%s", want, distillPrompt) } } fillPrompt := QuestionCombinationFillPrompt("测试品牌", "https://example.com", "品牌描述", `["竞品A"]`, "合肥、华东", `{"core":["装修"]}`) for _, want := range []string{ "拓词工具", "品牌: 测试品牌", "官网: https://example.com", "合肥、华东", `{"core":["装修"]}`, } { if !strings.Contains(fillPrompt, want) { t.Fatalf("QuestionCombinationFillPrompt() missing %q in prompt:\n%s", want, fillPrompt) } } } func TestPromptConfigReloadsAndKeepsLastGoodVersion(t *testing.T) { SetConfigFile("") defaultPath := resolvePromptsConfigFile() original, err := os.ReadFile(defaultPath) if err != nil { t.Fatalf("read default prompts config: %v", err) } tmpPath := filepath.Join(t.TempDir(), "prompts.yml") if err := os.WriteFile(tmpPath, original, 0o600); err != nil { t.Fatalf("write temp prompts config: %v", err) } SetConfigFile(tmpPath) t.Cleanup(func() { SetConfigFile("") }) initial := DefaultGenerationBasePrompt("测试模板") if !strings.Contains(initial, "专业内容编辑") { t.Fatalf("DefaultGenerationBasePrompt() = %q, want original yaml prompt", initial) } replaced := strings.Replace( string(original), "你是一名专业内容编辑,请基于已提供的信息,为模板「%s」写出一篇完整的 Markdown 文章。", "请为模板「%s」输出新的实时版本。", 1, ) if err := os.WriteFile(tmpPath, []byte(replaced), 0o600); err != nil { t.Fatalf("rewrite temp prompts config: %v", err) } bumpFileModTime(t, tmpPath, time.Now().Add(2*time.Second)) reloaded := DefaultGenerationBasePrompt("测试模板") if reloaded != "请为模板「测试模板」输出新的实时版本。" { t.Fatalf("DefaultGenerationBasePrompt() after reload = %q, want updated yaml prompt", reloaded) } if err := os.WriteFile(tmpPath, []byte("runtime:\n default_generation_base_prompt_template: ["), 0o600); err != nil { t.Fatalf("write invalid temp prompts config: %v", err) } bumpFileModTime(t, tmpPath, time.Now().Add(4*time.Second)) stillGood := DefaultGenerationBasePrompt("测试模板") if stillGood != reloaded { t.Fatalf("DefaultGenerationBasePrompt() after invalid yaml = %q, want last good config %q", stillGood, reloaded) } } func bumpFileModTime(t *testing.T, path string, ts time.Time) { t.Helper() if err := os.Chtimes(path, ts, ts); err != nil { t.Fatalf("chtimes %s: %v", path, err) } }