refactor: unify workflow and playbook creation

This commit is contained in:
Vukasin 2025-08-10 18:48:55 +02:00
parent 1676405b43
commit 4fae58b310
1 changed files with 70 additions and 231 deletions

View File

@ -107,168 +107,126 @@ class WorkflowTool(AgentBuilderBaseTool):
"type": "function",
"function": {
"name": "create_workflow",
"description": "Create a new workflow for the agent. NOTE: For playbooks, prefer 'create_playbook'. If you still use this, include a Start node with a single child step whose config.playbook contains { template, variables }.",
"description": "Create a new workflow/playbook. This stores a Start node with a single child that contains config.playbook { template, variables }.",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name of the workflow. Should be descriptive and indicate the workflow's purpose."
"description": "Name of the workflow"
},
"description": {
"template": {
"type": "string",
"description": "Brief description of what the workflow does and when it should be used."
"description": "Workflow/playbook instructions text. Use {{variable}} tokens."
},
"trigger_phrase": {
"type": "string",
"description": "Optional phrase that can trigger this workflow when mentioned in a conversation."
},
"is_default": {
"type": "boolean",
"description": "Whether this workflow should be the default workflow for the agent.",
"default": False
},
"validate_tools": {
"type": "boolean",
"description": "Whether to validate tool names against available tools. Recommended to keep true.",
"default": True
},
"steps": {
"variables": {
"type": "array",
"description": "List of steps. For playbooks: Start node (order=0) with one child step (order=1) where step.config.playbook = { template: string, variables: [{key,label,required}] }.",
"description": "Optional variable specs; all treated as strings currently",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name of the step"
},
"description": {
"type": "string",
"description": "Optional description of what this step does"
},
"type": {
"type": "string",
"enum": ["instruction", "tool", "condition"],
"description": "Type of step: 'instruction' for text instructions, 'tool' for tool calls, 'condition' for conditional logic",
"default": "instruction"
},
"config": {
"type": "object",
"description": "Configuration for the step. For tools include 'tool_name'. For playbooks include 'playbook' with { template, variables }.",
"additionalProperties": True
},
"conditions": {
"type": "object",
"description": "Conditional logic for this step. Used with type='condition'.",
"additionalProperties": True
},
"order": {
"type": "integer",
"description": "Order/sequence number for this step"
},
"children": {
"type": "array",
"description": "Nested steps that execute when this step's condition is met",
"items": {"$ref": "#"}
}
"key": {"type": "string"},
"label": {"type": "string"},
"required": {"type": "boolean", "default": True}
},
"required": ["name", "order"]
}
"required": ["key"]
},
"default": []
},
"description": {
"type": "string",
"description": "Optional short description (auto-summarized from template if omitted)"
},
"is_default": {
"type": "boolean",
"description": "Whether this should be the default workflow",
"default": False
}
},
"required": ["name", "steps"]
"required": ["name", "template"]
}
}
})
@usage_example('''
# Preferred for playbooks: use create_playbook.
# If using create_workflow for a playbook, use this structure:
<function_calls>
<invoke name="create_workflow">
<parameter name="name">Company Research Playbook</parameter>
<parameter name="description">Research companies and update sheets</parameter>
<parameter name="steps">[
{
"id": "start-node",
"name": "Start",
"description": "Click to add steps or use the Add Node button",
"type": "instruction",
"config": {},
"order": 0,
"children": [
{
"id": "playbook-exec",
"name": "Execute Playbook",
"type": "instruction",
"order": 1,
"config": {
"playbook": {
"template": "Do X then Y using {{var1}} and {{var2}}",
"variables": [{"key": "var1", "label": "Var 1", "required": true}, {"key": "var2", "label": "Var 2", "required": true}]
}
}
}
]
}
]</parameter>
<parameter name="template">Research companies and update sheets using {{sheet_id}} and {{limit}}</parameter>
<parameter name="variables">[{"key":"sheet_id","label":"Sheet ID","required":true},{"key":"limit","label":"Row Limit","required":false}]</parameter>
</invoke>
</function_calls>
''')
async def create_workflow(
self,
name: str,
steps: List[Dict[str, Any]],
template: str,
variables: Optional[List[Dict[str, Any]]] = None,
description: Optional[str] = None,
trigger_phrase: Optional[str] = None,
is_default: bool = False,
validate_tools: bool = True
) -> ToolResult:
try:
client = await self.db.client
if not isinstance(steps, list) or len(steps) == 0:
return self.fail_response("Steps must be a non-empty list")
if validate_tools:
available_tools = await self._get_available_tools_for_agent()
validation_errors = self._validate_tool_steps(steps, available_tools)
if validation_errors:
return self.fail_response(f"Tool validation failed:\n" + "\n".join(validation_errors))
steps_json = self._convert_steps_to_json(steps)
variables = variables or []
playbook_step = {
'id': 'workflow-exec',
'name': 'Execute Workflow Template',
'description': 'Execute the workflow/playbook template using provided variables.',
'type': 'instruction',
'config': {
'playbook': {
'template': template,
'variables': variables,
}
},
'order': 1,
'children': []
}
start_node = {
'id': 'start-node',
'name': 'Start',
'description': 'Click to add steps or use the Add Node button',
'type': 'instruction',
'config': {},
'order': 0,
'children': [playbook_step]
}
steps_json = self._convert_steps_to_json([start_node])
def _summarize(text: str) -> str:
s = (text or '').strip().replace('\n', ' ')
return s[:160] + ('' if len(s) > 160 else '')
workflow_data = {
'agent_id': self.agent_id,
'name': name,
'description': description,
'trigger_phrase': trigger_phrase,
'description': description if description is not None else _summarize(template),
'trigger_phrase': None,
'is_default': is_default,
'status': 'draft',
'steps': steps_json
'steps': steps_json,
}
result = await client.table('agent_workflows').insert(workflow_data).execute()
if not result.data:
return self.fail_response("Failed to create workflow")
workflow = result.data[0]
return self.success_response({
"message": f"Workflow '{name}' created successfully",
"workflow": {
"id": workflow["id"],
"name": workflow["name"],
"description": workflow.get("description"),
"trigger_phrase": workflow.get("trigger_phrase"),
"is_default": workflow["is_default"],
"status": workflow["status"],
"steps_count": len(steps_json),
"created_at": workflow["created_at"]
"created_at": workflow["created_at"],
}
})
except Exception as e:
return self.fail_response(f"Error creating workflow: {str(e)}")
@ -276,7 +234,7 @@ class WorkflowTool(AgentBuilderBaseTool):
"type": "function",
"function": {
"name": "get_workflows",
"description": "Get all workflows for the current agent. Use this to see what workflows are already configured.",
"description": "Get all workflows/playbooks for the current agent. Use this to see what workflows are already configured.",
"parameters": {
"type": "object",
"properties": {
@ -481,7 +439,7 @@ class WorkflowTool(AgentBuilderBaseTool):
"type": "function",
"function": {
"name": "delete_workflow",
"description": "Delete a workflow from the agent. This action cannot be undone.",
"description": "Delete a workflow/playbook from the agent. This action cannot be undone.",
"parameters": {
"type": "object",
"properties": {
@ -525,7 +483,7 @@ class WorkflowTool(AgentBuilderBaseTool):
"type": "function",
"function": {
"name": "activate_workflow",
"description": "Activate or deactivate a workflow. Only active workflows can be executed.",
"description": "Activate or deactivate a workflow/playbook. Only active workflows can be executed.",
"parameters": {
"type": "object",
"properties": {
@ -603,123 +561,4 @@ class WorkflowTool(AgentBuilderBaseTool):
return result
@openapi_schema({
"type": "function",
"function": {
"name": "create_playbook",
"description": "Create a new playbook stored as a workflow. The playbook template and variables are saved inside the first step's config (config.playbook).",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name of the playbook"
},
"template": {
"type": "string",
"description": "Playbook instructions text. Use {{variable}} tokens."
},
"variables": {
"type": "array",
"description": "Optional variable specs; all treated as strings currently",
"items": {
"type": "object",
"properties": {
"key": {"type": "string"},
"label": {"type": "string"},
"required": {"type": "boolean", "default": True}
},
"required": ["key"]
},
"default": []
},
"description": {
"type": "string",
"description": "Optional short description (will auto-summarize if omitted)"
},
"is_default": {
"type": "boolean",
"description": "Whether this should be the default workflow",
"default": False
}
},
"required": ["name", "template"]
}
}
})
async def create_playbook(
self,
name: str,
template: str,
variables: Optional[List[Dict[str, Any]]] = None,
description: Optional[str] = None,
is_default: bool = False,
) -> ToolResult:
"""Create a workflow representing a playbook with Start node + Execute Playbook child."""
try:
client = await self.db.client
variables = variables or []
# Build steps structure
playbook_step = {
'id': 'playbook-exec',
'name': 'Execute Playbook',
'description': 'Execute the playbook template using provided variables.',
'type': 'instruction',
'config': {
'playbook': {
'template': template,
'variables': variables,
}
},
'order': 1,
'children': []
}
start_node = {
'id': 'start-node',
'name': 'Start',
'description': 'Click to add steps or use the Add Node button',
'type': 'instruction',
'config': {},
'order': 0,
'children': [playbook_step]
}
steps_json = self._convert_steps_to_json([start_node])
def _summarize(text: str) -> str:
s = (text or '').strip().replace('\n', ' ')
return s[:160] + ('' if len(s) > 160 else '')
workflow_data = {
'agent_id': self.agent_id,
'name': name,
'description': description if description is not None else _summarize(template),
'trigger_phrase': None,
'is_default': is_default,
'status': 'draft',
'steps': steps_json,
}
result = await client.table('agent_workflows').insert(workflow_data).execute()
if not result.data:
return self.fail_response("Failed to create playbook workflow")
workflow = result.data[0]
return self.success_response({
"message": f"Playbook '{name}' created successfully",
"workflow": {
"id": workflow["id"],
"name": workflow["name"],
"description": workflow.get("description"),
"is_default": workflow["is_default"],
"status": workflow["status"],
"steps_count": len(steps_json),
"created_at": workflow["created_at"],
}
})
except Exception as e:
logger.error(f"Error creating playbook: {e}")
return self.fail_response(f"Error creating playbook: {str(e)}")
# Removed separate create_playbook in favor of playbook-style create_workflow