feat(knowledge): multi-query retrieval with LLM query rewrite

Add a shared structured query builder (knowledge_query.go) and switch
all generation paths to it, so retrieval queries carry task intent
(type, brand, region, keywords, key points) instead of a raw prompt.

ResolveContext now rewrites the query into multiple sub-questions via
the URL-markdown model, embeds and searches each, and merges/dedupes
candidates before rerank; falls back to parsed fallback questions when
rewrite is unavailable or returns too few. Resolve logs gain query
list, count, and rewrite stage/model for observability.

- knowledge_query.go/_test.go: BuildKnowledgeQuery + intent normalization
- knowledge_service.go: per-query embed/search loop, query rewrite, logs
- template_prompt/template_service/prompt_generate/article_imitation/
  kol_generation_worker: adopt the shared query builder
- generation_observability: richer task error logging
This commit is contained in:
2026-06-02 14:50:12 +08:00
parent 85bb373a8b
commit ba2f117265
11 changed files with 734 additions and 102 deletions
+27 -20
View File
@@ -48,32 +48,39 @@ func buildGenerationPrompt(
return strings.Join(sections, "\n\n")
}
func buildGenerationKnowledgeQuery(params map[string]interface{}) string {
func buildGenerationKnowledgeQuery(templateKey, templateName string, params map[string]interface{}) string {
if len(params) == 0 {
return ""
}
parts := make([]string, 0, 8)
appendValue := func(value string) {
value = strings.TrimSpace(value)
if value == "" {
return
}
parts = append(parts, value)
intent := knowledgeQueryIntent{
TaskType: templateKnowledgeTaskType(templateKey, templateName),
TemplateName: templateName,
Title: firstNonEmptyPromptString(params, "title", "topic", "product_name", "subject"),
Topic: firstNonEmptyPromptString(params, "topic", "subject", "product_name", "title"),
BrandName: stringValue(params["brand_name"]),
Region: firstNonEmptyPromptString(params, "region", "city", "location", "area", "target_region", "service_area"),
PrimaryKeyword: firstNonEmptyPromptString(params, "brand_question", "primary_keyword"),
Keywords: mergeKnowledgeQueryKeywords(params["keywords"], params["supplemental_questions"], params["primary_keyword"]),
KeyPoints: firstNonEmptyPromptString(params, "key_points", "review_intro_hook"),
}
return buildKnowledgeQueryInputText(intent)
}
appendValue(stringValue(params["title"]))
appendValue(stringValue(params["topic"]))
appendValue(stringValue(params["product_name"]))
appendValue(stringValue(params["subject"]))
appendValue(stringValue(params["brand_name"]))
appendValue(stringValue(params["brand_question"]))
appendValue(stringValue(params["primary_keyword"]))
appendValue(formatPromptValue(params["supplemental_questions"]))
appendValue(stringValue(params["key_points"]))
appendValue(stringValue(params["review_intro_hook"]))
return strings.Join(parts, "\n")
func templateKnowledgeTaskType(templateKey, templateName string) string {
switch strings.TrimSpace(templateKey) {
case "top_x_article":
return "推荐榜文章"
case "product_review":
return "产品评测文章"
case "research_report":
return "研究报告"
default:
if name := strings.TrimSpace(templateName); name != "" {
return name
}
return "文章生成"
}
}
func buildTemplateSpecificWritingRules(templateKey string, params map[string]interface{}) string {