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
@@ -718,7 +718,7 @@ func (s *PromptRuleGenerationService) failGeneration(ctx context.Context, job pr
now := time.Now()
logCtx := promptRuleGenerationJobLogContext(job, stage, "", "failed", GenerationTaskResultFailure)
RecordGenerationTaskEvent(GenerationTaskEventFailure, logCtx)
LogGenerationTaskWarn(s.logger, "prompt rule generation failed", logCtx, zap.Error(genErr))
LogGenerationTaskWarn(s.logger, "prompt rule generation failed", logCtx, GenerationTaskErrorFields(genErr)...)
failureTitle := strings.TrimSpace(extractString(job.InputParams, "task_name"))
if failureTitle == "" {
failureTitle = title
@@ -857,41 +857,28 @@ func buildPromptRuleGenerationPrompt(rule *promptRuleGenerationRecord, params ma
}
func buildPromptRuleKnowledgeQuery(rule *promptRuleGenerationRecord, params map[string]interface{}) string {
parts := make([]string, 0, 6)
intent := knowledgeQueryIntent{
TaskType: "自定义生成",
Title: firstNonEmptyPromptString(params, "task_name", "title", "topic", "subject"),
Topic: firstNonEmptyPromptString(params, "topic", "subject", "title"),
BrandName: extractString(params, "brand_name"),
PrimaryKeyword: extractString(params, "primary_keyword"),
Keywords: mergeKnowledgeQueryKeywords(params["keywords"], params["primary_keyword"]),
KeyPoints: extractString(params, "key_points"),
Extra: extractString(params, "extra_requirements"),
}
if rule != nil {
if text := strings.TrimSpace(rule.Name); text != "" {
parts = append(parts, text)
if name := strings.TrimSpace(rule.Name); name != "" {
intent.TemplateName = name
if intent.Title == "" {
intent.Title = name
}
}
if rule.Scene != nil && strings.TrimSpace(*rule.Scene) != "" {
parts = append(parts, strings.TrimSpace(*rule.Scene))
intent.TaskType = strings.TrimSpace(*rule.Scene)
}
}
if taskName := strings.TrimSpace(extractString(params, "task_name")); taskName != "" {
parts = append(parts, taskName)
}
for _, key := range []string{
"title",
"topic",
"subject",
"brand_name",
"product_name",
"primary_keyword",
"key_points",
"extra_requirements",
} {
if text := strings.TrimSpace(extractString(params, key)); text != "" {
parts = append(parts, text)
}
}
if keywords := extractStringList(params["keywords"], 16); len(keywords) > 0 {
parts = append(parts, strings.Join(keywords, "\n"))
}
if rule != nil {
if text := strings.TrimSpace(rule.PromptContent); text != "" {
parts = append(parts, text)
}
}
return strings.Join(parts, "\n")
return buildKnowledgeQueryInputText(intent)
}
func extractGenerateCount(extra map[string]interface{}) int {