hierarchical_match_analyzer.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 分层匹配分析模块
  5. 实现特征组合的分层匹配逻辑:
  6. 1. 优先匹配灵感点标签(特征名称)
  7. 2. 无标签匹配时,匹配第一层分类
  8. 3. 仍无结果时,匹配第二层上位分类
  9. 4. 对每个候选进行推理难度打分
  10. """
  11. from typing import List, Dict, Optional
  12. from agents import Agent, Runner, ModelSettings
  13. from agents.tracing.create import custom_span
  14. from lib.client import get_model
  15. from lib.utils import parse_json_from_text
  16. # ========== System Prompts ==========
  17. TAG_MATCH_SYSTEM_PROMPT = """
  18. # 任务
  19. 判断"当前特征列表"中的特征,是否有与"人设特征标签"在语义上相同或高度接近的。
  20. ## 评分标准
  21. - **相似度 ≥ 80**: 语义相同或高度接近,判定为匹配成功
  22. - **相似度 < 80**: 不够接近,判定为匹配失败
  23. ## 输出格式(严格JSON)
  24. ```json
  25. {
  26. "匹配成功": true/false,
  27. "匹配对": [
  28. {"当前特征": "...", "人设标签": "...", "相似度": 95}
  29. ],
  30. "最佳匹配": {"当前特征": "...", "人设标签": "...", "相似度": 95} or null,
  31. "说明": "匹配结果说明"
  32. }
  33. ```
  34. **要求**:
  35. 1. 逐一比较当前特征与人设标签
  36. 2. 找到所有相似度≥80的配对
  37. 3. 按相似度降序排列匹配对
  38. 4. 最佳匹配为相似度最高的配对
  39. """.strip()
  40. CATEGORY_MATCH_SYSTEM_PROMPT = """
  41. # 任务
  42. 为"当前特征列表"在"候选分类"中找到语义最接近的分类,并评估推理难度。
  43. ## 推理难度评估标准(0-10分)
  44. - **0-2分**: 几乎直接对应,推理非常容易
  45. - **3-4分**: 需要简单推理,难度较低
  46. - **5-6分**: 需要中等程度的推理
  47. - **7-8分**: 需要较复杂的推理
  48. - **9-10分**: 推理非常困难,关联很弱
  49. ## 推理难度得分计算
  50. ```
  51. 推理难度得分 = (10 - 推理难度) / 10
  52. ```
  53. 例如:推理难度=3,则得分=(10-3)/10=0.7
  54. ## 输出格式(严格JSON)
  55. ```json
  56. {
  57. "匹配成功": true/false,
  58. "最佳分类": "分类名称" or null,
  59. "推理难度": 3,
  60. "推理难度得分": 0.7,
  61. "推理路径": "从该分类如何推理到当前特征的说明",
  62. "说明": "为什么选择这个分类"
  63. }
  64. ```
  65. **要求**:
  66. 1. 判断当前特征整体的主题/领域
  67. 2. 在候选分类中找到最符合的分类
  68. 3. 评估推理难度(0-10)
  69. 4. 计算推理难度得分
  70. 5. 只有推理难度得分≥0.5时,判定为匹配成功
  71. """.strip()
  72. def create_tag_match_agent(model_name: str) -> Agent:
  73. """创建标签匹配的Agent"""
  74. return Agent(
  75. name="Tag Match Expert",
  76. instructions=TAG_MATCH_SYSTEM_PROMPT,
  77. model=get_model(model_name),
  78. model_settings=ModelSettings(
  79. temperature=0.0,
  80. max_tokens=65536,
  81. ),
  82. tools=[],
  83. )
  84. def create_category_match_agent(model_name: str) -> Agent:
  85. """创建分类匹配的Agent"""
  86. return Agent(
  87. name="Category Match Expert",
  88. instructions=CATEGORY_MATCH_SYSTEM_PROMPT,
  89. model=get_model(model_name),
  90. model_settings=ModelSettings(
  91. temperature=0.0,
  92. max_tokens=65536,
  93. ),
  94. tools=[],
  95. )
  96. async def match_current_features_to_persona_tags(
  97. current_features: List[str],
  98. persona_combination: List[Dict],
  99. model_name: Optional[str] = None
  100. ) -> Dict:
  101. """
  102. 第一层匹配: 将当前特征列表与人设组合的特征标签进行语义匹配
  103. Args:
  104. current_features: 当前特征列表,如 ["立冬", "教资查分", "时间巧合"]
  105. persona_combination: 人设组合特征列表,如:
  106. [
  107. {"特征名称": "猫孩子", "所属分类": ["宠物亲子化", "宠物情感", "实质"]},
  108. {"特征名称": "被拿捏住的无奈感", "所属分类": ["宠物关系主导", "宠物情感", "实质"]}
  109. ]
  110. model_name: 模型名称
  111. Returns:
  112. {
  113. "匹配成功": bool,
  114. "匹配的特征": str or None,
  115. "得分": 1 or 0,
  116. "详细结果": {...}
  117. }
  118. """
  119. if model_name is None:
  120. from lib.client import MODEL_NAME
  121. model_name = MODEL_NAME
  122. persona_tags = [f["特征名称"] for f in persona_combination]
  123. # 创建Agent
  124. agent = create_tag_match_agent(model_name)
  125. # 构建任务描述
  126. task_description = f"""## 本次匹配任务
  127. <当前特征列表>
  128. {', '.join(current_features)}
  129. </当前特征列表>
  130. <人设特征标签>
  131. {', '.join(persona_tags)}
  132. </人设特征标签>
  133. 请判断当前特征列表中是否有与人设标签语义相同或高度接近的(相似度≥80),输出JSON格式结果。
  134. """
  135. messages = [{
  136. "role": "user",
  137. "content": [{"type": "input_text", "text": task_description}]
  138. }]
  139. with custom_span(
  140. name=f"标签匹配: {current_features[:2]} vs {len(persona_tags)}个标签",
  141. data={
  142. "current_features": current_features,
  143. "persona_tags": persona_tags
  144. }
  145. ):
  146. result = await Runner.run(agent, input=messages)
  147. # 解析响应
  148. parsed_result = parse_json_from_text(result.final_output)
  149. if not parsed_result:
  150. return {
  151. "匹配成功": False,
  152. "匹配的特征": None,
  153. "得分": 0,
  154. "详细结果": {"说明": "解析失败"}
  155. }
  156. # 转换为标准格式
  157. if parsed_result.get("匹配成功"):
  158. best_match = parsed_result.get("最佳匹配", {})
  159. return {
  160. "匹配成功": True,
  161. "匹配的特征": best_match.get("人设标签"),
  162. "得分": 1,
  163. "详细结果": parsed_result
  164. }
  165. else:
  166. return {
  167. "匹配成功": False,
  168. "匹配的特征": None,
  169. "得分": 0,
  170. "详细结果": parsed_result
  171. }
  172. async def match_to_categories(
  173. current_features: List[str],
  174. persona_combination: List[Dict],
  175. layer: str, # "first" or "second"
  176. model_name: Optional[str] = None
  177. ) -> Dict:
  178. """
  179. 分类匹配(第一层或第二层)
  180. Args:
  181. current_features: 当前特征列表
  182. persona_combination: 人设组合特征列表(带分类)
  183. layer: "first"=第一层分类, "second"=第二层上位分类
  184. model_name: 模型名称
  185. Returns:
  186. {
  187. "匹配成功": bool,
  188. "匹配的分类": str or None,
  189. "推理难度得分": float (0-1),
  190. "详细结果": {...}
  191. }
  192. """
  193. if model_name is None:
  194. from lib.client import MODEL_NAME
  195. model_name = MODEL_NAME
  196. # 收集分类
  197. all_categories = set()
  198. for feature in persona_combination:
  199. categories = feature.get("所属分类", [])
  200. if layer == "first":
  201. # 第一层:过滤掉"实质"和"形式"
  202. filtered_cats = [c for c in categories if c not in ["实质", "形式"]]
  203. all_categories.update(filtered_cats)
  204. elif layer == "second":
  205. # 第二层:只保留"实质"和"形式"
  206. generic_cats = [c for c in categories if c in ["实质", "形式"]]
  207. all_categories.update(generic_cats)
  208. if not all_categories:
  209. # 如果没有可用分类
  210. if layer == "first":
  211. # 降级使用所有分类
  212. for feature in persona_combination:
  213. all_categories.update(feature.get("所属分类", []))
  214. else:
  215. # 第二层没有分类,返回失败
  216. return {
  217. "匹配成功": False,
  218. "匹配的分类": None,
  219. "推理难度得分": 0,
  220. "详细结果": {"说明": "没有可用的上位分类"}
  221. }
  222. categories_list = list(all_categories)
  223. persona_tags = [f["特征名称"] for f in persona_combination]
  224. # 创建Agent
  225. agent = create_category_match_agent(model_name)
  226. # 构建任务描述
  227. layer_desc = "第一层分类(具体领域分类)" if layer == "first" else "第二层上位分类(实质/形式)"
  228. task_description = f"""## 本次匹配任务 - {layer_desc}
  229. <当前特征列表>
  230. {', '.join(current_features)}
  231. </当前特征列表>
  232. <候选分类>
  233. {', '.join(categories_list)}
  234. </候选分类>
  235. <人设组合特征>
  236. {', '.join(persona_tags)}
  237. </人设组合特征>
  238. 请为当前特征列表在候选分类中找到最接近的分类,并评估推理难度(0-10),输出JSON格式结果。
  239. """
  240. messages = [{
  241. "role": "user",
  242. "content": [{"type": "input_text", "text": task_description}]
  243. }]
  244. layer_name = "第一层分类" if layer == "first" else "第二层上位分类"
  245. with custom_span(
  246. name=f"{layer_name}匹配: {current_features[:2]} vs {len(categories_list)}个分类",
  247. data={
  248. "current_features": current_features,
  249. "categories": categories_list,
  250. "layer": layer
  251. }
  252. ):
  253. result = await Runner.run(agent, input=messages)
  254. # 解析响应
  255. parsed_result = parse_json_from_text(result.final_output)
  256. if not parsed_result:
  257. return {
  258. "匹配成功": False,
  259. "匹配的分类": None,
  260. "推理难度得分": 0,
  261. "详细结果": {"说明": "解析失败"}
  262. }
  263. return {
  264. "匹配成功": parsed_result.get("匹配成功", False),
  265. "匹配的分类": parsed_result.get("最佳分类"),
  266. "推理难度得分": parsed_result.get("推理难度得分", 0),
  267. "详细结果": parsed_result
  268. }
  269. async def hierarchical_match(
  270. current_features: List[str],
  271. persona_combination: List[Dict],
  272. model_name: Optional[str] = None
  273. ) -> Dict:
  274. """
  275. 分层匹配主函数
  276. 依次尝试:
  277. 1. 标签匹配(特征名称)
  278. 2. 第一层分类匹配
  279. 3. 第二层上位分类匹配
  280. Args:
  281. current_features: 当前特征列表
  282. persona_combination: 人设组合特征列表(带分类)
  283. model_name: 模型名称
  284. Returns:
  285. {
  286. "最终得分": float, // 0-1
  287. "匹配层级": "标签匹配" | "第一层分类匹配" | "第二层上位分类匹配" | "无匹配",
  288. "匹配结果": str, // 匹配到的标签/分类名称
  289. "分层结果": {
  290. "标签匹配": {...},
  291. "第一层分类匹配": {...},
  292. "第二层上位分类匹配": {...}
  293. },
  294. "综合说明": str
  295. }
  296. """
  297. # 第一层: 标签匹配
  298. tag_match = await match_current_features_to_persona_tags(
  299. current_features, persona_combination, model_name
  300. )
  301. if tag_match["匹配成功"]:
  302. return {
  303. "最终得分": 1.0,
  304. "匹配层级": "标签匹配",
  305. "匹配结果": tag_match["匹配的特征"],
  306. "分层结果": {
  307. "标签匹配": tag_match
  308. },
  309. "综合说明": f"在标签层级找到完全匹配: {tag_match['匹配的特征']}"
  310. }
  311. # 第二层: 第一层分类匹配
  312. first_cat_match = await match_to_categories(
  313. current_features, persona_combination, "first", model_name
  314. )
  315. if first_cat_match["匹配成功"] and first_cat_match["推理难度得分"] >= 0.5:
  316. return {
  317. "最终得分": first_cat_match["推理难度得分"],
  318. "匹配层级": "第一层分类匹配",
  319. "匹配结果": first_cat_match["匹配的分类"],
  320. "分层结果": {
  321. "标签匹配": tag_match,
  322. "第一层分类匹配": first_cat_match
  323. },
  324. "综合说明": f"在第一层分类找到匹配: {first_cat_match['匹配的分类']}, 推理难度得分: {first_cat_match['推理难度得分']:.2f}"
  325. }
  326. # 第三层: 第二层上位分类匹配
  327. second_cat_match = await match_to_categories(
  328. current_features, persona_combination, "second", model_name
  329. )
  330. if second_cat_match["匹配成功"]:
  331. return {
  332. "最终得分": second_cat_match["推理难度得分"],
  333. "匹配层级": "第二层上位分类匹配",
  334. "匹配结果": second_cat_match["匹配的分类"],
  335. "分层结果": {
  336. "标签匹配": tag_match,
  337. "第一层分类匹配": first_cat_match,
  338. "第二层上位分类匹配": second_cat_match
  339. },
  340. "综合说明": f"在第二层上位分类找到匹配: {second_cat_match['匹配的分类']}, 推理难度得分: {second_cat_match['推理难度得分']:.2f}"
  341. }
  342. # 无匹配
  343. return {
  344. "最终得分": 0,
  345. "匹配层级": "无匹配",
  346. "匹配结果": None,
  347. "分层结果": {
  348. "标签匹配": tag_match,
  349. "第一层分类匹配": first_cat_match,
  350. "第二层上位分类匹配": second_cat_match
  351. },
  352. "综合说明": "在所有层级都未找到合适的匹配"
  353. }