test_real_god_helloagents.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. from unittest.mock import MagicMock, patch
  2. from hello_agents.tools import ToolResponse, ToolStatus
  3. from app.agent.real_god import RealGodAgent, _StepSearchToolAdapter
  4. def test_real_god_builds_react_agent_with_registered_stepsearch_tool():
  5. registry = MagicMock()
  6. react_agent = MagicMock()
  7. with patch.object(RealGodAgent, "_supports_persona_search", return_value=True), patch(
  8. "app.agent.real_god.ToolRegistry", return_value=registry
  9. ), patch(
  10. "app.agent.real_god.ReActAgent", return_value=react_agent
  11. ) as react_class, patch("app.agent.real_god.create_helloagents_llm"), patch(
  12. "app.agent.real_god.create_helloagents_config"
  13. ):
  14. result = RealGodAgent(max_steps=4)._build_agent("system")
  15. assert result is react_agent
  16. tool = registry.register_tool.call_args.args[0]
  17. assert isinstance(tool, _StepSearchToolAdapter)
  18. react_class.assert_called_once()
  19. assert react_class.call_args.kwargs["tool_registry"] is registry
  20. assert react_class.call_args.kwargs["max_steps"] == 4
  21. def test_real_god_rejects_non_stepfun_provider():
  22. registry = MagicMock()
  23. with patch.object(RealGodAgent, "_supports_persona_search", return_value=False), patch(
  24. "app.agent.real_god.ToolRegistry", return_value=registry
  25. ):
  26. try:
  27. RealGodAgent()._build_agent("system")
  28. except RuntimeError as exc:
  29. assert "StepFun" in str(exc)
  30. else:
  31. raise AssertionError("non-StepFun provider should be rejected")
  32. registry.register_tool.assert_not_called()
  33. def test_stepsearch_adapter_exposes_structured_helloagents_tool():
  34. backend = MagicMock()
  35. backend.search.return_value = "search result"
  36. tool = _StepSearchToolAdapter(backend)
  37. response = tool.run({"query": "Ada Lovelace"})
  38. assert response.status is ToolStatus.SUCCESS
  39. assert response.data["provider"] == "stepsearch"
  40. backend.search.assert_called_once_with("Ada Lovelace")
  41. def test_real_god_run_emits_valid_result_events():
  42. framework_agent = MagicMock()
  43. framework_agent.run.return_value = '{"name":"Ada Lovelace","title":"Mathematician","bio":"Bio","theories":["1","2","3","4","5","6","7"],"stance":"Analytical","system_prompt":"I am Ada."}'
  44. agent = RealGodAgent()
  45. with patch.object(agent, "_build_agent", return_value=framework_agent), patch.object(
  46. agent, "_matches_user_request", return_value=True
  47. ):
  48. events = list(agent.run("create one", n=1))
  49. assert [event["type"] for event in events] == ["count", "thought_start", "progress", "result"]
  50. assert events[-1]["content"][0]["name"] == "Ada Lovelace"
  51. def test_real_god_repairs_truncated_provider_json_once():
  52. framework_agent = MagicMock()
  53. framework_agent.run.side_effect = ["{\"name\": \"Ada", "{\"name\": \"Ada Lovelace\", \"title\": \"Math\", \"bio\": \"Bio\", \"theories\": [\"1\",\"2\",\"3\",\"4\",\"5\",\"6\",\"7\"], \"stance\": \"Analytical\", \"system_prompt\": \"I am Ada.\"}"]
  54. agent = RealGodAgent()
  55. with patch.object(agent, "_build_agent", return_value=framework_agent), patch.object(
  56. agent, "_matches_user_request", return_value=True
  57. ):
  58. events = list(agent.run("create one", n=1))
  59. assert [event["type"] for event in events] == ["count", "thought_start", "progress", "result"]
  60. assert framework_agent.run.call_count == 2
  61. assert events[-1]["content"][0]["name"] == "Ada Lovelace"
  62. def test_real_god_retries_with_fresh_agent_when_persona_is_off_topic():
  63. wrong_agent = MagicMock()
  64. wrong_agent.run.return_value = '{"name":"Marie Curie","title":"Scientist","bio":"Bio","theories":["1","2","3","4","5","6","7"],"stance":"Science","system_prompt":"I am Marie."}'
  65. corrected_agent = MagicMock()
  66. corrected_agent.run.return_value = '{"name":"Edsger Dijkstra","title":"Computer Scientist","bio":"Bio","theories":["1","2","3","4","5","6","7"],"stance":"Structured programming","system_prompt":"I am Dijkstra."}'
  67. agent = RealGodAgent()
  68. with patch.object(agent, "_build_agent", side_effect=[wrong_agent, corrected_agent]), patch.object(
  69. agent, "_matches_user_request", side_effect=[False, True]
  70. ):
  71. events = list(agent.run("Create Edsger Dijkstra", n=1))
  72. assert events[-1]["content"][0]["name"] == "Edsger Dijkstra"
  73. assert "Marie Curie" in corrected_agent.run.call_args.args[0]
  74. def test_multi_persona_generation_pins_each_agent_to_requested_position():
  75. first_agent = MagicMock()
  76. first_agent.run.return_value = '{"name":"Teacher A","title":"Innovator","bio":"Bio","theories":["1","2","3","4","5","6","7"],"stance":"Support","system_prompt":"I support adoption."}'
  77. second_agent = MagicMock()
  78. second_agent.run.return_value = '{"name":"Teacher B","title":"Ethicist","bio":"Bio","theories":["1","2","3","4","5","6","7"],"stance":"Cautious","system_prompt":"I focus on risks."}'
  79. agent = RealGodAgent()
  80. with patch.object(agent, "_build_agent", side_effect=[first_agent, second_agent]), patch.object(
  81. agent, "_matches_user_request", return_value=True
  82. ) as alignment:
  83. events = list(agent.run("第一位支持课堂 AI;第二位强调学术诚信风险", n=2))
  84. results = [event["content"][0]["name"] for event in events if event["type"] == "result"]
  85. assert results == ["Teacher A", "Teacher B"]
  86. assert "第 1 个描述" in first_agent.run.call_args.args[0]
  87. assert "第 2 个描述" in second_agent.run.call_args.args[0]
  88. assert "当前只校验第 1 位" in alignment.call_args_list[0].args[0]
  89. assert "当前只校验第 2 位" in alignment.call_args_list[1].args[0]
  90. assert "Teacher A" in alignment.call_args_list[1].args[0]
  91. def test_explicit_named_person_is_checked_before_model_alignment():
  92. with patch("app.agent.real_god.run_simple_agent") as completion:
  93. assert RealGodAgent._matches_user_request(
  94. "创建哈利波特",
  95. {"name": "阿斯特拉·韦斯莱-布莱克"},
  96. ) is False
  97. completion.assert_not_called()
  98. def test_explicit_named_person_allows_name_punctuation_variants():
  99. with patch("app.agent.real_god.run_simple_agent", return_value="YES"):
  100. assert RealGodAgent._matches_user_request(
  101. "创建阿不思邓布利多",
  102. {"name": "阿不思·邓布利多"},
  103. ) is True
  104. def test_explicit_named_person_allows_translated_name_when_bio_confirms_identity():
  105. with patch("app.agent.real_god.run_simple_agent", return_value="YES"):
  106. assert RealGodAgent._matches_user_request(
  107. "创建哈利波特",
  108. {
  109. "name": "Harry Potter",
  110. "bio": "哈利·波特是霍格沃茨格兰芬多学院的学生。",
  111. },
  112. ) is True
  113. def test_topic_request_does_not_force_persona_name():
  114. assert RealGodAgent._explicit_requested_name("创建一位算法专家") is None
  115. assert RealGodAgent._explicit_requested_name("生成一个科学家角色") is None
  116. def test_long_named_person_prompt_forces_single_person_without_model_counting():
  117. prompt = "请联网核实并创建真实人物王佑镁,作为教育伦理讨论角色。必须生成王佑镁本人。"
  118. agent = RealGodAgent()
  119. with patch("app.agent.real_god.run_simple_agent") as completion:
  120. assert agent._explicit_requested_name(prompt) == "王佑镁"
  121. assert agent._get_persona_count(prompt) == 1
  122. completion.assert_not_called()
  123. def test_stepsearch_adapter_converts_provider_errors():
  124. backend = MagicMock()
  125. backend.search.side_effect = RuntimeError("offline")
  126. response = _StepSearchToolAdapter(backend).run({"query": "Ada Lovelace"})
  127. assert isinstance(response, ToolResponse)
  128. assert response.status is ToolStatus.ERROR
  129. assert response.error_info["code"] == "SEARCH_FAILED"