match_analyzer.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  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, ModelSettings
  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. model_settings=ModelSettings(
  92. temperature=0.0,
  93. max_tokens=65536,
  94. ),
  95. tools=[],
  96. )
  97. return agent
  98. def parse_match_response(response_content: str) -> dict:
  99. """解析匹配响应
  100. Args:
  101. response_content: Agent 返回的响应内容
  102. Returns:
  103. 解析后的字典
  104. """
  105. try:
  106. # 如果响应包含在 markdown 代码块中,提取 JSON 部分
  107. if "```json" in response_content:
  108. json_start = response_content.index("```json") + 7
  109. json_end = response_content.index("```", json_start)
  110. json_text = response_content[json_start:json_end].strip()
  111. elif "```" in response_content:
  112. json_start = response_content.index("```") + 3
  113. json_end = response_content.index("```", json_start)
  114. json_text = response_content[json_start:json_end].strip()
  115. else:
  116. json_text = response_content.strip()
  117. return json.loads(json_text)
  118. except Exception as e:
  119. print(f"解析响应失败: {e}")
  120. return {
  121. "相同部分": {},
  122. "增量部分": {},
  123. "score": 0.0,
  124. "score说明": f"解析失败: {str(e)}"
  125. }
  126. def _create_batch_agent(model_name: str) -> Agent:
  127. """创建批量匹配的 Agent
  128. Args:
  129. model_name: 模型名称
  130. Returns:
  131. Agent 实例
  132. """
  133. # 批量匹配的 System Prompt(在单个匹配基础上修改输出格式)
  134. batch_prompt = MATCH_SYSTEM_PROMPT.replace(
  135. "## 输出格式(严格JSON)",
  136. "## 输出格式(JSON数组)\n对每个 <B> 输出一个匹配结果:"
  137. ).replace(
  138. "```json\n{",
  139. "```json\n[{"
  140. ).replace(
  141. "}\n```",
  142. "}]\n```"
  143. ) + "\n\n**额外要求**:数组长度必须等于 <B> 的数量,顺序对应"
  144. agent = Agent(
  145. name="Batch Information Match Expert",
  146. instructions=batch_prompt,
  147. model=get_model(model_name),
  148. tools=[],
  149. )
  150. return agent
  151. async def _run_match_agent(
  152. agent: Agent,
  153. b_content: str,
  154. a_content: str,
  155. request_desc: str,
  156. b_context: str = "",
  157. a_context: str = ""
  158. ) -> str:
  159. """运行匹配 Agent 的公共逻辑
  160. Args:
  161. agent: Agent 实例
  162. b_content: B 的内容
  163. a_content: A 的内容
  164. request_desc: 请求描述(如"并输出 JSON 格式"或"并输出 JSON 数组格式")
  165. b_context: B 的上下文(可选)
  166. a_context: A 的上下文(可选)
  167. Returns:
  168. Agent 的原始输出
  169. """
  170. # 构建任务描述
  171. b_section = f"<B>\n{b_content}\n</B>"
  172. if b_context:
  173. b_section += f"\n\n<B_Context>\n{b_context}\n</B_Context>"
  174. a_section = f"<A>\n{a_content}\n</A>"
  175. if a_context:
  176. a_section += f"\n\n<A_Context>\n{a_context}\n</A_Context>"
  177. task_description = f"""## 本次分析任务
  178. {b_section}
  179. {a_section}
  180. 请严格按照系统提示中的要求分析 <B> 在 <A> 中的字面语义匹配关系,{request_desc}的结果。"""
  181. # 构造消息
  182. messages = [{
  183. "role": "user",
  184. "content": [
  185. {
  186. "type": "input_text",
  187. "text": task_description
  188. }
  189. ]
  190. }]
  191. # 使用 custom_span 追踪匹配过程
  192. # 截断显示内容,避免 span name 过长
  193. b_short = (b_content[:40] + "...") if len(b_content) > 40 else b_content
  194. a_short = (a_content[:40] + "...") if len(a_content) > 40 else a_content
  195. with custom_span(
  196. name=f"匹配分析: {b_short} in {a_short}",
  197. data={
  198. "B": b_content,
  199. "A": a_content,
  200. "B_Context": b_context if b_context else None,
  201. "A_Context": a_context if a_context else None,
  202. "模式": request_desc
  203. }
  204. ):
  205. # 运行 Agent
  206. result = await Runner.run(agent, input=messages)
  207. return result.final_output
  208. async def match_single(
  209. b_content: str,
  210. a_content: str,
  211. model_name: str,
  212. b_context: str = "",
  213. a_context: str = ""
  214. ) -> dict:
  215. """单个匹配:分析一个 B 在 A 中的匹配
  216. Args:
  217. b_content: B(待匹配)的内容
  218. a_content: A(上下文)的内容
  219. model_name: 使用的模型名称
  220. b_context: B 的补充上下文(可选,默认为空)
  221. a_context: A 的补充上下文(可选,默认为空)
  222. Returns:
  223. 匹配结果字典:{"相同部分": {}, "增量部分": {}, "score": 0.0, "score说明": ""}
  224. """
  225. try:
  226. # 创建 Agent
  227. agent = create_match_agent(model_name)
  228. # 运行匹配
  229. output = await _run_match_agent(
  230. agent, b_content, a_content, "并输出 JSON 格式",
  231. b_context=b_context, a_context=a_context
  232. )
  233. # 解析响应
  234. parsed_result = parse_match_response(output)
  235. return parsed_result
  236. except Exception as e:
  237. return {
  238. "相同部分": {},
  239. "增量部分": {},
  240. "score": 0.0,
  241. "score说明": f"匹配过程出错: {str(e)}"
  242. }
  243. async def match_batch(
  244. b_items: List[str],
  245. a_content: str,
  246. model_name: str,
  247. b_context: str = "",
  248. a_context: str = ""
  249. ) -> List[dict]:
  250. """批量匹配:分析多个 B 在 A 中的匹配(一次调用)
  251. Args:
  252. b_items: B列表(多个待匹配项)
  253. a_content: A(上下文)的内容
  254. model_name: 使用的模型名称
  255. b_context: B 的补充上下文(可选,默认为空)
  256. a_context: A 的补充上下文(可选,默认为空)
  257. Returns:
  258. 匹配结果列表:[{"相同部分": {}, "增量部分": {}, "score": 0.0, "score说明": ""}, ...]
  259. """
  260. try:
  261. # 创建批量匹配 Agent
  262. agent = _create_batch_agent(model_name)
  263. # 构建 B 列表字符串
  264. b_list_str = "\n".join([f"- {item}" for item in b_items])
  265. # 运行匹配
  266. output = await _run_match_agent(
  267. agent, b_list_str, a_content, "并输出 JSON 数组格式",
  268. b_context=b_context, a_context=a_context
  269. )
  270. # 解析响应(期望是数组)
  271. parsed_result = parse_match_response(output)
  272. # 如果返回的是数组,直接返回;如果是单个对象,包装成数组
  273. if isinstance(parsed_result, list):
  274. return parsed_result
  275. else:
  276. return [parsed_result]
  277. except Exception as e:
  278. # 返回错误信息(为每个 B 创建一个错误条目)
  279. return [{
  280. "相同部分": {},
  281. "增量部分": {},
  282. "score": 0.0,
  283. "score说明": f"匹配过程出错: {str(e)}"
  284. } for _ in b_items]