Refactor Workflow and Chat Architecture (#68)

* refactor: overhaul workflow and chat architecture

- Separate Chat and Workflow API endpoints and database models
- Use JSONB to store workflow execution context in Postgres
- Convert workflow engine to use pydantic-ai execution graphs inside a Ray task
- Update frontend React components to support standalone workflow creation
- Remove obsolete and broken workflow runner tests

Co-authored-by: zhaoxi826 <198742034+zhaoxi826@users.noreply.github.com>

* refactor: overhaul workflow and chat architecture

- Separate Chat and Workflow API endpoints and database models
- Use JSONB to store workflow execution context in Postgres
- Convert workflow engine to use pydantic-ai execution graphs inside a Ray task
- Update frontend React components to support standalone workflow creation
- Remove obsolete and broken workflow runner tests

Co-authored-by: zhaoxi826 <198742034+zhaoxi826@users.noreply.github.com>

* refactor: overhaul workflow and chat architecture

- Separate Chat and Workflow API endpoints and database models
- Use JSONB to store workflow execution context in Postgres
- Convert workflow engine to use pydantic-ai execution graphs inside a Ray task
- Update frontend React components to support standalone workflow creation
- Move workflow_engine inside workflow package to keep core root clean
- Remove obsolete and broken workflow runner tests

Co-authored-by: zhaoxi826 <198742034+zhaoxi826@users.noreply.github.com>

---------

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:
2026-05-12 15:47:17 +08:00
committed by GitHub
parent ee9bbbf676
commit ff1ede47a0
33 changed files with 995 additions and 412 deletions
@@ -123,4 +123,6 @@ def test_get_provider_list_and_get_provider(gsm):
assert gsm._global_provider_manager.get_provider_list() == {"p1": mock_provider}
assert gsm._global_provider_manager.get_provider("p1") == mock_provider
assert gsm._global_provider_manager.get_provider("missing") is None
# noqa: E402
@@ -82,4 +82,6 @@ async def test_postgres_database(
mock_conn.run_sync.assert_called_once_with(mock_create_all)
assert await db.get_user_authority(user_id="123") == "test_auth"
# noqa: E402
@@ -1,197 +0,0 @@
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 "kilostar.core.workflow_running_engine.workflow_runner" in mod or "ray" in mod:
del sys.modules[mod]
from kilostar.core.workflow_running_engine.workflow_runner import ( # noqa: E402
WorkflowEngine,
WorkflowRunningEngine,
)
builtins.__import__ = real_import
@pytest.fixture
def mock_ray():
with patch("kilostar.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.regulatory_node == "supervisor"
@pytest.mark.asyncio
async def test_workflow_engine_run():
from kilostar.core.workflow_running_engine.workflow import kilostarWorkflow, WorkStep, WorkflowStatus
mock_wf = MagicMock(spec=kilostarWorkflow)
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(
"kilostar.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.regulatory_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 kilostar.api.platform.event import kilostarEvent
from kilostar.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 = kilostarEvent(
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(
"kilostar.core.workflow_running_engine.workflow_runner.WorkflowEngine"
) as mock_wf_engine_cls,
patch("builtins.open", new_callable=MagicMock) as mock_open,
patch(
"kilostar.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
+2
View File
@@ -96,4 +96,6 @@ def test_decode_token_validation_error():
assert excinfo.value.status_code == 401
assert excinfo.value.detail == "无效的认证凭证"
# noqa: E402