mirror of https://github.com/kortix-ai/suna.git
chore(dev): cleanup ux of workflows
This commit is contained in:
parent
f0440892ba
commit
2ff3c3cd65
|
@ -196,6 +196,8 @@ async def trigger_workflow_webhook(
|
|||
"metadata": {
|
||||
"workflow_id": workflow.id,
|
||||
"workflow_name": workflow.name,
|
||||
"is_workflow_execution": True,
|
||||
"workflow_run_name": f"Workflow Run: {workflow.name}",
|
||||
"triggered_by": "WEBHOOK",
|
||||
"execution_id": execution_id
|
||||
}
|
||||
|
|
|
@ -66,7 +66,8 @@ async def _create_workflow_thread_for_api(
|
|||
"metadata": {
|
||||
"workflow_id": workflow.id,
|
||||
"workflow_name": workflow.name,
|
||||
"is_workflow_execution": True
|
||||
"is_workflow_execution": True,
|
||||
"workflow_run_name": f"Workflow Run: {workflow.name}"
|
||||
},
|
||||
"created_at": datetime.now(timezone.utc).isoformat()
|
||||
}
|
||||
|
|
|
@ -60,6 +60,42 @@ class WorkflowConverter:
|
|||
|
||||
logger.info(f"Final enabled_tools list: {enabled_tools}")
|
||||
|
||||
# Extract model from input node configuration, default to Claude 3.5 Sonnet
|
||||
selected_model = "anthropic/claude-3-5-sonnet-latest"
|
||||
if input_config:
|
||||
# Look for model in input node data
|
||||
for node in nodes:
|
||||
if node.get('type') == 'inputNode':
|
||||
node_data = node.get('data', {})
|
||||
if node_data.get('model'):
|
||||
selected_model = node_data['model']
|
||||
# Ensure the model ID has the correct format
|
||||
if not selected_model.startswith(('anthropic/', 'openai/', 'google/', 'meta-llama/', 'mistralai/', 'deepseek/')):
|
||||
# Map common model names to their full IDs
|
||||
model_mapping = {
|
||||
'claude-sonnet-4': 'anthropic/claude-sonnet-4-20250514',
|
||||
'claude-sonnet-3.7': 'anthropic/claude-3-7-sonnet-latest',
|
||||
'claude-3.5': 'anthropic/claude-3-5-sonnet-latest',
|
||||
'claude-3-5-sonnet-latest': 'anthropic/claude-3-5-sonnet-latest',
|
||||
'claude-3-5-sonnet-20241022': 'anthropic/claude-3-5-sonnet-20241022',
|
||||
'claude-3-5-haiku-latest': 'anthropic/claude-3-5-haiku-latest',
|
||||
'gpt-4o': 'openai/gpt-4o',
|
||||
'gpt-4o-mini': 'openai/gpt-4o-mini',
|
||||
'gpt-4.1': 'openai/gpt-4.1',
|
||||
'gpt-4.1-mini': 'gpt-4.1-mini',
|
||||
'deepseek-chat': 'openrouter/deepseek/deepseek-chat',
|
||||
'deepseek': 'openrouter/deepseek/deepseek-chat',
|
||||
'deepseek-r1': 'openrouter/deepseek/deepseek-r1',
|
||||
'gemini-2.0-flash-exp': 'google/gemini-2.0-flash-exp',
|
||||
'gemini-flash-2.5': 'openrouter/google/gemini-2.5-flash-preview-05-20',
|
||||
'gemini-2.5-flash:thinking': 'openrouter/google/gemini-2.5-flash-preview-05-20:thinking',
|
||||
'gemini-2.5-pro-preview': 'openrouter/google/gemini-2.5-pro-preview',
|
||||
'gemini-2.5-pro': 'openrouter/google/gemini-2.5-pro-preview',
|
||||
'qwen3': 'openrouter/qwen/qwen3-235b-a22b'
|
||||
}
|
||||
selected_model = model_mapping.get(selected_model, f"anthropic/{selected_model}")
|
||||
break
|
||||
|
||||
agent_step = WorkflowStep(
|
||||
id="main_agent_step",
|
||||
name="Workflow Agent",
|
||||
|
@ -69,7 +105,7 @@ class WorkflowConverter:
|
|||
"tool_name": "workflow_agent",
|
||||
"system_prompt": workflow_prompt,
|
||||
"agent_id": metadata.get("agent_id"),
|
||||
"model": "anthropic/claude-3-5-sonnet-latest",
|
||||
"model": selected_model,
|
||||
"max_iterations": 10,
|
||||
"input_prompt": input_config.prompt if input_config else "",
|
||||
"tools": enabled_tools,
|
||||
|
@ -254,21 +290,33 @@ class WorkflowConverter:
|
|||
"""Generate a comprehensive system prompt that describes the workflow."""
|
||||
|
||||
prompt_parts = [
|
||||
"You are an AI agent executing a workflow. Follow these instructions carefully:",
|
||||
f"You are Suna - Workflows, an autonomous AI Agent created by the Kortix team, specialized in executing visual workflows.",
|
||||
"",
|
||||
"# 1. CORE IDENTITY & CAPABILITIES",
|
||||
"You are a workflow-specialized autonomous agent capable of executing complex visual workflows across domains including information gathering, content creation, software development, data analysis, and problem-solving. You operate within the Suna platform and have access to a comprehensive toolkit for workflow execution.",
|
||||
"",
|
||||
"# 2. EXECUTION ENVIRONMENT",
|
||||
"",
|
||||
"## 2.1 SYSTEM INFORMATION",
|
||||
f"- CURRENT YEAR: 2025",
|
||||
f"- UTC DATE: {datetime.now().strftime('%Y-%m-%d')}",
|
||||
f"- UTC TIME: {datetime.now().strftime('%H:%M:%S')}",
|
||||
"- TIME CONTEXT: When searching for latest news or time-sensitive information, ALWAYS use these current date/time values as reference points. Never use outdated information or assume different dates.",
|
||||
"- PLATFORM: Suna - Workflows",
|
||||
"",
|
||||
]
|
||||
|
||||
# Add input prompt if available
|
||||
if input_config and input_config.prompt:
|
||||
prompt_parts.extend([
|
||||
"## Workflow Input Prompt",
|
||||
"# 3. WORKFLOW INPUT PROMPT",
|
||||
input_config.prompt,
|
||||
"",
|
||||
])
|
||||
|
||||
prompt_parts.extend([
|
||||
"## Workflow Overview",
|
||||
"This workflow was created visually and consists of the following components:",
|
||||
"# 4. WORKFLOW OVERVIEW",
|
||||
"This workflow was created visually in Suna and consists of the following components:",
|
||||
""
|
||||
])
|
||||
|
||||
|
@ -303,7 +351,7 @@ class WorkflowConverter:
|
|||
# Add trigger information
|
||||
if input_config:
|
||||
prompt_parts.extend([
|
||||
"## Trigger Configuration",
|
||||
"# 5. TRIGGER CONFIGURATION",
|
||||
f"**Trigger Type**: {input_config.trigger_type}",
|
||||
])
|
||||
|
||||
|
@ -327,17 +375,7 @@ class WorkflowConverter:
|
|||
prompt_parts.append("")
|
||||
|
||||
prompt_parts.extend([
|
||||
"## Execution Instructions",
|
||||
"",
|
||||
"Execute this workflow by following these steps:",
|
||||
"1. Start with the input or trigger conditions",
|
||||
"2. Process each component in the logical order defined by the connections",
|
||||
"3. Use the available tools as specified in the workflow",
|
||||
"4. Follow the data flow between components",
|
||||
"5. Provide clear output at each step",
|
||||
"6. Handle errors gracefully and provide meaningful feedback",
|
||||
"",
|
||||
"## Available Tools",
|
||||
"# 6. AVAILABLE TOOLS",
|
||||
"You have access to the following tools based on the workflow configuration:"
|
||||
])
|
||||
|
||||
|
@ -405,7 +443,7 @@ class WorkflowConverter:
|
|||
if mcp_tool_descriptions:
|
||||
prompt_parts.extend([
|
||||
"",
|
||||
"### MCP Server Tools",
|
||||
"## MCP Server Tools",
|
||||
"The following tools are available from MCP servers:"
|
||||
])
|
||||
prompt_parts.extend(mcp_tool_descriptions)
|
||||
|
@ -416,7 +454,7 @@ class WorkflowConverter:
|
|||
if xml_examples:
|
||||
prompt_parts.extend([
|
||||
"",
|
||||
"## Tool Usage Examples",
|
||||
"# 7. TOOL USAGE EXAMPLES",
|
||||
"Use the following XML format to call tools. Each tool call must be wrapped in <function_calls> tags:",
|
||||
"",
|
||||
xml_examples
|
||||
|
@ -424,16 +462,52 @@ class WorkflowConverter:
|
|||
|
||||
prompt_parts.extend([
|
||||
"",
|
||||
"## Workflow Execution",
|
||||
"When executing this workflow:",
|
||||
"# 8. COMMUNICATION PROTOCOLS",
|
||||
"",
|
||||
"## 8.1 COMMUNICATION GUIDELINES",
|
||||
"- **Core Principle**: Communicate proactively, directly, and descriptively throughout your responses.",
|
||||
"- **Narrative-Style Communication**: Integrate descriptive Markdown-formatted text directly in your responses before, between, and after tool calls",
|
||||
"- **Communication Structure**: Use headers, brief paragraphs, and formatting for enhanced readability",
|
||||
"- Balance detail with conciseness - be informative without being verbose",
|
||||
"",
|
||||
"## 8.2 MESSAGE TYPES & USAGE",
|
||||
"- **Direct Narrative**: Embed clear, descriptive text directly in your responses explaining your actions, reasoning, and observations",
|
||||
"- **'ask' tool (USER CAN RESPOND)**: Use ONLY for essential needs requiring user input (clarification, confirmation, options, missing info, validation). This blocks execution until user responds.",
|
||||
"- **Minimize blocking operations ('ask')**: Maximize narrative descriptions in your regular responses.",
|
||||
"",
|
||||
"## 8.3 ATTACHMENT PROTOCOL",
|
||||
"- When using the 'ask' tool, ALWAYS attach ALL visualizations, markdown files, charts, graphs, reports, and any viewable content created",
|
||||
"- Include the 'attachments' parameter with file paths when sharing resources",
|
||||
"- Remember: If the user should SEE it, you must ATTACH it with the 'ask' tool",
|
||||
"",
|
||||
"# 9. WORKFLOW EXECUTION INSTRUCTIONS",
|
||||
"",
|
||||
"Execute this workflow by following these steps:",
|
||||
"1. **Start with the input or trigger conditions** - Begin execution based on the configured trigger",
|
||||
"2. **Process each component in logical order** - Follow the visual connections defined in the workflow",
|
||||
"3. **Use available tools as specified** - Employ the tools configured in this workflow using the XML format shown above",
|
||||
"4. **Follow the data flow between components** - Respect the connections and dependencies between workflow nodes",
|
||||
"5. **Provide clear output at each step** - Use narrative updates to keep the user informed of progress",
|
||||
"6. **Handle errors gracefully** - Provide meaningful feedback and suggest alternatives when steps fail",
|
||||
"7. **Complete the workflow successfully** - Deliver the expected output as defined by the workflow configuration",
|
||||
"",
|
||||
"## 9.1 EXECUTION BEST PRACTICES",
|
||||
"- Follow the logical flow defined by the visual connections",
|
||||
"- Use tools in the order and manner specified using the XML format shown above",
|
||||
"- For MCP tools, use the exact tool names and formats shown in the examples",
|
||||
"- Provide clear, step-by-step output",
|
||||
"- Provide clear, step-by-step output with narrative updates",
|
||||
"- If any step fails, explain what went wrong and suggest alternatives",
|
||||
"- Complete the workflow by providing the expected output",
|
||||
"- Use the 'ask' tool when you need essential user input to proceed",
|
||||
"- Attach all relevant files and visualizations when using the 'ask' tool",
|
||||
"",
|
||||
"Begin execution when the user provides input or triggers the workflow."
|
||||
"## 9.2 COMPLETION PROTOCOLS",
|
||||
"- Use 'ask' tool when you need essential user input to proceed (USER CAN RESPOND)",
|
||||
"- Provide narrative updates frequently to keep the user informed without requiring their input",
|
||||
"- Attach all deliverables, visualizations, and viewable content when using 'ask'",
|
||||
"- Signal completion appropriately based on workflow requirements",
|
||||
"",
|
||||
"**Begin execution when the user provides input or triggers the workflow. You are operating within the Suna platform as a specialized workflow execution agent.**"
|
||||
])
|
||||
|
||||
return "\n".join(prompt_parts)
|
||||
|
|
|
@ -54,6 +54,7 @@ class WorkflowExecutor:
|
|||
|
||||
main_step = workflow.steps[0]
|
||||
system_prompt = main_step.config.get("system_prompt", "")
|
||||
selected_model = main_step.config.get("model", "anthropic/claude-3-5-sonnet-latest")
|
||||
|
||||
if variables:
|
||||
variables_text = "\n\n## Workflow Variables\n"
|
||||
|
@ -100,7 +101,7 @@ class WorkflowExecutor:
|
|||
thread_id=thread_id,
|
||||
project_id=project_id,
|
||||
stream=True,
|
||||
model_name="anthropic/claude-3-5-sonnet-latest",
|
||||
model_name=selected_model,
|
||||
enable_thinking=False,
|
||||
reasoning_effort="low",
|
||||
enable_context_manager=True,
|
||||
|
@ -460,7 +461,8 @@ class WorkflowExecutor:
|
|||
"metadata": {
|
||||
"workflow_id": workflow.id,
|
||||
"workflow_name": workflow.name,
|
||||
"is_workflow_execution": True
|
||||
"is_workflow_execution": True,
|
||||
"workflow_run_name": f"Workflow Run: {workflow.name}"
|
||||
},
|
||||
"created_at": datetime.now(timezone.utc).isoformat()
|
||||
}
|
||||
|
|
|
@ -77,11 +77,17 @@ export function SidebarSearch() {
|
|||
const project = projectsById.get(projectId);
|
||||
if (!project) continue;
|
||||
|
||||
// Check if this is a workflow thread and use the workflow name if available
|
||||
let displayName = project.name || 'Unnamed Project';
|
||||
if (thread.metadata?.is_workflow_execution && thread.metadata?.workflow_run_name) {
|
||||
displayName = thread.metadata.workflow_run_name;
|
||||
}
|
||||
|
||||
// Add to our list
|
||||
threadsWithProjects.push({
|
||||
threadId: thread.thread_id,
|
||||
projectId: projectId,
|
||||
projectName: project.name || 'Unnamed Project',
|
||||
projectName: displayName,
|
||||
url: `/projects/${projectId}/thread/${thread.thread_id}`,
|
||||
updatedAt:
|
||||
thread.updated_at || project.updated_at || new Date().toISOString(),
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { memo, useState } from "react";
|
||||
import { Handle, Position, NodeProps } from "@xyflow/react";
|
||||
import { Play, Settings, Clock, Webhook, User, ChevronDown, ChevronUp } from "lucide-react";
|
||||
import { Play, Settings, Clock, Webhook, User, ChevronDown, ChevronUp, Brain } from "lucide-react";
|
||||
import { CardContent, CardHeader } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
@ -16,10 +16,13 @@ import { WebhookConfigDialog } from "../webhooks/WebhookConfigDialog";
|
|||
import { WebhookConfig } from "../webhooks/types";
|
||||
import { ScheduleConfigDialog } from "../scheduling/ScheduleConfigDialog";
|
||||
import { ScheduleConfig } from "../scheduling/types";
|
||||
import { ModelSelector } from "@/components/thread/chat-input/model-selector";
|
||||
import { useModelSelection } from "@/components/thread/chat-input/_use-model-selection";
|
||||
|
||||
interface InputNodeData {
|
||||
label?: string;
|
||||
prompt?: string;
|
||||
model?: string;
|
||||
trigger_type?: 'MANUAL' | 'WEBHOOK' | 'SCHEDULE';
|
||||
schedule_config?: ScheduleConfig;
|
||||
webhook_config?: WebhookConfig;
|
||||
|
@ -34,6 +37,23 @@ const InputNode = memo(({ data, selected, id }: NodeProps) => {
|
|||
const [isScheduleDialogOpen, setIsScheduleDialogOpen] = useState(false);
|
||||
const { updateNodeData, workflowId } = useWorkflow();
|
||||
|
||||
// Use the model selection hook
|
||||
const {
|
||||
selectedModel,
|
||||
setSelectedModel,
|
||||
allModels,
|
||||
canAccessModel,
|
||||
subscriptionStatus,
|
||||
refreshCustomModels
|
||||
} = useModelSelection();
|
||||
|
||||
// Initialize model if not set
|
||||
const currentModel = nodeData.model || selectedModel;
|
||||
|
||||
const handleModelChange = (modelId: string) => {
|
||||
updateNodeData(id, { model: modelId });
|
||||
};
|
||||
|
||||
const getTriggerIcon = () => {
|
||||
switch (nodeData.trigger_type) {
|
||||
case 'SCHEDULE':
|
||||
|
@ -131,6 +151,12 @@ const InputNode = memo(({ data, selected, id }: NodeProps) => {
|
|||
{nodeData.prompt || "No prompt configured"}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-xs font-medium text-muted-foreground">Model</Label>
|
||||
<p className="text-sm mt-1 text-foreground">
|
||||
{allModels.find(m => m.id === currentModel)?.label || currentModel || "Claude Sonnet 4"}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-xs font-medium text-muted-foreground">Trigger</Label>
|
||||
<p className="text-sm mt-1 text-foreground">
|
||||
|
@ -162,6 +188,23 @@ const InputNode = memo(({ data, selected, id }: NodeProps) => {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium flex items-center gap-2">
|
||||
<Brain className="h-4 w-4" />
|
||||
LLM Model *
|
||||
</Label>
|
||||
<div className="border rounded-lg p-2">
|
||||
<ModelSelector
|
||||
selectedModel={currentModel}
|
||||
onModelChange={handleModelChange}
|
||||
modelOptions={allModels}
|
||||
canAccessModel={canAccessModel}
|
||||
subscriptionStatus={subscriptionStatus}
|
||||
refreshCustomModels={refreshCustomModels}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium">Trigger Type *</Label>
|
||||
<Select
|
||||
|
|
|
@ -111,10 +111,15 @@ export const processThreadsWithProjects = (
|
|||
);
|
||||
continue;
|
||||
}
|
||||
let displayName = project.name || 'Unnamed Project';
|
||||
if (thread.metadata?.is_workflow_execution && thread.metadata?.workflow_run_name) {
|
||||
displayName = thread.metadata.workflow_run_name;
|
||||
}
|
||||
|
||||
threadsWithProjects.push({
|
||||
threadId: thread.thread_id,
|
||||
projectId: projectId,
|
||||
projectName: project.name || 'Unnamed Project',
|
||||
projectName: displayName,
|
||||
url: `/projects/${projectId}/thread/${thread.thread_id}`,
|
||||
updatedAt:
|
||||
thread.updated_at || project.updated_at || new Date().toISOString(),
|
||||
|
|
|
@ -11,6 +11,9 @@ export type Thread = {
|
|||
updated_at: string;
|
||||
metadata?: {
|
||||
workflow_id?: string;
|
||||
workflow_name?: string;
|
||||
workflow_run_name?: string;
|
||||
is_workflow_execution?: boolean;
|
||||
agent_id?: string;
|
||||
is_agent_builder?: boolean;
|
||||
[key: string]: any;
|
||||
|
|
|
@ -256,6 +256,7 @@ export const threadsApi = {
|
|||
project_id: thread.project_id,
|
||||
created_at: thread.created_at,
|
||||
updated_at: thread.updated_at,
|
||||
metadata: thread.metadata,
|
||||
}));
|
||||
|
||||
return { data: mappedThreads, error: null };
|
||||
|
|
|
@ -493,6 +493,7 @@ export const getThreads = async (projectId?: string): Promise<Thread[]> => {
|
|||
project_id: thread.project_id,
|
||||
created_at: thread.created_at,
|
||||
updated_at: thread.updated_at,
|
||||
metadata: thread.metadata,
|
||||
}));
|
||||
return mappedThreads;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue