test_failure_notices.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. import copy
  2. import unittest
  3. from src.step3_question_answer.recommendation import Recommender
  4. from test_recommendation import candidate, good_analysis, handoff, retrieval
  5. class FailureNoticeTests(unittest.TestCase):
  6. def test_default_analysis_visits_every_chunk_past_old_limit(self):
  7. item = candidate()
  8. item['record']['data'] = {'conditions': '合成长文认定标准。' * 2000 + '尾段独立条件'}
  9. calls = []
  10. def model(prompt, payload):
  11. calls.append(copy.deepcopy(payload))
  12. return {'relevant': False}
  13. result = Recommender(model)._analyze(item, handoff())
  14. self.assertGreater(result['chunk_count'], 12)
  15. self.assertTrue(result['complete'])
  16. self.assertEqual(len(calls), result['chunk_count'])
  17. self.assertEqual([p['evidence_scope']['chunk_index'] for p in calls],
  18. list(range(1, len(calls) + 1)))
  19. units = [u for p in calls for u in p['evidence'] if u['path'] == '/data/conditions']
  20. self.assertEqual(''.join(u['text'] for u in units), item['record']['data']['conditions'])
  21. self.assertEqual(units[-1]['offset'] + len(units[-1]['text']), len(item['record']['data']['conditions']))
  22. calls.clear()
  23. limited = Recommender(model, max_chunks=12)._analyze(item, handoff())
  24. self.assertFalse(limited['complete'])
  25. self.assertEqual(limited['chunk_count'], result['chunk_count'])
  26. self.assertEqual(limited['error'], 'analysis_budget_limit')
  27. self.assertEqual(calls, [])
  28. def test_partial_failure_keeps_verified_card_and_one_scoped_notice(self):
  29. for retrieval_complete in (True, False):
  30. with self.subTest(retrieval_complete=retrieval_complete):
  31. failed = candidate()
  32. failed['record']['id'] = 'failed'
  33. engine = Recommender(lambda *args: good_analysis())
  34. analyze = engine._prepare_analysis
  35. def mixed(item, context, chunk_executor=None):
  36. if item['record']['id'] == 'failed':
  37. return {'candidate': item, 'analyses': [], 'complete': False,
  38. 'error': 'model_request_timeout'}
  39. return analyze(item, context)
  40. engine._prepare_analysis = mixed
  41. result = engine.answer(handoff(), retrieval([candidate(), failed], retrieval_complete))
  42. self.assertEqual(len(result['items']), 1)
  43. self.assertFalse(result['retrieval_analysis_complete'])
  44. self.assertEqual(result['analysis_errors'][0]['code'], 'model_request_timeout')
  45. self.assertEqual(len(result['summary']['notices']), 1)
  46. self.assertIn('核验', result['summary']['notices'][0])
  47. self.assertNotIn(result['summary']['notices'][0], result['overview'])
  48. self.assertNotIn('部分候选', result['overview'])
  49. self.assertNotIn('逐条分析', str(result['summary']))
  50. def test_attachment_failure_is_not_reported_as_candidate_failure(self):
  51. context = handoff()
  52. context['attachments'] = [{'id': 'attachment', 'title': '合成附件',
  53. 'status': 'failed', 'error': 'document_no_text'}]
  54. result = Recommender(lambda *args: good_analysis()).answer(context, retrieval())
  55. self.assertEqual(len(result['items']), 1)
  56. self.assertFalse(result['complete'])
  57. self.assertEqual(len(result['summary']['notices']), 1)
  58. self.assertIn('合成附件未能读取', result['summary']['notices'][0])
  59. self.assertNotIn('部分候选', result['overview'])
  60. self.assertNotIn('部分资料', str(result['summary']))
  61. def test_total_failure_remains_distinct_from_no_results(self):
  62. bad = copy.deepcopy(good_analysis())
  63. bad['citations'][0]['quote'] = '伪造引文'
  64. result = Recommender(lambda *args: bad).answer(handoff(), retrieval())
  65. self.assertEqual(result['items'], [])
  66. self.assertEqual(result['summary']['status'], 'unavailable')
  67. self.assertIn('未完整完成', result['summary']['text'])
  68. self.assertEqual(result['summary']['notices'], [])
  69. self.assertTrue(result['analysis_errors'])
  70. if __name__ == '__main__':
  71. unittest.main()