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
@@ -469,7 +469,7 @@ func (s *ArticleImitationService) failGeneration(ctx context.Context, job articl
now := time.Now()
logCtx := articleImitationJobLogContext(job, stage, "", "failed", GenerationTaskResultFailure)
RecordGenerationTaskEvent(GenerationTaskEventFailure, logCtx)
LogGenerationTaskWarn(s.logger, "imitation generation failed", logCtx, zap.Error(genErr))
LogGenerationTaskWarn(s.logger, "imitation generation failed", logCtx, GenerationTaskErrorFields(genErr)...)
title := strings.TrimSpace(job.InitialTitle)
if title == "" {
title = articleImitationFallbackTitle
@@ -659,22 +659,18 @@ func buildImitationKnowledgeQuery(params map[string]interface{}) string {
return ""
}
parts := make([]string, 0, 8)
appendValue := func(value string) {
value = strings.TrimSpace(value)
if value == "" {
return
}
parts = append(parts, value)
intent := knowledgeQueryIntent{
TaskType: "仿写文章",
Title: extractString(params, "source_title"),
Topic: firstNonEmptyText(extractString(params, "source_title"), extractString(params, "extra_requirements")),
BrandName: extractString(params, "brand_name"),
Region: extractString(params, "region"),
PrimaryKeyword: firstNonEmptyText(extractString(params, "source_title"), knowledgeQueryKeywordText(extractStringList(params["keywords"], 16))),
Keywords: mergeKnowledgeQueryKeywords(params["keywords"]),
KeyPoints: extractString(params, "preserve_points"),
Extra: strings.Join([]string{extractString(params, "avoid_points"), extractString(params, "extra_requirements")}, "\n"),
}
appendValue(extractString(params, "source_title"))
appendValue(extractString(params, "brand_name"))
appendValue(extractString(params, "region"))
appendValue(strings.Join(extractStringList(params["keywords"], 16), "\n"))
appendValue(extractString(params, "extra_requirements"))
return strings.Join(parts, "\n")
return buildKnowledgeQueryInputText(intent)
}
func appendPromptLine(b *strings.Builder, label, value string) {