task planner works

This commit is contained in:
ValueOn AG 2025-06-21 03:06:00 +02:00
parent b907d068b3
commit f86b3a9e2e
15 changed files with 589 additions and 251 deletions

View file

@ -15,7 +15,7 @@ Connector_AiOpenai_MAX_TOKENS = 2000
# Anthropic configuration # Anthropic configuration
Connector_AiAnthropic_API_URL = https://api.anthropic.com/v1/messages Connector_AiAnthropic_API_URL = https://api.anthropic.com/v1/messages
Connector_AiAnthropic_API_SECRET = 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 Connector_AiAnthropic_API_SECRET = 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
Connector_AiAnthropic_MODEL_NAME = claude-3-opus-20240229 Connector_AiAnthropic_MODEL_NAME = claude-3-5-sonnet-20241022
Connector_AiAnthropic_TEMPERATURE = 0.2 Connector_AiAnthropic_TEMPERATURE = 0.2
Connector_AiAnthropic_MAX_TOKENS = 2000 Connector_AiAnthropic_MAX_TOKENS = 2000

View file

@ -9,12 +9,11 @@ import uuid
from datetime import datetime from datetime import datetime
from typing import Dict, Any, List, Optional, Union from typing import Dict, Any, List, Optional, Union
import hashlib
import asyncio import asyncio
from modules.interfaces.interfaceChatAccess import ChatAccess from modules.interfaces.interfaceChatAccess import ChatAccess
from modules.interfaces.interfaceChatModel import ( from modules.interfaces.interfaceChatModel import (
TaskStatus, UserInputRequest, ChatDocument, TaskItem, ChatStat, ChatLog, ChatMessage, ChatWorkflow, TaskAction TaskStatus, UserInputRequest, ChatDocument, TaskItem, ChatStat, ChatLog, ChatMessage, ChatWorkflow, TaskAction, TaskResult, ActionResult
) )
from modules.interfaces.interfaceAppModel import User from modules.interfaces.interfaceAppModel import User
@ -169,7 +168,7 @@ class ChatObjects:
logs=[ChatLog(**log) for log in workflow.get("logs", [])], logs=[ChatLog(**log) for log in workflow.get("logs", [])],
messages=[ChatMessage(**msg) for msg in workflow.get("messages", [])], messages=[ChatMessage(**msg) for msg in workflow.get("messages", [])],
stats=ChatStat(**workflow.get("dataStats", {})) if workflow.get("dataStats") else None, stats=ChatStat(**workflow.get("dataStats", {})) if workflow.get("dataStats") else None,
mandateId=workflow.get("mandateId", self.currentUser.get("mandateId")) mandateId=workflow.get("mandateId", self.currentUser.mandateId)
) )
except Exception as e: except Exception as e:
logger.error(f"Error validating workflow data: {str(e)}") logger.error(f"Error validating workflow data: {str(e)}")
@ -202,7 +201,7 @@ class ChatObjects:
logs=[], logs=[],
messages=[], messages=[],
stats=ChatStat(**created.get("dataStats", {})) if created.get("dataStats") else None, stats=ChatStat(**created.get("dataStats", {})) if created.get("dataStats") else None,
mandateId=created.get("mandateId", self.currentUser.get("mandateId")) mandateId=created.get("mandateId", self.currentUser.mandateId)
) )
def updateWorkflow(self, workflowId: str, workflowData: Dict[str, Any]) -> ChatWorkflow: def updateWorkflow(self, workflowId: str, workflowData: Dict[str, Any]) -> ChatWorkflow:
@ -264,6 +263,9 @@ class ChatObjects:
def createWorkflowMessage(self, messageData: Dict[str, Any]) -> ChatMessage: def createWorkflowMessage(self, messageData: Dict[str, Any]) -> ChatMessage:
"""Creates a message for a workflow if user has access.""" """Creates a message for a workflow if user has access."""
try: try:
# Ensure ID is present
if "id" not in messageData or not messageData["id"]:
messageData["id"] = f"msg_{uuid.uuid4()}"
# Check required fields # Check required fields
requiredFields = ["id", "workflowId"] requiredFields = ["id", "workflowId"]
for field in requiredFields: for field in requiredFields:
@ -299,16 +301,6 @@ class ChatObjects:
# Create message in database # Create message in database
createdMessage = self.db.recordCreate("workflowMessages", messageData) createdMessage = self.db.recordCreate("workflowMessages", messageData)
# Update workflow's messageIds if this is a new message
if createdMessage:
# Get current messageIds or initialize empty list
messageIds = workflow.messageIds if hasattr(workflow, 'messageIds') else []
# Add the new message ID if not already in the list
if createdMessage["id"] not in messageIds:
messageIds.append(createdMessage["id"])
self.updateWorkflow(workflowId, {"messageIds": messageIds})
# Convert to ChatMessage model # Convert to ChatMessage model
return ChatMessage( return ChatMessage(
id=createdMessage["id"], id=createdMessage["id"],
@ -319,7 +311,7 @@ class ChatObjects:
message=createdMessage.get("message"), message=createdMessage.get("message"),
role=createdMessage.get("role", "assistant"), role=createdMessage.get("role", "assistant"),
status=createdMessage.get("status", "step"), status=createdMessage.get("status", "step"),
sequenceNr=len(messageIds), # Set sequence number based on message position sequenceNr=len(workflow.messages) + 1, # Use messages list length for sequence number
publishedAt=createdMessage.get("publishedAt", self._getCurrentTimestamp()), publishedAt=createdMessage.get("publishedAt", self._getCurrentTimestamp()),
stats=ChatStat(**createdMessage.get("stats", {})) if createdMessage.get("stats") else None stats=ChatStat(**createdMessage.get("stats", {})) if createdMessage.get("stats") else None
) )
@ -702,15 +694,6 @@ class ChatObjects:
messageCount = len(messages) messageCount = len(messages)
logger.debug(f"Loaded {messageCount} messages for workflow {workflowId}") logger.debug(f"Loaded {messageCount} messages for workflow {workflowId}")
# Check if messageIds exists and is valid
messageIds = workflow.get("messageIds", [])
if not messageIds or len(messageIds) != len(messages):
# Rebuild messageIds from messages
messageIds = [msg.get("id") for msg in messages]
# Update in database
self.updateWorkflow(workflowId, {"messageIds": messageIds})
logger.debug(f"Rebuilt messageIds for workflow {workflowId}")
# Log document counts for each message # Log document counts for each message
for msg in messages: for msg in messages:
docCount = len(msg.get("documents", [])) docCount = len(msg.get("documents", []))
@ -725,7 +708,6 @@ class ChatObjects:
# Assemble complete workflow object # Assemble complete workflow object
completeWorkflow = workflow.copy() completeWorkflow = workflow.copy()
completeWorkflow["messages"] = messages completeWorkflow["messages"] = messages
completeWorkflow["messageIds"] = messageIds
completeWorkflow["logs"] = logs completeWorkflow["logs"] = logs
return completeWorkflow return completeWorkflow
@ -862,6 +844,7 @@ class ChatObjects:
return TaskItem( return TaskItem(
id=task["id"], id=task["id"],
workflowId=task["workflowId"], workflowId=task["workflowId"],
userInput=task.get("userInput", ""),
status=task.get("status", TaskStatus.PENDING), status=task.get("status", TaskStatus.PENDING),
error=task.get("error"), error=task.get("error"),
startedAt=task.get("startedAt"), startedAt=task.get("startedAt"),
@ -892,6 +875,9 @@ class ChatObjects:
def createTask(self, taskData: Dict[str, Any]) -> TaskItem: def createTask(self, taskData: Dict[str, Any]) -> TaskItem:
"""Creates a new task if user has access to the workflow.""" """Creates a new task if user has access to the workflow."""
try: try:
# Ensure ID is present
if "id" not in taskData or not taskData["id"]:
taskData["id"] = f"task_{uuid.uuid4()}"
# Check workflow access # Check workflow access
workflowId = taskData.get("workflowId") workflowId = taskData.get("workflowId")
if not workflowId: if not workflowId:
@ -908,9 +894,6 @@ class ChatObjects:
return None return None
# Ensure required fields # Ensure required fields
if "id" not in taskData:
taskData["id"] = f"task_{uuid.uuid4()}"
if "status" not in taskData: if "status" not in taskData:
taskData["status"] = TaskStatus.PENDING taskData["status"] = TaskStatus.PENDING
@ -924,6 +907,7 @@ class ChatObjects:
task = TaskItem( task = TaskItem(
id=createdTask["id"], id=createdTask["id"],
workflowId=createdTask["workflowId"], workflowId=createdTask["workflowId"],
userInput=createdTask.get("userInput", ""),
status=createdTask.get("status", TaskStatus.PENDING), status=createdTask.get("status", TaskStatus.PENDING),
error=createdTask.get("error"), error=createdTask.get("error"),
startedAt=createdTask.get("startedAt"), startedAt=createdTask.get("startedAt"),
@ -938,7 +922,7 @@ class ChatObjects:
) )
# Update workflow's task list # Update workflow's task list
workflowTasks = workflow.get("tasks", []) workflowTasks = workflow.tasks if hasattr(workflow, 'tasks') else []
if task.id not in workflowTasks: if task.id not in workflowTasks:
workflowTasks.append(task.id) workflowTasks.append(task.id)
self.updateWorkflow(workflowId, {"tasks": workflowTasks}) self.updateWorkflow(workflowId, {"tasks": workflowTasks})
@ -975,6 +959,7 @@ class ChatObjects:
return TaskItem( return TaskItem(
id=updatedTask["id"], id=updatedTask["id"],
workflowId=updatedTask["workflowId"], workflowId=updatedTask["workflowId"],
userInput=updatedTask.get("userInput", task.userInput),
status=updatedTask.get("status", task.status), status=updatedTask.get("status", task.status),
error=updatedTask.get("error", task.error), error=updatedTask.get("error", task.error),
startedAt=updatedTask.get("startedAt", task.startedAt), startedAt=updatedTask.get("startedAt", task.startedAt),
@ -1014,7 +999,7 @@ class ChatObjects:
# Delete task # Delete task
if self.db.recordDelete("tasks", taskId): if self.db.recordDelete("tasks", taskId):
# Update workflow's task list # Update workflow's task list
workflowTasks = workflow.get("tasks", []) workflowTasks = workflow.tasks if hasattr(workflow, 'tasks') else []
if taskId in workflowTasks: if taskId in workflowTasks:
workflowTasks.remove(taskId) workflowTasks.remove(taskId)
self.updateWorkflow(task.workflowId, {"tasks": workflowTasks}) self.updateWorkflow(task.workflowId, {"tasks": workflowTasks})
@ -1025,6 +1010,128 @@ class ChatObjects:
logger.error(f"Error deleting task: {str(e)}") logger.error(f"Error deleting task: {str(e)}")
return False return False
# Task Result Management
def createTaskResult(self, resultData: Dict[str, Any]) -> 'TaskResult':
"""Creates a new task result if user has access to the workflow."""
try:
# Ensure ID is present
if "id" not in resultData or not resultData["id"]:
resultData["id"] = f"result_{uuid.uuid4()}"
# Check workflow access if taskId is provided
taskId = resultData.get("taskId")
if taskId:
task = self.getTask(taskId)
if task:
workflow = self.getWorkflow(task.workflowId)
if not workflow:
logger.warning(f"No access to workflow {task.workflowId}")
return None
if not self._canModify("workflows", task.workflowId):
logger.warning(f"No permission to modify workflow {task.workflowId}")
return None
# Ensure required fields
if "status" not in resultData:
resultData["status"] = TaskStatus.PENDING
if "success" not in resultData:
resultData["success"] = False
# Create result in database
createdResult = self.db.recordCreate("taskResults", resultData)
# Convert to TaskResult model
return TaskResult(
taskId=createdResult.get("taskId", ""),
status=createdResult.get("status", TaskStatus.PENDING),
success=createdResult.get("success", False),
feedback=createdResult.get("feedback"),
error=createdResult.get("error")
)
except Exception as e:
logger.error(f"Error creating task result: {str(e)}")
return None
def createActionResult(self, resultData: Dict[str, Any]) -> 'ActionResult':
"""Creates a new action result."""
try:
# Ensure ID is present
if "id" not in resultData or not resultData["id"]:
resultData["id"] = f"action_result_{uuid.uuid4()}"
# Ensure required fields
if "success" not in resultData:
resultData["success"] = False
if "data" not in resultData:
resultData["data"] = {}
# Create result in database
createdResult = self.db.recordCreate("actionResults", resultData)
# Convert to ActionResult model
return ActionResult(
success=createdResult.get("success", False),
data=createdResult.get("data", {}),
metadata=createdResult.get("metadata", {}),
validation=createdResult.get("validation", []),
error=createdResult.get("error")
)
except Exception as e:
logger.error(f"Error creating action result: {str(e)}")
return None
def createTaskAction(self, actionData: Dict[str, Any]) -> TaskAction:
"""Creates a new task action."""
try:
# Ensure ID is present
if "id" not in actionData or not actionData["id"]:
actionData["id"] = f"action_{uuid.uuid4()}"
# Ensure required fields
if "status" not in actionData:
actionData["status"] = TaskStatus.PENDING
if "execMethod" not in actionData:
logger.error("execMethod is required for task action")
return None
if "execAction" not in actionData:
logger.error("execAction is required for task action")
return None
if "execParameters" not in actionData:
actionData["execParameters"] = {}
# Create action in database
createdAction = self.db.recordCreate("taskActions", actionData)
# Convert to TaskAction model
return TaskAction(
id=createdAction["id"],
execMethod=createdAction["execMethod"],
execAction=createdAction["execAction"],
execParameters=createdAction.get("execParameters", {}),
execResultLabel=createdAction.get("execResultLabel"),
status=createdAction.get("status", TaskStatus.PENDING),
error=createdAction.get("error"),
retryCount=createdAction.get("retryCount", 0),
retryMax=createdAction.get("retryMax", 3),
processingTime=createdAction.get("processingTime"),
timestamp=datetime.fromisoformat(createdAction.get("timestamp", datetime.now().isoformat())),
result=createdAction.get("result"),
resultDocuments=createdAction.get("resultDocuments", [])
)
except Exception as e:
logger.error(f"Error creating task action: {str(e)}")
return None
def getInterface(currentUser: Optional[User] = None) -> 'ChatObjects': def getInterface(currentUser: Optional[User] = None) -> 'ChatObjects':
""" """
Returns a ChatObjects instance for the current user. Returns a ChatObjects instance for the current user.

View file

@ -2,7 +2,7 @@ from typing import Dict, Any, Optional
import logging import logging
from datetime import datetime, UTC from datetime import datetime, UTC
from modules.methods.methodBase import MethodBase, ActionResult, action from modules.workflow.methodBase import MethodBase, ActionResult, action
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View file

@ -7,7 +7,7 @@ import logging
from typing import Dict, Any, List, Optional from typing import Dict, Any, List, Optional
from modules.workflow.managerDocument import DocumentManager from modules.workflow.managerDocument import DocumentManager
from modules.methods.methodBase import MethodBase, ActionResult, action from modules.workflow.methodBase import MethodBase, ActionResult, action
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View file

@ -9,7 +9,7 @@ from datetime import datetime, UTC
import json import json
import base64 import base64
from modules.methods.methodBase import MethodBase, ActionResult, action from modules.workflow.methodBase import MethodBase, ActionResult, action
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -23,14 +23,21 @@ class ExcelService:
"""Get Microsoft connection from connection reference""" """Get Microsoft connection from connection reference"""
try: try:
userConnection = self.serviceContainer.getUserConnectionFromConnectionReference(connectionReference) userConnection = self.serviceContainer.getUserConnectionFromConnectionReference(connectionReference)
if userConnection and userConnection.authority == "microsoft" and userConnection.enabled: if not userConnection or userConnection.authority != "msft" or userConnection.status != "active":
return None
# Get the corresponding token for this user and authority
token = self.serviceContainer.interfaceApp.getToken(userConnection.authority)
if not token:
logger.warning(f"No token found for user {userConnection.userId} and authority {userConnection.authority}")
return None
return { return {
"id": userConnection.id, "id": userConnection.id,
"accessToken": userConnection.accessToken, "accessToken": token.tokenAccess,
"refreshToken": userConnection.refreshToken, "refreshToken": token.tokenRefresh,
"scopes": userConnection.scopes "scopes": ["Mail.ReadWrite", "User.Read"] # Default Microsoft scopes
} }
return None
except Exception as e: except Exception as e:
logger.error(f"Error getting Microsoft connection: {str(e)}") logger.error(f"Error getting Microsoft connection: {str(e)}")
return None return None

View file

@ -4,7 +4,7 @@ from typing import Dict, List, Any, Optional
from datetime import datetime, UTC from datetime import datetime, UTC
import logging import logging
from modules.methods.methodBase import MethodBase, ActionResult, action from modules.workflow.methodBase import MethodBase, ActionResult, action
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View file

@ -8,7 +8,7 @@ from typing import Dict, Any, List, Optional
from datetime import datetime, UTC from datetime import datetime, UTC
import json import json
from modules.methods.methodBase import MethodBase, ActionResult, action from modules.workflow.methodBase import MethodBase, ActionResult, action
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -22,14 +22,21 @@ class OutlookService:
"""Get Microsoft connection from connection reference""" """Get Microsoft connection from connection reference"""
try: try:
userConnection = self.serviceContainer.getUserConnectionFromConnectionReference(connectionReference) userConnection = self.serviceContainer.getUserConnectionFromConnectionReference(connectionReference)
if userConnection and userConnection.authority == "microsoft" and userConnection.enabled: if not userConnection or userConnection.authority != "msft" or userConnection.status != "active":
return None
# Get the corresponding token for this user and authority
token = self.serviceContainer.interfaceApp.getToken(userConnection.authority)
if not token:
logger.warning(f"No token found for user {userConnection.userId} and authority {userConnection.authority}")
return None
return { return {
"id": userConnection.id, "id": userConnection.id,
"accessToken": userConnection.accessToken, "accessToken": token.tokenAccess,
"refreshToken": userConnection.refreshToken, "refreshToken": token.tokenRefresh,
"scopes": userConnection.scopes "scopes": ["Mail.ReadWrite", "User.Read"] # Default Microsoft scopes
} }
return None
except Exception as e: except Exception as e:
logger.error(f"Error getting Microsoft connection: {str(e)}") logger.error(f"Error getting Microsoft connection: {str(e)}")
return None return None

View file

@ -9,7 +9,7 @@ from datetime import datetime, UTC
import json import json
import base64 import base64
from modules.methods.methodBase import MethodBase, ActionResult, action from modules.workflow.methodBase import MethodBase, ActionResult, action
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -23,14 +23,21 @@ class PowerpointService:
"""Get Microsoft connection from connection reference""" """Get Microsoft connection from connection reference"""
try: try:
userConnection = self.serviceContainer.getUserConnectionFromConnectionReference(connectionReference) userConnection = self.serviceContainer.getUserConnectionFromConnectionReference(connectionReference)
if userConnection and userConnection.authority == "microsoft" and userConnection.enabled: if not userConnection or userConnection.authority != "msft" or userConnection.status != "active":
return None
# Get the corresponding token for this user and authority
token = self.serviceContainer.interfaceApp.getToken(userConnection.authority)
if not token:
logger.warning(f"No token found for user {userConnection.userId} and authority {userConnection.authority}")
return None
return { return {
"id": userConnection.id, "id": userConnection.id,
"accessToken": userConnection.accessToken, "accessToken": token.tokenAccess,
"refreshToken": userConnection.refreshToken, "refreshToken": token.tokenRefresh,
"scopes": userConnection.scopes "scopes": ["Mail.ReadWrite", "User.Read"] # Default Microsoft scopes
} }
return None
except Exception as e: except Exception as e:
logger.error(f"Error getting Microsoft connection: {str(e)}") logger.error(f"Error getting Microsoft connection: {str(e)}")
return None return None

View file

@ -8,7 +8,7 @@ from typing import Dict, Any, List, Optional
from datetime import datetime, UTC from datetime import datetime, UTC
import json import json
from modules.methods.methodBase import MethodBase, ActionResult, action from modules.workflow.methodBase import MethodBase, ActionResult, action
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -22,14 +22,21 @@ class SharepointService:
"""Get Microsoft connection from connection reference""" """Get Microsoft connection from connection reference"""
try: try:
userConnection = self.serviceContainer.getUserConnectionFromConnectionReference(connectionReference) userConnection = self.serviceContainer.getUserConnectionFromConnectionReference(connectionReference)
if userConnection and userConnection.authority == "microsoft" and userConnection.enabled: if not userConnection or userConnection.authority != "msft" or userConnection.status != "active":
return None
# Get the corresponding token for this user and authority
token = self.serviceContainer.interfaceApp.getToken(userConnection.authority)
if not token:
logger.warning(f"No token found for user {userConnection.userId} and authority {userConnection.authority}")
return None
return { return {
"id": userConnection.id, "id": userConnection.id,
"accessToken": userConnection.accessToken, "accessToken": token.tokenAccess,
"refreshToken": userConnection.refreshToken, "refreshToken": token.tokenRefresh,
"scopes": userConnection.scopes "scopes": ["Mail.ReadWrite", "User.Read"] # Default Microsoft scopes
} }
return None
except Exception as e: except Exception as e:
logger.error(f"Error getting Microsoft connection: {str(e)}") logger.error(f"Error getting Microsoft connection: {str(e)}")
return None return None

View file

@ -10,7 +10,7 @@ import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import time import time
from modules.methods.methodBase import MethodBase, ActionResult, action from modules.workflow.methodBase import MethodBase, ActionResult, action
from modules.shared.configuration import APP_CONFIG from modules.shared.configuration import APP_CONFIG
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View file

@ -2,8 +2,6 @@ import logging
from typing import Dict, Any, Optional, List, Union from typing import Dict, Any, Optional, List, Union
from datetime import datetime, UTC from datetime import datetime, UTC
import json import json
import uuid
import time
from modules.interfaces.interfaceAppModel import User from modules.interfaces.interfaceAppModel import User
from modules.interfaces.interfaceChatModel import ( from modules.interfaces.interfaceChatModel import (
@ -29,10 +27,39 @@ class ChatManager:
self.workflow = workflow self.workflow = workflow
self.service = ServiceContainer(self.currentUser, self.workflow) self.service = ServiceContainer(self.currentUser, self.workflow)
def _extractJsonFromResponse(self, response: str) -> Optional[Dict[str, Any]]:
"""Extract JSON from verbose AI response that may contain explanatory text"""
try:
# First try direct JSON parsing
return json.loads(response)
except json.JSONDecodeError:
# Try to find JSON in the response
import re
# Look for JSON object patterns
json_patterns = [
r'\{.*\}', # Basic JSON object
r'\[\{.*\}\]', # JSON array of objects
]
for pattern in json_patterns:
matches = re.findall(pattern, response, re.DOTALL)
for match in matches:
try:
return json.loads(match)
except json.JSONDecodeError:
continue
# If no JSON found, log the full response for debugging
logger.error(f"Could not extract JSON from response: {response[:500]}...")
return None
# ===== Task Creation and Management ===== # ===== Task Creation and Management =====
async def createInitialTask(self, workflow: ChatWorkflow, initialMessage: ChatMessage) -> Optional[TaskItem]: async def createInitialTask(self, workflow: ChatWorkflow, initialMessage: ChatMessage) -> Optional[TaskItem]:
"""Create the initial task from the first message""" """Create the initial task from the first message"""
try: try:
logger.info(f"Creating initial task for workflow {workflow.id}")
# Create task definition prompt # Create task definition prompt
prompt = await self._createTaskDefinitionPrompt(initialMessage.message, workflow) prompt = await self._createTaskDefinitionPrompt(initialMessage.message, workflow)
@ -40,13 +67,13 @@ class ChatManager:
response = await self.service.callAiTextAdvanced(prompt) response = await self.service.callAiTextAdvanced(prompt)
# Parse response # Parse response
try: taskDef = self._extractJsonFromResponse(response)
taskDef = json.loads(response)
except json.JSONDecodeError:
logger.error(f"Invalid JSON in task definition: {response}")
return None
# Validate task definition # Validate task definition
if not taskDef:
logger.error("Could not extract valid JSON from AI response")
return None
if not isinstance(taskDef, dict): if not isinstance(taskDef, dict):
logger.error("Task definition must be a JSON object") logger.error("Task definition must be a JSON object")
return None return None
@ -61,6 +88,8 @@ class ChatManager:
logger.error("Actions must be a list") logger.error("Actions must be a list")
return None return None
logger.info(f"Task definition validated: {len(taskDef['actions'])} actions")
# Create task using interface # Create task using interface
taskData = { taskData = {
"workflowId": workflow.id, "workflowId": workflow.id,
@ -79,17 +108,40 @@ class ChatManager:
if not all(field in actionDef for field in requiredFields): if not all(field in actionDef for field in requiredFields):
continue continue
action = TaskAction( # Create action using interface
id=str(uuid.uuid4()), actionData = {
execMethod=actionDef["method"], "execMethod": actionDef["method"],
execAction=actionDef["action"], "execAction": actionDef["action"],
execParameters=actionDef["parameters"], "execParameters": actionDef["parameters"],
execResultLabel=actionDef.get("resultLabel") "execResultLabel": actionDef.get("resultLabel")
) }
taskData["actionList"].append(action) action = self.chatInterface.createTaskAction(actionData)
if action:
# Convert TaskAction object to dictionary for database storage
actionDict = {
"id": action.id,
"execMethod": action.execMethod,
"execAction": action.execAction,
"execParameters": action.execParameters,
"execResultLabel": action.execResultLabel,
"status": action.status,
"error": action.error,
"retryCount": action.retryCount,
"retryMax": action.retryMax,
"processingTime": action.processingTime,
"timestamp": action.timestamp.isoformat() if action.timestamp else None,
"result": action.result,
"resultDocuments": action.resultDocuments
}
taskData["actionList"].append(actionDict)
# Create task using interface # Create task using interface
task = self.chatInterface.createTask(taskData) task = self.chatInterface.createTask(taskData)
if task:
logger.info(f"Task created successfully: {task.id}")
else:
logger.error("Failed to create task")
return task return task
except Exception as e: except Exception as e:
@ -99,6 +151,8 @@ class ChatManager:
async def createNextTask(self, workflow: ChatWorkflow, previousResult: TaskResult) -> Optional[TaskItem]: async def createNextTask(self, workflow: ChatWorkflow, previousResult: TaskResult) -> Optional[TaskItem]:
"""Create next task based on previous result""" """Create next task based on previous result"""
try: try:
logger.info(f"Creating next task for workflow {workflow.id}")
# Check if previous result was successful # Check if previous result was successful
if not previousResult.success: if not previousResult.success:
logger.error(f"Previous task failed: {previousResult.error}") logger.error(f"Previous task failed: {previousResult.error}")
@ -111,13 +165,13 @@ class ChatManager:
response = await self.service.callAiTextAdvanced(prompt) response = await self.service.callAiTextAdvanced(prompt)
# Parse response # Parse response
try: taskDef = self._extractJsonFromResponse(response)
taskDef = json.loads(response)
except json.JSONDecodeError:
logger.error(f"Invalid JSON in task definition: {response}")
return None
# Validate task definition # Validate task definition
if not taskDef:
logger.error("Could not extract valid JSON from AI response")
return None
if not isinstance(taskDef, dict): if not isinstance(taskDef, dict):
logger.error("Task definition must be a JSON object") logger.error("Task definition must be a JSON object")
return None return None
@ -132,6 +186,8 @@ class ChatManager:
logger.error("Actions must be a list") logger.error("Actions must be a list")
return None return None
logger.info(f"Next task definition validated: {len(taskDef['actions'])} actions")
# Create task using interface # Create task using interface
taskData = { taskData = {
"workflowId": workflow.id, "workflowId": workflow.id,
@ -150,17 +206,40 @@ class ChatManager:
if not all(field in actionDef for field in requiredFields): if not all(field in actionDef for field in requiredFields):
continue continue
action = TaskAction( # Create action using interface
id=str(uuid.uuid4()), actionData = {
execMethod=actionDef["method"], "execMethod": actionDef["method"],
execAction=actionDef["action"], "execAction": actionDef["action"],
execParameters=actionDef["parameters"], "execParameters": actionDef["parameters"],
execResultLabel=actionDef.get("resultLabel") "execResultLabel": actionDef.get("resultLabel")
) }
taskData["actionList"].append(action) action = self.chatInterface.createTaskAction(actionData)
if action:
# Convert TaskAction object to dictionary for database storage
actionDict = {
"id": action.id,
"execMethod": action.execMethod,
"execAction": action.execAction,
"execParameters": action.execParameters,
"execResultLabel": action.execResultLabel,
"status": action.status,
"error": action.error,
"retryCount": action.retryCount,
"retryMax": action.retryMax,
"processingTime": action.processingTime,
"timestamp": action.timestamp.isoformat() if action.timestamp else None,
"result": action.result,
"resultDocuments": action.resultDocuments
}
taskData["actionList"].append(actionDict)
# Create task using interface # Create task using interface
task = self.chatInterface.createTask(taskData) task = self.chatInterface.createTask(taskData)
if task:
logger.info(f"Next task created successfully: {task.id}")
else:
logger.error("Failed to create next task")
return task return task
except Exception as e: except Exception as e:
@ -198,9 +277,8 @@ Example format:
response = await self.service.callAiTextBasic(prompt) response = await self.service.callAiTextBasic(prompt)
# Parse response # Parse response
try: result = self._extractJsonFromResponse(response)
result = json.loads(response) if not result:
except json.JSONDecodeError:
logger.error(f"Invalid JSON in action result: {response}") logger.error(f"Invalid JSON in action result: {response}")
action.status = "failed" action.status = "failed"
action.error = "Invalid result format" action.error = "Invalid result format"
@ -229,6 +307,8 @@ Example format:
if message: if message:
self.workflow.messages.append(message) self.workflow.messages.append(message)
logger.info(f"Action execution logged: {action.execMethod}.{action.execAction} - {action.status}")
# If action failed, stop execution # If action failed, stop execution
if action.status == "failed": if action.status == "failed":
break break
@ -324,33 +404,35 @@ Example format:
response = await self.service.callAiTextBasic(prompt) response = await self.service.callAiTextBasic(prompt)
# Parse response # Parse response
try: result = self._extractJsonFromResponse(response)
result = json.loads(response) if not result:
except json.JSONDecodeError:
logger.error(f"Invalid JSON in next task identification: {response}") logger.error(f"Invalid JSON in next task identification: {response}")
return TaskResult( # Create error result using interface
taskId=str(uuid.uuid4()), errorResultData = {
status="failed", "status": "failed",
success=False, "success": False,
error="Invalid result format" "error": "Invalid result format"
) }
return self.chatInterface.createTaskResult(errorResultData)
return TaskResult( # Create result using interface
taskId=str(uuid.uuid4()), resultData = {
status="completed" if result.get("success", False) else "failed", "status": "completed" if result.get("success", False) else "failed",
success=result.get("success", False), "success": result.get("success", False),
feedback=result.get("feedback", ""), "feedback": result.get("feedback", ""),
error=result.get("error", "") "error": result.get("error", "")
) }
return self.chatInterface.createTaskResult(resultData)
except Exception as e: except Exception as e:
logger.error(f"Error identifying next task: {str(e)}") logger.error(f"Error identifying next task: {str(e)}")
return TaskResult( # Create error result using interface
taskId=str(uuid.uuid4()), errorResultData = {
status="failed", "status": "failed",
success=False, "success": False,
error=str(e) "error": str(e)
) }
return self.chatInterface.createTaskResult(errorResultData)
async def generateWorkflowFeedback(self, workflow: ChatWorkflow) -> str: async def generateWorkflowFeedback(self, workflow: ChatWorkflow) -> str:
"""Generate final feedback for the workflow""" """Generate final feedback for the workflow"""
@ -408,79 +490,93 @@ Please provide a comprehensive summary of this workflow."""
docRefs = self.service.getDocumentReferenceList() docRefs = self.service.getDocumentReferenceList()
connRefs = self.service.getConnectionReferenceList() connRefs = self.service.getConnectionReferenceList()
return f""" prompt = f"""You are a task planning AI that creates structured task definitions in JSON format.
Task Definition for: {userInput}
Chat History: TASK REQUEST: {userInput}
{messageSummary}
Available Methods: CONTEXT:
{chr(10).join(f"- {method}" for method in methodList)} Chat History: {messageSummary}
Available Documents: AVAILABLE RESOURCES:
{chr(10).join(f"- {doc['documentReference']} ({doc['datetime']})" for doc in docRefs.get('chat', []))} Methods: {chr(10).join(f"- {method}" for method in methodList)}
Available Connections: Documents: {chr(10).join(f"- {doc['documentReference']} ({doc['datetime']})" for doc in docRefs.get('chat', []))}
{chr(10).join(f"- {conn['connectionReference']} ({conn['authority']})" for conn in connRefs)}
Your Task: Connections: {chr(10).join(f"- {conn['connectionReference']} ({conn['authority']})" for conn in connRefs)}
1. Analyze the user input and chat history
2. Determine what actions are needed to accomplish the task
3. Create a sequence of actions using only the available methods, documents, and connections
4. Provide feedback about what will be done and what needs to be done next
Required Output: INSTRUCTIONS:
1. A JSON object containing: 1. Analyze the task request and available resources
- status: Current state of the task ("pending", "running", "completed", or "failed") 2. Create a sequence of actions to accomplish the task
- feedback: Explanation of what will be done and what needs to be done next 3. Use ONLY the provided methods, documents, and connections
- actions: List of actions to execute, each containing: 4. Return a VALID JSON object with the exact structure shown below
* method: The method to use
* action: The specific action to perform
* parameters: Required parameters for the action
* resultLabel: Label for the action's result
2. Available Data: REQUIRED JSON STRUCTURE:
- Use only provided document references in Available Documents section
- Use only provided connection references in Available Connections section
3. Method Usage Rules:
- Syntax: method.action([parameter:type])->resultLabel:type
- resultLabel format: documentList_<uuid>_<label>
- Actions must be in processing sequence
- Parameters must be from:
* Available document references
* Available connection references
* Result labels from previous actions
4. Result Labels:
- Use consistent naming for related documents
- Include descriptive labels for document sets
- Labels will be used to track document sets in messages
5. Error Handling:
- Include validation for each action
- Specify retry behavior if needed
- Provide clear error messages
- Errors will be recorded in messages with .error: suffix
Example Format:
{{ {{
"status": "pending", "status": "pending",
"feedback": "Will search for documents about project X and analyze them", "feedback": "Clear explanation of what will be done",
"actions": [
{{
"method": "method_name",
"action": "action_name",
"parameters": {{
"param1": "value1",
"param2": "value2"
}},
"resultLabel": "documentList_uuid_label"
}}
]
}}
JSON FIELD REQUIREMENTS:
- "status": Must be "pending", "running", "completed", or "failed"
- "feedback": Human-readable explanation of the task plan
- "actions": Array of action objects (can be empty if no actions needed)
- "method": Must be one of the available methods listed above
- "action": Must be a valid action for that method
- "parameters": Object with method-specific parameters
- "resultLabel": Format: "documentList_uuid_descriptive_label"
PARAMETER RULES:
- Use only document references from "Documents" section above
- Use only connection references from "Connections" section above
- Use result labels from previous actions in the sequence
- All parameter values must be strings
EXAMPLE VALID JSON:
{{
"status": "pending",
"feedback": "I will search SharePoint for sales documents and then analyze the quarterly data to create a business intelligence report.",
"actions": [ "actions": [
{{ {{
"method": "sharepoint", "method": "sharepoint",
"action": "search", "action": "search",
"parameters": {{ "parameters": {{
"query": "project X", "query": "sales quarterly report",
"site": "projects" "site": "connection_123_msft_testuser@example.com"
}}, }},
"resultLabel": "documentList_<uuid>_search_results" "resultLabel": "documentList_abc123_sales_documents"
}},
{{
"method": "excel",
"action": "analyze",
"parameters": {{
"document": "documentList_abc123_sales_documents"
}},
"resultLabel": "documentList_def456_analysis_results"
}} }}
] ]
}} }}
Please provide the task definition in JSON format following these rules.""" CRITICAL: Respond with ONLY the JSON object. Do not include any explanatory text, markdown formatting, or additional content outside the JSON structure."""
# Log the generated prompt for debugging
logger.debug("=" * 80)
logger.debug("TASK DEFINITION PROMPT:")
logger.debug("=" * 80)
logger.debug(prompt)
logger.debug("=" * 80)
return prompt
# ===== Utility Methods ===== # ===== Utility Methods =====
async def processFileIds(self, fileIds: List[str]) -> List[ChatDocument]: async def processFileIds(self, fileIds: List[str]) -> List[ChatDocument]:
@ -491,13 +587,15 @@ Please provide the task definition in JSON format following these rules."""
# Get file info from service # Get file info from service
fileInfo = self.service.getFileInfo(fileId) fileInfo = self.service.getFileInfo(fileId)
if fileInfo: if fileInfo:
document = ChatDocument( # Create document using interface
id=str(uuid.uuid4()), documentData = {
fileId=fileId, "fileId": fileId,
filename=fileInfo.get("filename", "unknown"), "filename": fileInfo.get("filename", "unknown"),
fileSize=fileInfo.get("size", 0), "fileSize": fileInfo.get("size", 0),
mimeType=fileInfo.get("mimeType", "application/octet-stream") "mimeType": fileInfo.get("mimeType", "application/octet-stream")
) }
document = self.chatInterface.createChatDocument(documentData)
if document:
documents.append(document) documents.append(document)
except Exception as e: except Exception as e:
logger.error(f"Error processing file ID {fileId}: {str(e)}") logger.error(f"Error processing file ID {fileId}: {str(e)}")

View file

@ -1,7 +1,6 @@
from typing import Dict, Any from typing import Dict, Any
import logging import logging
from datetime import datetime, UTC from datetime import datetime, UTC
import uuid
from modules.interfaces.interfaceAppObjects import User from modules.interfaces.interfaceAppObjects import User
@ -31,6 +30,8 @@ class WorkflowManager:
async def workflowProcess(self, userInput: UserInputRequest, workflow: ChatWorkflow) -> TaskItem: async def workflowProcess(self, userInput: UserInputRequest, workflow: ChatWorkflow) -> TaskItem:
"""Process a workflow with user input""" """Process a workflow with user input"""
logger.info(f"Processing workflow: {workflow.id}")
# Initialize chat manager # Initialize chat manager
await self.chatManager.initialize(workflow) await self.chatManager.initialize(workflow)

View file

@ -12,8 +12,9 @@ from modules.interfaces.interfaceAiCalls import AiCalls
from modules.interfaces.interfaceChatObjects import getInterface as getChatObjects from modules.interfaces.interfaceChatObjects import getInterface as getChatObjects
from modules.interfaces.interfaceChatModel import ActionResult from modules.interfaces.interfaceChatModel import ActionResult
from modules.interfaces.interfaceComponentObjects import getInterface as getComponentObjects from modules.interfaces.interfaceComponentObjects import getInterface as getComponentObjects
from modules.interfaces.interfaceAppObjects import getInterface as getAppObjects
from modules.workflow.managerDocument import DocumentManager from modules.workflow.managerDocument import DocumentManager
from modules.methods.methodBase import MethodBase from modules.workflow.methodBase import MethodBase
import uuid import uuid
import base64 import base64
@ -33,6 +34,7 @@ class ServiceContainer:
# Initialize managers # Initialize managers
self.interfaceChat = getChatObjects(currentUser) self.interfaceChat = getChatObjects(currentUser)
self.interfaceComponent = getComponentObjects(currentUser) self.interfaceComponent = getComponentObjects(currentUser)
self.interfaceApp = getAppObjects(currentUser)
self.interfaceAiCalls = AiCalls() self.interfaceAiCalls = AiCalls()
self.documentManager = DocumentManager(self) self.documentManager = DocumentManager(self)
@ -64,14 +66,12 @@ class ServiceContainer:
# Discover actions from public methods # Discover actions from public methods
actions = {} actions = {}
for methodName, method in inspect.getmembers(methodInstance, predicate=inspect.isfunction): for methodName, method in inspect.getmembers(type(methodInstance), predicate=inspect.iscoroutinefunction):
# Skip private methods and inherited methods if not methodName.startswith('_') and methodName not in ['execute', 'validateParameters']:
if not methodName.startswith('_') and methodName not in ['execute', 'actions', 'validateParameters']: # Bind the method to the instance
# Get method signature bound_method = method.__get__(methodInstance, type(methodInstance))
sig = inspect.signature(method) sig = inspect.signature(method)
params = {} params = {}
# Convert parameters to action definition
for paramName, param in sig.parameters.items(): for paramName, param in sig.parameters.items():
if paramName not in ['self', 'authData']: if paramName not in ['self', 'authData']:
params[paramName] = { params[paramName] = {
@ -79,12 +79,10 @@ class ServiceContainer:
'required': param.default == param.empty, 'required': param.default == param.empty,
'description': param.default.__doc__ if hasattr(param.default, '__doc__') else None 'description': param.default.__doc__ if hasattr(param.default, '__doc__') else None
} }
# Add action definition
actions[methodName] = { actions[methodName] = {
'description': method.__doc__ or '', 'description': method.__doc__ or '',
'parameters': params, 'parameters': params,
'method': method 'method': bound_method
} }
# Add method instance with discovered actions # Add method instance with discovered actions
@ -96,7 +94,7 @@ class ServiceContainer:
logger.info(f"Discovered method: {methodInstance.name} with {len(actions)} actions") logger.info(f"Discovered method: {methodInstance.name} with {len(actions)} actions")
except Exception as e: except Exception as e:
logger.error(f"Error loading method module {name}: {str(e)}") logger.error(f"Error loading method module {name}: {str(e)}", exc_info=True)
except Exception as e: except Exception as e:
logger.error(f"Error discovering methods: {str(e)}") logger.error(f"Error discovering methods: {str(e)}")
@ -255,9 +253,11 @@ class ServiceContainer:
def getConnectionReferenceList(self) -> List[Dict[str, str]]: def getConnectionReferenceList(self) -> List[Dict[str, str]]:
"""Get list of all UserConnection objects as references""" """Get list of all UserConnection objects as references"""
connections = [] connections = []
for conn in self.user.connections: # Get user connections through AppObjects interface
user_connections = self.interfaceApp.getUserConnections(self.user.id)
for conn in user_connections:
connections.append({ connections.append({
"connectionReference": f"connection_{conn.id}_{conn.authority}", "connectionReference": f"connection_{conn.id}_{conn.authority}_{conn.externalUsername}",
"authority": conn.authority "authority": conn.authority
}) })
# Sort by authority # Sort by authority
@ -265,22 +265,26 @@ class ServiceContainer:
def getConnectionReferenceFromUserConnection(self, connection: UserConnection) -> str: def getConnectionReferenceFromUserConnection(self, connection: UserConnection) -> str:
"""Get connection reference from UserConnection""" """Get connection reference from UserConnection"""
return f"connection_{connection.id}_{connection.authority}" return f"connection_{connection.id}_{connection.authority}_{connection.externalUsername}"
def getUserConnectionFromConnectionReference(self, connectionReference: str) -> Optional[UserConnection]: def getUserConnectionFromConnectionReference(self, connectionReference: str) -> Optional[UserConnection]:
"""Get UserConnection from reference string""" """Get UserConnection from reference string"""
try: try:
# Parse reference format: connection_{id}_{authority} # Parse reference format: connection_{id}_{authority}_{username}
parts = connectionReference.split('_') parts = connectionReference.split('_')
if len(parts) != 3 or parts[0] != "connection": if len(parts) != 4 or parts[0] != "connection":
return None return None
conn_id = parts[1] conn_id = parts[1]
authority = parts[2] authority = parts[2]
username = parts[3]
# Get user connections through AppObjects interface
user_connections = self.interfaceApp.getUserConnections(self.user.id)
# Find matching connection # Find matching connection
for conn in self.user.connections: for conn in user_connections:
if str(conn.id) == conn_id and conn.authority == authority: if str(conn.id) == conn_id and conn.authority == authority and conn.externalUsername == username:
return conn return conn
return None return None
@ -379,7 +383,17 @@ Please provide a clear summary of this message."""
def getFileInfo(self, fileId: str) -> Dict[str, Any]: def getFileInfo(self, fileId: str) -> Dict[str, Any]:
"""Get file information""" """Get file information"""
return self.interfaceComponent.getFileInfo(fileId) file_item = self.interfaceComponent.getFile(fileId)
if file_item:
return {
"id": file_item.id,
"filename": file_item.filename,
"size": file_item.fileSize,
"mimeType": file_item.mimeType,
"fileHash": file_item.fileHash,
"creationDate": file_item.creationDate
}
return None
def getFileData(self, fileId: str) -> bytes: def getFileData(self, fileId: str) -> bytes:
"""Get file data by ID""" """Get file data by ID"""

View file

@ -7,46 +7,47 @@ import asyncio
import logging import logging
import sys import sys
import os import os
from datetime import datetime, UTC import json
from datetime import datetime, UTC, timedelta
import uuid import uuid
print("Starting test_workflow.py...")
# Configure logging FIRST, before any other imports
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler('test_workflow.log', encoding='utf-8')
],
force=True # Force reconfiguration even if already configured
)
logger = logging.getLogger(__name__)
print("Logger level:", logger.level)
logger.info("Logger is working!")
print("Logger test done")
# Set up test configuration # Set up test configuration
os.environ['POWERON_CONFIG_FILE'] = 'test_config.ini' os.environ['POWERON_CONFIG_FILE'] = 'test_config.ini'
print("Set POWERON_CONFIG_FILE environment variable")
try:
# Simple imports from modules (same as app.py) # Simple imports from modules (same as app.py)
from modules.interfaces.interfaceAppObjects import User, UserConnection from modules.interfaces.interfaceAppObjects import User, UserConnection
from modules.interfaces.interfaceChatObjects import ChatObjects from modules.interfaces.interfaceChatObjects import ChatObjects
from modules.interfaces.interfaceChatModel import UserInputRequest, ChatWorkflow from modules.interfaces.interfaceChatModel import UserInputRequest, ChatWorkflow
from modules.workflow.managerWorkflow import WorkflowManager from modules.workflow.managerWorkflow import WorkflowManager
print("All imports successful")
# Configure logging except Exception as e:
logging.basicConfig( print(f"Import error: {e}")
level=logging.INFO, import traceback
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', traceback.print_exc()
handlers=[ sys.exit(1)
logging.StreamHandler(sys.stdout),
logging.FileHandler('test_workflow.log')
]
)
logger = logging.getLogger(__name__)
def create_test_user() -> User: def create_test_user() -> User:
"""Create a test user for the workflow""" """Create a test user for the workflow"""
# Create test connections for Microsoft services
connections = [
UserConnection(
id="conn-001",
authority="microsoft",
name="Test Microsoft Account",
enabled=True,
accessToken="test-token-123",
refreshToken="test-refresh-456",
expiresAt=datetime.now(UTC).isoformat(),
scopes=["Files.ReadWrite", "Mail.ReadWrite", "Sites.ReadWrite.All"]
)
]
return User( return User(
id="test-user-001", id="test-user-001",
mandateId="test-mandate-001", mandateId="test-mandate-001",
@ -56,8 +57,7 @@ def create_test_user() -> User:
enabled=True, enabled=True,
language="en", language="en",
privilege="user", privilege="user",
authenticationAuthority="local", authenticationAuthority="local"
connections=connections
) )
def create_test_workflow() -> ChatWorkflow: def create_test_workflow() -> ChatWorkflow:
@ -103,6 +103,7 @@ def create_test_user_input() -> UserInputRequest:
) )
async def test_workflow_process(): async def test_workflow_process():
print("Inside test_workflow_process()")
"""Test the workflowProcess function""" """Test the workflowProcess function"""
try: try:
logger.info("Starting workflow process test...") logger.info("Starting workflow process test...")
@ -112,17 +113,86 @@ async def test_workflow_process():
test_workflow = create_test_workflow() test_workflow = create_test_workflow()
test_user_input = create_test_user_input() test_user_input = create_test_user_input()
logger.info(f"Test user: {test_user.username}") # Create test user in database through AppObjects interface
logger.info(f"Test workflow: {test_workflow.id}") from modules.interfaces.interfaceAppObjects import getRootInterface
logger.info(f"Test input prompt: {test_user_input.prompt[:100]}...") from modules.interfaces.interfaceAppModel import AuthAuthority, ConnectionStatus, Token, UserPrivilege
logger.info(f"Test files: {test_user_input.listFileId}")
# Initialize ChatObjects interface root_interface = getRootInterface()
chat_interface = ChatObjects(test_user) created_user = root_interface.createUser(
logger.info("ChatObjects interface initialized") username=test_user.username,
password="testpassword123", # Required for local authentication
email=test_user.email,
fullName=test_user.fullName,
language=test_user.language,
enabled=test_user.enabled,
privilege=UserPrivilege.USER,
authenticationAuthority=AuthAuthority.LOCAL
)
logger.info(f"Created test user in database: {created_user.id}")
# Create test connection through AppObjects interface
from modules.interfaces.interfaceAppObjects import getInterface as getAppObjects
app_interface = getAppObjects(created_user)
test_connection = app_interface.addUserConnection(
userId=created_user.id,
authority=AuthAuthority.MSFT,
externalId="msft-user-123",
externalUsername="testuser@example.com",
externalEmail="testuser@example.com",
status=ConnectionStatus.ACTIVE
)
logger.info(f"Created test connection: {test_connection.id}")
# Create test token for the connection
test_token = Token(
userId=created_user.id,
authority=AuthAuthority.MSFT,
tokenAccess="test-access-token-123",
tokenRefresh="test-refresh-token-456",
tokenType="bearer",
expiresAt=datetime.now(UTC).timestamp() + 3600, # 1 hour from now
createdAt=datetime.now(UTC)
)
app_interface.saveToken(test_token)
logger.info(f"Created test token for connection: {test_token.id}")
logger.info(f"Test user: {created_user.username}")
logger.info(f"Test workflow: {test_workflow.id}")
# Log the full prompt in JSON format
logger.debug("=" * 60)
logger.debug("USER INPUT PROMPT (JSON):")
logger.debug("=" * 60)
prompt_data = {
"prompt": test_user_input.prompt,
"listFileId": test_user_input.listFileId,
"userLanguage": test_user_input.userLanguage
}
logger.debug(json.dumps(prompt_data, indent=2, ensure_ascii=False))
logger.debug("=" * 60)
logger.debug(f"Test files: {test_user_input.listFileId}")
# Create test workflow in database through ChatObjects interface
from modules.interfaces.interfaceChatObjects import getInterface as getChatObjects
chat_interface = getChatObjects(created_user)
workflow_data = {
"name": test_workflow.name,
"status": test_workflow.status,
"mandateId": created_user.mandateId,
"currentRound": test_workflow.currentRound,
"startedAt": test_workflow.startedAt,
"lastActivity": test_workflow.lastActivity
}
created_workflow = chat_interface.createWorkflow(workflow_data)
logger.info(f"Created test workflow: {created_workflow.id}")
# Update the test_workflow object with the created workflow's ID
test_workflow.id = created_workflow.id
# Initialize WorkflowManager # Initialize WorkflowManager
workflow_manager = WorkflowManager(chat_interface, test_user) workflow_manager = WorkflowManager(chat_interface, created_user)
logger.info("WorkflowManager initialized") logger.info("WorkflowManager initialized")
# Test the workflowProcess function # Test the workflowProcess function
@ -131,18 +201,37 @@ async def test_workflow_process():
# Log results # Log results
if task: if task:
logger.info("Task created successfully!") logger.debug("Task created successfully!")
logger.info(f"Task ID: {task.id}") logger.debug(f"Task ID: {task.id}")
logger.info(f"Task Status: {task.status}") logger.debug(f"Task Status: {task.status}")
logger.info(f"Task Feedback: {task.feedback}") logger.debug(f"Task Feedback: {task.feedback}")
logger.info(f"Number of actions: {len(task.actionList) if task.actionList else 0}") logger.info(f"Number of actions: {len(task.actionList) if task.actionList else 0}")
# Log the full task object in JSON format
logger.debug("=" * 60)
logger.debug("TASK OBJECT (JSON):")
logger.debug("=" * 60)
task_data = {
"id": task.id,
"status": task.status,
"feedback": task.feedback,
"actionList": [
{
"execMethod": action.execMethod,
"execAction": action.execAction,
"execParameters": action.execParameters
} for action in (task.actionList or [])
] if task.actionList else []
}
logger.debug(json.dumps(task_data, indent=2, ensure_ascii=False))
logger.debug("=" * 60)
if task.actionList: if task.actionList:
for i, action in enumerate(task.actionList): for i, action in enumerate(task.actionList):
logger.info(f"Action {i+1}: {action.execMethod}.{action.execAction}") logger.info(f"Action {i+1}: {action.execMethod}.{action.execAction}")
logger.info(f" Parameters: {action.execParameters}") logger.info(f" Parameters: {action.execParameters}")
else: else:
logger.warning("⚠️ No task was created") logger.warning("No task was created")
logger.info("Test completed successfully!") logger.info("Test completed successfully!")
return task return task
@ -153,7 +242,7 @@ async def test_workflow_process():
raise raise
async def main(): async def main():
"""Main function to run the test""" print("Inside main()")
logger.info("=" * 50) logger.info("=" * 50)
logger.info("BUSINESS INTELLIGENCE WORKFLOW TEST") logger.info("BUSINESS INTELLIGENCE WORKFLOW TEST")
logger.info("=" * 50) logger.info("=" * 50)
@ -171,5 +260,6 @@ async def main():
raise raise
if __name__ == "__main__": if __name__ == "__main__":
# Run the test print("About to run main()")
asyncio.run(main()) asyncio.run(main())
print("main() finished")