242 lines
7.4 KiB
Python
242 lines
7.4 KiB
Python
"""AI PMO workflow handlers and registry."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Optional, TYPE_CHECKING
|
|
|
|
from ..models import ConversationSession, IngestEvent, Project, WorkItem
|
|
from ..schemas.world_model import WorldModel
|
|
from .ai_pmo_intent_routing import (
|
|
INTENT_FOLLOWUP_CONTINUE,
|
|
ROUTE_ACTION_DISCOVER,
|
|
ROUTE_FAMILY_FOLLOWUP,
|
|
ROUTE_FAMILY_PROJECT_CREATE,
|
|
ROUTE_FAMILY_PROJECT_SIGNAL,
|
|
ROUTE_FAMILY_QUERY,
|
|
ROUTE_FAMILY_SIGNAL_SOURCE,
|
|
CanonicalIntent,
|
|
WorkflowRoute,
|
|
_safe_int,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from .ai_pmo_kernel_service import AIPMOKernelService
|
|
|
|
|
|
class ContextAssembler:
|
|
"""拼装 world model 和工作流上下文,不承担执行逻辑。"""
|
|
|
|
def __init__(self, kernel: "AIPMOKernelService") -> None:
|
|
self.kernel = kernel
|
|
|
|
async def assemble_world_model(
|
|
self,
|
|
*,
|
|
sender: str,
|
|
message_text: str,
|
|
project_hint_id: Optional[int],
|
|
session: Optional[ConversationSession],
|
|
) -> tuple[Optional[Project], WorldModel]:
|
|
query_tables: set[str] = set()
|
|
project = await self.kernel._resolve_project(
|
|
message_text=message_text,
|
|
project_hint_id=project_hint_id,
|
|
query_tables=query_tables,
|
|
session=session,
|
|
)
|
|
world_model = await self.kernel.build_world_model(
|
|
tenant_id=self.kernel.tenant_id,
|
|
project_id=_safe_int(getattr(project, "id", None)),
|
|
sender=sender,
|
|
project=project,
|
|
pre_queried_tables=query_tables,
|
|
)
|
|
return project, world_model
|
|
|
|
|
|
class WorkflowHandler:
|
|
"""工作流处理器基类。"""
|
|
|
|
def __init__(self, kernel: "AIPMOKernelService") -> None:
|
|
self.kernel = kernel
|
|
|
|
async def start(
|
|
self,
|
|
*,
|
|
route: WorkflowRoute,
|
|
intent: CanonicalIntent,
|
|
message_text: str,
|
|
sender: str,
|
|
operator_user_id: Optional[int],
|
|
source_system: str,
|
|
source_message_id: str,
|
|
channel_ref: Optional[str],
|
|
) -> dict[str, object]:
|
|
raise NotImplementedError
|
|
|
|
async def resume(
|
|
self,
|
|
*,
|
|
route: WorkflowRoute,
|
|
target_work_item: WorkItem,
|
|
target_ingest: IngestEvent,
|
|
followup_sender: str,
|
|
operator_user_id: Optional[int],
|
|
source_system: str,
|
|
source_message_id: str,
|
|
message_text: str,
|
|
world_model: dict[str, object],
|
|
) -> tuple[dict[str, object], dict[str, object]]:
|
|
return await self.kernel._resume_followup_target(
|
|
target_work_item=target_work_item,
|
|
target_ingest=target_ingest,
|
|
followup_intent=CanonicalIntent(
|
|
name=INTENT_FOLLOWUP_CONTINUE,
|
|
confidence=0.9,
|
|
project_id=_safe_int(target_work_item.project_id),
|
|
signal_title="",
|
|
signal_summary="",
|
|
metadata={
|
|
"workflow_route": route.key,
|
|
"workflow_family": route.family,
|
|
"workflow_action": route.action,
|
|
},
|
|
),
|
|
followup_sender=followup_sender,
|
|
operator_user_id=operator_user_id,
|
|
source_system=source_system,
|
|
source_message_id=source_message_id,
|
|
world_model=world_model,
|
|
message_text=message_text,
|
|
workflow_route=route,
|
|
)
|
|
|
|
|
|
class QueryWorkflowHandler(WorkflowHandler):
|
|
async def start(
|
|
self,
|
|
*,
|
|
route: WorkflowRoute,
|
|
intent: CanonicalIntent,
|
|
message_text: str,
|
|
sender: str,
|
|
operator_user_id: Optional[int],
|
|
source_system: str,
|
|
source_message_id: str,
|
|
channel_ref: Optional[str],
|
|
) -> dict[str, object]:
|
|
_ = route
|
|
return await self.kernel.executor.execute(
|
|
intent=intent,
|
|
message_text=message_text,
|
|
sender=sender,
|
|
operator_user_id=operator_user_id,
|
|
source_system=source_system,
|
|
source_message_id=source_message_id,
|
|
channel_ref=channel_ref,
|
|
)
|
|
|
|
|
|
class ProjectCreateWorkflowHandler(WorkflowHandler):
|
|
async def start(
|
|
self,
|
|
*,
|
|
route: WorkflowRoute,
|
|
intent: CanonicalIntent,
|
|
message_text: str,
|
|
sender: str,
|
|
operator_user_id: Optional[int],
|
|
source_system: str,
|
|
source_message_id: str,
|
|
channel_ref: Optional[str],
|
|
) -> dict[str, object]:
|
|
_ = route
|
|
return await self.kernel.executor.execute(
|
|
intent=intent,
|
|
message_text=message_text,
|
|
sender=sender,
|
|
operator_user_id=operator_user_id,
|
|
source_system=source_system,
|
|
source_message_id=source_message_id,
|
|
channel_ref=channel_ref,
|
|
)
|
|
|
|
|
|
class SignalSourceWorkflowHandler(WorkflowHandler):
|
|
async def start(
|
|
self,
|
|
*,
|
|
route: WorkflowRoute,
|
|
intent: CanonicalIntent,
|
|
message_text: str,
|
|
sender: str,
|
|
operator_user_id: Optional[int],
|
|
source_system: str,
|
|
source_message_id: str,
|
|
channel_ref: Optional[str],
|
|
) -> dict[str, object]:
|
|
_ = route
|
|
return await self.kernel.executor.execute(
|
|
intent=intent,
|
|
message_text=message_text,
|
|
sender=sender,
|
|
operator_user_id=operator_user_id,
|
|
source_system=source_system,
|
|
source_message_id=source_message_id,
|
|
channel_ref=channel_ref,
|
|
)
|
|
|
|
|
|
class ProjectSignalWorkflowHandler(WorkflowHandler):
|
|
async def start(
|
|
self,
|
|
*,
|
|
route: WorkflowRoute,
|
|
intent: CanonicalIntent,
|
|
message_text: str,
|
|
sender: str,
|
|
operator_user_id: Optional[int],
|
|
source_system: str,
|
|
source_message_id: str,
|
|
channel_ref: Optional[str],
|
|
) -> dict[str, object]:
|
|
_ = sender
|
|
_ = operator_user_id
|
|
if route.action == ROUTE_ACTION_DISCOVER:
|
|
return await self.kernel.executor.discover_project_signal_candidates(
|
|
intent=intent,
|
|
message_text=message_text,
|
|
source_system=source_system,
|
|
source_message_id=source_message_id,
|
|
channel_ref=channel_ref,
|
|
)
|
|
return await self.kernel.executor.execute(
|
|
intent=intent,
|
|
message_text=message_text,
|
|
sender=sender,
|
|
operator_user_id=operator_user_id,
|
|
source_system=source_system,
|
|
source_message_id=source_message_id,
|
|
channel_ref=channel_ref,
|
|
)
|
|
|
|
|
|
class WorkflowRegistry:
|
|
"""按 family.action 路由处理器。"""
|
|
|
|
def __init__(self, kernel: "AIPMOKernelService") -> None:
|
|
self.kernel = kernel
|
|
self._handlers: dict[str, WorkflowHandler] = {
|
|
ROUTE_FAMILY_QUERY: QueryWorkflowHandler(kernel),
|
|
ROUTE_FAMILY_PROJECT_CREATE: ProjectCreateWorkflowHandler(kernel),
|
|
ROUTE_FAMILY_PROJECT_SIGNAL: ProjectSignalWorkflowHandler(kernel),
|
|
ROUTE_FAMILY_SIGNAL_SOURCE: SignalSourceWorkflowHandler(kernel),
|
|
ROUTE_FAMILY_FOLLOWUP: WorkflowHandler(kernel),
|
|
}
|
|
|
|
def get_handler(self, *, route: WorkflowRoute) -> WorkflowHandler:
|
|
handler = self._handlers.get(route.family)
|
|
if handler is None:
|
|
return self._handlers[ROUTE_FAMILY_QUERY]
|
|
return handler
|