relation_analyzer.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. """
  2. 短语关系分析模块
  3. 分析两个短语之间的语义关系
  4. 提供接口:
  5. analyze_relation(phrase_a, phrase_b, model_name, context_a="", context_b="") - 分析两个短语的关系
  6. 支持可选的 Context 参数:
  7. - context_a: phrase_a 的补充上下文(帮助理解 phrase_a)
  8. - context_b: phrase_b 的补充上下文(帮助理解 phrase_b)
  9. - Context 默认为空,不提供时不会出现在 prompt 中
  10. 返回格式:
  11. {
  12. "relation": "same", # 7种关系之一
  13. "score": 0.95, # 0-1,语义接近程度
  14. "explanation": "说明" # 关系判断的依据
  15. }
  16. """
  17. import json
  18. from agents import Agent, Runner, ModelSettings
  19. from agents.tracing.create import custom_span
  20. from lib.client import get_model
  21. # ========== System Prompt ==========
  22. RELATION_SYSTEM_PROMPT = """
  23. # 任务
  24. 分析两个短语 <A> 和 <B> 之间的语义关系。
  25. ## 输入说明
  26. - **<A></A>**: 第一个短语(必选)
  27. - **<B></B>**: 第二个短语(必选)
  28. - **<A_Context></A_Context>**: A 的补充上下文(可选,帮助理解 A)
  29. - **<B_Context></B_Context>**: B 的补充上下文(可选,帮助理解 B)
  30. **重要**:关系分析发生在 <A> 和 <B> 之间,Context 仅作为补充理解的辅助信息。
  31. ---
  32. ## 关系类型(7种)
  33. ### 1. same(同义)
  34. - **定义**:意思完全相同或非常接近,可以互相替换
  35. - **例子**:
  36. - "医生" 和 "大夫" → same
  37. - "计算机" 和 "电脑" → same
  38. - "快乐" 和 "高兴" → same
  39. ### 2. coordinate(同级)
  40. - **定义**:有共同的上位概念,属于并列关系,通常无交集
  41. - **例子**:
  42. - "轿车" 和 "SUV" → coordinate(都是汽车)
  43. - "苹果" 和 "香蕉" → coordinate(都是水果)
  44. - "数学" 和 "物理" → coordinate(都是学科)
  45. ### 3. contains(包含)
  46. - **定义**:A 的概念范围包含 B,B 是 A 的子类或特例
  47. - **例子**:
  48. - "水果" contains "苹果"
  49. - "汽车" contains "轿车"
  50. - "动物" contains "狗"
  51. ### 4. contained_by(被包含)
  52. - **定义**:A 被 B 包含,A 是 B 的子类或特例
  53. - **例子**:
  54. - "苹果" contained_by "水果"
  55. - "轿车" contained_by "汽车"
  56. - "狗" contained_by "动物"
  57. ### 5. overlap(部分重叠)
  58. - **定义**:两个概念有交集,但互不包含
  59. - **例子**:
  60. - "红苹果" 和 "大苹果" → overlap(有又红又大的苹果)
  61. - "亚洲国家" 和 "发展中国家" → overlap(如中国、印度等)
  62. - "学生" 和 "运动员" → overlap(有学生运动员)
  63. ### 6. related(相关)
  64. - **定义**:有语义联系,但不属于上述任何层级关系
  65. - **例子**:
  66. - "医生" 和 "医院" → related(工作场所关系)
  67. - "阅读" 和 "书籍" → related(动作-对象关系)
  68. - "钥匙" 和 "锁" → related(工具-用途关系)
  69. - "老师" 和 "学生" → related(角色关系)
  70. ### 7. unrelated(无关)
  71. - **定义**:无明显语义关系
  72. - **例子**:
  73. - "医生" 和 "石头" → unrelated
  74. - "苹果" 和 "数学" → unrelated
  75. ---
  76. ## 评分标准(score: 0-1)
  77. **score 表示两个短语的语义接近程度:**
  78. - **0.9-1.0**:几乎完全相同(完全同义)
  79. - **0.8-0.9**:非常接近(高度同义、直接包含关系)
  80. - **0.7-0.8**:比较接近(近义、明确的同级或包含)
  81. - **0.6-0.7**:有一定接近度(同级但层级稍远、间接包含)
  82. - **0.5-0.6**:中等程度的关系(中等交集、中度相关)
  83. - **0.4-0.5**:关系较弱(小交集、弱相关)
  84. - **0.3-0.4**:关系很弱(勉强算同级、很弱的相关)
  85. - **0.0-0.3**:几乎无关或完全无关
  86. **不同关系类型的 score 范围参考:**
  87. - same: 通常 0.7-1.0(完全同义接近1.0,近义0.7-0.8)
  88. - contains/contained_by: 通常 0.5-0.9(直接包含0.8+,跨层级0.5-0.7)
  89. - coordinate: 通常 0.3-0.8(同级且上位概念近0.7+,同级但距离远0.3-0.5)
  90. - overlap: 通常 0.2-0.8(交集大0.6+,交集小0.2-0.4)
  91. - related: 通常 0.1-0.7(强相关0.5+,弱相关0.1-0.3)
  92. - unrelated: 通常 0.0-0.2
  93. ---
  94. ## 判断逻辑(按优先级)
  95. 1. **A 和 B 意思相同或非常接近?** → same
  96. 2. **A 包含 B 或 B 包含 A?** → contains 或 contained_by
  97. 3. **A 和 B 有共同上位概念且无交集?** → coordinate
  98. 4. **A 和 B 有交集但互不包含?** → overlap
  99. 5. **A 和 B 有语义联系但不属于上述?** → related
  100. 6. **A 和 B 完全无关?** → unrelated
  101. ---
  102. ## 输出格式(严格JSON)
  103. ```json
  104. {
  105. "relation": "same",
  106. "score": 0.95,
  107. "explanation": "简要说明为什么是这个关系,以及 score 的依据"
  108. }
  109. ```
  110. **输出要求**:
  111. 1. 必须严格按照上述JSON格式输出
  112. 2. 所有字段都必须填写
  113. 3. **relation字段**:必须是以下7个值之一:same, coordinate, contains, contained_by, overlap, related, unrelated
  114. 4. **score字段**:必须是0-1之间的浮点数,保留2位小数
  115. 5. **explanation字段**:必须简洁说明关系类型和评分依据(1-2句话)
  116. """.strip()
  117. def create_relation_agent(model_name: str) -> Agent:
  118. """创建关系分析的 Agent
  119. Args:
  120. model_name: 模型名称
  121. Returns:
  122. Agent 实例
  123. """
  124. agent = Agent(
  125. name="Phrase Relation Expert",
  126. instructions=RELATION_SYSTEM_PROMPT,
  127. model=get_model(model_name),
  128. model_settings=ModelSettings(
  129. temperature=0.0,
  130. max_tokens=65536,
  131. ),
  132. tools=[],
  133. )
  134. return agent
  135. def parse_relation_response(response_content: str) -> dict:
  136. """解析关系分析响应
  137. Args:
  138. response_content: Agent 返回的响应内容
  139. Returns:
  140. 解析后的字典
  141. """
  142. try:
  143. # 如果响应包含在 markdown 代码块中,提取 JSON 部分
  144. if "```json" in response_content:
  145. json_start = response_content.index("```json") + 7
  146. json_end = response_content.index("```", json_start)
  147. json_text = response_content[json_start:json_end].strip()
  148. elif "```" in response_content:
  149. json_start = response_content.index("```") + 3
  150. json_end = response_content.index("```", json_start)
  151. json_text = response_content[json_start:json_end].strip()
  152. else:
  153. json_text = response_content.strip()
  154. return json.loads(json_text)
  155. except Exception as e:
  156. print(f"解析响应失败: {e}")
  157. return {
  158. "relation": "unrelated",
  159. "score": 0.0,
  160. "explanation": f"解析失败: {str(e)}"
  161. }
  162. async def analyze_relation(
  163. phrase_a: str,
  164. phrase_b: str,
  165. model_name: str = None,
  166. context_a: str = "",
  167. context_b: str = ""
  168. ) -> dict:
  169. """分析两个短语之间的关系
  170. Args:
  171. phrase_a: 第一个短语
  172. phrase_b: 第二个短语
  173. model_name: 使用的模型名称(可选,默认使用 client.py 中的 MODEL_NAME)
  174. context_a: phrase_a 的补充上下文(可选,默认为空)
  175. context_b: phrase_b 的补充上下文(可选,默认为空)
  176. Returns:
  177. 关系分析结果字典:{"relation": "same", "score": 0.95, "explanation": "..."}
  178. """
  179. try:
  180. # 如果未指定模型,使用默认模型
  181. if model_name is None:
  182. from lib.client import MODEL_NAME
  183. model_name = MODEL_NAME
  184. # 创建 Agent
  185. agent = create_relation_agent(model_name)
  186. # 构建任务描述
  187. a_section = f"<A>\n{phrase_a}\n</A>"
  188. if context_a:
  189. a_section += f"\n\n<A_Context>\n{context_a}\n</A_Context>"
  190. b_section = f"<B>\n{phrase_b}\n</B>"
  191. if context_b:
  192. b_section += f"\n\n<B_Context>\n{context_b}\n</B_Context>"
  193. task_description = f"""## 本次分析任务
  194. {a_section}
  195. {b_section}
  196. 请严格按照系统提示中的要求分析 <A> 和 <B> 之间的语义关系,并输出 JSON 格式的结果。"""
  197. # 构造消息
  198. messages = [{
  199. "role": "user",
  200. "content": [
  201. {
  202. "type": "input_text",
  203. "text": task_description
  204. }
  205. ]
  206. }]
  207. # 使用 custom_span 追踪分析过程
  208. # 截断显示内容,避免 span name 过长
  209. a_short = (phrase_a[:30] + "...") if len(phrase_a) > 30 else phrase_a
  210. b_short = (phrase_b[:30] + "...") if len(phrase_b) > 30 else phrase_b
  211. with custom_span(
  212. name=f"关系分析: {a_short} <-> {b_short}",
  213. data={
  214. "phrase_a": phrase_a,
  215. "phrase_b": phrase_b,
  216. "context_a": context_a if context_a else None,
  217. "context_b": context_b if context_b else None,
  218. }
  219. ):
  220. # 运行 Agent
  221. result = await Runner.run(agent, input=messages)
  222. # 解析响应
  223. parsed_result = parse_relation_response(result.final_output)
  224. return parsed_result
  225. except Exception as e:
  226. return {
  227. "relation": "unrelated",
  228. "score": 0.0,
  229. "explanation": f"分析过程出错: {str(e)}"
  230. }