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
@@ -251,7 +251,7 @@ func (w *ArticleGenerationWorker) handleTaskFailure(ctx context.Context, task te
task.TaskType = strings.TrimSpace(task.TaskType)
logCtx := generationWorkerTaskLogContext(task, stage, "", "failed", tenantapp.GenerationTaskResultFailure, 0, "")
tenantapp.RecordGenerationTaskEvent(tenantapp.GenerationTaskEventFailure, logCtx)
tenantapp.LogGenerationTaskWarn(w.logger, "article generation task failed", logCtx, zap.Error(taskErr))
tenantapp.LogGenerationTaskWarn(w.logger, "article generation task failed", logCtx, tenantapp.GenerationTaskErrorFields(taskErr)...)
var err error
switch {
case task.TaskType == kolGenerationTaskType && w.kolWorker != nil:
@@ -333,15 +333,11 @@ func dedupeKolIDs(ids []int64) []int64 {
}
func buildKolKnowledgeQuery(input map[string]interface{}) string {
parts := make([]string, 0, 8)
if promptName := strings.TrimSpace(kolStringInput(input, "prompt_name")); promptName != "" {
parts = append(parts, promptName)
}
if platformHint := strings.TrimSpace(kolStringInput(input, "platform_hint")); platformHint != "" {
parts = append(parts, platformHint)
}
promptName := strings.TrimSpace(kolStringInput(input, "prompt_name"))
platformHint := strings.TrimSpace(kolStringInput(input, "platform_hint"))
variables, _ := input["variables"].(map[string]interface{})
keywords := make([]string, 0)
keyPoints := make([]string, 0)
if len(variables) > 0 {
keys := make([]string, 0, len(variables))
for key := range variables {
@@ -353,17 +349,41 @@ func buildKolKnowledgeQuery(input map[string]interface{}) string {
if valueText == "" {
continue
}
parts = append(parts, fmt.Sprintf("%s: %s", key, valueText))
switch {
case strings.Contains(strings.ToLower(key), "keyword") || strings.Contains(key, "关键词"):
keywords = append(keywords, valueText)
case strings.Contains(strings.ToLower(key), "brand") || strings.Contains(key, "品牌"):
keyPoints = append(keyPoints, fmt.Sprintf("%s: %s", key, valueText))
default:
keyPoints = append(keyPoints, fmt.Sprintf("%s: %s", key, valueText))
}
}
}
if len(parts) == 0 {
if renderedPrompt := strings.TrimSpace(kolStringInput(input, "rendered_prompt")); renderedPrompt != "" {
parts = append(parts, renderedPrompt)
renderedPrompt := strings.TrimSpace(kolStringInput(input, "rendered_prompt"))
query := tenantapp.BuildKnowledgeQuery(tenantapp.KnowledgeQueryInput{
TaskType: firstNonEmptyKolText(platformHint, "KOL 内容生成"),
TemplateName: promptName,
Title: firstNonEmptyKolText(promptName, renderedPrompt),
Topic: firstNonEmptyKolText(promptName, renderedPrompt),
PrimaryKeyword: firstNonEmptyKolText(strings.Join(keywords, "、"), promptName),
Keywords: keywords,
KeyPoints: strings.Join(keyPoints, "\n"),
Extra: renderedPrompt,
})
if strings.TrimSpace(query) != "" {
return kolLimitText(query, 2000)
}
return kolLimitText(renderedPrompt, 2000)
}
func firstNonEmptyKolText(values ...string) string {
for _, value := range values {
if text := strings.TrimSpace(value); text != "" {
return text
}
}
return kolLimitText(strings.Join(parts, "\n"), 2000)
return ""
}
func kolValueString(value interface{}) string {