test_link_placement.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import unittest
  2. from unittest.mock import Mock
  3. from src.step3_question_answer.finance_display import add_detail_links, answer_link_position
  4. from src.step3_question_answer.knowledge_answers import synthesize_answers, LINK_RULE, synthesis_prompts
  5. def entry(index, kind='service_guide'):
  6. return {'source_id': str(index), 'title': f'合成事项{index}', 'knowledge_type': kind,
  7. 'detail_url': f'https://example.invalid/product/{index}',
  8. 'service_link': {'url': f'https://example.invalid/service/{index}',
  9. 'title': f'合成事项{index}', 'label': '查看办事指南'}}
  10. class LinkPlacementTests(unittest.TestCase):
  11. def test_original_and_other_question_bind_model_anchors_without_extra_calls(self):
  12. for question in ('我想开奶茶店,有什么惠企政策', '经营许可延续要多久'):
  13. records = [entry(0), entry(1, 'finance')]
  14. for record in records:
  15. record.update(answer='合成支持说明。', citations=[{'path': '/data/body', 'quote': '合成支持说明。'}])
  16. model = Mock(return_value={'text': '### 支持\n\n事项办理说明。\n\n产品条件说明。',
  17. 'source_ids': ['0', '1'], 'link_sections': [
  18. {'source_id': '0', 'heading': '支持', 'after': '事项办理说明。'},
  19. {'source_id': '1', 'heading': '支持', 'after': '产品条件说明。'}]})
  20. result = synthesize_answers(model, question, records, [], 'policy_overview')
  21. model.assert_called_once()
  22. self.assertIn(LINK_RULE, model.call_args.args[0])
  23. self.assertEqual(model.call_args.args[1]['question'], question)
  24. text = result['text']
  25. self.assertLess(text.index('事项办理说明。'), text.index('/service/0'))
  26. self.assertLess(text.index('/service/0'), text.index('产品条件说明。'))
  27. self.assertLess(text.index('产品条件说明。'), text.index('/product/1'))
  28. prompt, _ = synthesis_prompts({'question': '普通咨询', 'evidence': [{}]})
  29. self.assertNotIn(LINK_RULE, prompt)
  30. def test_channel_link_follows_explanation_and_precedes_materials(self):
  31. for channel, materials in (('**办理渠道**', '**申请材料**'),
  32. ('#### 办理渠道', '#### 申请材料')):
  33. body = f'### 合成事项0\n\n{channel}\n\n支持在线提交。\n\n{materials}\n\n提交申请表。'
  34. text, links = add_detail_links(body, [entry(0)], ['0'])
  35. pos = text.index('https://')
  36. self.assertGreater(pos, text.index('支持在线提交。'))
  37. self.assertLess(pos, text.index(materials))
  38. self.assertEqual(len(links), 1)
  39. def test_finance_and_service_anchors_follow_each_answer_in_shared_chapter(self):
  40. for count in (1, 3, 7):
  41. entries = [entry(i, 'finance' if i % 2 else 'service_guide') for i in range(count)]
  42. paragraphs = [f'第{i}项支持说明,限合成地区的对应主体。' for i in range(count)]
  43. body = '### 支持与办理\n\n' + '\n\n'.join(paragraphs) + '\n\n### 其他说明\n\n其他。'
  44. mapping = [{'source_id': str(i), 'heading': '支持与办理', 'after': p}
  45. for i, p in enumerate(paragraphs)]
  46. text, links = add_detail_links(body, entries, [str(i) for i in range(count)], mapping)
  47. self.assertEqual(len(links), count)
  48. for i, link in enumerate(links):
  49. pos = text.index(link['url'])
  50. self.assertGreater(pos, text.index(paragraphs[i]))
  51. following = paragraphs[i + 1] if i + 1 < count else '### 其他说明'
  52. self.assertLess(pos, text.index(following))
  53. again, again_links = add_detail_links(text, entries, [str(i) for i in range(count)], mapping)
  54. self.assertEqual((again, again_links), (text, links))
  55. def test_anchor_without_headings_keeps_each_list_answer_and_qualifier_intact(self):
  56. body = '- 第一项支持。限合成主体。\n- 第二项说明。\n\n补充说明。'
  57. text, _ = add_detail_links(body, [entry(0)], ['0'], [
  58. {'source_id': '0', 'heading': '', 'after': '第一项支持。'}])
  59. self.assertGreater(text.index('https://'), text.index('限合成主体。'))
  60. self.assertLess(text.index('https://'), text.index('第二项说明。'))
  61. def test_short_answer_early_inline_and_duplicate_markers_move_after_answer(self):
  62. for kind, prefix in (('finance', 'finance'), ('service_guide', 'service')):
  63. marker = f'[[{prefix}_detail:0]]'
  64. for body in (marker + '\n\n合成事项需要三个工作日。',
  65. '合成事项' + marker + '需要三个工作日。',
  66. '合成事项需要三个工作日。' + marker + marker):
  67. text, links = add_detail_links(body, [entry(0, kind)], ['0'], [])
  68. self.assertEqual(len(links), 1)
  69. self.assertGreater(text.index('https://'), text.index('需要三个工作日。'))
  70. self.assertNotIn('[[', text)
  71. self.assertEqual(add_detail_links(text, [entry(0, kind)], ['0'], []), (text, links))
  72. def test_empty_answer_and_ambiguous_multi_source_footer_do_not_gain_links(self):
  73. for body in ('### 合成事项0\n\n[[finance_detail:0]]', '[[finance_detail:0]]'):
  74. text, links = add_detail_links(body, [entry(0, 'finance')], ['0'])
  75. self.assertEqual(links, [])
  76. self.assertNotIn('https://', text)
  77. body = '甲段说明内容。\n\n乙段说明内容。\n\n[[finance_detail:0]]\n\n[[finance_detail:1]]'
  78. text, links = add_detail_links(body, [entry(0, 'finance'), entry(1, 'finance')], ['0', '1'])
  79. self.assertEqual(links, [])
  80. self.assertNotIn('https://', text)
  81. def test_wrong_footer_marker_cannot_override_a_known_chapter(self):
  82. body = ('### 合成事项0\n\n本项支持说明。\n\n### 无关事项\n\n其他说明。'
  83. '\n\n[[finance_detail:0]]')
  84. text, _ = add_detail_links(body, [entry(0, 'finance')], ['0'])
  85. self.assertGreater(text.index('https://'), text.index('本项支持说明。'))
  86. self.assertLess(text.index('https://'), text.index('### 无关事项'))
  87. def test_invalid_or_ambiguous_anchors_do_not_attach_to_unrelated_footer(self):
  88. body = '重复说明内容。\n\n重复说明内容。'
  89. for anchor in ('重复说明内容。', '不存在的说明内容。'):
  90. text, links = add_detail_links(body, [entry(0, 'finance')], ['0'], [
  91. {'source_id': '0', 'heading': '', 'after': anchor}])
  92. self.assertEqual(text, body)
  93. self.assertEqual(links, [])
  94. self.assertIsNone(answer_link_position('### 办理渠道\n\n实际办理说明。', '办理渠道'))
  95. def test_finance_nested_sections_are_complete_before_link(self):
  96. body = '### 合成事项0\n\n#### 额度\n\n上限说明。\n\n#### 条件\n\n限制说明。\n\n### 其他\n\n其他说明。'
  97. text, _ = add_detail_links(body, [entry(0, 'finance')], ['0'])
  98. self.assertGreater(text.index('https://'), text.index('限制说明。'))
  99. self.assertLess(text.index('https://'), text.index('### 其他'))