intent_check.py 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. """Opt-in real intent model check with frozen synthetic inputs and downstream spies.
  2. Calls DeepSeek only; never calls QCC, Milvus or the local embedding model.
  3. """
  4. import json
  5. from pathlib import Path
  6. from unittest.mock import Mock
  7. from .graph import build_graph
  8. from .intent import deepseek_analyzer
  9. from .persona import DIRECT_RESPONSES
  10. def main():
  11. root = Path(__file__).resolve().parents[2]
  12. cases = json.loads(Path(__file__).with_name('tests').joinpath('conversation_cases.json').read_text(encoding='utf-8'))
  13. analyzer = deepseek_analyzer(root / '.env')
  14. from src.step1_knowledge_prepare.summarize import load_config
  15. model = load_config(root / '.env').model
  16. rows = []
  17. for case in cases['cases']:
  18. client, retriever, recommender = Mock(), Mock(), Mock()
  19. retriever.search.return_value = {'candidates': [], 'complete': True}
  20. recommender.plan.return_value = {'queries': [], 'error': None}
  21. recommender.answer.return_value = {'overview': '合成下游答复'}
  22. graph = build_graph(client, analyzer, retriever=retriever, recommender=recommender)
  23. state = graph.invoke({'question': case['question']}, {'configurable': {'thread_id': case['id']}})
  24. actual = state['conversation_kind']
  25. expected = case['kind']
  26. no_downstream = not client.mock_calls and not retriever.mock_calls and not recommender.mock_calls
  27. routing_ok = (state.get('__interrupt__') or retriever.search.called) if expected == 'specific_need' else (
  28. no_downstream if expected in ('smalltalk',) else retriever.search.called and not client.mock_calls)
  29. direct = expected in ('smalltalk',)
  30. reply_ok = (not state.get('errors') and bool(state.get('direct_response'))
  31. and state['response'] != DIRECT_RESPONSES.get(expected)) if direct else True
  32. if case.get('identity'):
  33. reply_ok = reply_ok and '青浦区营商助手' in state['response']
  34. row = {'id': case['id'], 'expected': expected, 'actual': actual,
  35. 'passed': bool(actual == expected and routing_ok and reply_ok)}
  36. if direct:
  37. # Frozen synthetic prompts only, for semantic review of model replies.
  38. row['response'] = state['response']
  39. rows.append(row)
  40. print(json.dumps(row, ensure_ascii=False), flush=True)
  41. report = {'version': cases['version'], 'model': model, 'rows': rows,
  42. 'passed': all(row['passed'] for row in rows)}
  43. print(json.dumps(report, ensure_ascii=False), flush=True)
  44. return 0 if report['passed'] else 1
  45. if __name__ == '__main__':
  46. raise SystemExit(main())