match_analyzer.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. """
  2. 通用的信息匹配分析模块
  3. 分析 <B> 在 <A> 中的字面语义匹配关系
  4. 适用于任何信息匹配场景
  5. 提供两个接口:
  6. 1. match_single(b_content, a_content, model_name, b_context="", a_context="") - 单个匹配
  7. 2. match_batch(b_items, a_content, model_name, b_context="", a_context="") - 批量匹配
  8. 支持可选的 Context 参数:
  9. - b_context: B 的补充上下文(帮助理解 B)
  10. - a_context: A 的补充上下文(帮助理解 A)
  11. - Context 默认为空,不提供时不会出现在 prompt 中
  12. """
  13. import json
  14. from typing import List
  15. from agents import Agent, Runner
  16. from agents.tracing.create import custom_span
  17. from lib.client import get_model
  18. # ========== System Prompt ==========
  19. MATCH_SYSTEM_PROMPT = """
  20. # 任务
  21. 分析 <B> 在 <A> 中的字面语义匹配关系。
  22. ## 输入说明
  23. - **<B></B>**: 待匹配的内容(必选)
  24. - **<A></A>**: 上下文内容(必选)
  25. - **<B_Context></B_Context>**: B 的补充上下文(可选,帮助理解 B)
  26. - **<A_Context></A_Context>**: A 的补充上下文(可选,帮助理解 A)
  27. **重要**:匹配分析发生在 <B> 和 <A> 之间,Context 仅作为补充理解的辅助信息。
  28. ## 分析方法
  29. ### 核心原则:字面语义匹配
  30. 只关注 <B> 和 <A> 在**字面词语和概念**上的重叠度,不考虑抽象关系。
  31. ### 分析步骤
  32. 1. **提取关键词/概念**
  33. - 从 <B> 中提取:关键词语和核心概念
  34. - 从 <A> 中提取:关键词语和核心概念
  35. 2. **识别相同部分**
  36. - 完全相同的词语(字面一致)
  37. - 同义词或近义词
  38. 3. **识别增量部分**
  39. - <B> 中有,但 <A> 中没有的词语/概念
  40. - 这些是 <B> 相对于 <A> 的额外信息
  41. 4. **计算匹配分数**
  42. - 基于相同部分的覆盖度
  43. - 考虑词语/概念的重要性
  44. ---
  45. ## 评分标准(0-1分)
  46. **字面匹配度评分:**
  47. - **0.9-1.0**:<B> 和 <A> 几乎完全一致,词语高度重叠
  48. - **0.7-0.8**:大部分核心词语/概念匹配,少量增量
  49. - **0.5-0.6**:部分核心词语/概念匹配,有一定增量
  50. - **0.3-0.4**:少量词语/概念匹配,大部分不同
  51. - **0.1-0.2**:几乎无字面匹配,仅有概念联系
  52. - **0.0**:完全无关
  53. **重要原则:**
  54. - 如果 <A> 是抽象/元级别的描述,而 <B> 是具体内容,字面上无词语重叠,应给低分(0.1-0.3)
  55. - 优先考虑具体词语的匹配,而非抽象概念的包含关系
  56. ---
  57. ## 输出格式(严格JSON)
  58. ```json
  59. {
  60. "score": 0.75,
  61. "score说明": "简要说明分数是如何计算的,基于哪些词语/概念的匹配",
  62. "相同部分": {
  63. "B中的词1": "与A中的'某词'完全相同",
  64. "B中的词2": "与A中的'某词'同义"
  65. },
  66. "增量部分": {
  67. "B中的词3": "A中无此概念"
  68. }
  69. }
  70. ```
  71. **输出要求**:
  72. 1. 必须严格按照上述JSON格式输出(score 和 score说明在最前面)
  73. 2. 所有字段都必须填写
  74. 3. **score字段**:必须是0-1之间的浮点数,保留2位小数
  75. 4. **score说明**:必须简洁说明评分依据(基于相同部分的覆盖度)
  76. 5. **相同部分**:字典格式,key是<B>中的词语,value说明它与<A>中哪个词的关系(完全相同/同义);如果没有则填写空字典 {}
  77. 6. **增量部分**:字典格式,key是<B>中的词语,value说明为什么是增量(如"A中无此概念");如果没有增量部分,填写空字典 {}
  78. 7. **关键约束**:相同部分和增量部分的key必须只能是<B>中的词语,不能是<A>中的词语
  79. """.strip()
  80. def create_match_agent(model_name: str) -> Agent:
  81. """创建信息匹配分析的 Agent
  82. Args:
  83. model_name: 模型名称
  84. Returns:
  85. Agent 实例
  86. """
  87. agent = Agent(
  88. name="Information Match Expert",
  89. instructions=MATCH_SYSTEM_PROMPT,
  90. model=get_model(model_name),
  91. tools=[],
  92. )
  93. return agent
  94. def parse_match_response(response_content: str) -> dict:
  95. """解析匹配响应
  96. Args:
  97. response_content: Agent 返回的响应内容
  98. Returns:
  99. 解析后的字典
  100. """
  101. try:
  102. # 如果响应包含在 markdown 代码块中,提取 JSON 部分
  103. if "```json" in response_content:
  104. json_start = response_content.index("```json") + 7
  105. json_end = response_content.index("```", json_start)
  106. json_text = response_content[json_start:json_end].strip()
  107. elif "```" in response_content:
  108. json_start = response_content.index("```") + 3
  109. json_end = response_content.index("```", json_start)
  110. json_text = response_content[json_start:json_end].strip()
  111. else:
  112. json_text = response_content.strip()
  113. return json.loads(json_text)
  114. except Exception as e:
  115. print(f"解析响应失败: {e}")
  116. return {
  117. "相同部分": {},
  118. "增量部分": {},
  119. "score": 0.0,
  120. "score说明": f"解析失败: {str(e)}"
  121. }
  122. def _create_batch_agent(model_name: str) -> Agent:
  123. """创建批量匹配的 Agent
  124. Args:
  125. model_name: 模型名称
  126. Returns:
  127. Agent 实例
  128. """
  129. # 批量匹配的 System Prompt(在单个匹配基础上修改输出格式)
  130. batch_prompt = MATCH_SYSTEM_PROMPT.replace(
  131. "## 输出格式(严格JSON)",
  132. "## 输出格式(JSON数组)\n对每个 <B> 输出一个匹配结果:"
  133. ).replace(
  134. "```json\n{",
  135. "```json\n[{"
  136. ).replace(
  137. "}\n```",
  138. "}]\n```"
  139. ) + "\n\n**额外要求**:数组长度必须等于 <B> 的数量,顺序对应"
  140. agent = Agent(
  141. name="Batch Information Match Expert",
  142. instructions=batch_prompt,
  143. model=get_model(model_name),
  144. tools=[],
  145. )
  146. return agent
  147. async def _run_match_agent(
  148. agent: Agent,
  149. b_content: str,
  150. a_content: str,
  151. request_desc: str,
  152. b_context: str = "",
  153. a_context: str = ""
  154. ) -> str:
  155. """运行匹配 Agent 的公共逻辑
  156. Args:
  157. agent: Agent 实例
  158. b_content: B 的内容
  159. a_content: A 的内容
  160. request_desc: 请求描述(如"并输出 JSON 格式"或"并输出 JSON 数组格式")
  161. b_context: B 的上下文(可选)
  162. a_context: A 的上下文(可选)
  163. Returns:
  164. Agent 的原始输出
  165. """
  166. # 构建任务描述
  167. b_section = f"<B>\n{b_content}\n</B>"
  168. if b_context:
  169. b_section += f"\n\n<B_Context>\n{b_context}\n</B_Context>"
  170. a_section = f"<A>\n{a_content}\n</A>"
  171. if a_context:
  172. a_section += f"\n\n<A_Context>\n{a_context}\n</A_Context>"
  173. task_description = f"""## 本次分析任务
  174. {b_section}
  175. {a_section}
  176. 请严格按照系统提示中的要求分析 <B> 在 <A> 中的字面语义匹配关系,{request_desc}的结果。"""
  177. # 构造消息
  178. messages = [{
  179. "role": "user",
  180. "content": [
  181. {
  182. "type": "input_text",
  183. "text": task_description
  184. }
  185. ]
  186. }]
  187. # 使用 custom_span 追踪匹配过程
  188. # 截断显示内容,避免 span name 过长
  189. b_short = (b_content[:40] + "...") if len(b_content) > 40 else b_content
  190. a_short = (a_content[:40] + "...") if len(a_content) > 40 else a_content
  191. with custom_span(
  192. name=f"匹配分析: {b_short} in {a_short}",
  193. data={
  194. "B": b_content,
  195. "A": a_content,
  196. "B_Context": b_context if b_context else None,
  197. "A_Context": a_context if a_context else None,
  198. "模式": request_desc
  199. }
  200. ):
  201. # 运行 Agent
  202. result = await Runner.run(agent, input=messages)
  203. return result.final_output
  204. async def match_single(
  205. b_content: str,
  206. a_content: str,
  207. model_name: str,
  208. b_context: str = "",
  209. a_context: str = ""
  210. ) -> dict:
  211. """单个匹配:分析一个 B 在 A 中的匹配
  212. Args:
  213. b_content: B(待匹配)的内容
  214. a_content: A(上下文)的内容
  215. model_name: 使用的模型名称
  216. b_context: B 的补充上下文(可选,默认为空)
  217. a_context: A 的补充上下文(可选,默认为空)
  218. Returns:
  219. 匹配结果字典:{"相同部分": {}, "增量部分": {}, "score": 0.0, "score说明": ""}
  220. """
  221. try:
  222. # 创建 Agent
  223. agent = create_match_agent(model_name)
  224. # 运行匹配
  225. output = await _run_match_agent(
  226. agent, b_content, a_content, "并输出 JSON 格式",
  227. b_context=b_context, a_context=a_context
  228. )
  229. # 解析响应
  230. parsed_result = parse_match_response(output)
  231. return parsed_result
  232. except Exception as e:
  233. return {
  234. "相同部分": {},
  235. "增量部分": {},
  236. "score": 0.0,
  237. "score说明": f"匹配过程出错: {str(e)}"
  238. }
  239. async def match_batch(
  240. b_items: List[str],
  241. a_content: str,
  242. model_name: str,
  243. b_context: str = "",
  244. a_context: str = ""
  245. ) -> List[dict]:
  246. """批量匹配:分析多个 B 在 A 中的匹配(一次调用)
  247. Args:
  248. b_items: B列表(多个待匹配项)
  249. a_content: A(上下文)的内容
  250. model_name: 使用的模型名称
  251. b_context: B 的补充上下文(可选,默认为空)
  252. a_context: A 的补充上下文(可选,默认为空)
  253. Returns:
  254. 匹配结果列表:[{"相同部分": {}, "增量部分": {}, "score": 0.0, "score说明": ""}, ...]
  255. """
  256. try:
  257. # 创建批量匹配 Agent
  258. agent = _create_batch_agent(model_name)
  259. # 构建 B 列表字符串
  260. b_list_str = "\n".join([f"- {item}" for item in b_items])
  261. # 运行匹配
  262. output = await _run_match_agent(
  263. agent, b_list_str, a_content, "并输出 JSON 数组格式",
  264. b_context=b_context, a_context=a_context
  265. )
  266. # 解析响应(期望是数组)
  267. parsed_result = parse_match_response(output)
  268. # 如果返回的是数组,直接返回;如果是单个对象,包装成数组
  269. if isinstance(parsed_result, list):
  270. return parsed_result
  271. else:
  272. return [parsed_result]
  273. except Exception as e:
  274. # 返回错误信息(为每个 B 创建一个错误条目)
  275. return [{
  276. "相同部分": {},
  277. "增量部分": {},
  278. "score": 0.0,
  279. "score说明": f"匹配过程出错: {str(e)}"
  280. } for _ in b_items]