task planner works
This commit is contained in:
parent
b907d068b3
commit
f86b3a9e2e
15 changed files with 589 additions and 251 deletions
|
|
@ -15,7 +15,7 @@ Connector_AiOpenai_MAX_TOKENS = 2000
|
|||
# Anthropic configuration
|
||||
Connector_AiAnthropic_API_URL = https://api.anthropic.com/v1/messages
|
||||
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_MAX_TOKENS = 2000
|
||||
|
||||
|
|
|
|||
|
|
@ -9,12 +9,11 @@ import uuid
|
|||
from datetime import datetime
|
||||
from typing import Dict, Any, List, Optional, Union
|
||||
|
||||
import hashlib
|
||||
import asyncio
|
||||
|
||||
from modules.interfaces.interfaceChatAccess import ChatAccess
|
||||
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
|
||||
|
||||
|
|
@ -169,7 +168,7 @@ class ChatObjects:
|
|||
logs=[ChatLog(**log) for log in workflow.get("logs", [])],
|
||||
messages=[ChatMessage(**msg) for msg in workflow.get("messages", [])],
|
||||
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:
|
||||
logger.error(f"Error validating workflow data: {str(e)}")
|
||||
|
|
@ -202,7 +201,7 @@ class ChatObjects:
|
|||
logs=[],
|
||||
messages=[],
|
||||
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:
|
||||
|
|
@ -264,6 +263,9 @@ class ChatObjects:
|
|||
def createWorkflowMessage(self, messageData: Dict[str, Any]) -> ChatMessage:
|
||||
"""Creates a message for a workflow if user has access."""
|
||||
try:
|
||||
# Ensure ID is present
|
||||
if "id" not in messageData or not messageData["id"]:
|
||||
messageData["id"] = f"msg_{uuid.uuid4()}"
|
||||
# Check required fields
|
||||
requiredFields = ["id", "workflowId"]
|
||||
for field in requiredFields:
|
||||
|
|
@ -299,16 +301,6 @@ class ChatObjects:
|
|||
# Create message in database
|
||||
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
|
||||
return ChatMessage(
|
||||
id=createdMessage["id"],
|
||||
|
|
@ -319,7 +311,7 @@ class ChatObjects:
|
|||
message=createdMessage.get("message"),
|
||||
role=createdMessage.get("role", "assistant"),
|
||||
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()),
|
||||
stats=ChatStat(**createdMessage.get("stats", {})) if createdMessage.get("stats") else None
|
||||
)
|
||||
|
|
@ -702,15 +694,6 @@ class ChatObjects:
|
|||
messageCount = len(messages)
|
||||
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
|
||||
for msg in messages:
|
||||
docCount = len(msg.get("documents", []))
|
||||
|
|
@ -725,7 +708,6 @@ class ChatObjects:
|
|||
# Assemble complete workflow object
|
||||
completeWorkflow = workflow.copy()
|
||||
completeWorkflow["messages"] = messages
|
||||
completeWorkflow["messageIds"] = messageIds
|
||||
completeWorkflow["logs"] = logs
|
||||
|
||||
return completeWorkflow
|
||||
|
|
@ -862,6 +844,7 @@ class ChatObjects:
|
|||
return TaskItem(
|
||||
id=task["id"],
|
||||
workflowId=task["workflowId"],
|
||||
userInput=task.get("userInput", ""),
|
||||
status=task.get("status", TaskStatus.PENDING),
|
||||
error=task.get("error"),
|
||||
startedAt=task.get("startedAt"),
|
||||
|
|
@ -892,6 +875,9 @@ class ChatObjects:
|
|||
def createTask(self, taskData: Dict[str, Any]) -> TaskItem:
|
||||
"""Creates a new task if user has access to the workflow."""
|
||||
try:
|
||||
# Ensure ID is present
|
||||
if "id" not in taskData or not taskData["id"]:
|
||||
taskData["id"] = f"task_{uuid.uuid4()}"
|
||||
# Check workflow access
|
||||
workflowId = taskData.get("workflowId")
|
||||
if not workflowId:
|
||||
|
|
@ -908,9 +894,6 @@ class ChatObjects:
|
|||
return None
|
||||
|
||||
# Ensure required fields
|
||||
if "id" not in taskData:
|
||||
taskData["id"] = f"task_{uuid.uuid4()}"
|
||||
|
||||
if "status" not in taskData:
|
||||
taskData["status"] = TaskStatus.PENDING
|
||||
|
||||
|
|
@ -924,6 +907,7 @@ class ChatObjects:
|
|||
task = TaskItem(
|
||||
id=createdTask["id"],
|
||||
workflowId=createdTask["workflowId"],
|
||||
userInput=createdTask.get("userInput", ""),
|
||||
status=createdTask.get("status", TaskStatus.PENDING),
|
||||
error=createdTask.get("error"),
|
||||
startedAt=createdTask.get("startedAt"),
|
||||
|
|
@ -938,7 +922,7 @@ class ChatObjects:
|
|||
)
|
||||
|
||||
# Update workflow's task list
|
||||
workflowTasks = workflow.get("tasks", [])
|
||||
workflowTasks = workflow.tasks if hasattr(workflow, 'tasks') else []
|
||||
if task.id not in workflowTasks:
|
||||
workflowTasks.append(task.id)
|
||||
self.updateWorkflow(workflowId, {"tasks": workflowTasks})
|
||||
|
|
@ -975,6 +959,7 @@ class ChatObjects:
|
|||
return TaskItem(
|
||||
id=updatedTask["id"],
|
||||
workflowId=updatedTask["workflowId"],
|
||||
userInput=updatedTask.get("userInput", task.userInput),
|
||||
status=updatedTask.get("status", task.status),
|
||||
error=updatedTask.get("error", task.error),
|
||||
startedAt=updatedTask.get("startedAt", task.startedAt),
|
||||
|
|
@ -1014,7 +999,7 @@ class ChatObjects:
|
|||
# Delete task
|
||||
if self.db.recordDelete("tasks", taskId):
|
||||
# Update workflow's task list
|
||||
workflowTasks = workflow.get("tasks", [])
|
||||
workflowTasks = workflow.tasks if hasattr(workflow, 'tasks') else []
|
||||
if taskId in workflowTasks:
|
||||
workflowTasks.remove(taskId)
|
||||
self.updateWorkflow(task.workflowId, {"tasks": workflowTasks})
|
||||
|
|
@ -1025,6 +1010,128 @@ class ChatObjects:
|
|||
logger.error(f"Error deleting task: {str(e)}")
|
||||
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':
|
||||
"""
|
||||
Returns a ChatObjects instance for the current user.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from typing import Dict, Any, Optional
|
|||
import logging
|
||||
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__)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import logging
|
|||
from typing import Dict, Any, List, Optional
|
||||
|
||||
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__)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from datetime import datetime, UTC
|
|||
import json
|
||||
import base64
|
||||
|
||||
from modules.methods.methodBase import MethodBase, ActionResult, action
|
||||
from modules.workflow.methodBase import MethodBase, ActionResult, action
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -23,14 +23,21 @@ class ExcelService:
|
|||
"""Get Microsoft connection from connection reference"""
|
||||
try:
|
||||
userConnection = self.serviceContainer.getUserConnectionFromConnectionReference(connectionReference)
|
||||
if userConnection and userConnection.authority == "microsoft" and userConnection.enabled:
|
||||
return {
|
||||
"id": userConnection.id,
|
||||
"accessToken": userConnection.accessToken,
|
||||
"refreshToken": userConnection.refreshToken,
|
||||
"scopes": userConnection.scopes
|
||||
}
|
||||
return None
|
||||
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 {
|
||||
"id": userConnection.id,
|
||||
"accessToken": token.tokenAccess,
|
||||
"refreshToken": token.tokenRefresh,
|
||||
"scopes": ["Mail.ReadWrite", "User.Read"] # Default Microsoft scopes
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting Microsoft connection: {str(e)}")
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from typing import Dict, List, Any, Optional
|
|||
from datetime import datetime, UTC
|
||||
import logging
|
||||
|
||||
from modules.methods.methodBase import MethodBase, ActionResult, action
|
||||
from modules.workflow.methodBase import MethodBase, ActionResult, action
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from typing import Dict, Any, List, Optional
|
|||
from datetime import datetime, UTC
|
||||
import json
|
||||
|
||||
from modules.methods.methodBase import MethodBase, ActionResult, action
|
||||
from modules.workflow.methodBase import MethodBase, ActionResult, action
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -22,14 +22,21 @@ class OutlookService:
|
|||
"""Get Microsoft connection from connection reference"""
|
||||
try:
|
||||
userConnection = self.serviceContainer.getUserConnectionFromConnectionReference(connectionReference)
|
||||
if userConnection and userConnection.authority == "microsoft" and userConnection.enabled:
|
||||
return {
|
||||
"id": userConnection.id,
|
||||
"accessToken": userConnection.accessToken,
|
||||
"refreshToken": userConnection.refreshToken,
|
||||
"scopes": userConnection.scopes
|
||||
}
|
||||
return None
|
||||
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 {
|
||||
"id": userConnection.id,
|
||||
"accessToken": token.tokenAccess,
|
||||
"refreshToken": token.tokenRefresh,
|
||||
"scopes": ["Mail.ReadWrite", "User.Read"] # Default Microsoft scopes
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting Microsoft connection: {str(e)}")
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from datetime import datetime, UTC
|
|||
import json
|
||||
import base64
|
||||
|
||||
from modules.methods.methodBase import MethodBase, ActionResult, action
|
||||
from modules.workflow.methodBase import MethodBase, ActionResult, action
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -23,14 +23,21 @@ class PowerpointService:
|
|||
"""Get Microsoft connection from connection reference"""
|
||||
try:
|
||||
userConnection = self.serviceContainer.getUserConnectionFromConnectionReference(connectionReference)
|
||||
if userConnection and userConnection.authority == "microsoft" and userConnection.enabled:
|
||||
return {
|
||||
"id": userConnection.id,
|
||||
"accessToken": userConnection.accessToken,
|
||||
"refreshToken": userConnection.refreshToken,
|
||||
"scopes": userConnection.scopes
|
||||
}
|
||||
return None
|
||||
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 {
|
||||
"id": userConnection.id,
|
||||
"accessToken": token.tokenAccess,
|
||||
"refreshToken": token.tokenRefresh,
|
||||
"scopes": ["Mail.ReadWrite", "User.Read"] # Default Microsoft scopes
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting Microsoft connection: {str(e)}")
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from typing import Dict, Any, List, Optional
|
|||
from datetime import datetime, UTC
|
||||
import json
|
||||
|
||||
from modules.methods.methodBase import MethodBase, ActionResult, action
|
||||
from modules.workflow.methodBase import MethodBase, ActionResult, action
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -22,14 +22,21 @@ class SharepointService:
|
|||
"""Get Microsoft connection from connection reference"""
|
||||
try:
|
||||
userConnection = self.serviceContainer.getUserConnectionFromConnectionReference(connectionReference)
|
||||
if userConnection and userConnection.authority == "microsoft" and userConnection.enabled:
|
||||
return {
|
||||
"id": userConnection.id,
|
||||
"accessToken": userConnection.accessToken,
|
||||
"refreshToken": userConnection.refreshToken,
|
||||
"scopes": userConnection.scopes
|
||||
}
|
||||
return None
|
||||
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 {
|
||||
"id": userConnection.id,
|
||||
"accessToken": token.tokenAccess,
|
||||
"refreshToken": token.tokenRefresh,
|
||||
"scopes": ["Mail.ReadWrite", "User.Read"] # Default Microsoft scopes
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting Microsoft connection: {str(e)}")
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import requests
|
|||
from bs4 import BeautifulSoup
|
||||
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
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ import logging
|
|||
from typing import Dict, Any, Optional, List, Union
|
||||
from datetime import datetime, UTC
|
||||
import json
|
||||
import uuid
|
||||
import time
|
||||
|
||||
from modules.interfaces.interfaceAppModel import User
|
||||
from modules.interfaces.interfaceChatModel import (
|
||||
|
|
@ -29,10 +27,39 @@ class ChatManager:
|
|||
self.workflow = 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 =====
|
||||
async def createInitialTask(self, workflow: ChatWorkflow, initialMessage: ChatMessage) -> Optional[TaskItem]:
|
||||
"""Create the initial task from the first message"""
|
||||
try:
|
||||
logger.info(f"Creating initial task for workflow {workflow.id}")
|
||||
|
||||
# Create task definition prompt
|
||||
prompt = await self._createTaskDefinitionPrompt(initialMessage.message, workflow)
|
||||
|
||||
|
|
@ -40,13 +67,13 @@ class ChatManager:
|
|||
response = await self.service.callAiTextAdvanced(prompt)
|
||||
|
||||
# Parse response
|
||||
try:
|
||||
taskDef = json.loads(response)
|
||||
except json.JSONDecodeError:
|
||||
logger.error(f"Invalid JSON in task definition: {response}")
|
||||
return None
|
||||
taskDef = self._extractJsonFromResponse(response)
|
||||
|
||||
# Validate task definition
|
||||
if not taskDef:
|
||||
logger.error("Could not extract valid JSON from AI response")
|
||||
return None
|
||||
|
||||
if not isinstance(taskDef, dict):
|
||||
logger.error("Task definition must be a JSON object")
|
||||
return None
|
||||
|
|
@ -61,6 +88,8 @@ class ChatManager:
|
|||
logger.error("Actions must be a list")
|
||||
return None
|
||||
|
||||
logger.info(f"Task definition validated: {len(taskDef['actions'])} actions")
|
||||
|
||||
# Create task using interface
|
||||
taskData = {
|
||||
"workflowId": workflow.id,
|
||||
|
|
@ -79,17 +108,40 @@ class ChatManager:
|
|||
if not all(field in actionDef for field in requiredFields):
|
||||
continue
|
||||
|
||||
action = TaskAction(
|
||||
id=str(uuid.uuid4()),
|
||||
execMethod=actionDef["method"],
|
||||
execAction=actionDef["action"],
|
||||
execParameters=actionDef["parameters"],
|
||||
execResultLabel=actionDef.get("resultLabel")
|
||||
)
|
||||
taskData["actionList"].append(action)
|
||||
# Create action using interface
|
||||
actionData = {
|
||||
"execMethod": actionDef["method"],
|
||||
"execAction": actionDef["action"],
|
||||
"execParameters": actionDef["parameters"],
|
||||
"execResultLabel": actionDef.get("resultLabel")
|
||||
}
|
||||
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
|
||||
task = self.chatInterface.createTask(taskData)
|
||||
if task:
|
||||
logger.info(f"Task created successfully: {task.id}")
|
||||
else:
|
||||
logger.error("Failed to create task")
|
||||
|
||||
return task
|
||||
|
||||
except Exception as e:
|
||||
|
|
@ -99,6 +151,8 @@ class ChatManager:
|
|||
async def createNextTask(self, workflow: ChatWorkflow, previousResult: TaskResult) -> Optional[TaskItem]:
|
||||
"""Create next task based on previous result"""
|
||||
try:
|
||||
logger.info(f"Creating next task for workflow {workflow.id}")
|
||||
|
||||
# Check if previous result was successful
|
||||
if not previousResult.success:
|
||||
logger.error(f"Previous task failed: {previousResult.error}")
|
||||
|
|
@ -111,13 +165,13 @@ class ChatManager:
|
|||
response = await self.service.callAiTextAdvanced(prompt)
|
||||
|
||||
# Parse response
|
||||
try:
|
||||
taskDef = json.loads(response)
|
||||
except json.JSONDecodeError:
|
||||
logger.error(f"Invalid JSON in task definition: {response}")
|
||||
return None
|
||||
taskDef = self._extractJsonFromResponse(response)
|
||||
|
||||
# Validate task definition
|
||||
if not taskDef:
|
||||
logger.error("Could not extract valid JSON from AI response")
|
||||
return None
|
||||
|
||||
if not isinstance(taskDef, dict):
|
||||
logger.error("Task definition must be a JSON object")
|
||||
return None
|
||||
|
|
@ -132,6 +186,8 @@ class ChatManager:
|
|||
logger.error("Actions must be a list")
|
||||
return None
|
||||
|
||||
logger.info(f"Next task definition validated: {len(taskDef['actions'])} actions")
|
||||
|
||||
# Create task using interface
|
||||
taskData = {
|
||||
"workflowId": workflow.id,
|
||||
|
|
@ -150,17 +206,40 @@ class ChatManager:
|
|||
if not all(field in actionDef for field in requiredFields):
|
||||
continue
|
||||
|
||||
action = TaskAction(
|
||||
id=str(uuid.uuid4()),
|
||||
execMethod=actionDef["method"],
|
||||
execAction=actionDef["action"],
|
||||
execParameters=actionDef["parameters"],
|
||||
execResultLabel=actionDef.get("resultLabel")
|
||||
)
|
||||
taskData["actionList"].append(action)
|
||||
# Create action using interface
|
||||
actionData = {
|
||||
"execMethod": actionDef["method"],
|
||||
"execAction": actionDef["action"],
|
||||
"execParameters": actionDef["parameters"],
|
||||
"execResultLabel": actionDef.get("resultLabel")
|
||||
}
|
||||
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
|
||||
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
|
||||
|
||||
except Exception as e:
|
||||
|
|
@ -198,9 +277,8 @@ Example format:
|
|||
response = await self.service.callAiTextBasic(prompt)
|
||||
|
||||
# Parse response
|
||||
try:
|
||||
result = json.loads(response)
|
||||
except json.JSONDecodeError:
|
||||
result = self._extractJsonFromResponse(response)
|
||||
if not result:
|
||||
logger.error(f"Invalid JSON in action result: {response}")
|
||||
action.status = "failed"
|
||||
action.error = "Invalid result format"
|
||||
|
|
@ -229,6 +307,8 @@ Example format:
|
|||
if 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.status == "failed":
|
||||
break
|
||||
|
|
@ -324,33 +404,35 @@ Example format:
|
|||
response = await self.service.callAiTextBasic(prompt)
|
||||
|
||||
# Parse response
|
||||
try:
|
||||
result = json.loads(response)
|
||||
except json.JSONDecodeError:
|
||||
result = self._extractJsonFromResponse(response)
|
||||
if not result:
|
||||
logger.error(f"Invalid JSON in next task identification: {response}")
|
||||
return TaskResult(
|
||||
taskId=str(uuid.uuid4()),
|
||||
status="failed",
|
||||
success=False,
|
||||
error="Invalid result format"
|
||||
)
|
||||
# Create error result using interface
|
||||
errorResultData = {
|
||||
"status": "failed",
|
||||
"success": False,
|
||||
"error": "Invalid result format"
|
||||
}
|
||||
return self.chatInterface.createTaskResult(errorResultData)
|
||||
|
||||
return TaskResult(
|
||||
taskId=str(uuid.uuid4()),
|
||||
status="completed" if result.get("success", False) else "failed",
|
||||
success=result.get("success", False),
|
||||
feedback=result.get("feedback", ""),
|
||||
error=result.get("error", "")
|
||||
)
|
||||
# Create result using interface
|
||||
resultData = {
|
||||
"status": "completed" if result.get("success", False) else "failed",
|
||||
"success": result.get("success", False),
|
||||
"feedback": result.get("feedback", ""),
|
||||
"error": result.get("error", "")
|
||||
}
|
||||
return self.chatInterface.createTaskResult(resultData)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error identifying next task: {str(e)}")
|
||||
return TaskResult(
|
||||
taskId=str(uuid.uuid4()),
|
||||
status="failed",
|
||||
success=False,
|
||||
error=str(e)
|
||||
)
|
||||
# Create error result using interface
|
||||
errorResultData = {
|
||||
"status": "failed",
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}
|
||||
return self.chatInterface.createTaskResult(errorResultData)
|
||||
|
||||
async def generateWorkflowFeedback(self, workflow: ChatWorkflow) -> str:
|
||||
"""Generate final feedback for the workflow"""
|
||||
|
|
@ -408,79 +490,93 @@ Please provide a comprehensive summary of this workflow."""
|
|||
docRefs = self.service.getDocumentReferenceList()
|
||||
connRefs = self.service.getConnectionReferenceList()
|
||||
|
||||
return f"""
|
||||
Task Definition for: {userInput}
|
||||
prompt = f"""You are a task planning AI that creates structured task definitions in JSON format.
|
||||
|
||||
Chat History:
|
||||
{messageSummary}
|
||||
TASK REQUEST: {userInput}
|
||||
|
||||
Available Methods:
|
||||
{chr(10).join(f"- {method}" for method in methodList)}
|
||||
CONTEXT:
|
||||
Chat History: {messageSummary}
|
||||
|
||||
Available Documents:
|
||||
{chr(10).join(f"- {doc['documentReference']} ({doc['datetime']})" for doc in docRefs.get('chat', []))}
|
||||
AVAILABLE RESOURCES:
|
||||
Methods: {chr(10).join(f"- {method}" for method in methodList)}
|
||||
|
||||
Available Connections:
|
||||
{chr(10).join(f"- {conn['connectionReference']} ({conn['authority']})" for conn in connRefs)}
|
||||
Documents: {chr(10).join(f"- {doc['documentReference']} ({doc['datetime']})" for doc in docRefs.get('chat', []))}
|
||||
|
||||
Your Task:
|
||||
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
|
||||
Connections: {chr(10).join(f"- {conn['connectionReference']} ({conn['authority']})" for conn in connRefs)}
|
||||
|
||||
Required Output:
|
||||
1. A JSON object containing:
|
||||
- status: Current state of the task ("pending", "running", "completed", or "failed")
|
||||
- feedback: Explanation of what will be done and what needs to be done next
|
||||
- actions: List of actions to execute, each containing:
|
||||
* method: The method to use
|
||||
* action: The specific action to perform
|
||||
* parameters: Required parameters for the action
|
||||
* resultLabel: Label for the action's result
|
||||
INSTRUCTIONS:
|
||||
1. Analyze the task request and available resources
|
||||
2. Create a sequence of actions to accomplish the task
|
||||
3. Use ONLY the provided methods, documents, and connections
|
||||
4. Return a VALID JSON object with the exact structure shown below
|
||||
|
||||
2. Available Data:
|
||||
- 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:
|
||||
REQUIRED JSON STRUCTURE:
|
||||
{{
|
||||
"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": [
|
||||
{{
|
||||
"method": "sharepoint",
|
||||
"action": "search",
|
||||
"parameters": {{
|
||||
"query": "project X",
|
||||
"site": "projects"
|
||||
"query": "sales quarterly report",
|
||||
"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 =====
|
||||
async def processFileIds(self, fileIds: List[str]) -> List[ChatDocument]:
|
||||
|
|
@ -491,14 +587,16 @@ Please provide the task definition in JSON format following these rules."""
|
|||
# Get file info from service
|
||||
fileInfo = self.service.getFileInfo(fileId)
|
||||
if fileInfo:
|
||||
document = ChatDocument(
|
||||
id=str(uuid.uuid4()),
|
||||
fileId=fileId,
|
||||
filename=fileInfo.get("filename", "unknown"),
|
||||
fileSize=fileInfo.get("size", 0),
|
||||
mimeType=fileInfo.get("mimeType", "application/octet-stream")
|
||||
)
|
||||
documents.append(document)
|
||||
# Create document using interface
|
||||
documentData = {
|
||||
"fileId": fileId,
|
||||
"filename": fileInfo.get("filename", "unknown"),
|
||||
"fileSize": fileInfo.get("size", 0),
|
||||
"mimeType": fileInfo.get("mimeType", "application/octet-stream")
|
||||
}
|
||||
document = self.chatInterface.createChatDocument(documentData)
|
||||
if document:
|
||||
documents.append(document)
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing file ID {fileId}: {str(e)}")
|
||||
return documents
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
from typing import Dict, Any
|
||||
import logging
|
||||
from datetime import datetime, UTC
|
||||
import uuid
|
||||
|
||||
from modules.interfaces.interfaceAppObjects import User
|
||||
|
||||
|
|
@ -31,6 +30,8 @@ class WorkflowManager:
|
|||
async def workflowProcess(self, userInput: UserInputRequest, workflow: ChatWorkflow) -> TaskItem:
|
||||
"""Process a workflow with user input"""
|
||||
|
||||
logger.info(f"Processing workflow: {workflow.id}")
|
||||
|
||||
# Initialize chat manager
|
||||
await self.chatManager.initialize(workflow)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,8 +12,9 @@ from modules.interfaces.interfaceAiCalls import AiCalls
|
|||
from modules.interfaces.interfaceChatObjects import getInterface as getChatObjects
|
||||
from modules.interfaces.interfaceChatModel import ActionResult
|
||||
from modules.interfaces.interfaceComponentObjects import getInterface as getComponentObjects
|
||||
from modules.interfaces.interfaceAppObjects import getInterface as getAppObjects
|
||||
from modules.workflow.managerDocument import DocumentManager
|
||||
from modules.methods.methodBase import MethodBase
|
||||
from modules.workflow.methodBase import MethodBase
|
||||
import uuid
|
||||
import base64
|
||||
|
||||
|
|
@ -33,6 +34,7 @@ class ServiceContainer:
|
|||
# Initialize managers
|
||||
self.interfaceChat = getChatObjects(currentUser)
|
||||
self.interfaceComponent = getComponentObjects(currentUser)
|
||||
self.interfaceApp = getAppObjects(currentUser)
|
||||
self.interfaceAiCalls = AiCalls()
|
||||
self.documentManager = DocumentManager(self)
|
||||
|
||||
|
|
@ -64,14 +66,12 @@ class ServiceContainer:
|
|||
|
||||
# Discover actions from public methods
|
||||
actions = {}
|
||||
for methodName, method in inspect.getmembers(methodInstance, predicate=inspect.isfunction):
|
||||
# Skip private methods and inherited methods
|
||||
if not methodName.startswith('_') and methodName not in ['execute', 'actions', 'validateParameters']:
|
||||
# Get method signature
|
||||
for methodName, method in inspect.getmembers(type(methodInstance), predicate=inspect.iscoroutinefunction):
|
||||
if not methodName.startswith('_') and methodName not in ['execute', 'validateParameters']:
|
||||
# Bind the method to the instance
|
||||
bound_method = method.__get__(methodInstance, type(methodInstance))
|
||||
sig = inspect.signature(method)
|
||||
params = {}
|
||||
|
||||
# Convert parameters to action definition
|
||||
for paramName, param in sig.parameters.items():
|
||||
if paramName not in ['self', 'authData']:
|
||||
params[paramName] = {
|
||||
|
|
@ -79,12 +79,10 @@ class ServiceContainer:
|
|||
'required': param.default == param.empty,
|
||||
'description': param.default.__doc__ if hasattr(param.default, '__doc__') else None
|
||||
}
|
||||
|
||||
# Add action definition
|
||||
actions[methodName] = {
|
||||
'description': method.__doc__ or '',
|
||||
'parameters': params,
|
||||
'method': method
|
||||
'method': bound_method
|
||||
}
|
||||
|
||||
# Add method instance with discovered actions
|
||||
|
|
@ -96,7 +94,7 @@ class ServiceContainer:
|
|||
logger.info(f"Discovered method: {methodInstance.name} with {len(actions)} actions")
|
||||
|
||||
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:
|
||||
logger.error(f"Error discovering methods: {str(e)}")
|
||||
|
|
@ -255,9 +253,11 @@ class ServiceContainer:
|
|||
def getConnectionReferenceList(self) -> List[Dict[str, str]]:
|
||||
"""Get list of all UserConnection objects as references"""
|
||||
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({
|
||||
"connectionReference": f"connection_{conn.id}_{conn.authority}",
|
||||
"connectionReference": f"connection_{conn.id}_{conn.authority}_{conn.externalUsername}",
|
||||
"authority": conn.authority
|
||||
})
|
||||
# Sort by authority
|
||||
|
|
@ -265,22 +265,26 @@ class ServiceContainer:
|
|||
|
||||
def getConnectionReferenceFromUserConnection(self, connection: UserConnection) -> str:
|
||||
"""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]:
|
||||
"""Get UserConnection from reference string"""
|
||||
try:
|
||||
# Parse reference format: connection_{id}_{authority}
|
||||
# Parse reference format: connection_{id}_{authority}_{username}
|
||||
parts = connectionReference.split('_')
|
||||
if len(parts) != 3 or parts[0] != "connection":
|
||||
if len(parts) != 4 or parts[0] != "connection":
|
||||
return None
|
||||
|
||||
conn_id = parts[1]
|
||||
authority = parts[2]
|
||||
username = parts[3]
|
||||
|
||||
# Get user connections through AppObjects interface
|
||||
user_connections = self.interfaceApp.getUserConnections(self.user.id)
|
||||
|
||||
# Find matching connection
|
||||
for conn in self.user.connections:
|
||||
if str(conn.id) == conn_id and conn.authority == authority:
|
||||
for conn in user_connections:
|
||||
if str(conn.id) == conn_id and conn.authority == authority and conn.externalUsername == username:
|
||||
return conn
|
||||
return None
|
||||
|
||||
|
|
@ -379,7 +383,17 @@ Please provide a clear summary of this message."""
|
|||
|
||||
def getFileInfo(self, fileId: str) -> Dict[str, Any]:
|
||||
"""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:
|
||||
"""Get file data by ID"""
|
||||
|
|
|
|||
180
test_workflow.py
180
test_workflow.py
|
|
@ -7,46 +7,47 @@ import asyncio
|
|||
import logging
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime, UTC
|
||||
import json
|
||||
from datetime import datetime, UTC, timedelta
|
||||
import uuid
|
||||
|
||||
# Set up test configuration
|
||||
os.environ['POWERON_CONFIG_FILE'] = 'test_config.ini'
|
||||
print("Starting test_workflow.py...")
|
||||
|
||||
# Simple imports from modules (same as app.py)
|
||||
from modules.interfaces.interfaceAppObjects import User, UserConnection
|
||||
from modules.interfaces.interfaceChatObjects import ChatObjects
|
||||
from modules.interfaces.interfaceChatModel import UserInputRequest, ChatWorkflow
|
||||
from modules.workflow.managerWorkflow import WorkflowManager
|
||||
|
||||
# Configure logging
|
||||
# Configure logging FIRST, before any other imports
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
level=logging.DEBUG,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.StreamHandler(sys.stdout),
|
||||
logging.FileHandler('test_workflow.log')
|
||||
]
|
||||
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
|
||||
os.environ['POWERON_CONFIG_FILE'] = 'test_config.ini'
|
||||
print("Set POWERON_CONFIG_FILE environment variable")
|
||||
|
||||
try:
|
||||
# Simple imports from modules (same as app.py)
|
||||
from modules.interfaces.interfaceAppObjects import User, UserConnection
|
||||
from modules.interfaces.interfaceChatObjects import ChatObjects
|
||||
from modules.interfaces.interfaceChatModel import UserInputRequest, ChatWorkflow
|
||||
from modules.workflow.managerWorkflow import WorkflowManager
|
||||
print("All imports successful")
|
||||
except Exception as e:
|
||||
print(f"Import error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
def create_test_user() -> User:
|
||||
"""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(
|
||||
id="test-user-001",
|
||||
mandateId="test-mandate-001",
|
||||
|
|
@ -56,8 +57,7 @@ def create_test_user() -> User:
|
|||
enabled=True,
|
||||
language="en",
|
||||
privilege="user",
|
||||
authenticationAuthority="local",
|
||||
connections=connections
|
||||
authenticationAuthority="local"
|
||||
)
|
||||
|
||||
def create_test_workflow() -> ChatWorkflow:
|
||||
|
|
@ -103,6 +103,7 @@ def create_test_user_input() -> UserInputRequest:
|
|||
)
|
||||
|
||||
async def test_workflow_process():
|
||||
print("Inside test_workflow_process()")
|
||||
"""Test the workflowProcess function"""
|
||||
try:
|
||||
logger.info("Starting workflow process test...")
|
||||
|
|
@ -112,17 +113,86 @@ async def test_workflow_process():
|
|||
test_workflow = create_test_workflow()
|
||||
test_user_input = create_test_user_input()
|
||||
|
||||
logger.info(f"Test user: {test_user.username}")
|
||||
logger.info(f"Test workflow: {test_workflow.id}")
|
||||
logger.info(f"Test input prompt: {test_user_input.prompt[:100]}...")
|
||||
logger.info(f"Test files: {test_user_input.listFileId}")
|
||||
# Create test user in database through AppObjects interface
|
||||
from modules.interfaces.interfaceAppObjects import getRootInterface
|
||||
from modules.interfaces.interfaceAppModel import AuthAuthority, ConnectionStatus, Token, UserPrivilege
|
||||
|
||||
# Initialize ChatObjects interface
|
||||
chat_interface = ChatObjects(test_user)
|
||||
logger.info("ChatObjects interface initialized")
|
||||
root_interface = getRootInterface()
|
||||
created_user = root_interface.createUser(
|
||||
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
|
||||
workflow_manager = WorkflowManager(chat_interface, test_user)
|
||||
workflow_manager = WorkflowManager(chat_interface, created_user)
|
||||
logger.info("WorkflowManager initialized")
|
||||
|
||||
# Test the workflowProcess function
|
||||
|
|
@ -131,18 +201,37 @@ async def test_workflow_process():
|
|||
|
||||
# Log results
|
||||
if task:
|
||||
logger.info("✅ Task created successfully!")
|
||||
logger.info(f"Task ID: {task.id}")
|
||||
logger.info(f"Task Status: {task.status}")
|
||||
logger.info(f"Task Feedback: {task.feedback}")
|
||||
logger.debug("Task created successfully!")
|
||||
logger.debug(f"Task ID: {task.id}")
|
||||
logger.debug(f"Task Status: {task.status}")
|
||||
logger.debug(f"Task Feedback: {task.feedback}")
|
||||
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:
|
||||
for i, action in enumerate(task.actionList):
|
||||
logger.info(f"Action {i+1}: {action.execMethod}.{action.execAction}")
|
||||
logger.info(f" Parameters: {action.execParameters}")
|
||||
else:
|
||||
logger.warning("⚠️ No task was created")
|
||||
logger.warning("No task was created")
|
||||
|
||||
logger.info("Test completed successfully!")
|
||||
return task
|
||||
|
|
@ -153,7 +242,7 @@ async def test_workflow_process():
|
|||
raise
|
||||
|
||||
async def main():
|
||||
"""Main function to run the test"""
|
||||
print("Inside main()")
|
||||
logger.info("=" * 50)
|
||||
logger.info("BUSINESS INTELLIGENCE WORKFLOW TEST")
|
||||
logger.info("=" * 50)
|
||||
|
|
@ -171,5 +260,6 @@ async def main():
|
|||
raise
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run the test
|
||||
asyncio.run(main())
|
||||
print("About to run main()")
|
||||
asyncio.run(main())
|
||||
print("main() finished")
|
||||
Loading…
Reference in a new issue