Skip to content

Commit aa50d35

Browse files
authored
Merge pull request #4 from CrewsBoard/3-rule-engine-integration-and-ui-improvements
Rule engine integration and UI improvements
2 parents 54105f0 + 50f24cf commit aa50d35

File tree

78 files changed

+4794
-94
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+4794
-94
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ source .venv/bin/activate # On Windows: .venv\Scripts\activate
7474
uv pip install -e .
7575
```
7676

77+
4. Create DB named `canvas` in postgres
78+
7779
## 🏃 Running the Application
7880

7981
Start the development server:

core/configs/dev.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,5 @@ server:
33
debug: True
44

55
database:
6-
type: "postgresql"
76
url: "postgresql+asyncpg://postgres:postgres@localhost:5432/canvas"
87
schema_package: "core.repositories.schemas"

core/configs/prod.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,5 @@ server:
33
debug: False
44

55
database:
6-
type: "postgresql"
76
url: "postgresql+asyncpg://postgres:postgres@localhost:5432/canvas"
87
schema_package: "core.repositories.schemas"

core/configs/stage.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,5 @@ server:
33
debug: False
44

55
database:
6-
type: "postgresql"
76
url: "postgresql+asyncpg://postgres:postgres@localhost:5432/canvas"
87
schema_package: "core.repositories.schemas"

core/controllers/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from .agent_controller import AgentController
2-
from .base_controller import BaseController
32
from .crew_controller import CrewController
43
from .model_controller import ModelController
54
from .prompt_controller import PromptController
65
from .relation_controller import RelationController
76
from .task_controller import TaskController
7+
from .flow_engine_controller import FlowEngineController

core/controllers/base_controller.py

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,14 @@
1-
from core.services.agent.agent_service import AgentService
2-
from core.services.crew.crew_service import CrewService
3-
from core.services.model.model_service import ModelService
4-
from core.services.prompt.prompt_service import PromptService
5-
from core.services.relation.relation_service import RelationService
6-
from core.services.task.task_service import TaskService
1+
from shared.services.context_manager.context_manager_service import ContextManager
72

83

9-
class BaseController:
4+
class BaseController(ContextManager):
105
def __init__(self):
11-
self.prompt_swagger_tags = ["Prompt"]
12-
self.prompt_service: PromptService = PromptService()
6+
super().__init__()
137

8+
self.prompt_swagger_tags = ["Prompt"]
149
self.agent_swagger_tags = ["Agent"]
15-
self.agent_service: AgentService = AgentService(
16-
RelationService(), ModelService(), PromptService()
17-
)
18-
1910
self.model_swagger_tags = ["Model"]
20-
self.model_service: ModelService = ModelService()
21-
2211
self.relation_swagger_tags = ["Relation"]
23-
self.relation_service: RelationService = RelationService()
24-
2512
self.task_swagger_tags = ["Task"]
26-
self.task_service: TaskService = TaskService(
27-
AgentService(RelationService(), ModelService(), PromptService()),
28-
RelationService(),
29-
PromptService(),
30-
)
31-
3213
self.crew_swagger_tags = ["Crew"]
33-
self.crew_service: CrewService = CrewService(
34-
AgentService(RelationService(), ModelService(), PromptService()),
35-
TaskService(
36-
AgentService(RelationService(), ModelService(), PromptService()),
37-
RelationService(),
38-
PromptService(),
39-
),
40-
RelationService(),
41-
)
14+
self.flow_engine_swagger_tags = ["Flow Engine"]
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import importlib
2+
import json
3+
import os
4+
import uuid
5+
from typing import Dict, Any, List
6+
7+
from fastapi import APIRouter, HTTPException
8+
from starlette.responses import StreamingResponse
9+
10+
from core.controllers.base_controller import BaseController
11+
from flow_engine.flow_chain.dtos import (
12+
NodeUiConfig,
13+
FlowChain,
14+
NodeTypes,
15+
)
16+
from flow_engine.flow_chain.services import FlowNodeRegistry
17+
from shared.utils.funcs import get_root_path
18+
19+
20+
class FlowEngineController(BaseController):
21+
def __init__(self):
22+
super().__init__()
23+
self.router = APIRouter(tags=self.flow_engine_swagger_tags)
24+
25+
self.router.add_api_route(
26+
"/node-types",
27+
self.get_node_types,
28+
methods=["GET"],
29+
response_model=List[NodeUiConfig],
30+
)
31+
self.router.add_api_route(
32+
"/crewai/flow-chains",
33+
self.create_crewai_flow_chain,
34+
methods=["POST"],
35+
response_model=Dict[str, Any],
36+
)
37+
self.router.add_api_route(
38+
"/crewai/flow-chains/{chain_id}/execute",
39+
self.execute_crewai_flow_chain,
40+
methods=["POST"],
41+
response_model=Dict[str, Any],
42+
)
43+
44+
@staticmethod
45+
async def get_node_types() -> List[NodeUiConfig]:
46+
node_types = []
47+
root_path = get_root_path()
48+
flow_nodes_path = os.path.join(root_path, "flow_engine", "flow_node")
49+
node_ui_configs: List[NodeUiConfig] = []
50+
for root, dirs, files in os.walk(flow_nodes_path):
51+
if "ui_config.py" in files:
52+
ui_config_path = os.path.join(root, "ui_config.py")
53+
try:
54+
spec = importlib.util.spec_from_file_location(
55+
"ui_config", ui_config_path
56+
)
57+
ui_config_module = importlib.util.module_from_spec(spec)
58+
spec.loader.exec_module(ui_config_module)
59+
if hasattr(ui_config_module, "ui_config"):
60+
node_ui_configs.append(ui_config_module.ui_config)
61+
except Exception as e:
62+
print(f"Error loading {ui_config_path}: {e}")
63+
return node_ui_configs
64+
65+
async def create_crewai_flow_chain(self, request: Dict[str, Any]) -> Dict[str, Any]:
66+
try:
67+
flow_chain = FlowChain(
68+
id=str(uuid.uuid4()),
69+
name=request.get("name", "Unnamed CrewAI Flow"),
70+
description=request.get("description"),
71+
nodes=request.get("nodes", []),
72+
connections=request.get("connections", []),
73+
debug_mode=request.get("debug_mode", False),
74+
first_node_id=request.get("first_node_id"),
75+
)
76+
self.flow_chains[flow_chain.id] = flow_chain
77+
nodes: List[Any] = []
78+
for node_request in flow_chain.nodes:
79+
node_class = FlowNodeRegistry.get_plugin(
80+
request.get("node_template_id")
81+
)
82+
if not isinstance(node_request.configuration, dict):
83+
node_request.configuration = node_request.configuration.model_dump()
84+
node = node_class(
85+
flow_chain_id=flow_chain.id,
86+
node_id=node_request.id,
87+
name=node_request.name,
88+
node_type=node_request.node_type,
89+
configuration=node_request.configuration,
90+
connections=flow_chain.connections,
91+
)
92+
nodes.append(node)
93+
self.flow_nodes[flow_chain.id] = nodes
94+
return {
95+
"flow_chain_id": flow_chain.id,
96+
"message": "CrewAI flow chain created successfully",
97+
"crew_created": True,
98+
}
99+
except Exception as e:
100+
raise HTTPException(status_code=500, detail=str(e))
101+
102+
async def execute_crewai_flow_chain(self, chain_id: str, request: Dict[str, Any]):
103+
try:
104+
flow_chain = self.flow_chains[chain_id]
105+
if not flow_chain:
106+
raise HTTPException(
107+
status_code=404, detail="CrewAI flow chain not found"
108+
)
109+
first_node_id = flow_chain.first_node_id
110+
self.flow_chain_events[chain_id] = {}
111+
self.flow_chain_events[chain_id]["traversed_node"] = []
112+
self.flow_chain_events[chain_id]["traversed_node"].append(first_node_id)
113+
flow_nodes = self.flow_nodes.get(flow_chain.id)
114+
total_tool_nodes = len(
115+
[
116+
node
117+
for node in flow_nodes
118+
if node.type == NodeTypes.TOOL or node.type == NodeTypes.CREW
119+
]
120+
)
121+
122+
async def stream_data():
123+
last_index = 0
124+
user_msg = request
125+
has_init = False
126+
while True:
127+
if not has_init:
128+
self.event.set()
129+
has_init = True
130+
await self.event.wait()
131+
self.event.clear()
132+
133+
while last_index <= total_tool_nodes - 1:
134+
traversed_node = self.flow_chain_events.get(flow_chain.id).get(
135+
"traversed_node"
136+
)
137+
if len(traversed_node) == last_index:
138+
yield json.dumps(
139+
{
140+
"flow_chain_id": chain_id,
141+
"message": "Flow chain completed",
142+
}
143+
).encode("utf-8")
144+
return
145+
flow_node_id = traversed_node[last_index]
146+
all_msg = self.flow_chain_events.get(flow_chain.id).get(
147+
"message"
148+
)
149+
node_msg = None
150+
if all_msg:
151+
node_msg = all_msg.get(flow_node_id)
152+
message = node_msg if node_msg else user_msg
153+
154+
flow_node = next(
155+
(node for node in flow_nodes if node.id == flow_node_id),
156+
None,
157+
)
158+
flow_node.process(message)
159+
last_index += 1
160+
yield json.dumps(
161+
self.flow_chain_events.get(flow_chain.id)
162+
).encode("utf-8")
163+
if last_index == len(flow_chain.nodes) - 1:
164+
yield json.dumps(
165+
{"flow_chain_id": chain_id, "result": message}
166+
).encode("utf-8")
167+
168+
return StreamingResponse(stream_data(), media_type="text/event-stream")
169+
except Exception as e:
170+
raise HTTPException(status_code=500, detail=str(e))

core/controllers/task_controller.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from fastapi import APIRouter
22
from pydantic import UUID4
33

4-
from core.controllers import BaseController
4+
from core.controllers.base_controller import BaseController
55
from core.dtos.task import TaskDto
66

77

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
from pydantic import BaseModel
22

3-
from core.dtos.settings.database_types import DatabaseTypes
4-
53

64
class DatabaseSettingsDto(BaseModel):
7-
type: DatabaseTypes = DatabaseTypes.postgresql
85
url: str
96
schema_package: str

core/dtos/settings/database_types.py

Lines changed: 0 additions & 5 deletions
This file was deleted.

core/repositories/agent_repository.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from core.repositories.base_repository import BaseRepository
22
from core.repositories.schemas.agent_schema import AgentSchema
3-
from core.services.database import database_service
3+
from shared.services.database import database_service
44

55

66
class AgentRepository(BaseRepository[AgentSchema]):

core/repositories/crew_repository.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from core.repositories.base_repository import BaseRepository
22
from core.repositories.schemas.crew_schema import CrewSchema
3-
from core.services.database import database_service
3+
from shared.services.database import database_service
44

55

66
class CrewRepository(BaseRepository[CrewSchema]):

core/repositories/model_repository.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from core.repositories.base_repository import BaseRepository
22
from core.repositories.schemas.model_schema import ModelSchema
3-
from core.services.database import database_service
3+
from shared.services.database import database_service
44

55

66
class ModelRepository(BaseRepository[ModelSchema]):

core/repositories/prompt_repository.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from core.repositories.base_repository import BaseRepository
22
from core.repositories.schemas.prompt_schema import PromptSchema
3-
from core.services.database import database_service
3+
from shared.services.database import database_service
44

55

66
class PromptRepository(BaseRepository[PromptSchema]):

core/repositories/relation_repository.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
from typing import List
2+
23
from sqlmodel import select
34

45
from core.dtos.entity import EntityType
56
from core.dtos.entity.entity import Entity
67
from core.dtos.relation.relation_direction import RelationDirection
78
from core.repositories.base_repository import BaseRepository
89
from core.repositories.schemas.relation_schema import RelationSchema
9-
from core.services.database import database_service
10+
from shared.services.database import database_service
1011

1112

1213
class RelationRepository(BaseRepository[RelationSchema]):

core/repositories/schemas/model_schema.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class ModelSchema(BaseSchema, table=True):
1616
model_type: str = Field(nullable=False)
1717
description: Optional[str] = Field(nullable=True)
1818
api_key: Optional[str] = Field(nullable=True)
19+
# @todo add is_default prop
1920

2021
created_at: datetime = Field(
2122
sa_column=Column(DateTime(timezone=True), server_default=func.now())

core/repositories/task_repository.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from core.repositories.base_repository import BaseRepository
22
from core.repositories.schemas.task_schema import TaskSchema
3-
from core.services.database import database_service
3+
from shared.services.database import database_service
44

55

66
class TaskRepository(BaseRepository[TaskSchema]):

core/routers.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from fastapi.routing import APIRouter
1+
from fastapi import APIRouter
22

33
from core.controllers import (
44
AgentController,
@@ -7,6 +7,7 @@
77
ModelController,
88
RelationController,
99
CrewController,
10+
FlowEngineController,
1011
)
1112

1213
routes: list[APIRouter] = [
@@ -16,4 +17,5 @@
1617
ModelController().router,
1718
RelationController().router,
1819
CrewController().router,
20+
FlowEngineController().router,
1921
]

core/services/agent/agent_service.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Optional, List
1+
from typing import Optional, List, Dict
22

33
from crewai import Agent
44
from pydantic import validate_call
@@ -18,6 +18,8 @@
1818

1919

2020
class AgentService(BaseService[AgentDto, Agent]):
21+
agent_flows: Dict[str, List["AgentFlow"]] = {}
22+
2123
def __init__(
2224
self,
2325
relation_service: RelationService,
@@ -40,10 +42,10 @@ async def build(self, entity: AgentEntity):
4042
llm = await self.model_service.build(
4143
ModelEntity(agent_entity_details.llm_id)
4244
)
43-
prompt_entities: List[
44-
PromptEntity
45-
] = await self.relation_service.get_related_entities(
46-
entity, RelationDirection.TO, PromptEntity
45+
prompt_entities: List[PromptEntity] = (
46+
await self.relation_service.get_related_entities(
47+
entity, RelationDirection.TO, PromptEntity
48+
)
4749
)
4850
prompt_details: list[PromptDto] = await self.prompt_service.read_by_ids(
4951
prompt_entities

0 commit comments

Comments
 (0)