| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """
- 统一匹配分析模块 (v4 - 优化版)
- 使用单个prompt同时完成标签匹配和分类匹配,一步到位。
- 输出格式:当前标签列表中每个标签的匹配结果。
- """
- from typing import List, Dict, Optional
- from agents import Agent, Runner, ModelSettings
- from agents.tracing.create import custom_span
- from lib.client import get_model
- from lib.utils import parse_json_from_text
- # ========== System Prompt ==========
- UNIFIED_MATCH_SYSTEM_PROMPT = """
- # 任务
- 对"当前标签列表"中的每个标签,与"人设标签组合"进行综合匹配分析。
- ## 输入说明
- - **当前标签列表**: 需要匹配的标签列表
- - **人设标签组合**: 包含标签名称及其分类的组合
- - 每个标签有:标签名称、所属分类(多层级,从具体到抽象)
- - 分类是树状结构,按数组顺序从具体到抽象排列
- ## 匹配策略
- 对当前标签列表中的**每个标签**:
- **重要约束 - 分类排他性**:
- - 如果某个人设标签已经被标签匹配,则该标签的所有所属分类都不能再被其他当前标签使用
- **匹配优先级和提前终止**:
- 1. 优先进行标签匹配,如果匹配成功则立即停止,不再进行分类匹配
- 2. 如果标签匹配失败,则进行分类匹配
- 3. 分类匹配按层级从下到上(从具体到抽象),一旦某层匹配成功则立即停止,不再检查更抽象的层级
- ### 1. 标签匹配(同义关系)
- - **逐个判断**每个人设标签
- - **核心判断**: "A 和 B 是同一个东西吗?是同义词吗?"
- - **输出**: 是否匹配(true/false)
- - **严格要求**: 必须是同义词或几乎相同的表述才能匹配
- - **如果匹配成功**: 立即返回结果,不再进行分类匹配
- ### 2. 分类匹配(从属关系)
- - **仅在标签匹配全部失败时进行**
- - **按层级从下到上**遍历分类(从具体到抽象)
- - **每层判断所有分类**
- - **核心判断**: "当前标签 本身就是 {分类} 的一种吗?"
- - **输出**:
- - 该层候选分类:列出该层所有分类名称
- - 该层匹配结果:对该层每个分类逐个判断,输出分类名称、从属关系判断、是否有从属关系、相似度分析、语义相似度
- - **严格要求**: 必须是直接从属关系,不能是间接关系或关联关系
- - **禁止**:
- - ✗ "A 可能会有 B"(间接推理)
- - ✗ "A 与 B 有关"(关联不等于从属)
- - **语义相似度计算规则**:
- - **重要**:语义相似度和从属关系是两个完全独立的维度!
- * 从属关系判断:"A 本身就是 B 的一种吗?"(层级关系)
- * 语义相似度:"A 和 B 这两个词本身像吗?"(词义距离)
- - **核心原则**:计算语义相似度时,**完全不考虑**从属关系的判断结果
- - **判断方法**:想象你不知道这两个词之间有任何关系,只是单独看这两个词的字面含义,它们像吗?
- - **禁止思路**:不要因为"A 是 B 的一种"就给高相似度
- - 计算标准:
- * 两个词几乎是同义词:0.8-1.0
- * 两个词意思比较接近:0.5-0.7
- * 两个词意思差距较大:0.2-0.4
- * 两个词意思完全不同:0.0-0.1
- - **相似度分析**:说明两个词本身的字面含义有多相似(30字以内),不要提及从属关系
- - **如果某层匹配成功**: 立即返回该层的匹配结果,不再检查更抽象的层级
- ## 输出格式 (严格JSON数组)
- ```json
- [
- {
- "当前标签": "<标签名称>",
- "匹配过程": {
- "标签匹配": [
- {
- "人设标签": "<标签名称>",
- "是否匹配": <true|false>
- }
- ],
- "分类匹配_按层级": [
- {
- "该层候选分类": ["<分类1>", "<分类2>", "..."],
- "该层匹配结果": [
- {
- "分类名称": "<分类1>",
- "从属关系判断": "<判断过程和理由>",
- "是否有从属关系": <true|false>,
- "相似度分析": "<两个词本身的相似度分析>",
- "语义相似度": <0到1之间的数值>
- },
- {
- "分类名称": "<分类2>",
- "从属关系判断": "<判断过程和理由>",
- "是否有从属关系": <true|false>,
- "相似度分析": "<两个词本身的相似度分析>",
- "语义相似度": <0到1之间的数值>
- }
- ]
- }
- ]
- },
- "匹配结果": {
- "匹配类型": "<标签匹配|分类匹配|无匹配>",
- "匹配到": "<标签或分类名称,无匹配时为null>",
- "语义相似度": <0到1之间的数值>
- }
- }
- ]
- ```
- ## 要求
- 1. **数组长度必须等于当前标签列表的长度**
- 2. **标签匹配**: 对人设组合中每个标签都要输出判断结果(true/false)
- 3. **提前终止**:
- - 如果标签匹配成功,则"分类匹配_按层级"为空数组[],不进行分类匹配
- - 如果标签匹配失败,进行分类匹配:
- * 从第一层开始逐层判断,每层都输出到"分类匹配_按层级"数组
- * 每层的"该层匹配结果"数组长度必须等于"该层候选分类"数组长度,每个分类都要判断
- * 一旦某层有匹配成功的分类(是否有从属关系=true),该层之后的层级不再输出
- * 例如:第2层匹配成功,则数组长度=2(包含第1层和第2层)
- 4. **匹配结果**:
- - 标签匹配成功时:匹配类型="标签匹配",语义相似度=1.0
- - 分类匹配成功时:匹配类型="分类匹配",语义相似度为该分类的语义相似度
- - 都不成功时:匹配类型="无匹配",语义相似度=0
- 5. **严格遵守分类排他性约束**
- """.strip()
- def create_unified_match_agent(model_name: str) -> Agent:
- """创建统一匹配的Agent"""
- return Agent(
- name="Unified Match Expert",
- instructions=UNIFIED_MATCH_SYSTEM_PROMPT,
- model=get_model(model_name),
- model_settings=ModelSettings(
- temperature=0.0,
- max_tokens=65536,
- ),
- tools=[],
- )
- async def unified_match(
- current_tags: List[str],
- persona_combination: List[Dict],
- model_name: Optional[str] = None
- ) -> List[Dict]:
- """
- 统一匹配函数 - 一次调用完成所有层级的匹配
- 返回当前标签列表中每个标签的匹配结果
- Args:
- current_tags: 当前标签列表,如 ["立冬", "教资查分", "时间巧合"]
- persona_combination: 人设标签组合(带分类),如:
- [
- {"标签名称": "猫孩子", "所属分类": ["宠物亲子化", "宠物情感", "实质"]},
- {"标签名称": "被拿捏住的无奈感", "所属分类": ["宠物关系主导", "宠物情感", "实质"]}
- ]
- model_name: 模型名称
- Returns:
- List[Dict]: 每个当前标签的匹配结果
- [
- {
- "当前标签": "立冬",
- "最终得分": 0.7,
- "匹配层级": "第一层分类匹配",
- "匹配到": "节气习俗",
- "匹配详情": {...},
- "综合说明": "..."
- },
- ...
- ]
- """
- if model_name is None:
- from lib.client import MODEL_NAME
- model_name = MODEL_NAME
- # 提取人设标签和分类信息
- persona_tags = [f.get("特征名称", f.get("标签名称")) for f in persona_combination]
- # 收集所有分类
- all_categories = set()
- for feature in persona_combination:
- categories = feature.get("所属分类", [])
- all_categories.update(categories)
- # 创建Agent
- agent = create_unified_match_agent(model_name)
- # 构建任务描述
- task_description = f"""## 本次匹配任务
- <当前标签列表>
- {', '.join(current_tags)}
- </当前标签列表>
- <人设标签组合>
- {persona_combination}
- </人设标签组合>
- **重要提醒**:
- 1. **标签匹配**: 对人设组合中每个"特征名称"逐个判断是否与当前标签同义(true/false)
- 2. **提前终止机制**:
- - 如果标签匹配成功,立即停止,"分类匹配_按层级"输出空数组[]
- - 如果标签匹配失败,进行分类匹配
- 3. **分类匹配**: 按层级(从具体到抽象)逐层判断
- - 分类在"所属分类"数组中的顺序就是从具体到抽象
- - 从第一层开始,判断该层所有分类
- - 在"分类匹配_按层级"数组中,按顺序输出每一层的判断结果
- - **重要**:每层的"该层匹配结果"必须对"该层候选分类"中的每个分类逐一判断
- - 一旦某层有匹配成功的分类(是否有从属关系=true),该层后面不再输出更多层级
- - 示例:如果第2层匹配成功,则只输出第1层和第2层,不输出第3层及以后
- 4. **语义相似度(核心规则)**:
- - ⚠️ **严格要求**:语义相似度和从属关系是**完全独立**的两个维度!
- - 从属关系看层级:判断"A 是不是 B 的一种"
- - 语义相似度看词义:判断"A 和 B 这两个词本身像不像"
- - **禁止**:不要因为"是一种"就给高相似度!
- 5. **匹配结果**:
- - 标签匹配成功:匹配类型="标签匹配",语义相似度=1.0
- - 分类匹配成功:匹配类型="分类匹配",语义相似度为该分类的语义相似度
- - 都不成功:匹配类型="无匹配",语义相似度=0
- 请对当前标签列表中的**每个标签**(共{len(current_tags)}个)进行匹配评估。
- 输出JSON数组,长度必须等于{len(current_tags)},顺序与当前标签列表一一对应。
- """
- messages = [{
- "role": "user",
- "content": [{"type": "input_text", "text": task_description}]
- }]
- with custom_span(
- name=f"统一匹配: 当前{len(current_tags)}个标签 vs 人设组合{persona_tags}",
- data={
- "当前标签列表": current_tags,
- "人设标签": persona_tags,
- "可用分类": list(all_categories)
- }
- ):
- result = await Runner.run(agent, input=messages)
- # 解析响应
- parsed_result = parse_json_from_text(result.final_output)
- if not parsed_result:
- # 解析失败,返回默认结果
- print("警告: JSON解析失败,返回默认结果")
- return [
- {
- "当前标签": tag,
- "匹配过程": {
- "标签匹配": [],
- "分类匹配_按层级": []
- },
- "匹配结果": {
- "匹配类型": "无匹配",
- "匹配到": None,
- "语义相似度": 0
- }
- }
- for tag in current_tags
- ]
- # 确保返回的是列表
- if not isinstance(parsed_result, list):
- print(f"警告: 返回结果不是列表,转换中: {type(parsed_result)}")
- parsed_result = [parsed_result]
- # 验证结果数量
- if len(parsed_result) != len(current_tags):
- print(f"警告: 返回结果数量({len(parsed_result)})与当前标签数量({len(current_tags)})不匹配")
- # 补齐或截断
- while len(parsed_result) < len(current_tags):
- parsed_result.append({
- "当前标签": current_tags[len(parsed_result)],
- "最终得分": 0,
- "匹配层级": "无匹配",
- "匹配到": None,
- "匹配详情": {},
- "综合说明": "结果数量不匹配,自动补齐"
- })
- parsed_result = parsed_result[:len(current_tags)]
- return parsed_result
|