Files
kefu/deploy/recognition-audit-20260916/test_visual_knowledge_trace_audit.py
T
2026-09-21 10:34:06 +08:00

85 lines
4.5 KiB
Python

"""Visual orchestration must retain compact current-turn knowledge references."""
import tempfile
import threading
import unittest
from pathlib import Path
from unittest import mock
import ai_chat
from wechat_bot import WeChatBot
class VisualKnowledgeTraceAuditTest(unittest.TestCase):
def setUp(self):
self.fp = b'k' * 40
self.bot = WeChatBot.__new__(WeChatBot)
self.bot._pending_lock = threading.RLock()
self.bot._pending_reply_sessions = {self.fp.hex(): {'confirmed_unread': True}}
self.bot._strict_visual_actions = False
self.bot._pending_reply_path = ''
self.bot.report_operation = mock.Mock()
self.bot._forget_uncertain_tracking = mock.Mock()
ai_chat.take_last_gateway_trace()
self.addCleanup(ai_chat.take_last_gateway_trace)
def trace(self):
return {'task_id': 'current-model-task', 'chosen': 'answer-model',
'knowledge': {'enabled': True, 'mode': 'keyword', 'elapsed_ms': 4,
'hits': [{'id': 'kb-7', 'revision': 3, 'score': .91}]}}
def test_visual_gateway_preserves_history_and_knowledge_without_judge(self):
history = [{'role': 'user', 'content': '先前问题'}, {'role': 'assistant', 'content': '先前回答'}]
def generate(**kwargs):
self.assertEqual(kwargs['history'], history)
self.assertEqual(kwargs['image_bytes'], b'synthetic-image')
ai_chat._TRACE.last = self.trace()
return '按已审核知识回答'
with mock.patch.object(ai_chat, 'gateway_provider', return_value=object()), mock.patch.object(ai_chat, 'get_ai_reply', side_effect=generate), mock.patch('backend_client.report_model_call') as report:
result = self.bot._orchestrated_reply(self.fp, chat_text='后续问题', history=history, image_bytes=b'synthetic-image', force_vision=True)
self.assertEqual(result, '按已审核知识回答')
state = self.bot._pending_reply_sessions[self.fp.hex()]
self.assertEqual(state['knowledge_trace'], self.trace()['knowledge'])
self.assertEqual(state['model_task_id'], 'current-model-task')
report.assert_not_called()
def test_knowledge_metadata_survives_pending_state_roundtrip(self):
with tempfile.TemporaryDirectory() as temporary:
self.bot._pending_reply_path = str(Path(temporary) / 'pending.json')
self.bot._record_model_call(self.fp, self.trace())
restored = self.bot._load_pending_replies()[self.fp.hex()]
self.assertEqual(restored['knowledge_trace'], self.trace()['knowledge'])
self.assertEqual(restored['model_task_id'], 'current-model-task')
def test_knowledge_body_and_unexpected_nested_data_are_not_stored(self):
value = self.trace()
value['knowledge']['content'] = 'private full text' * 1000
value['knowledge']['hits'][0]['answer'] = 'large answer' * 1000
value['knowledge']['hits'][0]['source'] = {'chat': 'private conversation'}
self.bot._record_model_call(self.fp, value)
self.assertEqual(self.bot._pending_reply_sessions[self.fp.hex()]['knowledge_trace'], self.trace()['knowledge'])
def test_new_customer_turn_clears_previous_knowledge_reference(self):
state = self.bot._pending_reply_sessions[self.fp.hex()]
state['knowledge_trace'] = self.trace()['knowledge']
state['model_task_id'] = 'previous-task'
self.bot._reset_pending_batch(self.fp)
self.assertNotIn('knowledge_trace', state)
self.assertNotIn('model_task_id', state)
def test_internal_old_trace_is_not_attributed_to_safe_fallback_reply(self):
ai_chat._TRACE.last = self.trace()
with mock.patch.object(ai_chat, 'gateway_provider', return_value=object()), mock.patch.object(ai_chat, 'get_ai_reply', return_value='请补充文字'), mock.patch.object(self.bot, '_record_model_call') as record:
self.bot._orchestrated_reply(self.fp, chat_text='')
record.assert_not_called()
self.assertEqual(ai_chat.take_last_gateway_trace(), {})
def test_next_model_result_with_no_knowledge_clears_previous_reference(self):
self.bot._record_model_call(self.fp, self.trace())
self.bot._record_model_call(self.fp, {'task_id': 'next-task', 'knowledge': {}})
state = self.bot._pending_reply_sessions[self.fp.hex()]
self.assertEqual(state['knowledge_trace'], {})
self.assertEqual(state['model_task_id'], 'next-task')
if __name__ == '__main__':
unittest.main()