feat: included service center in playground feature to replace old services
This commit is contained in:
parent
1e678a8897
commit
13322e7cf8
3 changed files with 142 additions and 20 deletions
|
|
@ -6,7 +6,7 @@ Handles feature initialization and RBAC catalog registration.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict, List, Any
|
from typing import Dict, List, Any, Optional
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -48,6 +48,16 @@ RESOURCE_OBJECTS = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Service requirements - services this feature needs from the service center
|
||||||
|
# Same as automation: chatplayground runs the same WorkflowManager and workflow methods
|
||||||
|
REQUIRED_SERVICES = [
|
||||||
|
{"serviceKey": "chat", "meta": {"usage": "Workflow CRUD, messages, logs"}},
|
||||||
|
{"serviceKey": "ai", "meta": {"usage": "AI planning for workflow execution"}},
|
||||||
|
{"serviceKey": "utils", "meta": {"usage": "Timestamps, utilities"}},
|
||||||
|
{"serviceKey": "billing", "meta": {"usage": "AI call billing"}},
|
||||||
|
{"serviceKey": "extraction", "meta": {"usage": "Workflow method actions"}},
|
||||||
|
{"serviceKey": "sharepoint", "meta": {"usage": "SharePoint actions (listDocuments, uploadDocument, etc.)"}},
|
||||||
|
]
|
||||||
# Template roles for this feature
|
# Template roles for this feature
|
||||||
# Role names MUST follow convention: {featureCode}-{roleName}
|
# Role names MUST follow convention: {featureCode}-{roleName}
|
||||||
TEMPLATE_ROLES = [
|
TEMPLATE_ROLES = [
|
||||||
|
|
@ -104,6 +114,88 @@ TEMPLATE_ROLES = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def getRequiredServiceKeys() -> List[str]:
|
||||||
|
"""Return list of service keys this feature requires."""
|
||||||
|
return [s["serviceKey"] for s in REQUIRED_SERVICES]
|
||||||
|
|
||||||
|
|
||||||
|
def getChatplaygroundServices(
|
||||||
|
user,
|
||||||
|
mandateId: Optional[str] = None,
|
||||||
|
featureInstanceId: Optional[str] = None,
|
||||||
|
workflow=None,
|
||||||
|
) -> "_ChatplaygroundServiceHub":
|
||||||
|
"""
|
||||||
|
Get a service hub for the chatplayground feature using the service center.
|
||||||
|
Resolves only the services declared in REQUIRED_SERVICES.
|
||||||
|
No legacy fallback - service center only.
|
||||||
|
|
||||||
|
Returns a hub-like object with: chat, ai, utils, billing, extraction,
|
||||||
|
sharepoint, rbac, interfaceDbApp, interfaceDbComponent, interfaceDbChat.
|
||||||
|
"""
|
||||||
|
from modules.serviceCenter import getService
|
||||||
|
from modules.serviceCenter.context import ServiceCenterContext
|
||||||
|
|
||||||
|
_workflow = workflow
|
||||||
|
if _workflow is None:
|
||||||
|
_workflow = type("_Placeholder", (), {"featureCode": FEATURE_CODE})()
|
||||||
|
ctx = ServiceCenterContext(
|
||||||
|
user=user,
|
||||||
|
mandate_id=mandateId,
|
||||||
|
feature_instance_id=featureInstanceId,
|
||||||
|
workflow=_workflow,
|
||||||
|
)
|
||||||
|
|
||||||
|
hub = _ChatplaygroundServiceHub()
|
||||||
|
hub.user = user
|
||||||
|
hub.mandateId = mandateId
|
||||||
|
hub.featureInstanceId = featureInstanceId
|
||||||
|
hub.workflow = workflow
|
||||||
|
hub.featureCode = FEATURE_CODE
|
||||||
|
hub.allowedProviders = None
|
||||||
|
|
||||||
|
for spec in REQUIRED_SERVICES:
|
||||||
|
key = spec["serviceKey"]
|
||||||
|
try:
|
||||||
|
svc = getService(key, ctx, legacy_hub=None)
|
||||||
|
setattr(hub, key, svc)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not resolve service '{key}' for chatplayground: {e}")
|
||||||
|
setattr(hub, key, None)
|
||||||
|
|
||||||
|
# Copy interfaces from chat service for WorkflowManager compatibility
|
||||||
|
if hub.chat:
|
||||||
|
hub.interfaceDbApp = getattr(hub.chat, "interfaceDbApp", None)
|
||||||
|
hub.interfaceDbComponent = getattr(hub.chat, "interfaceDbComponent", None)
|
||||||
|
hub.interfaceDbChat = getattr(hub.chat, "interfaceDbChat", None)
|
||||||
|
|
||||||
|
# RBAC for MethodBase action permission checks (workflow methods)
|
||||||
|
hub.rbac = getattr(hub.interfaceDbApp, "rbac", None) if hub.interfaceDbApp else None
|
||||||
|
|
||||||
|
return hub
|
||||||
|
|
||||||
|
|
||||||
|
class _ChatplaygroundServiceHub:
|
||||||
|
"""Lightweight hub exposing only services required by the chatplayground feature."""
|
||||||
|
|
||||||
|
user = None
|
||||||
|
mandateId = None
|
||||||
|
featureInstanceId = None
|
||||||
|
workflow = None
|
||||||
|
featureCode = "chatplayground"
|
||||||
|
allowedProviders = None
|
||||||
|
interfaceDbApp = None
|
||||||
|
interfaceDbComponent = None
|
||||||
|
interfaceDbChat = None
|
||||||
|
rbac = None
|
||||||
|
chat = None
|
||||||
|
ai = None
|
||||||
|
utils = None
|
||||||
|
billing = None
|
||||||
|
extraction = None
|
||||||
|
sharepoint = None
|
||||||
|
|
||||||
|
|
||||||
def getFeatureDefinition() -> Dict[str, Any]:
|
def getFeatureDefinition() -> Dict[str, Any]:
|
||||||
"""Return the feature definition for registration."""
|
"""Return the feature definition for registration."""
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ from modules.datamodels.datamodelChat import ChatWorkflow, UserInputRequest, Wor
|
||||||
|
|
||||||
# Import workflow control functions
|
# Import workflow control functions
|
||||||
from modules.workflows.automation import chatStart, chatStop
|
from modules.workflows.automation import chatStart, chatStop
|
||||||
|
from modules.features.chatplayground.mainChatplayground import getChatplaygroundServices
|
||||||
|
|
||||||
# Configure logger
|
# Configure logger
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -95,6 +96,16 @@ async def start_workflow(
|
||||||
# Validate access and get mandate ID
|
# Validate access and get mandate ID
|
||||||
mandateId = _validateInstanceAccess(instanceId, context)
|
mandateId = _validateInstanceAccess(instanceId, context)
|
||||||
|
|
||||||
|
# Get chatplayground services from service center (not automation)
|
||||||
|
services = getChatplaygroundServices(
|
||||||
|
context.user,
|
||||||
|
mandateId=mandateId,
|
||||||
|
featureInstanceId=instanceId,
|
||||||
|
)
|
||||||
|
services.featureCode = 'chatplayground'
|
||||||
|
if hasattr(userInput, 'allowedProviders') and userInput.allowedProviders:
|
||||||
|
services.allowedProviders = userInput.allowedProviders
|
||||||
|
|
||||||
# Start or continue workflow
|
# Start or continue workflow
|
||||||
workflow = await chatStart(
|
workflow = await chatStart(
|
||||||
context.user,
|
context.user,
|
||||||
|
|
@ -103,7 +114,8 @@ async def start_workflow(
|
||||||
workflowId,
|
workflowId,
|
||||||
mandateId=mandateId,
|
mandateId=mandateId,
|
||||||
featureInstanceId=instanceId,
|
featureInstanceId=instanceId,
|
||||||
featureCode='chatplayground'
|
featureCode='chatplayground',
|
||||||
|
services=services,
|
||||||
)
|
)
|
||||||
|
|
||||||
return workflow
|
return workflow
|
||||||
|
|
@ -132,12 +144,22 @@ async def stop_workflow(
|
||||||
# Validate access and get mandate ID
|
# Validate access and get mandate ID
|
||||||
mandateId = _validateInstanceAccess(instanceId, context)
|
mandateId = _validateInstanceAccess(instanceId, context)
|
||||||
|
|
||||||
|
# Get chatplayground services from service center (not automation)
|
||||||
|
services = getChatplaygroundServices(
|
||||||
|
context.user,
|
||||||
|
mandateId=mandateId,
|
||||||
|
featureInstanceId=instanceId,
|
||||||
|
)
|
||||||
|
services.featureCode = 'chatplayground'
|
||||||
|
|
||||||
# Stop workflow (pass featureInstanceId for proper RBAC filtering)
|
# Stop workflow (pass featureInstanceId for proper RBAC filtering)
|
||||||
workflow = await chatStop(
|
workflow = await chatStop(
|
||||||
context.user,
|
context.user,
|
||||||
workflowId,
|
workflowId,
|
||||||
mandateId=mandateId,
|
mandateId=mandateId,
|
||||||
featureInstanceId=instanceId
|
featureInstanceId=instanceId,
|
||||||
|
featureCode='chatplayground',
|
||||||
|
services=services,
|
||||||
)
|
)
|
||||||
|
|
||||||
return workflow
|
return workflow
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ from .subAutomationUtils import parseScheduleToCron, planToPrompt, replacePlaceh
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def chatStart(currentUser: User, userInput: UserInputRequest, workflowMode: WorkflowModeEnum, workflowId: Optional[str] = None, mandateId: Optional[str] = None, featureInstanceId: Optional[str] = None, featureCode: Optional[str] = None) -> ChatWorkflow:
|
async def chatStart(currentUser: User, userInput: UserInputRequest, workflowMode: WorkflowModeEnum, workflowId: Optional[str] = None, mandateId: Optional[str] = None, featureInstanceId: Optional[str] = None, featureCode: Optional[str] = None, services=None) -> ChatWorkflow:
|
||||||
"""
|
"""
|
||||||
Starts a new chat or continues an existing one, then launches processing asynchronously.
|
Starts a new chat or continues an existing one, then launches processing asynchronously.
|
||||||
|
|
||||||
|
|
@ -36,9 +36,11 @@ async def chatStart(currentUser: User, userInput: UserInputRequest, workflowMode
|
||||||
mandateId: Mandate ID (required for billing)
|
mandateId: Mandate ID (required for billing)
|
||||||
featureInstanceId: Feature instance ID (required for billing)
|
featureInstanceId: Feature instance ID (required for billing)
|
||||||
featureCode: Feature code (e.g., 'chatplayground', 'automation')
|
featureCode: Feature code (e.g., 'chatplayground', 'automation')
|
||||||
|
services: Pre-built service hub from the calling feature (required). Each feature must pass its own services.
|
||||||
"""
|
"""
|
||||||
|
if services is None:
|
||||||
|
raise ValueError("services is required: each feature must pass its own service hub (e.g. getChatplaygroundServices, getAutomationServices)")
|
||||||
try:
|
try:
|
||||||
services = getAutomationServices(currentUser, mandateId=mandateId, featureInstanceId=featureInstanceId)
|
|
||||||
|
|
||||||
# Store allowedProviders in services context for model selection
|
# Store allowedProviders in services context for model selection
|
||||||
if hasattr(userInput, 'allowedProviders') and userInput.allowedProviders:
|
if hasattr(userInput, 'allowedProviders') and userInput.allowedProviders:
|
||||||
|
|
@ -56,10 +58,11 @@ async def chatStart(currentUser: User, userInput: UserInputRequest, workflowMode
|
||||||
logger.error(f"Error starting chat: {str(e)}")
|
logger.error(f"Error starting chat: {str(e)}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
async def chatStop(currentUser: User, workflowId: str, mandateId: Optional[str] = None, featureInstanceId: Optional[str] = None, featureCode: Optional[str] = None) -> ChatWorkflow:
|
async def chatStop(currentUser: User, workflowId: str, mandateId: Optional[str] = None, featureInstanceId: Optional[str] = None, featureCode: Optional[str] = None, services=None) -> ChatWorkflow:
|
||||||
"""Stops a running chat."""
|
"""Stops a running chat. Caller must pass services from the owning feature."""
|
||||||
|
if services is None:
|
||||||
|
raise ValueError("services is required: each feature must pass its own service hub (e.g. getChatplaygroundServices, getAutomationServices)")
|
||||||
try:
|
try:
|
||||||
services = getAutomationServices(currentUser, mandateId=mandateId, featureInstanceId=featureInstanceId)
|
|
||||||
if featureCode:
|
if featureCode:
|
||||||
services.featureCode = featureCode
|
services.featureCode = featureCode
|
||||||
workflowManager = WorkflowManager(services)
|
workflowManager = WorkflowManager(services)
|
||||||
|
|
@ -146,6 +149,12 @@ async def executeAutomation(automationId: str, automation, creatorUser: User, se
|
||||||
|
|
||||||
# 3. Start workflow using chatStart with creator's context
|
# 3. Start workflow using chatStart with creator's context
|
||||||
# mandateId and featureInstanceId come from the automation definition
|
# mandateId and featureInstanceId come from the automation definition
|
||||||
|
# Each feature must pass its own services - no fallback
|
||||||
|
creatorServices = getAutomationServices(
|
||||||
|
creatorUser,
|
||||||
|
mandateId=automationMandateId,
|
||||||
|
featureInstanceId=automationFeatureInstanceId,
|
||||||
|
)
|
||||||
workflow = await chatStart(
|
workflow = await chatStart(
|
||||||
currentUser=creatorUser,
|
currentUser=creatorUser,
|
||||||
userInput=userInput,
|
userInput=userInput,
|
||||||
|
|
@ -153,7 +162,8 @@ async def executeAutomation(automationId: str, automation, creatorUser: User, se
|
||||||
workflowId=None,
|
workflowId=None,
|
||||||
mandateId=automationMandateId,
|
mandateId=automationMandateId,
|
||||||
featureInstanceId=automationFeatureInstanceId,
|
featureInstanceId=automationFeatureInstanceId,
|
||||||
featureCode='automation'
|
featureCode='automation',
|
||||||
|
services=creatorServices,
|
||||||
)
|
)
|
||||||
|
|
||||||
executionLog["workflowId"] = workflow.id
|
executionLog["workflowId"] = workflow.id
|
||||||
|
|
@ -161,9 +171,7 @@ async def executeAutomation(automationId: str, automation, creatorUser: User, se
|
||||||
executionLog["messages"].append(f"Workflow {workflow.id} started successfully")
|
executionLog["messages"].append(f"Workflow {workflow.id} started successfully")
|
||||||
logger.info(f"Started workflow {workflow.id} with plan containing {len(plan.get('tasks', []))} tasks (plan embedded in userInput)")
|
logger.info(f"Started workflow {workflow.id} with plan containing {len(plan.get('tasks', []))} tasks (plan embedded in userInput)")
|
||||||
|
|
||||||
# Set workflow name with "automated" prefix — use creatorUser's Services
|
# Set workflow name with "automated" prefix — use creatorServices from chatStart
|
||||||
# (services parameter is eventServices with eventUser context, must use creatorUser context)
|
|
||||||
creatorServices = getAutomationServices(creatorUser, mandateId=automationMandateId, featureInstanceId=automationFeatureInstanceId)
|
|
||||||
automationLabel = automation.label or "Unknown Automation"
|
automationLabel = automation.label or "Unknown Automation"
|
||||||
workflowName = f"automated: {automationLabel}"
|
workflowName = f"automated: {automationLabel}"
|
||||||
creatorServices.interfaceDbChat.updateWorkflow(workflow.id, {"name": workflowName})
|
creatorServices.interfaceDbChat.updateWorkflow(workflow.id, {"name": workflowName})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue