refactor(core): decouple actors and remove workflow templates (#67)
Removes the deprecated `workflow_template` concept entirely across both backend API routers, internal logic handling within the `supervisory_node` and `consciousness_node`, and front-end components. Enables `consciousness_node` to work autonomously. Also refactors core package structure to enforce the "one python package, one Ray Actor" architectural rule. `GlobalWorkflowManager`, `WorkflowRunningEngine`, `PostgresDatabase`, and `WorkerCluster` have been moved to their own top-level decoupled package directories with properly exported `__init__.py` modules. Test suites have been relocated and import paths updated across the system. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: zhaoxi826 <198742034+zhaoxi826@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, AsyncMock, patch
|
||||
import asyncio
|
||||
|
||||
import sys
|
||||
import builtins
|
||||
|
||||
real_import = builtins.__import__
|
||||
|
||||
|
||||
def mock_import(name, globals=None, locals=None, fromlist=(), level=0):
|
||||
if name == "ray":
|
||||
mock_ray = MagicMock()
|
||||
|
||||
def mock_remote(*args, **kwargs):
|
||||
if len(args) == 1 and callable(args[0]):
|
||||
return args[0]
|
||||
|
||||
def decorator(cls):
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
mock_ray.remote = mock_remote
|
||||
return mock_ray
|
||||
return real_import(name, globals, locals, fromlist, level)
|
||||
|
||||
|
||||
builtins.__import__ = mock_import
|
||||
for mod in list(sys.modules.keys()):
|
||||
if "pretor.core.workflow_running_engine.workflow_runner" in mod or "ray" in mod:
|
||||
del sys.modules[mod]
|
||||
from pretor.core.workflow_running_engine.workflow_runner import ( # noqa: E402
|
||||
WorkflowEngine,
|
||||
WorkflowRunningEngine,
|
||||
)
|
||||
|
||||
builtins.__import__ = real_import
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_ray():
|
||||
with patch("pretor.core.workflow_running_engine.workflow_runner.ray") as mock_ray:
|
||||
mock_ray.get = lambda x: x
|
||||
yield mock_ray
|
||||
|
||||
|
||||
def test_workflow_engine_init():
|
||||
mock_wf = MagicMock()
|
||||
mock_wf.work_link = []
|
||||
engine = WorkflowEngine(mock_wf, "conscious", "control", "supervisor")
|
||||
assert engine.workflow == mock_wf
|
||||
assert engine.consciousness_node == "conscious"
|
||||
assert engine.control_node == "control"
|
||||
assert engine.supervisory_node == "supervisor"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_workflow_engine_run():
|
||||
from pretor.core.workflow.workflow import PretorWorkflow, WorkStep, WorkflowStatus
|
||||
|
||||
mock_wf = MagicMock(spec=PretorWorkflow)
|
||||
|
||||
step1 = MagicMock(spec=WorkStep)
|
||||
step1.step = 1
|
||||
step1.status = "waiting"
|
||||
step1.node = "control_node"
|
||||
step1.name = "mock_name"
|
||||
step1.desc = "mock_desc"
|
||||
step1.action = "mock_action"
|
||||
step1.inputs = []
|
||||
step1.outputs = "res"
|
||||
step1.logic_gate = None
|
||||
mock_wf.work_link = [step1]
|
||||
|
||||
mock_status = MagicMock(spec=WorkflowStatus)
|
||||
mock_status.step = 1
|
||||
mock_status.status = "running"
|
||||
mock_wf.status = mock_status
|
||||
mock_wf.context_memory = {}
|
||||
mock_wf.title = "mock_title"
|
||||
mock_wf.trace_id = "mock_trace_id"
|
||||
mock_wf.command = "mock_command"
|
||||
mock_wf.event_info = MagicMock()
|
||||
mock_wf.event_info.platform = "test"
|
||||
mock_wf.event_info.user_name = "test_user"
|
||||
|
||||
mock_control = MagicMock()
|
||||
mock_control.working.remote = AsyncMock(return_value="process_result")
|
||||
|
||||
mock_conscious = MagicMock()
|
||||
mock_conscious.working.remote = AsyncMock(return_value="report")
|
||||
|
||||
mock_supervisor = MagicMock()
|
||||
mock_supervisor.working.remote = AsyncMock(return_value="response")
|
||||
|
||||
engine = WorkflowEngine(mock_wf, mock_conscious, mock_control, mock_supervisor)
|
||||
|
||||
with patch(
|
||||
"pretor.core.workflow_running_engine.workflow_runner.ray"
|
||||
) as mock_ray_patch:
|
||||
mock_gsm = MagicMock()
|
||||
mock_ray_patch.get_actor.return_value = mock_gsm
|
||||
await engine.run()
|
||||
|
||||
assert step1.status == "completed"
|
||||
assert mock_wf.context_memory["res"] == "process_result"
|
||||
|
||||
|
||||
def test_workflow_running_engine_init():
|
||||
engine = WorkflowRunningEngine("conscious", "control", "supervisor")
|
||||
assert engine.consciousness_node == "conscious"
|
||||
assert engine.control_node == "control"
|
||||
assert engine.supervisory_node == "supervisor"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_workflow_running_engine_submit():
|
||||
engine = WorkflowRunningEngine("conscious", "control", "supervisor")
|
||||
engine.workflow_queue = asyncio.Queue()
|
||||
|
||||
mock_wf = MagicMock()
|
||||
await engine.workflow_queue.put(mock_wf)
|
||||
|
||||
item = await engine.workflow_queue.get()
|
||||
assert item == mock_wf
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_workflow_running_engine_runner():
|
||||
from pretor.api.platform.event import PretorEvent
|
||||
from pretor.core.individual.consciousness_node.template import ForWorkflowEngine
|
||||
|
||||
mock_consciousness = MagicMock()
|
||||
mock_wf = MagicMock()
|
||||
mock_wf.trace_id = "test_trace"
|
||||
mock_wf.title = "test_title"
|
||||
|
||||
mock_result = MagicMock(spec=ForWorkflowEngine)
|
||||
mock_result.workflow = mock_wf
|
||||
mock_consciousness.working.remote = AsyncMock(return_value=mock_result)
|
||||
|
||||
engine = WorkflowRunningEngine(mock_consciousness, "control", "supervisor")
|
||||
engine.workflow_queue = asyncio.Queue()
|
||||
|
||||
mock_event = PretorEvent(
|
||||
platform="test_platform",
|
||||
user_id="test_user",
|
||||
user_name="test_user",
|
||||
message="test_message",
|
||||
context={},
|
||||
)
|
||||
await engine.workflow_queue.put(mock_event)
|
||||
|
||||
# Mock the global_state_machine get_skill_list.remote method properly
|
||||
mock_gsm = MagicMock()
|
||||
mock_gsm.list_individuals.remote = AsyncMock(
|
||||
return_value={
|
||||
"test_skill": {
|
||||
"agent_type": "skill_individual",
|
||||
"agent_name": "TestSkill",
|
||||
"description": "desc",
|
||||
}
|
||||
}
|
||||
)
|
||||
engine.global_state_machine = mock_gsm
|
||||
|
||||
with (
|
||||
patch(
|
||||
"pretor.core.workflow_running_engine.workflow_runner.WorkflowEngine"
|
||||
) as mock_wf_engine_cls,
|
||||
patch("builtins.open", new_callable=MagicMock) as mock_open,
|
||||
patch(
|
||||
"pretor.core.workflow_running_engine.workflow_runner.ray_actor_hook"
|
||||
) as mock_hook,
|
||||
):
|
||||
# Instead of patching hook, we inject it directly
|
||||
# engine.global_state_machine = AsyncMock()
|
||||
|
||||
mock_open.return_value.__enter__.return_value.read.return_value = "{}"
|
||||
|
||||
mock_gwm = MagicMock()
|
||||
mock_gwm.update_workflow.remote = AsyncMock()
|
||||
mock_hook.return_value.global_workflow_manager = mock_gwm
|
||||
|
||||
mock_engine_instance = MagicMock()
|
||||
mock_engine_instance.run = AsyncMock()
|
||||
mock_wf_engine_cls.return_value = mock_engine_instance
|
||||
|
||||
task = asyncio.create_task(engine.runner(1))
|
||||
await asyncio.sleep(0.05)
|
||||
task.cancel()
|
||||
|
||||
mock_wf_engine_cls.assert_called_with(
|
||||
mock_wf, mock_consciousness, "control", "supervisor"
|
||||
)
|
||||
# noqa: E402
|
||||
Reference in New Issue
Block a user