match_analyzer.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  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. - 同一概念:可以比较上下位/同义关系
  143. - 不同概念:标记为无关
  144. 2. **确定关系类型**(仅对同一概念)
  145. - **同义/近义**:在各自上下文中的含义基本相同
  146. - **上位词**:B在其上下文中的含义 比 A在其上下文中的含义 更抽象、更上位(B ⊇ A)
  147. - **下位词**:B在其上下文中的含义 比 A在其上下文中的含义 更具体、更下位(B ⊆ A)
  148. - **无关**:不同概念,无法比较
  149. 3. **计算匹配分数**
  150. - 关系得分:同义=1.0, 上位词=0.6, 下位词=0.4, 无关=0.0
  151. - 只基于实质语义匹配计算score
  152. - 计算公式:
  153. ```
  154. score = Σ(实质语义的关系得分) / 实质语义总数
  155. ```
  156. - 在score说明中,可以提及形式语义的差异作为补充参考
  157. ---
  158. ## 评分标准(0-1分)
  159. **语义匹配度评分:**
  160. - **0.9-1.0**:<B> 的核心语义与名称/定义几乎完全对应
  161. - **0.7-0.8**:大部分核心语义匹配,少量增量
  162. - **0.5-0.6**:部分核心语义匹配,有一定增量
  163. - **0.3-0.4**:少量语义匹配,大部分概念不同
  164. - **0.1-0.2**:几乎无语义匹配,仅有弱关联
  165. - **0.0**:完全无关
  166. **重要原则:**
  167. - 关注有价值的语义片段,而非孤立的字面词语
  168. - 考虑概念之间的语义关系,不仅是字面匹配
  169. ---
  170. ## 输出格式(严格JSON)
  171. ```json
  172. {
  173. "B语义分析": {
  174. "实质": {
  175. "语义片段1": "说明"
  176. },
  177. "形式": {
  178. "语义片段2": "说明",
  179. "语义片段3": "说明"
  180. }
  181. },
  182. "A语义分析": {
  183. "实质": {
  184. "语义片段1": "说明",
  185. "语义片段2": "说明"
  186. },
  187. "形式": {
  188. "语义片段3": "说明"
  189. }
  190. },
  191. "匹配关系": [
  192. {
  193. "B语义": "语义片段1",
  194. "B语义说明": "从B实质语义分析中复述该片段的说明",
  195. "A语义": "语义片段1",
  196. "A语义说明": "从A实质语义分析中复述该片段的说明",
  197. "是否同一概念": true,
  198. "关系": "下位词",
  199. "说明": "说明两者的关系"
  200. },
  201. {
  202. "B语义": "语义片段2",
  203. "B语义说明": "从B实质语义分析中复述该片段的说明",
  204. "A语义": "语义片段2",
  205. "A语义说明": "从A实质语义分析中复述该片段的说明",
  206. "是否同一概念": false,
  207. "关系": "无关",
  208. "说明": "说明为什么无关"
  209. }
  210. ],
  211. "score": 0.4,
  212. "score说明": "只基于实质语义计算。B有2个实质语义,第1个是A的下位词(0.4),第2个无关(0.0),最终score = (0.4 + 0.0) / 2 = 0.4。形式语义差异:B有'修饰词x',A有'修饰词y',作为补充参考。"
  213. }
  214. ```
  215. **输出要求**:
  216. 1. 必须严格按照上述JSON格式输出
  217. 2. **B语义分析**:必须包含"实质"、"形式"两个字段,其中实质通常为1-2个核心概念
  218. 3. **A语义分析**:必须包含"实质"、"形式"两个字段
  219. 4. **实质和形式**:都是字典格式,key是语义片段,value是对该片段的说明
  220. 5. **匹配关系**:数组格式,**只包含实质语义的匹配**,字段顺序为:B语义 → B语义说明 → A语义 → A语义说明 → 是否同一概念 → 关系 → 说明
  221. 6. **B语义说明和A语义说明**:必须从前面的实质语义分析中复述,保持一致
  222. 7. **是否同一概念**:布尔值,true或false
  223. 8. **关系**:必须是以下之一:"同义/近义"、"上位词"、"下位词"、"无关"
  224. 9. **score**:0-1之间的浮点数,保留2位小数,只基于实质语义匹配计算
  225. 10. **score说明**:说明分数的计算依据,可以提及形式语义的差异作为补充
  226. """.strip()
  227. def create_match_agent(model_name: str) -> Agent:
  228. """创建信息匹配分析的 Agent
  229. Args:
  230. model_name: 模型名称
  231. Returns:
  232. Agent 实例
  233. """
  234. agent = Agent(
  235. name="Information Match Expert",
  236. instructions=MATCH_SYSTEM_PROMPT,
  237. model=get_model(model_name),
  238. tools=[],
  239. )
  240. return agent
  241. def calculate_score_by_code(match_result: dict) -> float:
  242. """根据匹配关系用代码计算score(只基于实质语义)
  243. Args:
  244. match_result: 匹配结果字典,包含 B语义分析、A语义分析、匹配关系
  245. Returns:
  246. 计算得出的score(0-1之间)
  247. Raises:
  248. ValueError: 当匹配关系为空或数据不完整时
  249. """
  250. # 关系得分映射
  251. RELATION_SCORES = {
  252. "同义/近义": 1.0,
  253. "上位词": 0.6,
  254. "下位词": 0.4,
  255. "无关": 0.0
  256. }
  257. b_semantics = match_result.get("B语义分析", {})
  258. match_relations = match_result.get("匹配关系", [])
  259. if not match_relations:
  260. raise ValueError("匹配关系为空,无法计算score_by_code")
  261. if not b_semantics:
  262. raise ValueError("B语义分析为空,无法计算score_by_code")
  263. # 提取B的实质语义片段
  264. b_essence = set(b_semantics.get("实质", {}).keys())
  265. if not b_essence:
  266. raise ValueError("B语义分析中实质为空,无法计算score_by_code")
  267. # 只计算实质语义的得分
  268. essence_scores = []
  269. for relation in match_relations:
  270. b_semantic = relation.get("B语义", "")
  271. relation_type = relation.get("关系", "无关")
  272. score = RELATION_SCORES.get(relation_type, 0.0)
  273. # 验证该语义确实属于实质
  274. if b_semantic in b_essence:
  275. essence_scores.append(score)
  276. else:
  277. # 如果匹配关系中包含了非实质语义,报错
  278. raise ValueError(f"匹配关系中包含非实质语义: {b_semantic}")
  279. if not essence_scores:
  280. raise ValueError("匹配关系中没有实质语义,无法计算score_by_code")
  281. # 只基于实质语义计算平均分
  282. final_score = sum(essence_scores) / len(essence_scores)
  283. return round(final_score, 2)
  284. def parse_match_response(response_content: str) -> dict:
  285. """解析匹配响应
  286. Args:
  287. response_content: Agent 返回的响应内容
  288. Returns:
  289. 解析后的字典
  290. """
  291. try:
  292. # 如果响应包含在 markdown 代码块中,提取 JSON 部分
  293. if "```json" in response_content:
  294. json_start = response_content.index("```json") + 7
  295. json_end = response_content.index("```", json_start)
  296. json_text = response_content[json_start:json_end].strip()
  297. elif "```" in response_content:
  298. json_start = response_content.index("```") + 3
  299. json_end = response_content.index("```", json_start)
  300. json_text = response_content[json_start:json_end].strip()
  301. else:
  302. json_text = response_content.strip()
  303. return json.loads(json_text)
  304. except Exception as e:
  305. print(f"解析响应失败: {e}")
  306. return {
  307. "相同部分": {},
  308. "增量部分": {},
  309. "score": 0.0,
  310. "score说明": f"解析失败: {str(e)}"
  311. }
  312. def _create_batch_agent(model_name: str) -> Agent:
  313. """创建批量匹配的 Agent
  314. Args:
  315. model_name: 模型名称
  316. Returns:
  317. Agent 实例
  318. """
  319. # 批量匹配的 System Prompt(在单个匹配基础上修改输出格式)
  320. batch_prompt = MATCH_SYSTEM_PROMPT.replace(
  321. "## 输出格式(严格JSON)",
  322. "## 输出格式(JSON数组)\n对每个 <B> 输出一个匹配结果:"
  323. ).replace(
  324. "```json\n{",
  325. "```json\n[{"
  326. ).replace(
  327. "}\n```",
  328. "}]\n```"
  329. ) + "\n\n**额外要求**:数组长度必须等于 <B> 的数量,顺序对应"
  330. agent = Agent(
  331. name="Batch Information Match Expert",
  332. instructions=batch_prompt,
  333. model=get_model(model_name),
  334. tools=[],
  335. )
  336. return agent
  337. async def _run_match_agent(
  338. agent: Agent,
  339. b_content: str,
  340. a_content: str,
  341. request_desc: str,
  342. b_context: str = "",
  343. a_context: str = ""
  344. ) -> str:
  345. """运行匹配 Agent 的公共逻辑
  346. Args:
  347. agent: Agent 实例
  348. b_content: B 的内容
  349. a_content: A 的内容
  350. request_desc: 请求描述(如"并输出 JSON 格式"或"并输出 JSON 数组格式")
  351. b_context: B 的上下文(可选)
  352. a_context: A 的上下文(可选)
  353. Returns:
  354. Agent 的原始输出
  355. """
  356. # 构建任务描述
  357. b_section = f"<B>\n{b_content}\n</B>"
  358. if b_context:
  359. b_section += f"\n\n<B_Context>\n{b_context}\n</B_Context>"
  360. a_section = f"<A>\n{a_content}\n</A>"
  361. if a_context:
  362. a_section += f"\n\n<A_Context>\n{a_context}\n</A_Context>"
  363. task_description = f"""## 本次分析任务
  364. {b_section}
  365. {a_section}
  366. 请严格按照系统提示中的要求分析 <B> 在 <A> 中的字面语义匹配关系,{request_desc}的结果。"""
  367. # 构造消息
  368. messages = [{
  369. "role": "user",
  370. "content": [
  371. {
  372. "type": "input_text",
  373. "text": task_description
  374. }
  375. ]
  376. }]
  377. # 使用 custom_span 追踪匹配过程
  378. # 截断显示内容,避免 span name 过长
  379. b_short = (b_content[:40] + "...") if len(b_content) > 40 else b_content
  380. a_short = (a_content[:40] + "...") if len(a_content) > 40 else a_content
  381. with custom_span(
  382. name=f"匹配分析: {b_short} in {a_short}",
  383. data={
  384. "B": b_content,
  385. "A": a_content,
  386. "B_Context": b_context if b_context else None,
  387. "A_Context": a_context if a_context else None,
  388. "模式": request_desc
  389. }
  390. ):
  391. # 运行 Agent
  392. result = await Runner.run(agent, input=messages)
  393. return result.final_output
  394. async def match_single(
  395. b_content: str,
  396. a_content: str,
  397. model_name: str,
  398. b_context: str = "",
  399. a_context: str = ""
  400. ) -> dict:
  401. """单个匹配:分析一个 B 在 A 中的匹配
  402. Args:
  403. b_content: B(待匹配)的内容
  404. a_content: A(上下文)的内容
  405. model_name: 使用的模型名称
  406. b_context: B 的补充上下文(可选,默认为空)
  407. a_context: A 的补充上下文(可选,默认为空)
  408. Returns:
  409. 匹配结果字典:{"相同部分": {}, "增量部分": {}, "score": 0.0, "score说明": ""}
  410. """
  411. try:
  412. # 创建 Agent
  413. agent = create_match_agent(model_name)
  414. # 运行匹配
  415. output = await _run_match_agent(
  416. agent, b_content, a_content, "并输出 JSON 格式",
  417. b_context=b_context, a_context=a_context
  418. )
  419. # 解析响应
  420. parsed_result = parse_match_response(output)
  421. return parsed_result
  422. except Exception as e:
  423. return {
  424. "相同部分": {},
  425. "增量部分": {},
  426. "score": 0.0,
  427. "score说明": f"匹配过程出错: {str(e)}"
  428. }
  429. async def match_batch(
  430. b_items: List[str],
  431. a_content: str,
  432. model_name: str,
  433. b_context: str = "",
  434. a_context: str = ""
  435. ) -> List[dict]:
  436. """批量匹配:分析多个 B 在 A 中的匹配(一次调用)
  437. Args:
  438. b_items: B列表(多个待匹配项)
  439. a_content: A(上下文)的内容
  440. model_name: 使用的模型名称
  441. b_context: B 的补充上下文(可选,默认为空)
  442. a_context: A 的补充上下文(可选,默认为空)
  443. Returns:
  444. 匹配结果列表:[{"相同部分": {}, "增量部分": {}, "score": 0.0, "score说明": ""}, ...]
  445. """
  446. try:
  447. # 创建批量匹配 Agent
  448. agent = _create_batch_agent(model_name)
  449. # 构建 B 列表字符串
  450. b_list_str = "\n".join([f"- {item}" for item in b_items])
  451. # 运行匹配
  452. output = await _run_match_agent(
  453. agent, b_list_str, a_content, "并输出 JSON 数组格式",
  454. b_context=b_context, a_context=a_context
  455. )
  456. # 解析响应(期望是数组)
  457. parsed_result = parse_match_response(output)
  458. # 如果返回的是数组,直接返回;如果是单个对象,包装成数组
  459. if isinstance(parsed_result, list):
  460. return parsed_result
  461. else:
  462. return [parsed_result]
  463. except Exception as e:
  464. # 返回错误信息(为每个 B 创建一个错误条目)
  465. return [{
  466. "相同部分": {},
  467. "增量部分": {},
  468. "score": 0.0,
  469. "score说明": f"匹配过程出错: {str(e)}"
  470. } for _ in b_items]
  471. async def match_with_definition(
  472. b_content: str,
  473. element_name: str,
  474. element_definition: str,
  475. model_name: str,
  476. b_context: str = "",
  477. a_context: str = ""
  478. ) -> dict:
  479. """名称+定义匹配:分析 B 分别与名称、定义的语义匹配关系
  480. Args:
  481. b_content: B(待匹配)的内容
  482. element_name: 要素名称
  483. element_definition: 要素定义
  484. model_name: 使用的模型名称
  485. b_context: B 的补充上下文(可选,默认为空)
  486. a_context: A 的补充上下文(可选,默认为空)
  487. Returns:
  488. 匹配结果字典:{"名称匹配": {...}, "定义匹配": {...}}
  489. """
  490. try:
  491. # 创建使用新 prompt 的 Agent
  492. agent = Agent(
  493. name="Name+Definition Match Expert",
  494. instructions=MATCH_WITH_DEFINITION_PROMPT,
  495. model=get_model(model_name),
  496. tools=[],
  497. )
  498. # A 内容只包含名称,定义放在 A_Context 中
  499. a_content = element_name
  500. # 将定义添加到 A_Context(如果已有 a_context,则追加)
  501. if element_definition:
  502. definition_context = f"定义:{element_definition}"
  503. if a_context:
  504. full_a_context = f"{a_context}\n{definition_context}"
  505. else:
  506. full_a_context = definition_context
  507. else:
  508. full_a_context = a_context
  509. # 运行匹配
  510. output = await _run_match_agent(
  511. agent, b_content, a_content, "并输出 JSON 格式",
  512. b_context=b_context, a_context=full_a_context
  513. )
  514. # 解析响应
  515. parsed_result = parse_match_response(output)
  516. # 验证返回格式
  517. required_fields = ["B语义分析", "A语义分析", "匹配关系", "score", "score说明"]
  518. missing_fields = [f for f in required_fields if f not in parsed_result]
  519. if missing_fields:
  520. return {
  521. "B语义分析": {
  522. "实质": {},
  523. "形式": {}
  524. },
  525. "A语义分析": {
  526. "实质": {},
  527. "形式": {}
  528. },
  529. "匹配关系": [],
  530. "score": 0.0,
  531. "score_by_code": 0.0,
  532. "score说明": f"解析失败:缺少字段 {missing_fields}"
  533. }
  534. # 计算程序score(如果数据不完整会抛出异常)
  535. score_by_code = calculate_score_by_code(parsed_result)
  536. parsed_result["score_by_code"] = score_by_code
  537. return parsed_result
  538. except Exception as e:
  539. return {
  540. "B语义分析": {
  541. "实质": {},
  542. "形式": {}
  543. },
  544. "A语义分析": {
  545. "实质": {},
  546. "形式": {}
  547. },
  548. "匹配关系": [],
  549. "score": 0.0,
  550. "score_by_code": 0.0,
  551. "score说明": f"匹配过程出错: {str(e)}"
  552. }