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:
@@ -176,15 +176,15 @@ var routeDocs = map[string]routeDoc{
|
||||
"PUT /api/tenant/brands/:id/keywords/:kid": {"更新关键词", "修改关键词文本/分组。"},
|
||||
"DELETE /api/tenant/brands/:id/keywords/:kid": {"删除关键词", "删除某条关键词。"},
|
||||
|
||||
"GET /api/tenant/brands/:id/questions": {"品牌问题列表", "返回品牌下的监控问题,可按 keyword_id 过滤。"},
|
||||
"POST /api/tenant/brands/:id/questions": {"新增监控问题", "为品牌添加一条 GEO 监控问题。"},
|
||||
"POST /api/tenant/brands/:id/questions/combination-preview": {"拓词工具预览", "按地域词、前缀词、核心词、行业词、后缀词组合生成问题候选,仅预览不入库。"},
|
||||
"GET /api/tenant/brands/:id/questions": {"搜索词列表", "返回品牌下的监控搜索词,可按 keyword_id 过滤。"},
|
||||
"POST /api/tenant/brands/:id/questions": {"新增搜索词", "为品牌添加一条 GEO 监控搜索词。"},
|
||||
"POST /api/tenant/brands/:id/questions/combination-preview": {"拓词工具预览", "按地域词、前缀词、精准词、行业词、后缀词组合生成搜索词候选,仅预览不入库。"},
|
||||
"POST /api/tenant/brands/:id/questions/combination-fill": {"拓词工具 AI 填词", "根据用户 IP 本地识别地域,并结合品牌信息补全拓词工具的五列词组。"},
|
||||
"POST /api/tenant/brands/:id/questions/ai-distill": {"AI 扩展问题", "围绕品牌和主题生成问题候选,使用结构化输出并按 AI 点计费。"},
|
||||
"POST /api/tenant/brands/:id/questions/classify-metadata": {"问题元数据分类", "批量为问题文本推断 layer 和 intent 元数据。"},
|
||||
"POST /api/tenant/brands/:id/questions/materialize": {"保存问题候选", "将用户选中的问题候选保存到当前品牌问题集,执行配额、去重和审计。"},
|
||||
"PUT /api/tenant/brands/:id/questions/:qid": {"更新监控问题", "修改问题文本或所属关键词。"},
|
||||
"DELETE /api/tenant/brands/:id/questions/:qid": {"删除监控问题", "删除某条监控问题。"},
|
||||
"POST /api/tenant/brands/:id/questions/ai-distill": {"AI 扩展搜索词", "围绕品牌和主题生成搜索词候选,使用结构化输出并按 AI 点计费。"},
|
||||
"POST /api/tenant/brands/:id/questions/classify-metadata": {"搜索词元数据分类", "批量为搜索词文本推断 layer 和 intent 元数据。"},
|
||||
"POST /api/tenant/brands/:id/questions/materialize": {"保存搜索词候选", "将用户选中的搜索词候选保存到当前关键词库(搜索词),执行配额、去重和审计。"},
|
||||
"PUT /api/tenant/brands/:id/questions/:qid": {"更新监控搜索词", "修改搜索词文本或所属关键词。"},
|
||||
"DELETE /api/tenant/brands/:id/questions/:qid": {"删除监控搜索词", "删除某条监控搜索词。"},
|
||||
|
||||
"GET /api/tenant/brands/:id/competitors": {"竞品列表", "返回品牌下登记的竞品。"},
|
||||
"POST /api/tenant/brands/:id/competitors": {"新增竞品", "为品牌添加竞品记录。"},
|
||||
|
||||
@@ -689,12 +689,12 @@ func (s *MonitoringService) CollectNow(
|
||||
limitApplied = true
|
||||
}
|
||||
if len(configuredQuestions) == 0 {
|
||||
message := "当前品牌下暂无品牌库问题,未创建采集任务"
|
||||
message := "当前品牌下暂无关键词库(搜索词),未创建采集任务"
|
||||
if keywordID != nil {
|
||||
message = "当前问题集下暂无品牌库问题,未创建采集任务"
|
||||
message = "当前关键词库(搜索词)下暂无搜索词,未创建采集任务"
|
||||
}
|
||||
if questionID != nil {
|
||||
message = "当前问题不可用,未创建采集任务"
|
||||
message = "当前搜索词不可用,未创建采集任务"
|
||||
}
|
||||
return &MonitoringCollectNowResponse{
|
||||
CollectionMode: quota.CollectionMode,
|
||||
|
||||
@@ -23,10 +23,48 @@ func TestBuildQuestionCombinationFillFallback(t *testing.T) {
|
||||
if !reflect.DeepEqual(got.Region, []string{"上海", "华东"}) {
|
||||
t.Fatalf("region = %#v", got.Region)
|
||||
}
|
||||
if len(got.Core) < 3 || got.Core[0] != "GEO" {
|
||||
t.Fatalf("core fallback = %#v", got.Core)
|
||||
wantPrefix := []string{"性价比高的", "口碑好的", "专业的", "靠谱的"}
|
||||
if !reflect.DeepEqual(got.Prefix, wantPrefix) {
|
||||
t.Fatalf("prefix fallback = %#v, want %#v", got.Prefix, wantPrefix)
|
||||
}
|
||||
wantCore := []string{"品牌词", "AI搜索", "获客", "中小企业"}
|
||||
if !reflect.DeepEqual(got.Core, wantCore) {
|
||||
t.Fatalf("core fallback = %#v, want %#v", got.Core, wantCore)
|
||||
}
|
||||
wantIndustry := []string{"GEO优化", "AI搜索优化", "品牌推广", "搜索营销"}
|
||||
if !reflect.DeepEqual(got.Industry, wantIndustry) {
|
||||
t.Fatalf("industry fallback = %#v, want %#v", got.Industry, wantIndustry)
|
||||
}
|
||||
if len(got.Prefix) != 4 || len(got.Industry) != 4 || len(got.Suffix) != 4 {
|
||||
t.Fatalf("expected four hot terms for non-region columns, got %#v", got)
|
||||
}
|
||||
wantSuffix := []string{"哪家好", "怎么选", "推荐", "避坑"}
|
||||
if !reflect.DeepEqual(got.Suffix, wantSuffix) {
|
||||
t.Fatalf("suffix fallback = %#v, want %#v", got.Suffix, wantSuffix)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeQuestionTextRemovesPunctuation(t *testing.T) {
|
||||
got := normalizeQuestionText(" 合肥全屋定制哪家好? ")
|
||||
if got != "合肥全屋定制哪家好" {
|
||||
t.Fatalf("normalizeQuestionText() = %q, want %q", got, "合肥全屋定制哪家好")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompactQuestionCombinationTermsRemovesPunctuation(t *testing.T) {
|
||||
got := compactQuestionCombinationTerms([]string{"官网?", "怎么选?", "推荐!", "对比"}, 4)
|
||||
want := []string{"官网", "怎么选", "推荐", "对比"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("compactQuestionCombinationTerms() = %#v, want %#v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuestionContainsExcludedEntityMatchesCompanyShortName(t *testing.T) {
|
||||
brandName := "安徽海翔家居用品销售有限公司"
|
||||
if !questionContainsExcludedEntity("安徽海翔家居全屋定制口碑怎么样", brandName, nil) {
|
||||
t.Fatal("expected candidate containing company short name to be excluded")
|
||||
}
|
||||
if questionContainsExcludedEntity("合肥全屋定制哪家好", brandName, nil) {
|
||||
t.Fatal("expected generic user search term to be allowed")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,15 +191,10 @@ func (s *QuestionExpansionService) GenerateByCombination(ctx context.Context, br
|
||||
for _, co := range core {
|
||||
for _, in := range industry {
|
||||
for _, sf := range suffix {
|
||||
text := strings.TrimSpace(strings.Join(nonEmptyParts(rg, pf, co, in, sf), ""))
|
||||
text := normalizeQuestionText(strings.Join(nonEmptyParts(rg, pf, co, in, sf), ""))
|
||||
if text == "" {
|
||||
continue
|
||||
}
|
||||
if sf != "" && !strings.HasSuffix(text, "?") && !strings.HasSuffix(text, "?") {
|
||||
if !strings.Contains(sf, "?") && !strings.Contains(sf, "?") {
|
||||
text += "?"
|
||||
}
|
||||
}
|
||||
key := normalizeQuestionKey(text)
|
||||
if _, ok := seen[key]; ok {
|
||||
continue
|
||||
@@ -312,6 +307,9 @@ func (s *QuestionExpansionService) GenerateByAIDistill(ctx context.Context, bran
|
||||
if text == "" {
|
||||
continue
|
||||
}
|
||||
if questionContainsExcludedEntity(text, brandCtx.BrandName, brandCtx.CompetitorNames) {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[key]; ok {
|
||||
continue
|
||||
}
|
||||
@@ -754,11 +752,89 @@ func buildCandidate(text, source string, missingSuffix bool, existing map[string
|
||||
}
|
||||
}
|
||||
|
||||
func questionContainsExcludedEntity(text, brandName string, competitorNames []string) bool {
|
||||
normalized := normalizeQuestionKey(text)
|
||||
for _, entity := range append([]string{brandName}, competitorNames...) {
|
||||
for _, entityKey := range questionEntityExclusionKeys(entity) {
|
||||
if strings.Contains(normalized, entityKey) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func questionEntityExclusionKeys(entity string) []string {
|
||||
base := normalizeQuestionKey(entity)
|
||||
if utf8.RuneCountInString(base) < 2 {
|
||||
return nil
|
||||
}
|
||||
keys := make([]string, 0, 4)
|
||||
addKey := func(value string) {
|
||||
value = strings.TrimSpace(value)
|
||||
if utf8.RuneCountInString(value) < 2 {
|
||||
return
|
||||
}
|
||||
for _, key := range keys {
|
||||
if key == value {
|
||||
return
|
||||
}
|
||||
}
|
||||
keys = append(keys, value)
|
||||
}
|
||||
addKey(base)
|
||||
|
||||
companyName := base
|
||||
for _, suffix := range []string{
|
||||
"有限责任公司",
|
||||
"股份有限公司",
|
||||
"集团有限公司",
|
||||
"控股有限公司",
|
||||
"科技有限公司",
|
||||
"销售有限公司",
|
||||
"用品有限公司",
|
||||
"有限公司",
|
||||
"股份公司",
|
||||
"集团公司",
|
||||
"公司",
|
||||
} {
|
||||
if strings.HasSuffix(companyName, suffix) {
|
||||
companyName = strings.TrimSuffix(companyName, suffix)
|
||||
addKey(companyName)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
descriptorName := companyName
|
||||
for _, suffix := range []string{
|
||||
"用品销售",
|
||||
"产品销售",
|
||||
"销售",
|
||||
"用品",
|
||||
"产品",
|
||||
"服务",
|
||||
"科技",
|
||||
"商贸",
|
||||
"贸易",
|
||||
"网络",
|
||||
"信息",
|
||||
"文化",
|
||||
"传媒",
|
||||
} {
|
||||
if strings.HasSuffix(descriptorName, suffix) {
|
||||
descriptorName = strings.TrimSuffix(descriptorName, suffix)
|
||||
addKey(descriptorName)
|
||||
break
|
||||
}
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func normalizeQuestionParts(values []string) []string {
|
||||
result := make([]string, 0, len(values))
|
||||
seen := map[string]struct{}{}
|
||||
for _, value := range values {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
trimmed := normalizeQuestionText(value)
|
||||
if trimmed == "" {
|
||||
continue
|
||||
}
|
||||
@@ -790,27 +866,27 @@ func buildQuestionCombinationFillFallback(brandCtx *questionBrandContext, region
|
||||
region = compactQuestionCombinationTerms(req.Region, questionCombinationFillMax)
|
||||
}
|
||||
if len(region) == 0 {
|
||||
region = []string{"全国", "本地", "华东"}
|
||||
region = []string{"全国", "合肥", "华东"}
|
||||
}
|
||||
|
||||
prefix := compactQuestionCombinationTerms(req.Prefix, questionCombinationFillMax)
|
||||
if len(prefix) == 0 {
|
||||
prefix = []string{"好用的", "专业的", "靠谱的", "热门的"}
|
||||
prefix = []string{"性价比高的", "口碑好的", "专业的", "靠谱的"}
|
||||
}
|
||||
|
||||
core := compactQuestionCombinationTerms(req.Core, questionCombinationFillMax)
|
||||
if len(core) == 0 {
|
||||
core = questionCombinationCoreFallback(brandName, description)
|
||||
core = questionCombinationPrecisionFallback(brandName, description)
|
||||
}
|
||||
|
||||
industry := compactQuestionCombinationTerms(req.Industry, questionCombinationFillMax)
|
||||
if len(industry) == 0 {
|
||||
industry = []string{category, "平台", "工具", "服务商"}
|
||||
industry = questionCombinationIndustryFallback(category)
|
||||
}
|
||||
|
||||
suffix := compactQuestionCombinationTerms(req.Suffix, questionCombinationFillMax)
|
||||
if len(suffix) == 0 {
|
||||
suffix = []string{"哪家好", "怎么选", "推荐", "多少钱"}
|
||||
suffix = []string{"哪家好", "怎么选", "推荐", "避坑"}
|
||||
}
|
||||
|
||||
return QuestionCombinationFillResult{
|
||||
@@ -1017,21 +1093,40 @@ func chineseMacroRegion(province string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func questionCombinationCoreFallback(brandName, description string) []string {
|
||||
func questionCombinationPrecisionFallback(brandName, description string) []string {
|
||||
text := strings.ToLower(strings.TrimSpace(brandName + " " + description))
|
||||
if strings.Contains(text, "geo") || strings.Contains(text, "ai") || strings.Contains(text, "搜索") || strings.Contains(text, "排名") {
|
||||
return []string{"GEO", "AI搜索优化", "AI搜索排名", "品牌可见度"}
|
||||
return []string{"品牌词", "AI搜索", "获客", "中小企业"}
|
||||
}
|
||||
if strings.Contains(text, "教育") || strings.Contains(text, "培训") || strings.Contains(text, "课程") {
|
||||
return []string{"课程", "培训", "学习", "机构"}
|
||||
return []string{"成人", "少儿", "线上", "入门"}
|
||||
}
|
||||
if strings.Contains(text, "医疗") || strings.Contains(text, "健康") || strings.Contains(text, "诊所") {
|
||||
return []string{"医疗服务", "健康管理", "医生", "诊疗"}
|
||||
return []string{"本地", "复诊", "儿童", "上班族"}
|
||||
}
|
||||
if strings.Contains(text, "家居") || strings.Contains(text, "装修") || strings.Contains(text, "建材") {
|
||||
return []string{"装修", "家居", "建材", "设计"}
|
||||
return []string{"精装", "100平", "小户型", "旧房改造"}
|
||||
}
|
||||
return []string{"企业", "本地", "新手", "中小企业"}
|
||||
}
|
||||
|
||||
func questionCombinationIndustryFallback(category string) []string {
|
||||
switch category {
|
||||
case "营销":
|
||||
return []string{"GEO优化", "AI搜索优化", "品牌推广", "搜索营销"}
|
||||
case "教育":
|
||||
return []string{"培训机构", "在线课程", "教育机构", "学习平台"}
|
||||
case "健康":
|
||||
return []string{"健康管理", "医疗服务", "诊疗机构", "医生服务"}
|
||||
case "家居":
|
||||
return []string{"全屋定制", "家具定制", "家居定制", "定制家装"}
|
||||
case "餐饮":
|
||||
return []string{"餐饮店", "美食店", "连锁餐饮", "餐饮服务"}
|
||||
case "旅游":
|
||||
return []string{"旅游服务", "酒店预订", "旅行社", "旅游攻略"}
|
||||
default:
|
||||
return []string{"服务商", "解决方案", "平台", "公司"}
|
||||
}
|
||||
return []string{strings.TrimSpace(brandName), "解决方案", "服务", "平台"}
|
||||
}
|
||||
|
||||
func inferQuestionCombinationCategory(brandName, description string) string {
|
||||
|
||||
@@ -29,11 +29,20 @@ type ClassifiedQuestion struct {
|
||||
}
|
||||
|
||||
func normalizeQuestionText(text string) string {
|
||||
return strings.TrimSpace(text)
|
||||
trimmed := strings.TrimSpace(text)
|
||||
if trimmed == "" {
|
||||
return ""
|
||||
}
|
||||
return strings.Map(func(r rune) rune {
|
||||
if unicode.IsPunct(r) {
|
||||
return -1
|
||||
}
|
||||
return r
|
||||
}, trimmed)
|
||||
}
|
||||
|
||||
func normalizeQuestionKey(text string) string {
|
||||
return strings.ToLower(strings.TrimSpace(text))
|
||||
return strings.ToLower(normalizeQuestionText(text))
|
||||
}
|
||||
|
||||
func isValidQuestionLayer(value string) bool {
|
||||
|
||||
@@ -72,10 +72,10 @@ func TestNormalizeAnalyzeResultKeepsTenQueryKeywords(t *testing.T) {
|
||||
"全屋定制哪家好",
|
||||
"靠谱的全屋定制品牌有哪些",
|
||||
"口碑好的全屋定制公司推荐",
|
||||
"性价比高的全屋定制怎么选",
|
||||
"专业的全屋定制设计公司哪家好",
|
||||
"全屋定制和木工打柜子哪个更划算",
|
||||
"全屋定制一般多少钱一平米",
|
||||
"全屋定制服务商推荐",
|
||||
"全屋定制官网怎么找",
|
||||
"全屋定制案例怎么看",
|
||||
"全屋定制常用的板材哪种环保",
|
||||
"全屋定制十大品牌排名最新",
|
||||
"全屋定制需要注意哪些坑",
|
||||
|
||||
@@ -20,7 +20,7 @@ func TestBuildPromptContextUsesChineseLabels(t *testing.T) {
|
||||
"- 语言: zh-CN",
|
||||
"- 标题: 测试标题",
|
||||
"- 品牌名: Rankly",
|
||||
"- 品牌主问题: GEO 排名",
|
||||
"- 优化关键词: GEO 排名",
|
||||
"- 已选段落: 引言 > 结论",
|
||||
} {
|
||||
if !strings.Contains(context, expected) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -57,7 +57,7 @@ var platformTemplateSeeds = []PlatformTemplateSeed{
|
||||
},
|
||||
"keywords": {
|
||||
"title": "品牌问题",
|
||||
"hint": "品牌主问题必选且只能选一个;补充覆盖问题可选,最多 3 个,只进入文章主体和结尾点题。"
|
||||
"hint": "优化关键词必选且只能选一个;补充关键词可选,最多 3 个,只进入文章主体和结尾点题。"
|
||||
},
|
||||
"template_fields": {
|
||||
"title": "模板字段",
|
||||
@@ -73,7 +73,7 @@ var platformTemplateSeeds = []PlatformTemplateSeed{
|
||||
"cards": {
|
||||
"choose_title": {
|
||||
"title": "选择文章标题",
|
||||
"hint": "AI 会结合品牌主问题和竞品上下文给出 5 个更适合的标题候选,你可以直接选择或改写。"
|
||||
"hint": "AI 会结合优化关键词和竞品上下文给出 5 个更适合的标题候选,你可以直接选择或改写。"
|
||||
},
|
||||
"outline": {
|
||||
"title": "文章结构",
|
||||
@@ -145,7 +145,7 @@ var platformTemplateSeeds = []PlatformTemplateSeed{
|
||||
},
|
||||
"keywords": {
|
||||
"title": "品牌问题",
|
||||
"hint": "品牌主问题控制标题和主线;补充覆盖问题只进入文章主体与结尾点题。"
|
||||
"hint": "优化关键词控制标题和主线;补充关键词只进入文章主体与结尾点题。"
|
||||
},
|
||||
"competitors": {
|
||||
"visible": false
|
||||
@@ -235,7 +235,7 @@ var platformTemplateSeeds = []PlatformTemplateSeed{
|
||||
},
|
||||
"keywords": {
|
||||
"title": "品牌问题",
|
||||
"hint": "品牌主问题控制报告核心视角;补充覆盖问题只进入主体覆盖和结尾点题。"
|
||||
"hint": "优化关键词控制报告核心视角;补充关键词只进入主体覆盖和结尾点题。"
|
||||
},
|
||||
"competitors": {
|
||||
"visible": false
|
||||
@@ -318,7 +318,7 @@ var platformTemplateSeeds = []PlatformTemplateSeed{
|
||||
},
|
||||
"keywords": {
|
||||
"title": "品牌问题",
|
||||
"hint": "品牌主问题参与标题与主线;补充覆盖问题只进入正文主体和结尾点题。"
|
||||
"hint": "优化关键词参与标题与主线;补充关键词只进入正文主体和结尾点题。"
|
||||
},
|
||||
"template_fields": {
|
||||
"title": "模板字段",
|
||||
|
||||
Reference in New Issue
Block a user