test_agent_mode.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import os
  2. import tempfile
  3. import unittest
  4. from unittest.mock import AsyncMock, patch
  5. from cyber_agent.core.agent_mode import (
  6. AgentMode,
  7. assert_removed_config_absent,
  8. policy_from_context,
  9. policy_from_environment,
  10. )
  11. from cyber_agent.core.runner import AgentRunner, RunConfig
  12. from cyber_agent.trace.goal_models import GoalTree
  13. from cyber_agent.trace.models import Trace
  14. from cyber_agent.trace.store import FileSystemTraceStore
  15. class AgentModePolicyTest(unittest.IsolatedAsyncioTestCase):
  16. async def asyncSetUp(self):
  17. self.temp_dir = tempfile.TemporaryDirectory()
  18. self.store = FileSystemTraceStore(self.temp_dir.name)
  19. self.runner = AgentRunner(trace_store=self.store)
  20. self.previous = {
  21. name: os.environ.get(name)
  22. for name in ("AGENT_MODE", "AGENT_RECURSION_ENABLED")
  23. }
  24. os.environ.pop("AGENT_MODE", None)
  25. os.environ.pop("AGENT_RECURSION_ENABLED", None)
  26. async def asyncTearDown(self):
  27. for name, value in self.previous.items():
  28. if value is None:
  29. os.environ.pop(name, None)
  30. else:
  31. os.environ[name] = value
  32. self.temp_dir.cleanup()
  33. async def _prepare_new(self, mode=None):
  34. if mode is None:
  35. os.environ.pop("AGENT_MODE", None)
  36. else:
  37. os.environ["AGENT_MODE"] = mode
  38. return await self.runner._prepare_trace(
  39. [{"role": "user", "content": "test"}],
  40. RunConfig(name="mode test"),
  41. )
  42. async def test_unset_defaults_to_legacy(self):
  43. trace, _, _ = await self._prepare_new()
  44. self.assertEqual("legacy", trace.context["agent_mode"])
  45. self.assertEqual(1, trace.context["agent_mode_revision"])
  46. self.assertEqual(0, trace.context["agent_depth"])
  47. self.assertEqual(trace.trace_id, trace.context["root_trace_id"])
  48. async def test_legacy_and_recursive_are_parsed_explicitly(self):
  49. self.assertIs(AgentMode.LEGACY, policy_from_environment().mode)
  50. os.environ["AGENT_MODE"] = "recursive"
  51. self.assertIs(AgentMode.RECURSIVE, policy_from_environment().mode)
  52. async def test_invalid_mode_does_not_create_trace(self):
  53. with self.assertRaisesRegex(ValueError, "Invalid AGENT_MODE"):
  54. await self._prepare_new("unsupported")
  55. traces = await self.store.list_traces(limit=10)
  56. self.assertEqual([], traces)
  57. async def test_invalid_mode_is_rejected_before_task_name_generation(self):
  58. os.environ["AGENT_MODE"] = "unsupported"
  59. with patch.object(
  60. self.runner,
  61. "_generate_task_name",
  62. new=AsyncMock(return_value="should not run"),
  63. ) as generate_name:
  64. with self.assertRaisesRegex(ValueError, "Invalid AGENT_MODE"):
  65. await self.runner._prepare_trace(
  66. [{"role": "user", "content": "test"}],
  67. RunConfig(),
  68. )
  69. generate_name.assert_not_awaited()
  70. async def test_removed_recursion_variable_fails_explicitly(self):
  71. os.environ["AGENT_RECURSION_ENABLED"] = "true"
  72. with self.assertRaisesRegex(ValueError, "has been removed"):
  73. assert_removed_config_absent()
  74. with self.assertRaisesRegex(ValueError, "AGENT_MODE"):
  75. await self._prepare_new("recursive")
  76. async def test_existing_trace_keeps_persisted_mode_after_env_switch(self):
  77. trace, _, _ = await self._prepare_new("recursive")
  78. self.assertEqual(2, trace.context["agent_mode_revision"])
  79. self.assertIn("task_protocol", trace.context)
  80. os.environ["AGENT_MODE"] = "legacy"
  81. continued, _, _ = await self.runner._prepare_trace(
  82. [],
  83. RunConfig(trace_id=trace.trace_id, name="continue"),
  84. )
  85. self.assertEqual("recursive", continued.context["agent_mode"])
  86. persisted = await self.store.get_trace(trace.trace_id)
  87. self.assertEqual("recursive", persisted.context["agent_mode"])
  88. async def test_historical_trace_is_persisted_as_legacy_on_continue(self):
  89. trace = Trace(trace_id="historical", mode="agent", task="old", context={})
  90. await self.store.create_trace(trace)
  91. os.environ["AGENT_MODE"] = "recursive"
  92. continued, _, _ = await self.runner._prepare_trace(
  93. [],
  94. RunConfig(trace_id=trace.trace_id, name="continue"),
  95. )
  96. self.assertEqual("legacy", continued.context["agent_mode"])
  97. self.assertIs(AgentMode.LEGACY, policy_from_context(continued.context).mode)
  98. async def test_legacy_goal_tree_keeps_automatic_sibling_and_parent_progress(self):
  99. tree = GoalTree(mission="legacy goals")
  100. parent = tree.add_goals(["parent"])[0]
  101. first, second = tree.add_goals(["first", "second"], parent_id=parent.id)
  102. tree.focus(parent.id)
  103. tree.focus(first.id)
  104. tree.complete(first.id, "done")
  105. self.assertEqual(second.id, tree.current_id)
  106. self.assertEqual("in_progress", parent.status)
  107. tree.complete(second.id, "done")
  108. self.assertEqual("completed", parent.status)
  109. self.assertEqual("所有子目标已完成", parent.summary)
  110. if __name__ == "__main__":
  111. unittest.main()