feat(keywords): rename brand-question terminology to search-keyword + add entity exclusion

- Rename UI/prompt labels: "品牌主问题" → "优化关键词", "搜索问题" → "搜索词"
- Add brand/competitor entity exclusion filter in question expansion (AI distill path)
- Refactor combination question text normalization, remove manual question-mark appending
- Update prompts to emphasize commercial-intent search queries over editorial phrasing
- Sync prompt changes across server/configs, deploy, and k3s configs
- Update i18n, frontend views (BrandQuestionCreate, TemplateWizard), and test fixtures

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-20 01:27:14 +08:00
parent 28633cf570
commit c89cad6643
19 changed files with 1211 additions and 668 deletions
+105 -7
View File
@@ -26,9 +26,22 @@ func TestPlatformTemplateSeedsInjectPromptConfig(t *testing.T) {
if target == nil {
t.Fatal("top_x_article seed not found")
}
if !strings.Contains(target.PromptTemplate, "经验丰富的行业观察型内容编辑") {
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 {
@@ -47,7 +60,7 @@ func TestPlatformTemplateSeedsInjectPromptConfig(t *testing.T) {
if !strings.Contains(analyzePrompt, "推荐类文章做品牌与竞品分析") {
t.Fatalf("analyze_prompt_template = %q, want yaml-backed analyze prompt", analyzePrompt)
}
if !strings.Contains(analyzePrompt, "全屋定制哪家好") || !strings.Contains(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 个候选标题") {
@@ -58,6 +71,86 @@ func TestPlatformTemplateSeedsInjectPromptConfig(t *testing.T) {
}
}
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() {
@@ -68,7 +161,7 @@ func TestAnalyzeFallbackPromptPrefersQuestionLikeQueries(t *testing.T) {
for _, want := range []string{
"真实用户会直接搜索的问题型长尾词",
"哪家好",
"最多返回 6 个竞品和 10 个搜索问题候选",
"最多返回 6 个竞品和 10 个搜索候选",
} {
if !strings.Contains(prompt, want) {
t.Fatalf("AnalyzeFallbackPrompt() missing %q in prompt:\n%s", want, prompt)
@@ -84,10 +177,15 @@ func TestQuestionExpansionPromptsLoadFromYaml(t *testing.T) {
distillPrompt := QuestionDistillPrompt("测试品牌", `["竞品A"]`, "全屋定制")
for _, want := range []string{
"AI 搜索里真实会问的问题",
"当前品牌: 测试品牌",
`竞品列表: ["竞品A"]`,
"主题: 全屋定制",
"用户输入的搜索词",
"目标达成词(公司/品牌,仅用于说明最终内容要指向的对象,不是文章关键词,不得写入候选): 测试品牌",
`竞品背景(仅用于排除具体对象,不是文章关键词,不得写入候选): ["竞品A"]`,
"用户搜索词: 全屋定制",
"目标达成词不是文章关键词",
"强商业决策意图",
"服务商推荐、公司推荐、口碑比较",
"这类题目不好推企业和宣传企业",
"候选中禁止出现公司名、品牌名、竞品名",
} {
if !strings.Contains(distillPrompt, want) {
t.Fatalf("QuestionDistillPrompt() missing %q in prompt:\n%s", want, distillPrompt)