match_analyzer.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749
  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. # ========== System Prompt for Name+Definition Match ==========
  81. MATCH_WITH_DEFINITION_PROMPT = """
  82. # 任务
  83. 分析 <B> 与 <A> 的语义匹配关系。<A> 包含"名称"和"定义",定义作为辅助上下文帮助理解名称的含义。
  84. ## 输入说明
  85. - **<B></B>**: 待匹配的内容(灵感点)
  86. - **<A></A>**: 名称(必选)
  87. - **<B_Context></B_Context>**: B 的补充上下文(可选)
  88. - **<A_Context></A_Context>**: A 的补充上下文,包含定义等信息(可选)
  89. **重要**:A_Context 中的定义仅用于辅助理解名称 <A> 的含义,主要分析 <B> 与 <A> 的匹配关系。
  90. ## 分析方法
  91. ### 核心原则:语义概念匹配
  92. 先独立分析 <B> 和 <A> 的语义,再进行匹配,避免强行解释。
  93. ### 分析步骤
  94. **第一步:独立分析 <B> 的语义**
  95. 对 <B>(灵感点)进行语义分解,提取:
  96. **拆分步骤**:
  97. 1. 先识别出所有语义成分(主体、动作/状态、属性、修饰等)
  98. 2. 对每个成分做"去除测试":
  99. - 如果去掉这个成分后,剩余部分的核心语义**没有发生本质变化** → 该成分是**形式**
  100. - 如果去掉这个成分后,剩余部分的核心语义**发生了本质变化** → 该成分是**实质**
  101. 3. 将成分分类到实质或形式中,每个成分只能出现在一个类别
  102. - **实质语义**:核心概念、本质内容(字典格式)
  103. - 通常为1-2个核心概念
  104. - 不要过度拆分复合词,保持核心概念的完整性
  105. - key:语义片段(可以是词组)
  106. - value:**客观地说明这个片段在B的上下文中的具体含义**
  107. - **形式语义**:具体限定、修饰、程度等(字典格式)
  108. - 包括:主体限定词、属性修饰词、程度副词、时间/空间限定等
  109. - key:语义片段
  110. - value:**说明在B的上下文中修饰/限定什么,如何修饰/限定**
  111. **关键原则**:
  112. - 每个语义片段只能出现在一个类别中,不要重复
  113. - 表示主体、场景、对象的限定词通常属于形式,不要合并到实质中
  114. - 不要孤立地解释词语本身,要说明它**在当前B的上下文中**的具体含义
  115. - 不要过度推理,保持客观:描述B字面上说的是什么,而不是可能引发什么
  116. **注意**:纯粹分析 <B> 本身的语义,不要考虑与 <A> 的关系。
  117. **第二步:独立分析 <A> 的语义**
  118. 对 <A>(名称)进行语义分解,提取:
  119. - **实质语义**:核心概念、本质内容(字典格式)
  120. - **判断标准:单独拿出来看,语义跟原来相比没有发生本质变化**
  121. - key:语义片段(可以是词组,保持完整的核心概念)
  122. - value:**客观地说明这个片段在A的上下文中的具体含义和作用**
  123. - **形式语义**:具体限定、修饰等(字典格式)
  124. - **判断标准:去掉后,核心概念本身不会丢失,只是失去了限定、修饰等细节**
  125. - key:语义片段
  126. - value:**说明在A的上下文中修饰/限定什么,如何修饰/限定**
  127. **关键**:
  128. - 可以参考<A_Context>中的定义来辅助理解名称的含义,但只分析名称本身
  129. - 同样要说明语义片段**在当前A的上下文中**的具体含义,不要孤立解释
  130. - 不要过度拆分复合词,保持核心概念的完整性
  131. **注意**:纯粹分析 <A> 本身的语义,不要考虑与 <B> 的关系。
  132. **第三步:建立匹配关系**
  133. **只匹配实质语义**,形式语义作为补充信息不参与匹配。
  134. 遍历 <B> 的每个**实质语义**片段,找到与 <A> 中最接近的**实质语义**,判断关系:
  135. **重要**:
  136. - 在匹配关系中必须复述B语义说明和A语义说明,直接从前面的语义分析中复制过来
  137. - 形式语义(修饰词、限定词等)不单独匹配,因为它们脱离实质语义就失去意义
  138. - 形式语义的差异可以在score说明中提及,作为参考信息
  139. 1. **判断是否可比较**(基于抽象层级和本体范畴)
  140. - **参考B语义说明和A语义说明**,判断两个语义是否处于可比较的层级
  141. - **不可比较的情况**(直接标记为"无关"):
  142. - **元概念 vs 具体概念**:元概念(如"概念"、"内容"、"事物"、"要素"、"方面"、"维度"、"属性"等用于描述其他概念的抽象术语)不能与具体概念(如"期待"、"行为"、"猫咪"等实际指代特定事物的词)比较
  143. - **抽象层级相差过大**:相差2级以上(如"存在" vs "猫的行为")
  144. - **本体范畴完全不同**:不同的基本范畴(如数学概念 vs 生物实体、物理对象 vs 心理状态等,除非有明确的跨域映射关系)
  145. - **可比较的情况**:
  146. - 同级具体概念(如"期待" vs "焦虑")
  147. - 有明确上下位关系(如"情感" vs "期待")
  148. - 有交叉或关联关系(如"工作压力" vs "假期期待")
  149. - **输出要求**:
  150. - 如果不可比较:输出 "是否可比较": false, "不可比较原因": "说明为什么不可比较", "关系": "无关"
  151. - 如果可比较:输出 "是否可比较": true, 继续判断关系类型
  152. 2. **确定关系类型**(仅对可比较的语义)
  153. - **同义**:在各自上下文中的含义完全相同,可以互相替换
  154. - **近义**:在各自上下文中的含义非常接近,但有细微差异
  155. - **上位词**:B在其上下文中的含义 比 A在其上下文中的含义 更抽象、更上位(B ⊇ A)
  156. - **下位词**:B在其上下文中的含义 比 A在其上下文中的含义 更具体、更下位(B ⊆ A)
  157. - **无关**:虽然可比较,但没有明确的语义关系
  158. 3. **计算规则分数**
  159. - 关系得分:同义=1.0, 近义=0.9, 上位词=0.6, 下位词=0.4, 无关=0.0
  160. - 只基于实质语义匹配计算规则分数
  161. - 计算公式:
  162. ```
  163. 规则分数 = Σ(实质语义的关系得分) / 实质语义总数
  164. ```
  165. 4. **评估整体相关性**
  166. - 在完成上述匹配关系分析后,从宏观角度评估 <B> 和 <A> 的整体相关性
  167. - 考虑因素:
  168. - 语义距离:核心概念之间的距离
  169. - 上下文契合度:在各自上下文中的关联程度
  170. - 表达意图的一致性
  171. - **评分标准**(严格遵守):
  172. - **1.0**:<B> 和 <A> 完全同义,可以互相替换,表达完全相同的含义
  173. - **0.8-0.9**:高度相关,核心语义非常接近,只有细微差异
  174. - **0.6-0.7**:明显相关,有明确的语义重叠或上下位关系
  175. - **0.4-0.5**:一般相关,有一定的语义关联或主题相关性
  176. - **0.2-0.3**:弱相关,只有间接关联(如因果、触发等)
  177. - **0.0-0.1**:几乎无关或完全无关
  178. - 给出相关性分数(0-1之间的浮点数,保留2位小数)
  179. - 给出相关性说明
  180. 5. **计算最终分数**
  181. - 最终分数 = 0.8 × 规则分数 + 0.2 × 相关性分数
  182. - 在score说明中说明计算过程,可以提及形式语义的差异作为补充参考
  183. ---
  184. ## 评分标准(0-1分)
  185. **语义匹配度评分:**
  186. - **0.9-1.0**:<B> 的核心语义与名称/定义几乎完全对应
  187. - **0.7-0.8**:大部分核心语义匹配,少量增量
  188. - **0.5-0.6**:部分核心语义匹配,有一定增量
  189. - **0.3-0.4**:少量语义匹配,大部分概念不同
  190. - **0.1-0.2**:几乎无语义匹配,仅有弱关联
  191. - **0.0**:完全无关
  192. **重要原则:**
  193. - 关注有价值的语义片段,而非孤立的字面词语
  194. - 考虑概念之间的语义关系,不仅是字面匹配
  195. ---
  196. ## 输出格式(严格JSON)
  197. ```json
  198. {
  199. "B语义分析": {
  200. "实质": {
  201. "语义片段1": "说明"
  202. },
  203. "形式": {
  204. "语义片段2": "说明",
  205. "语义片段3": "说明"
  206. }
  207. },
  208. "A语义分析": {
  209. "实质": {
  210. "语义片段1": "说明",
  211. "语义片段2": "说明"
  212. },
  213. "形式": {
  214. "语义片段3": "说明"
  215. }
  216. },
  217. "匹配关系": [
  218. {
  219. "B语义": "语义片段1",
  220. "B语义说明": "从B实质语义分析中复述该片段的说明",
  221. "A语义": "语义片段1",
  222. "A语义说明": "从A实质语义分析中复述该片段的说明",
  223. "是否可比较": true,
  224. "关系": "下位词",
  225. "说明": "说明两者的关系"
  226. },
  227. {
  228. "B语义": "语义片段2",
  229. "B语义说明": "从B实质语义分析中复述该片段的说明",
  230. "A语义": "概念",
  231. "A语义说明": "从A实质语义分析中复述该片段的说明",
  232. "是否可比较": false,
  233. "不可比较原因": "A是元概念(用于描述其他概念的术语),B是具体概念,抽象层级完全不同",
  234. "关系": "无关",
  235. "说明": "元概念不能与具体概念比较"
  236. }
  237. ],
  238. "规则分数": 0.2,
  239. "规则分数说明": "B有2个实质语义,第1个是A的下位词(0.4),第2个无关(0.0),规则分数 = (0.4 + 0.0) / 2 = 0.2",
  240. "相关性分数": 0.3,
  241. "相关性说明": "虽然有部分语义重叠,但整体上下文契合度较低,因为B强调具体事物,A强调抽象概念",
  242. "score": 0.22,
  243. "score说明": "最终分数 = 0.8 × 0.2 + 0.2 × 0.3 = 0.22。形式语义差异:B有'修饰词x',A有'修饰词y',作为补充参考。"
  244. }
  245. ```
  246. **输出要求**:
  247. 1. 必须严格按照上述JSON格式输出
  248. 2. **B语义分析**:必须包含"实质"、"形式"两个字段,其中实质通常为1-2个核心概念
  249. 3. **A语义分析**:必须包含"实质"、"形式"两个字段
  250. 4. **实质和形式**:都是字典格式,key是语义片段,value是对该片段的说明
  251. 5. **匹配关系**:数组格式,**只包含实质语义的匹配**,字段顺序为:
  252. - B语义 → B语义说明 → A语义 → A语义说明 → 是否可比较
  253. - 如果是否可比较=false:添加"不可比较原因"字段
  254. - 关系 → 说明
  255. 6. **B语义说明和A语义说明**:必须从前面的实质语义分析中复述,保持一致
  256. 7. **是否可比较**:布尔值,true或false
  257. 8. **不可比较原因**:字符串,仅在是否可比较=false时提供,说明为什么不可比较
  258. 9. **关系**:必须是以下之一:"同义"、"近义"、"上位词"、"下位词"、"无关"
  259. 10. **规则分数**:0-1之间的浮点数,保留2位小数,基于匹配关系计算
  260. 11. **规则分数说明**:说明规则分数的计算依据
  261. 12. **相关性分数**:0-1之间的浮点数,保留2位小数,整体相关性评估
  262. 13. **相关性说明**:说明相关性分数的判断依据
  263. 14. **score**:0-1之间的浮点数,保留2位小数,= 0.8 × 规则分数 + 0.2 × 相关性分数
  264. 15. **score说明**:说明最终分数的计算过程,可以提及形式语义的差异作为补充
  265. """.strip()
  266. def create_match_agent(model_name: str) -> Agent:
  267. """创建信息匹配分析的 Agent
  268. Args:
  269. model_name: 模型名称
  270. Returns:
  271. Agent 实例
  272. """
  273. agent = Agent(
  274. name="Information Match Expert",
  275. instructions=MATCH_SYSTEM_PROMPT,
  276. model=get_model(model_name),
  277. tools=[],
  278. )
  279. return agent
  280. def calculate_score_by_code(match_result: dict) -> float:
  281. """根据匹配关系和相关性分数用代码计算最终score
  282. 计算公式:score = 0.8 × 规则分数 + 0.2 × 相关性分数
  283. 其中规则分数基于匹配关系计算,相关性分数由LLM给出
  284. Args:
  285. match_result: 匹配结果字典,包含 B语义分析、匹配关系、相关性分数
  286. Returns:
  287. 计算得出的最终score(0-1之间)
  288. Raises:
  289. ValueError: 当数据不完整时
  290. """
  291. # 关系得分映射
  292. RELATION_SCORES = {
  293. "同义": 1.0,
  294. "近义": 0.9,
  295. "上位词": 0.6,
  296. "下位词": 0.4,
  297. "无关": 0.0
  298. }
  299. b_semantics = match_result.get("B语义分析", {})
  300. match_relations = match_result.get("匹配关系", [])
  301. relevance_score = match_result.get("相关性分数")
  302. # 验证必需字段
  303. if not match_relations:
  304. raise ValueError("匹配关系为空,无法计算score_by_code")
  305. if not b_semantics:
  306. raise ValueError("B语义分析为空,无法计算score_by_code")
  307. if relevance_score is None:
  308. raise ValueError("相关性分数为空,无法计算score_by_code")
  309. # 提取B的实质语义片段
  310. b_essence = set(b_semantics.get("实质", {}).keys())
  311. if not b_essence:
  312. raise ValueError("B语义分析中实质为空,无法计算score_by_code")
  313. # 计算规则分数:基于实质语义的匹配关系
  314. essence_scores = []
  315. for relation in match_relations:
  316. b_semantic = relation.get("B语义", "")
  317. relation_type = relation.get("关系", "无关")
  318. score = RELATION_SCORES.get(relation_type, 0.0)
  319. # 验证该语义确实属于实质
  320. if b_semantic in b_essence:
  321. essence_scores.append(score)
  322. else:
  323. # 如果匹配关系中包含了非实质语义,报错
  324. raise ValueError(f"匹配关系中包含非实质语义: {b_semantic}")
  325. if not essence_scores:
  326. raise ValueError("匹配关系中没有实质语义,无法计算score_by_code")
  327. # 计算规则分数(基于匹配关系)
  328. rule_score = sum(essence_scores) / len(essence_scores)
  329. # 计算最终分数:0.8 × 规则分数 + 0.2 × 相关性分数
  330. final_score = 0.8 * rule_score + 0.2 * relevance_score
  331. return round(final_score, 2)
  332. def parse_match_response(response_content: str) -> dict:
  333. """解析匹配响应
  334. Args:
  335. response_content: Agent 返回的响应内容
  336. Returns:
  337. 解析后的字典
  338. """
  339. try:
  340. # 如果响应包含在 markdown 代码块中,提取 JSON 部分
  341. if "```json" in response_content:
  342. json_start = response_content.index("```json") + 7
  343. json_end = response_content.index("```", json_start)
  344. json_text = response_content[json_start:json_end].strip()
  345. elif "```" in response_content:
  346. json_start = response_content.index("```") + 3
  347. json_end = response_content.index("```", json_start)
  348. json_text = response_content[json_start:json_end].strip()
  349. else:
  350. json_text = response_content.strip()
  351. return json.loads(json_text)
  352. except Exception as e:
  353. print(f"解析响应失败: {e}")
  354. return {
  355. "相同部分": {},
  356. "增量部分": {},
  357. "score": 0.0,
  358. "score说明": f"解析失败: {str(e)}"
  359. }
  360. def _create_batch_agent(model_name: str) -> Agent:
  361. """创建批量匹配的 Agent
  362. Args:
  363. model_name: 模型名称
  364. Returns:
  365. Agent 实例
  366. """
  367. # 批量匹配的 System Prompt(在单个匹配基础上修改输出格式)
  368. batch_prompt = MATCH_SYSTEM_PROMPT.replace(
  369. "## 输出格式(严格JSON)",
  370. "## 输出格式(JSON数组)\n对每个 <B> 输出一个匹配结果:"
  371. ).replace(
  372. "```json\n{",
  373. "```json\n[{"
  374. ).replace(
  375. "}\n```",
  376. "}]\n```"
  377. ) + "\n\n**额外要求**:数组长度必须等于 <B> 的数量,顺序对应"
  378. agent = Agent(
  379. name="Batch Information Match Expert",
  380. instructions=batch_prompt,
  381. model=get_model(model_name),
  382. tools=[],
  383. )
  384. return agent
  385. async def _run_match_agent(
  386. agent: Agent,
  387. b_content: str,
  388. a_content: str,
  389. request_desc: str,
  390. b_context: str = "",
  391. a_context: str = ""
  392. ) -> str:
  393. """运行匹配 Agent 的公共逻辑
  394. Args:
  395. agent: Agent 实例
  396. b_content: B 的内容
  397. a_content: A 的内容
  398. request_desc: 请求描述(如"并输出 JSON 格式"或"并输出 JSON 数组格式")
  399. b_context: B 的上下文(可选)
  400. a_context: A 的上下文(可选)
  401. Returns:
  402. Agent 的原始输出
  403. """
  404. # 构建任务描述
  405. b_section = f"<B>\n{b_content}\n</B>"
  406. if b_context:
  407. b_section += f"\n\n<B_Context>\n{b_context}\n</B_Context>"
  408. a_section = f"<A>\n{a_content}\n</A>"
  409. if a_context:
  410. a_section += f"\n\n<A_Context>\n{a_context}\n</A_Context>"
  411. task_description = f"""## 本次分析任务
  412. {b_section}
  413. {a_section}
  414. 请严格按照系统提示中的要求分析 <B> 在 <A> 中的字面语义匹配关系,{request_desc}的结果。"""
  415. # 构造消息
  416. messages = [{
  417. "role": "user",
  418. "content": [
  419. {
  420. "type": "input_text",
  421. "text": task_description
  422. }
  423. ]
  424. }]
  425. # 使用 custom_span 追踪匹配过程
  426. # 截断显示内容,避免 span name 过长
  427. b_short = (b_content[:40] + "...") if len(b_content) > 40 else b_content
  428. a_short = (a_content[:40] + "...") if len(a_content) > 40 else a_content
  429. with custom_span(
  430. name=f"匹配分析: {b_short} in {a_short}",
  431. data={
  432. "B": b_content,
  433. "A": a_content,
  434. "B_Context": b_context if b_context else None,
  435. "A_Context": a_context if a_context else None,
  436. "模式": request_desc
  437. }
  438. ):
  439. # 运行 Agent
  440. result = await Runner.run(agent, input=messages)
  441. return result.final_output
  442. async def match_single(
  443. b_content: str,
  444. a_content: str,
  445. model_name: str,
  446. b_context: str = "",
  447. a_context: str = ""
  448. ) -> dict:
  449. """单个匹配:分析一个 B 在 A 中的匹配
  450. Args:
  451. b_content: B(待匹配)的内容
  452. a_content: A(上下文)的内容
  453. model_name: 使用的模型名称
  454. b_context: B 的补充上下文(可选,默认为空)
  455. a_context: A 的补充上下文(可选,默认为空)
  456. Returns:
  457. 匹配结果字典:{"相同部分": {}, "增量部分": {}, "score": 0.0, "score说明": ""}
  458. """
  459. try:
  460. # 创建 Agent
  461. agent = create_match_agent(model_name)
  462. # 运行匹配
  463. output = await _run_match_agent(
  464. agent, b_content, a_content, "并输出 JSON 格式",
  465. b_context=b_context, a_context=a_context
  466. )
  467. # 解析响应
  468. parsed_result = parse_match_response(output)
  469. return parsed_result
  470. except Exception as e:
  471. return {
  472. "相同部分": {},
  473. "增量部分": {},
  474. "score": 0.0,
  475. "score说明": f"匹配过程出错: {str(e)}"
  476. }
  477. async def match_batch(
  478. b_items: List[str],
  479. a_content: str,
  480. model_name: str,
  481. b_context: str = "",
  482. a_context: str = ""
  483. ) -> List[dict]:
  484. """批量匹配:分析多个 B 在 A 中的匹配(一次调用)
  485. Args:
  486. b_items: B列表(多个待匹配项)
  487. a_content: A(上下文)的内容
  488. model_name: 使用的模型名称
  489. b_context: B 的补充上下文(可选,默认为空)
  490. a_context: A 的补充上下文(可选,默认为空)
  491. Returns:
  492. 匹配结果列表:[{"相同部分": {}, "增量部分": {}, "score": 0.0, "score说明": ""}, ...]
  493. """
  494. try:
  495. # 创建批量匹配 Agent
  496. agent = _create_batch_agent(model_name)
  497. # 构建 B 列表字符串
  498. b_list_str = "\n".join([f"- {item}" for item in b_items])
  499. # 运行匹配
  500. output = await _run_match_agent(
  501. agent, b_list_str, a_content, "并输出 JSON 数组格式",
  502. b_context=b_context, a_context=a_context
  503. )
  504. # 解析响应(期望是数组)
  505. parsed_result = parse_match_response(output)
  506. # 如果返回的是数组,直接返回;如果是单个对象,包装成数组
  507. if isinstance(parsed_result, list):
  508. return parsed_result
  509. else:
  510. return [parsed_result]
  511. except Exception as e:
  512. # 返回错误信息(为每个 B 创建一个错误条目)
  513. return [{
  514. "相同部分": {},
  515. "增量部分": {},
  516. "score": 0.0,
  517. "score说明": f"匹配过程出错: {str(e)}"
  518. } for _ in b_items]
  519. async def match_with_definition(
  520. b_content: str,
  521. element_name: str,
  522. element_definition: str,
  523. model_name: str,
  524. b_context: str = "",
  525. a_context: str = ""
  526. ) -> dict:
  527. """名称+定义匹配:分析 B 分别与名称、定义的语义匹配关系
  528. Args:
  529. b_content: B(待匹配)的内容
  530. element_name: 要素名称
  531. element_definition: 要素定义
  532. model_name: 使用的模型名称
  533. b_context: B 的补充上下文(可选,默认为空)
  534. a_context: A 的补充上下文(可选,默认为空)
  535. Returns:
  536. 匹配结果字典:{"名称匹配": {...}, "定义匹配": {...}}
  537. """
  538. try:
  539. # 创建使用新 prompt 的 Agent
  540. agent = Agent(
  541. name="Name+Definition Match Expert",
  542. instructions=MATCH_WITH_DEFINITION_PROMPT,
  543. model=get_model(model_name),
  544. tools=[],
  545. )
  546. # A 内容只包含名称,定义放在 A_Context 中
  547. a_content = element_name
  548. # 将定义添加到 A_Context(如果已有 a_context,则追加)
  549. if element_definition:
  550. definition_context = f"定义:{element_definition}"
  551. if a_context:
  552. full_a_context = f"{a_context}\n{definition_context}"
  553. else:
  554. full_a_context = definition_context
  555. else:
  556. full_a_context = a_context
  557. # 运行匹配
  558. output = await _run_match_agent(
  559. agent, b_content, a_content, "并输出 JSON 格式",
  560. b_context=b_context, a_context=full_a_context
  561. )
  562. # 解析响应
  563. parsed_result = parse_match_response(output)
  564. # 验证返回格式
  565. required_fields = [
  566. "B语义分析", "A语义分析", "匹配关系",
  567. "规则分数", "规则分数说明",
  568. "相关性分数", "相关性说明",
  569. "score", "score说明"
  570. ]
  571. missing_fields = [f for f in required_fields if f not in parsed_result]
  572. if missing_fields:
  573. return {
  574. "B语义分析": {
  575. "实质": {},
  576. "形式": {}
  577. },
  578. "A语义分析": {
  579. "实质": {},
  580. "形式": {}
  581. },
  582. "匹配关系": [],
  583. "规则分数": 0.0,
  584. "规则分数说明": f"解析失败:缺少字段 {missing_fields}",
  585. "相关性分数": 0.0,
  586. "相关性说明": f"解析失败:缺少字段 {missing_fields}",
  587. "score": 0.0,
  588. "score_by_code": 0.0,
  589. "score说明": f"解析失败:缺少字段 {missing_fields}"
  590. }
  591. # 计算程序score(如果数据不完整会抛出异常)
  592. score_by_code = calculate_score_by_code(parsed_result)
  593. parsed_result["score_by_code"] = score_by_code
  594. return parsed_result
  595. except Exception as e:
  596. return {
  597. "B语义分析": {
  598. "实质": {},
  599. "形式": {}
  600. },
  601. "A语义分析": {
  602. "实质": {},
  603. "形式": {}
  604. },
  605. "匹配关系": [],
  606. "规则分数": 0.0,
  607. "规则分数说明": f"匹配过程出错: {str(e)}",
  608. "相关性分数": 0.0,
  609. "相关性说明": f"匹配过程出错: {str(e)}",
  610. "score": 0.0,
  611. "score_by_code": 0.0,
  612. "score说明": f"匹配过程出错: {str(e)}"
  613. }