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:
@@ -0,0 +1,82 @@
|
||||
# Copyright 2026 zhaoxi826
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from sqlalchemy import select, func
|
||||
from typing import List
|
||||
from kilostar.core.postgres_database.model.chat_history import (
|
||||
ChatHistoryRegister,
|
||||
ChatHistoryMessage,
|
||||
)
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker, AsyncSession
|
||||
from ulid import ULID
|
||||
|
||||
|
||||
class ChatHistoryDatabase:
|
||||
def __init__(self, async_session_maker: async_sessionmaker[AsyncSession]):
|
||||
self.async_session_maker = async_session_maker
|
||||
|
||||
async def create_chat_session(
|
||||
self, user_id: str, title: str = "新对话"
|
||||
) -> ChatHistoryRegister:
|
||||
async with self.async_session_maker() as session:
|
||||
chat_id = str(ULID())
|
||||
chat = ChatHistoryRegister(chat_id=chat_id, user_id=user_id, title=title)
|
||||
session.add(chat)
|
||||
await session.commit()
|
||||
await session.refresh(chat)
|
||||
return chat
|
||||
|
||||
async def list_chat_sessions(self, user_id: str) -> List[ChatHistoryRegister]:
|
||||
async with self.async_session_maker() as session:
|
||||
statement = (
|
||||
select(ChatHistoryRegister)
|
||||
.where(ChatHistoryRegister.user_id == user_id)
|
||||
.order_by(ChatHistoryRegister.updated_at.desc())
|
||||
)
|
||||
results = await session.execute(statement)
|
||||
return results.scalars().all()
|
||||
|
||||
async def add_chat_message(
|
||||
self, chat_id: str, message: str, message_owner: str
|
||||
) -> ChatHistoryMessage:
|
||||
async with self.async_session_maker() as session:
|
||||
msg_id = str(ULID())
|
||||
msg = ChatHistoryMessage(
|
||||
message_id=msg_id,
|
||||
chat_id=chat_id,
|
||||
message=message,
|
||||
message_owner=message_owner,
|
||||
)
|
||||
session.add(msg)
|
||||
# Update the chat session's updated_at
|
||||
statement = select(ChatHistoryRegister).where(
|
||||
ChatHistoryRegister.chat_id == chat_id
|
||||
)
|
||||
results = await session.execute(statement)
|
||||
chat = results.scalar_one_or_none()
|
||||
if chat:
|
||||
chat.updated_at = func.now()
|
||||
await session.commit()
|
||||
await session.refresh(msg)
|
||||
return msg
|
||||
|
||||
async def list_chat_messages(self, chat_id: str) -> List[ChatHistoryMessage]:
|
||||
async with self.async_session_maker() as session:
|
||||
statement = (
|
||||
select(ChatHistoryMessage)
|
||||
.where(ChatHistoryMessage.chat_id == chat_id)
|
||||
.order_by(ChatHistoryMessage.created_at.asc())
|
||||
)
|
||||
results = await session.execute(statement)
|
||||
return results.scalars().all()
|
||||
@@ -0,0 +1,96 @@
|
||||
# Copyright 2026 zhaoxi826
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from sqlalchemy import select
|
||||
from typing import List, Optional
|
||||
from kilostar.core.postgres_database.model.workflow import (
|
||||
Workflow,
|
||||
WorkflowContextModel,
|
||||
)
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker, AsyncSession
|
||||
|
||||
|
||||
class WorkflowDatabase:
|
||||
def __init__(self, async_session_maker: async_sessionmaker[AsyncSession]):
|
||||
self.async_session_maker = async_session_maker
|
||||
|
||||
async def create_workflow(
|
||||
self, trace_id: str, user_id: str, title: str, command: str
|
||||
) -> Workflow:
|
||||
async with self.async_session_maker() as session:
|
||||
wf = Workflow(
|
||||
trace_id=trace_id,
|
||||
user_id=user_id,
|
||||
title=title,
|
||||
command=command,
|
||||
status="creating",
|
||||
)
|
||||
session.add(wf)
|
||||
await session.commit()
|
||||
await session.refresh(wf)
|
||||
return wf
|
||||
|
||||
async def get_workflow(self, trace_id: str) -> Optional[Workflow]:
|
||||
async with self.async_session_maker() as session:
|
||||
statement = select(Workflow).where(Workflow.trace_id == trace_id)
|
||||
results = await session.execute(statement)
|
||||
return results.scalar_one_or_none()
|
||||
|
||||
async def update_workflow_status(
|
||||
self, trace_id: str, status: str
|
||||
) -> Optional[Workflow]:
|
||||
async with self.async_session_maker() as session:
|
||||
statement = select(Workflow).where(Workflow.trace_id == trace_id)
|
||||
results = await session.execute(statement)
|
||||
record = results.scalar_one_or_none()
|
||||
if record:
|
||||
record.status = status
|
||||
await session.commit()
|
||||
await session.refresh(record)
|
||||
return record
|
||||
|
||||
async def list_workflows(self, user_id: str) -> List[Workflow]:
|
||||
async with self.async_session_maker() as session:
|
||||
statement = select(Workflow).where(Workflow.user_id == user_id)
|
||||
results = await session.execute(statement)
|
||||
return results.scalars().all()
|
||||
|
||||
async def upsert_workflow_context(
|
||||
self, trace_id: str, **kwargs
|
||||
) -> WorkflowContextModel:
|
||||
async with self.async_session_maker() as session:
|
||||
statement = select(WorkflowContextModel).where(
|
||||
WorkflowContextModel.trace_id == trace_id
|
||||
)
|
||||
results = await session.execute(statement)
|
||||
record = results.scalar_one_or_none()
|
||||
if record:
|
||||
for key, value in kwargs.items():
|
||||
setattr(record, key, value)
|
||||
else:
|
||||
record = WorkflowContextModel(trace_id=trace_id, **kwargs)
|
||||
session.add(record)
|
||||
await session.commit()
|
||||
await session.refresh(record)
|
||||
return record
|
||||
|
||||
async def get_workflow_context(
|
||||
self, trace_id: str
|
||||
) -> Optional[WorkflowContextModel]:
|
||||
async with self.async_session_maker() as session:
|
||||
statement = select(WorkflowContextModel).where(
|
||||
WorkflowContextModel.trace_id == trace_id
|
||||
)
|
||||
results = await session.execute(statement)
|
||||
return results.scalar_one_or_none()
|
||||
Reference in New Issue
Block a user