package app import ( "encoding/json" "fmt" "strings" ) const maxKnowledgeQueryQuestions = 3 type knowledgeQueryIntent struct { TaskType string TemplateName string Title string Topic string BrandName string Region string PrimaryKeyword string Keywords []string KeyPoints string Extra string } type KnowledgeQueryInput struct { TaskType string TemplateName string Title string Topic string BrandName string Region string PrimaryKeyword string Keywords []string KeyPoints string Extra string } func BuildKnowledgeQuery(input KnowledgeQueryInput) string { intent := knowledgeQueryIntent{ TaskType: input.TaskType, TemplateName: input.TemplateName, Title: input.Title, Topic: input.Topic, BrandName: input.BrandName, Region: input.Region, PrimaryKeyword: input.PrimaryKeyword, Keywords: input.Keywords, KeyPoints: input.KeyPoints, Extra: input.Extra, } return buildKnowledgeQueryInputText(intent) } func buildKnowledgeQueryQuestions(intent knowledgeQueryIntent) string { questions := buildKnowledgeQueryQuestionList(intent) return strings.Join(questions, "\n") } func buildKnowledgeQueryInputText(intent knowledgeQueryIntent) string { intent = normalizeKnowledgeQueryIntent(intent) lines := make([]string, 0, 12) appendLine := func(label, value string) { value = strings.TrimSpace(value) if value != "" { lines = append(lines, fmt.Sprintf("%s:%s", label, value)) } } appendLine("文章类型", intent.TaskType) appendLine("模板名称", intent.TemplateName) appendLine("标题", intent.Title) appendLine("主题", intent.Topic) appendLine("品牌", intent.BrandName) appendLine("地域", intent.Region) appendLine("主关键词", intent.PrimaryKeyword) appendLine("关键词", knowledgeQueryKeywordText(intent.Keywords)) appendLine("重点要求", intent.KeyPoints) appendLine("其他要求", intent.Extra) fallback := buildKnowledgeQueryQuestionList(intent) if len(fallback) > 0 { lines = append(lines, "兜底检索问题:") for _, question := range fallback { lines = append(lines, "- "+question) } } return strings.Join(lines, "\n") } func buildKnowledgeQueryRewritePrompt(rawQuery string) string { rawQuery = strings.TrimSpace(rawQuery) if rawQuery == "" { return "" } return strings.TrimSpace(fmt.Sprintf(`你是知识库检索 Query Rewrite 助手。请根据用户的文章生成输入,改写出 3 个最适合去企业/产品知识库中检索的自然语言问题。 目标: 1. 让检索问题覆盖“文章类型/写作任务”真正需要的知识,而不是只重复品牌名或地域词。 2. 优先围绕用户输入的标题、主题、品牌、地域、关键词、重点要求生成问题。 3. 问题应适合在知识库中召回:业务范围、产品服务、优势案例、地址联系方式、资质事实、用户痛点、对比维度、避坑点、适用场景。 4. 如果输入中出现“兜底检索问题”,只能作为参考;你需要根据上方结构化字段重新生成更贴近本次文章的 3 个问题。 规则: - 必须输出 3 个问题。 - 每个问题 18-60 个中文字符,尽量具体。 - 不要生成泛泛的问题,如“这个品牌怎么样”。 - 不要编造输入中没有的品牌、地域、产品或事实。 - 只输出 JSON,不要 Markdown,不要解释。 用户输入: %s`, rawQuery)) } func parseKnowledgeQueryRewriteOutput(content string) []string { content = strings.TrimSpace(content) if content == "" { return []string{} } content = strings.TrimPrefix(content, "```json") content = strings.TrimPrefix(content, "```") content = strings.TrimSuffix(content, "```") content = strings.TrimSpace(content) var payload struct { Questions []string `json:"questions"` } if err := json.Unmarshal([]byte(content), &payload); err == nil && len(payload.Questions) > 0 { return normalizeKnowledgeQueryQuestions(payload.Questions) } var list []string if err := json.Unmarshal([]byte(content), &list); err == nil && len(list) > 0 { return normalizeKnowledgeQueryQuestions(list) } return []string{} } func normalizeKnowledgeQueryQuestions(values []string) []string { questions := make([]string, 0, maxKnowledgeQueryQuestions) for _, value := range values { appendKnowledgeQuestion(&questions, value) if len(questions) >= maxKnowledgeQueryQuestions { break } } return questions } func buildKnowledgeQueryQuestionList(intent knowledgeQueryIntent) []string { intent = normalizeKnowledgeQueryIntent(intent) questions := make([]string, 0, maxKnowledgeQueryQuestions) subject := firstNonEmptyText(intent.Topic, intent.Title, intent.PrimaryKeyword, intent.BrandName, "当前主题") brandScope := firstNonEmptyText(joinKnowledgeQueryParts(",", intent.BrandName, intent.Region), intent.BrandName, subject) keywordScope := knowledgeQueryKeywordText(intent.Keywords) switch { case strings.Contains(intent.TaskType, "推荐榜") || strings.Contains(strings.ToLower(intent.TaskType), "top"): appendKnowledgeQuestion(&questions, fmt.Sprintf("%s 中有哪些与 %s 相关的品牌、产品、服务能力和选型依据?", subject, brandScope)) appendKnowledgeQuestion(&questions, fmt.Sprintf("%s 写作时需要引用哪些关于 %s 的准确事实、优势、案例、地址或联系方式?", intent.TaskType, firstNonEmptyText(intent.BrandName, subject))) appendKnowledgeQuestion(&questions, fmt.Sprintf("%s 相关关键词 %s 对应的用户关注点、避坑点和对比维度是什么?", subject, firstNonEmptyText(keywordScope, intent.PrimaryKeyword, subject))) case strings.Contains(intent.TaskType, "评测") || strings.Contains(strings.ToLower(intent.TaskType), "review"): appendKnowledgeQuestion(&questions, fmt.Sprintf("%s 的产品特性、参数、卖点、适用人群和使用场景是什么?", subject)) appendKnowledgeQuestion(&questions, fmt.Sprintf("%s 有哪些真实体验、优缺点、案例、价格或售后信息可用于评测?", firstNonEmptyText(intent.BrandName, subject))) appendKnowledgeQuestion(&questions, fmt.Sprintf("围绕 %s 和 %s,用户最关心哪些购买决策问题?", subject, firstNonEmptyText(keywordScope, intent.PrimaryKeyword, subject))) case strings.Contains(intent.TaskType, "仿写"): appendKnowledgeQuestion(&questions, fmt.Sprintf("仿写 %s 时,%s 有哪些必须保留或补充的品牌事实和业务信息?", subject, firstNonEmptyText(intent.BrandName, subject))) appendKnowledgeQuestion(&questions, fmt.Sprintf("%s 相关关键词 %s 对应的产品、服务、案例和用户需求是什么?", subject, firstNonEmptyText(keywordScope, intent.PrimaryKeyword, subject))) appendKnowledgeQuestion(&questions, fmt.Sprintf("生成 %s 时需要避免遗漏哪些地址、联系方式、优势、资质或落地案例?", firstNonEmptyText(intent.TaskType, "仿写文章"))) default: appendKnowledgeQuestion(&questions, fmt.Sprintf("%s 这篇%s需要引用哪些核心事实、产品服务、案例和用户关注点?", subject, firstNonEmptyText(intent.TaskType, "文章"))) appendKnowledgeQuestion(&questions, fmt.Sprintf("%s 与 %s 相关的准确资料、品牌信息、地址、联系方式和业务范围是什么?", firstNonEmptyText(intent.BrandName, subject), firstNonEmptyText(keywordScope, intent.PrimaryKeyword, subject))) appendKnowledgeQuestion(&questions, fmt.Sprintf("围绕 %s,用户最可能关心哪些问题、痛点、对比维度和解决方案?", firstNonEmptyText(keywordScope, subject))) } appendKnowledgeQuestion(&questions, intent.KeyPoints) appendKnowledgeQuestion(&questions, intent.Extra) return questions } func normalizeKnowledgeQueryIntent(intent knowledgeQueryIntent) knowledgeQueryIntent { intent.TaskType = strings.TrimSpace(intent.TaskType) intent.TemplateName = strings.TrimSpace(intent.TemplateName) intent.Title = strings.TrimSpace(intent.Title) intent.Topic = strings.TrimSpace(intent.Topic) intent.BrandName = strings.TrimSpace(intent.BrandName) intent.Region = strings.TrimSpace(intent.Region) intent.PrimaryKeyword = strings.TrimSpace(intent.PrimaryKeyword) intent.KeyPoints = strings.TrimSpace(intent.KeyPoints) intent.Extra = strings.TrimSpace(intent.Extra) intent.Keywords = normalizeKnowledgeQueryKeywords(intent.Keywords, 6) if intent.TaskType == "" { intent.TaskType = strings.TrimSpace(intent.TemplateName) } return intent } func appendKnowledgeQuestion(questions *[]string, question string) { if questions == nil || len(*questions) >= maxKnowledgeQueryQuestions { return } question = normalizeKnowledgeQueryQuestion(question) if question == "" { return } for _, existing := range *questions { if normalizeKnowledgeQueryQuestion(existing) == question { return } } *questions = append(*questions, question) } func normalizeKnowledgeQueryQuestion(question string) string { question = strings.TrimSpace(question) if question == "" { return "" } question = strings.Join(strings.Fields(question), " ") for _, bad := range []string{" ,", ", ", " ?", " ?"} { question = strings.ReplaceAll(question, bad, strings.TrimSpace(bad)) } question = strings.Trim(question, " ,,;;") if question == "" { return "" } if !strings.HasSuffix(question, "?") && !strings.HasSuffix(question, "?") { question += "?" } return question } func mergeKnowledgeQueryKeywords(values ...interface{}) []string { keywords := make([]string, 0) for _, value := range values { keywords = append(keywords, extractStringList(value, 16)...) if text := strings.TrimSpace(formatPromptValue(value)); text != "" && len(extractStringList(value, 1)) == 0 { keywords = append(keywords, text) } } return normalizeKnowledgeQueryKeywords(keywords, 6) } func normalizeKnowledgeQueryKeywords(keywords []string, limit int) []string { result := make([]string, 0, len(keywords)) seen := make(map[string]struct{}, len(keywords)) for _, keyword := range keywords { keyword = strings.TrimSpace(keyword) if keyword == "" { continue } key := strings.ToLower(strings.Join(strings.Fields(keyword), " ")) if _, ok := seen[key]; ok { continue } seen[key] = struct{}{} result = append(result, keyword) if limit > 0 && len(result) >= limit { break } } return result } func knowledgeQueryKeywordText(keywords []string) string { keywords = normalizeKnowledgeQueryKeywords(keywords, 6) return strings.Join(keywords, "、") } func firstNonEmptyPromptString(params map[string]interface{}, keys ...string) string { for _, key := range keys { if text := strings.TrimSpace(stringValue(params[key])); text != "" { return text } } return "" } func firstNonEmptyText(values ...string) string { for _, value := range values { if text := strings.TrimSpace(value); text != "" { return text } } return "" } func joinKnowledgeQueryParts(sep string, values ...string) string { parts := make([]string, 0, len(values)) for _, value := range values { if text := strings.TrimSpace(value); text != "" { parts = append(parts, text) } } return strings.Join(parts, sep) }