|
|
@@ -267,6 +267,293 @@ class LLMEvaluator:
|
|
|
|
|
|
return all_results
|
|
|
|
|
|
+ def generate_queries_from_candidates(
|
|
|
+ self,
|
|
|
+ original_feature: str,
|
|
|
+ base_word: str,
|
|
|
+ candidate_words: List[str],
|
|
|
+ max_queries: int = 10
|
|
|
+ ) -> List[Dict[str, Any]]:
|
|
|
+ """
|
|
|
+ 基于中心词和候选词列表,让LLM生成搜索query
|
|
|
+
|
|
|
+ Args:
|
|
|
+ original_feature: 原始特征名称
|
|
|
+ base_word: 中心词
|
|
|
+ candidate_words: 候选词列表
|
|
|
+ max_queries: 最大query数量
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ query数组(与旧格式兼容)
|
|
|
+ """
|
|
|
+ logger.info(f"LLM生成query(中心词: {base_word}, 候选词: {len(candidate_words)}个)")
|
|
|
+
|
|
|
+ candidate_words_str = "、".join(candidate_words)
|
|
|
+
|
|
|
+ prompt = f"""# 角色定位
|
|
|
+你是"内容创作搜索顾问"。任务:
|
|
|
+围绕中心词为主体,并结合待选词中明显的 高频词/高权重词,生成完整、不重不漏、可检索的 query。
|
|
|
+核心流程:
|
|
|
+先判断搜索类型(具体案例 / 案例集合) → 再围绕"中心词 + 高权重词(如果有)"生成 query → 完整覆盖 → 去重 → 输出。
|
|
|
+
|
|
|
+# 输入
|
|
|
+中心词:{base_word}
|
|
|
+待选词:{candidate_words_str}
|
|
|
+
|
|
|
+# 核心原则
|
|
|
+1.中心词优先原则:所有 query 必须围绕中心词构造,中心词在所有 query 的出现率必须 ≥ 80%
|
|
|
+2.高权重词优先构造:对待选词做频次分析:同义/包含关系归并后,出现次数最高的词为"高权重主体词",若存在高权重词 → 所有 query 必须围绕 中心词 + 高权重词,若无高权重词 → 使用中心词 + 去重词合理组合
|
|
|
+3. **去重不漏** - 去重同义、保留关键差异、所有有效组合需覆盖
|
|
|
+4. **query是问题** - 不包含多模态信息,例如XXvlog、视频等,如原始输入中不存在此类信息则不加入query
|
|
|
+5. 组合需有语义逻辑:不能随机堆词,query 必须是自然、可搜索的有真实含义的问题句
|
|
|
+6.主体与场景必须出现之一:若中心词是场景 → 等同主体优先级
|
|
|
+
|
|
|
+# 处理流程
|
|
|
+
|
|
|
+## Step 1: 词汇分析与去重
|
|
|
+对输入的词汇进行分类和合并:
|
|
|
+
|
|
|
+**分类维度**:
|
|
|
+- **主体类**: 内容核心对象(猫咪、美食、旅行地)
|
|
|
+- **手法类**: 创作表现方式(拟人化、对比、测评)
|
|
|
+- **特征类**: 风格特点(反差、温馨、搞笑)
|
|
|
+- **场景类**: 具体情境(穿衣服、戴墨镜、吃饭)
|
|
|
+- **行为类**: 创作动作(分享、记录、教程)
|
|
|
+
|
|
|
+**核心主体识别规则**:
|
|
|
+中心词默认最高优先级
|
|
|
+待选词中出现频次最高者 = 高权重主体词
|
|
|
+Query 必须围绕:中心词 + 高权重词(如果有)
|
|
|
+
|
|
|
+**去重规则**:
|
|
|
+- 同义词: 猫/猫咪 → 猫咪
|
|
|
+- 包含关系归纳: 宠物猫咪/猫咪 → 猫咪
|
|
|
+- 修饰词判断: 若修饰词不改变核心意图则去除,若改变则保留
|
|
|
+
|
|
|
+**原词保留原则**:
|
|
|
+- 所有 query 中语言必须来自清洗后的词汇,允许添加:
|
|
|
+ 连接词(的、和、与)
|
|
|
+ 必要动词(分享、展示、记录)
|
|
|
+ 集合词(有哪些、合集、大全)
|
|
|
+- 禁止任何同义替换。: 猫咪✗改为宠物猫/小猫, 服饰✗改为穿搭/衣服
|
|
|
+
|
|
|
+## Step 2: 词汇关系分析
|
|
|
+**目标**: 确定哪些词汇可以合理组合
|
|
|
+
|
|
|
+**关系判断规则**:
|
|
|
+1. **强关联** 可直接组合:
|
|
|
+- 中心词 + 高权重主体词(必选)
|
|
|
+- 中心词 + 特征词
|
|
|
+- 高权重主体词 + 特征词
|
|
|
+
|
|
|
+2. **中等关联** 需通过主体连接:
|
|
|
+ - 中心词 + 主体 + 特征
|
|
|
+
|
|
|
+3. **禁止组合**
|
|
|
+- 特征 + 特征;特征独立成句;与中心词无关系的随机组合
|
|
|
+
|
|
|
+## Step 3: 判断搜索类型
|
|
|
+
|
|
|
+根据词汇的**具体化程度**判断搜索粒度:
|
|
|
+
|
|
|
+1、THEN → 类型: specific_case (具体案例)
|
|
|
+IF 满足以下任一条件:
|
|
|
+ 1. 包含具体场景/道具/动作
|
|
|
+ 2. 词汇组合后可想象出明确画面
|
|
|
+ 3. 描述足够详细,指向单一呈现形式
|
|
|
+
|
|
|
+**创作者需求**:
|
|
|
+找一个可以直接参考模仿的成品案例,想看"就是这样的内容"
|
|
|
+
|
|
|
+2、THEN → 类型: case_collection (案例集合)
|
|
|
+ELSE IF 满足以下条件:
|
|
|
+ 1. 只有主体 + 手法/特征,缺少具体场景
|
|
|
+ 2. 词汇组合较抽象,无法想象单一画面
|
|
|
+ 3. 需要看到多个变化形式
|
|
|
+
|
|
|
+**创作者需求**:
|
|
|
+了解这一类内容有哪些玩法,想看"这种类型都有什么"
|
|
|
+
|
|
|
+## Step 4: 生成完整Query列表
|
|
|
+
|
|
|
+### 核心原则: 词汇组合完整覆盖
|
|
|
+**中心词权重规则**:
|
|
|
+- 中心词必须出现在≥80%的query中
|
|
|
+**组词逻辑规则**:
|
|
|
+- 每个query必须遵循词汇关系矩阵中的强关联或中等关联
|
|
|
+- 找案例不是找方法,query需明确找案例、案例集、集合、有哪些等适配case和案例集类型的词汇
|
|
|
+
|
|
|
+**严格去重规则**:
|
|
|
+- 提取每个query的核心要素: [主体]+[场景/手法]+[特征/行为]
|
|
|
+- 两个query的核心要素若完全相同或高度重叠(≥2个要素相同),则判定为重复
|
|
|
+- 生成每个新query时立即与已生成的query对比,重复则舍弃
|
|
|
+- 判断标准: 搜索意图是否相同,而非文字是否相同
|
|
|
+
|
|
|
+**覆盖策略**:
|
|
|
+1. **主干组合** - 主体+核心手法/场景 必须覆盖
|
|
|
+2. **特征叠加** - 在主干上叠加不同特征词
|
|
|
+3. **表述多样** - 同一组合用不同表述方式
|
|
|
+4. **避免重复** - 去除语义相同的query
|
|
|
+
|
|
|
+**原词保真规则**:
|
|
|
+- 只能使用去重后词汇清单中的词汇
|
|
|
+- 不允许用同义词替换原词
|
|
|
+- 允许添加的词: 连接词(的、和、与)、必要动词(分享、展示)、集合词(有哪些、合集)
|
|
|
+- 每生成一个query立即检查是否包含不在清单中的新概念词,若有则删除该query
|
|
|
+
|
|
|
+#### A类Query生成规则(具体案例)
|
|
|
+
|
|
|
+**结构**: 主体 + 具体场景/道具 + 手法/特征
|
|
|
+**长度**: 6-15字
|
|
|
+**语言风格**: 描述性、具象化
|
|
|
+
|
|
|
+**数量要求**: 根据去重后词汇丰富度生成,确保覆盖所有有意义的组合
|
|
|
+- 词汇简单(2-8个): 生成2-4个query
|
|
|
+- 词汇中等(9-12个): 生成4-6个query
|
|
|
+- 词汇丰富(12+个): 生成6-10个query
|
|
|
+
|
|
|
+#### B类Query生成规则(案例集合)
|
|
|
+
|
|
|
+**结构**: 主体 + 手法/特征 + 集合词
|
|
|
+**长度**: 6-12字
|
|
|
+
|
|
|
+**数量要求**: 根据去重后词汇丰富度生成
|
|
|
+- 词汇简单(2-8个): 生成2-4个query
|
|
|
+- 词汇中等(8-10个): 生成4-6个query
|
|
|
+- 词汇丰富(10+个): 生成6-10个query
|
|
|
+
|
|
|
+## 质量检查标准
|
|
|
+
|
|
|
+生成query后,必须进行覆盖度检查:
|
|
|
+
|
|
|
+**检查清单**:
|
|
|
+1. 词汇覆盖检查:
|
|
|
+ - 列出所有去重后的词汇
|
|
|
+ - 标注每个词汇出现在哪些query中
|
|
|
+ - 确保去重后每个词汇至少被使用1次
|
|
|
+
|
|
|
+2. 组合覆盖检查:
|
|
|
+- 逐个检查query是否符合词汇关系矩阵
|
|
|
+- 检查是否存在弱关联或无关联的词汇组合
|
|
|
+- 弱关联组合 → 删除或重写
|
|
|
+
|
|
|
+3. 重复检查:
|
|
|
+- 提取每个query的核心要素
|
|
|
+- 两两对比核心要素
|
|
|
+- 核心要素一致 → 删除
|
|
|
+
|
|
|
+4.原词保真检查
|
|
|
+- 拆解每个query的词汇
|
|
|
+- 验证每个实词是否在去重后清单中
|
|
|
+- 允许存在的词: 连接词、动词、集合词
|
|
|
+- 不允许存在的词: 同义替换词、新概念词
|
|
|
+- 发现不允许的词 → 删除该query或替换回原词
|
|
|
+
|
|
|
+5. 补充生成:
|
|
|
+词汇未覆盖 / 关键组合缺失 → 补充生成
|
|
|
+
|
|
|
+# 输出
|
|
|
+最终按以下格式输出结果(JSON数组格式):
|
|
|
+[
|
|
|
+ {{
|
|
|
+ "search_word": "猫咪服饰造型元素有哪些",
|
|
|
+ "中心词": "服饰造型元素",
|
|
|
+ "source_word": "猫 猫咪 服饰造型元素 传递快乐 宠物猫咪 猫咪宠物 猫咪主体",
|
|
|
+ "reasoning": "判断依据说明"
|
|
|
+ }},
|
|
|
+ {{
|
|
|
+ "search_word": "猫咪传递快乐的服饰造型元素",
|
|
|
+ "中心词": "服饰造型元素",
|
|
|
+ "source_word": "猫 猫咪 服饰造型元素 传递快乐 宠物猫咪 猫咪宠物 猫咪主体",
|
|
|
+ "reasoning": "判断依据说明"
|
|
|
+ }}
|
|
|
+]
|
|
|
+
|
|
|
+**source_word规则**(重要):
|
|
|
+1. 格式:空格分隔的词汇
|
|
|
+2. 来源:**必须且只能**从"中心词 + 待选词"中提取
|
|
|
+3. 提取规则:该query实际使用到的所有原始词汇
|
|
|
+4. 禁止:同义替换、添加新词
|
|
|
+5. 必须包含:中心词(如果query中使用了中心词)
|
|
|
+
|
|
|
+# 执行顺序
|
|
|
+词汇分析 → 中心词确定 → 高权重词识别 → 关系分析 → 类型判定 →
|
|
|
+围绕"中心词+高权重词"生成 query → 质量检查 → 补充 → 输出
|
|
|
+
|
|
|
+注意:只返回JSON数组,不要其他内容。"""
|
|
|
+
|
|
|
+ # 调用 LLM
|
|
|
+ llm_results = self.client.chat_json(prompt=prompt, max_retries=3)
|
|
|
+
|
|
|
+ if not llm_results or not isinstance(llm_results, list):
|
|
|
+ logger.error("LLM返回格式错误")
|
|
|
+ return []
|
|
|
+
|
|
|
+ logger.info(f"LLM生成了 {len(llm_results)} 个query")
|
|
|
+
|
|
|
+ # 解析并验证
|
|
|
+ formatted_results = []
|
|
|
+ for rank, item in enumerate(llm_results[:max_queries], 1):
|
|
|
+ validated_source_word = self._validate_and_fix_source_word(
|
|
|
+ llm_source_word=item.get("source_word", ""),
|
|
|
+ query=item.get("search_word", ""),
|
|
|
+ base_word=base_word,
|
|
|
+ candidate_words=candidate_words
|
|
|
+ )
|
|
|
+
|
|
|
+ formatted_results.append({
|
|
|
+ "search_word": item.get("search_word", ""),
|
|
|
+ "source_word": validated_source_word,
|
|
|
+ "score": 0.0,
|
|
|
+ "reasoning": item.get("reasoning", ""),
|
|
|
+ "rank": rank,
|
|
|
+ "original_feature": original_feature
|
|
|
+ })
|
|
|
+
|
|
|
+ return formatted_results
|
|
|
+
|
|
|
+ def _validate_and_fix_source_word(
|
|
|
+ self,
|
|
|
+ llm_source_word: str,
|
|
|
+ query: str,
|
|
|
+ base_word: str,
|
|
|
+ candidate_words: List[str]
|
|
|
+ ) -> str:
|
|
|
+ """
|
|
|
+ 验证并修正 LLM 输出的 source_word
|
|
|
+ 确保只包含"中心词 + 候选词"中的词
|
|
|
+
|
|
|
+ Args:
|
|
|
+ llm_source_word: LLM 输出的 source_word
|
|
|
+ query: 生成的 search_word
|
|
|
+ base_word: 中心词
|
|
|
+ candidate_words: 候选词列表
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ 验证后的 source_word
|
|
|
+ """
|
|
|
+ words = llm_source_word.split()
|
|
|
+ valid_words = []
|
|
|
+
|
|
|
+ # 验证每个词是否在允许列表中
|
|
|
+ for word in words:
|
|
|
+ if word == base_word or word in candidate_words:
|
|
|
+ valid_words.append(word)
|
|
|
+
|
|
|
+ # 确保中心词存在(如果query中包含)
|
|
|
+ if base_word in query and base_word not in valid_words:
|
|
|
+ valid_words.insert(0, base_word)
|
|
|
+
|
|
|
+ # 去重
|
|
|
+ seen = set()
|
|
|
+ deduplicated = []
|
|
|
+ for word in valid_words:
|
|
|
+ if word not in seen:
|
|
|
+ seen.add(word)
|
|
|
+ deduplicated.append(word)
|
|
|
+
|
|
|
+ return ' '.join(deduplicated)
|
|
|
+
|
|
|
def evaluate_single_note(
|
|
|
self,
|
|
|
original_feature: str,
|