feat(admin-brand): add question expansion service and related functionality

- Implemented QuestionExpansionService for generating and materializing questions based on combinations and AI distillation.
- Added question metadata classification logic to infer intent and layer of questions.
- Created API handlers for question expansion operations including combination preview, AI distillation, metadata classification, and materialization.
- Introduced database migrations to support new question-related fields and constraints in brand_questions and brand_keywords tables.
- Added caching mechanism for AI distillation results to improve performance.
- Defined JSON schemas for question distillation responses to ensure data integrity.
This commit is contained in:
2026-05-12 21:53:36 +08:00
parent 77d542c282
commit 37b0b32327
27 changed files with 4619 additions and 908 deletions
@@ -0,0 +1,68 @@
package app
import (
"encoding/json"
"fmt"
"strings"
)
var questionDistillSchema = []byte(`{
"type": "object",
"additionalProperties": false,
"properties": {
"candidates": {
"type": "array",
"maxItems": 20,
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"text": {
"type": "string",
"minLength": 4,
"maxLength": 80
},
"intent": {
"type": "string",
"enum": ["informational", "evaluative", "decisional"]
},
"layer": {
"type": "string",
"enum": ["L1", "L2", "L3", "L4", "L5"]
}
},
"required": ["text", "intent", "layer"]
}
}
},
"required": ["candidates"]
}`)
const questionDistillPromptVersion = "question_distill_v1"
func buildQuestionDistillPrompt(brandName string, competitorNames []string, seedTopic string) string {
competitors := "无"
if len(competitorNames) > 0 {
encoded, _ := json.Marshal(competitorNames)
competitors = string(encoded)
}
return fmt.Sprintf(`你是一个 GEOGenerative Engine Optimization)资深策略师。
请围绕当前品牌和输入主题,生成中国用户在 AI 搜索里真实会问的问题。
【输入】
- 当前品牌: %s
- 竞品列表: %s
- 主题: %s
【约束】
1. candidates 长度不超过 20。
2. 问题必须是自然口语问句,避免口号、短词和营销文案。
3. 尽量覆盖 informational、evaluative、decisional 三类意图。
4. 至少 4 条包含地域、价位、人群或场景修饰。
5. 每条问题尽量不超过 40 个中文字符。
6. 严格按响应格式输出,不要解释。`,
strings.TrimSpace(brandName),
competitors,
strings.TrimSpace(seedTopic),
)
}