Workflow Copilot: Work on Workflow instead of WorkflowDefinition level (#4523)
This commit is contained in:
committed by
GitHub
parent
8be0669b04
commit
a52a174e28
@@ -7,7 +7,7 @@ import { ReloadIcon, Cross2Icon } from "@radix-ui/react-icons";
|
|||||||
import { stringify as convertToYAML } from "yaml";
|
import { stringify as convertToYAML } from "yaml";
|
||||||
import { useWorkflowHasChangesStore } from "@/store/WorkflowHasChangesStore";
|
import { useWorkflowHasChangesStore } from "@/store/WorkflowHasChangesStore";
|
||||||
import { WorkflowCreateYAMLRequest } from "@/routes/workflows/types/workflowYamlTypes";
|
import { WorkflowCreateYAMLRequest } from "@/routes/workflows/types/workflowYamlTypes";
|
||||||
import { WorkflowDefinition } from "@/routes/workflows/types/workflowTypes";
|
import { WorkflowApiResponse } from "@/routes/workflows/types/workflowTypes";
|
||||||
import { toast } from "@/components/ui/use-toast";
|
import { toast } from "@/components/ui/use-toast";
|
||||||
import { getSseClient } from "@/api/sse";
|
import { getSseClient } from "@/api/sse";
|
||||||
import {
|
import {
|
||||||
@@ -67,7 +67,7 @@ const MessageItem = memo(({ message }: { message: ChatMessage }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
interface WorkflowCopilotChatProps {
|
interface WorkflowCopilotChatProps {
|
||||||
onWorkflowUpdate?: (workflow: WorkflowDefinition) => void;
|
onWorkflowUpdate?: (workflow: WorkflowApiResponse) => void;
|
||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
onMessageCountChange?: (count: number) => void;
|
onMessageCountChange?: (count: number) => void;
|
||||||
@@ -425,7 +425,7 @@ export function WorkflowCopilotChat({
|
|||||||
|
|
||||||
if (response.updated_workflow && onWorkflowUpdate) {
|
if (response.updated_workflow && onWorkflowUpdate) {
|
||||||
try {
|
try {
|
||||||
onWorkflowUpdate(response.updated_workflow as WorkflowDefinition);
|
onWorkflowUpdate(response.updated_workflow);
|
||||||
} catch (updateError) {
|
} catch (updateError) {
|
||||||
console.error("Failed to update workflow:", updateError);
|
console.error("Failed to update workflow:", updateError);
|
||||||
toast({
|
toast({
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { WorkflowApiResponse } from "@/routes/workflows/types/workflowTypes";
|
||||||
|
|
||||||
export type WorkflowCopilotChatSender = "user" | "ai";
|
export type WorkflowCopilotChatSender = "user" | "ai";
|
||||||
|
|
||||||
export interface WorkflowCopilotChat {
|
export interface WorkflowCopilotChat {
|
||||||
@@ -53,7 +55,7 @@ export interface WorkflowCopilotStreamResponseUpdate {
|
|||||||
type: "response";
|
type: "response";
|
||||||
workflow_copilot_chat_id: string;
|
workflow_copilot_chat_id: string;
|
||||||
message: string;
|
message: string;
|
||||||
updated_workflow?: Record<string, unknown> | null;
|
updated_workflow?: WorkflowApiResponse | null;
|
||||||
response_time: string;
|
response_time: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ import {
|
|||||||
import { WorkflowHeader } from "./WorkflowHeader";
|
import { WorkflowHeader } from "./WorkflowHeader";
|
||||||
import { WorkflowHistoryPanel } from "./panels/WorkflowHistoryPanel";
|
import { WorkflowHistoryPanel } from "./panels/WorkflowHistoryPanel";
|
||||||
import { WorkflowVersion } from "../hooks/useWorkflowVersionsQuery";
|
import { WorkflowVersion } from "../hooks/useWorkflowVersionsQuery";
|
||||||
import { WorkflowApiResponse, WorkflowSettings } from "../types/workflowTypes";
|
import { WorkflowSettings } from "../types/workflowTypes";
|
||||||
import { ProxyLocation } from "@/api/types";
|
import { ProxyLocation } from "@/api/types";
|
||||||
import {
|
import {
|
||||||
nodeAdderNode,
|
nodeAdderNode,
|
||||||
@@ -1698,36 +1698,36 @@ function Workspace({
|
|||||||
buttonRef={copilotButtonRef}
|
buttonRef={copilotButtonRef}
|
||||||
onWorkflowUpdate={(workflowData) => {
|
onWorkflowUpdate={(workflowData) => {
|
||||||
try {
|
try {
|
||||||
const saveData = workflowChangesStore.getSaveData?.();
|
|
||||||
|
|
||||||
const settings: WorkflowSettings = {
|
const settings: WorkflowSettings = {
|
||||||
proxyLocation:
|
proxyLocation:
|
||||||
saveData?.settings.proxyLocation ?? ProxyLocation.Residential,
|
workflowData.proxy_location ?? ProxyLocation.Residential,
|
||||||
webhookCallbackUrl: saveData?.settings.webhookCallbackUrl || "",
|
webhookCallbackUrl: workflowData.webhook_callback_url || "",
|
||||||
persistBrowserSession:
|
persistBrowserSession:
|
||||||
saveData?.settings.persistBrowserSession ?? false,
|
workflowData.persist_browser_session ?? false,
|
||||||
model: saveData?.settings.model ?? null,
|
model: workflowData.model ?? null,
|
||||||
maxScreenshotScrolls:
|
maxScreenshotScrolls: workflowData.max_screenshot_scrolls || 3,
|
||||||
saveData?.settings.maxScreenshotScrolls || 3,
|
extraHttpHeaders: workflowData.extra_http_headers
|
||||||
extraHttpHeaders: saveData?.settings.extraHttpHeaders ?? null,
|
? JSON.stringify(workflowData.extra_http_headers)
|
||||||
runWith: saveData?.settings.runWith ?? null,
|
: null,
|
||||||
scriptCacheKey: saveData?.settings.scriptCacheKey ?? null,
|
runWith: workflowData.run_with ?? null,
|
||||||
aiFallback: saveData?.settings.aiFallback ?? true,
|
scriptCacheKey: workflowData.cache_key ?? null,
|
||||||
runSequentially: saveData?.settings.runSequentially ?? false,
|
aiFallback: workflowData.ai_fallback ?? true,
|
||||||
sequentialKey: saveData?.settings.sequentialKey ?? null,
|
runSequentially: workflowData.run_sequentially ?? false,
|
||||||
finallyBlockLabel: workflowData?.finally_block_label ?? null,
|
sequentialKey: workflowData.sequential_key ?? null,
|
||||||
|
finallyBlockLabel:
|
||||||
|
workflowData.workflow_definition?.finally_block_label ?? null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const elements = getElements(workflowData.blocks, settings, true);
|
const elements = getElements(
|
||||||
|
workflowData.workflow_definition.blocks,
|
||||||
|
settings,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
setNodes(elements.nodes);
|
setNodes(elements.nodes);
|
||||||
setEdges(elements.edges);
|
setEdges(elements.edges);
|
||||||
|
|
||||||
const initialParameters = getInitialParameters({
|
const initialParameters = getInitialParameters(workflowData);
|
||||||
workflow_definition: {
|
|
||||||
parameters: workflowData.parameters,
|
|
||||||
},
|
|
||||||
} as WorkflowApiResponse);
|
|
||||||
useWorkflowParametersStore
|
useWorkflowParametersStore
|
||||||
.getState()
|
.getState()
|
||||||
.setParameters(initialParameters);
|
.setParameters(initialParameters);
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ const workflowParameterTypeOptions = [
|
|||||||
{ label: "integer", value: WorkflowParameterValueType.Integer },
|
{ label: "integer", value: WorkflowParameterValueType.Integer },
|
||||||
{ label: "boolean", value: WorkflowParameterValueType.Boolean },
|
{ label: "boolean", value: WorkflowParameterValueType.Boolean },
|
||||||
{ label: "file", value: WorkflowParameterValueType.FileURL },
|
{ label: "file", value: WorkflowParameterValueType.FileURL },
|
||||||
|
{ label: "credential", value: WorkflowParameterValueType.CredentialId },
|
||||||
{ label: "JSON", value: WorkflowParameterValueType.JSON },
|
{ label: "JSON", value: WorkflowParameterValueType.JSON },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ from skyvern.forge.sdk.schemas.workflow_copilot import (
|
|||||||
from skyvern.forge.sdk.services import org_auth_service
|
from skyvern.forge.sdk.services import org_auth_service
|
||||||
from skyvern.forge.sdk.workflow.exceptions import BaseWorkflowHTTPException
|
from skyvern.forge.sdk.workflow.exceptions import BaseWorkflowHTTPException
|
||||||
from skyvern.forge.sdk.workflow.models.parameter import ParameterType
|
from skyvern.forge.sdk.workflow.models.parameter import ParameterType
|
||||||
from skyvern.forge.sdk.workflow.models.workflow import WorkflowDefinition
|
from skyvern.forge.sdk.workflow.models.workflow import Workflow
|
||||||
from skyvern.forge.sdk.workflow.workflow_definition_converter import convert_workflow_definition
|
from skyvern.forge.sdk.workflow.workflow_definition_converter import convert_workflow_definition
|
||||||
from skyvern.schemas.workflows import (
|
from skyvern.schemas.workflows import (
|
||||||
LoginBlockYAML,
|
LoginBlockYAML,
|
||||||
@@ -124,7 +124,7 @@ async def copilot_call_llm(
|
|||||||
chat_history: list[WorkflowCopilotChatHistoryMessage],
|
chat_history: list[WorkflowCopilotChatHistoryMessage],
|
||||||
global_llm_context: str | None,
|
global_llm_context: str | None,
|
||||||
debug_run_info_text: str,
|
debug_run_info_text: str,
|
||||||
) -> tuple[str, WorkflowDefinition | None, str | None]:
|
) -> tuple[str, Workflow | None, str | None]:
|
||||||
chat_history_text = _format_chat_history(chat_history)
|
chat_history_text = _format_chat_history(chat_history)
|
||||||
|
|
||||||
workflow_knowledge_base = WORKFLOW_KNOWLEDGE_BASE_PATH.read_text(encoding="utf-8")
|
workflow_knowledge_base = WORKFLOW_KNOWLEDGE_BASE_PATH.read_text(encoding="utf-8")
|
||||||
@@ -142,15 +142,19 @@ async def copilot_call_llm(
|
|||||||
|
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Calling LLM",
|
"Calling LLM",
|
||||||
user_message=chat_request.message,
|
workflow_permanent_id=chat_request.workflow_permanent_id,
|
||||||
|
workflow_id=chat_request.workflow_id,
|
||||||
user_message_len=len(chat_request.message),
|
user_message_len=len(chat_request.message),
|
||||||
|
user_message=chat_request.message,
|
||||||
workflow_yaml_len=len(chat_request.workflow_yaml or ""),
|
workflow_yaml_len=len(chat_request.workflow_yaml or ""),
|
||||||
|
workflow_yaml=chat_request.workflow_yaml or "",
|
||||||
chat_history_len=len(chat_history_text),
|
chat_history_len=len(chat_history_text),
|
||||||
|
chat_history=chat_history_text,
|
||||||
global_llm_context_len=len(global_llm_context or ""),
|
global_llm_context_len=len(global_llm_context or ""),
|
||||||
debug_run_info_len=len(debug_run_info_text),
|
global_llm_context=global_llm_context or "",
|
||||||
workflow_knowledge_base_len=len(workflow_knowledge_base),
|
workflow_knowledge_base_len=len(workflow_knowledge_base),
|
||||||
|
debug_run_info_len=len(debug_run_info_text),
|
||||||
llm_prompt_len=len(llm_prompt),
|
llm_prompt_len=len(llm_prompt),
|
||||||
llm_prompt=llm_prompt,
|
|
||||||
)
|
)
|
||||||
llm_api_handler = (
|
llm_api_handler = (
|
||||||
await get_llm_handler_for_prompt_type("workflow-copilot", chat_request.workflow_permanent_id, organization_id)
|
await get_llm_handler_for_prompt_type("workflow-copilot", chat_request.workflow_permanent_id, organization_id)
|
||||||
@@ -164,6 +168,8 @@ async def copilot_call_llm(
|
|||||||
)
|
)
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"LLM response",
|
"LLM response",
|
||||||
|
workflow_permanent_id=chat_request.workflow_permanent_id,
|
||||||
|
workflow_id=chat_request.workflow_id,
|
||||||
duration_seconds=time.monotonic() - llm_start_time,
|
duration_seconds=time.monotonic() - llm_start_time,
|
||||||
user_message_len=len(chat_request.message),
|
user_message_len=len(chat_request.message),
|
||||||
workflow_yaml_len=len(chat_request.workflow_yaml or ""),
|
workflow_yaml_len=len(chat_request.workflow_yaml or ""),
|
||||||
@@ -185,6 +191,8 @@ async def copilot_call_llm(
|
|||||||
user_response = str(user_response_value)
|
user_response = str(user_response_value)
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"LLM response received",
|
"LLM response received",
|
||||||
|
workflow_permanent_id=chat_request.workflow_permanent_id,
|
||||||
|
workflow_id=chat_request.workflow_id,
|
||||||
organization_id=organization_id,
|
organization_id=organization_id,
|
||||||
action_type=action_type,
|
action_type=action_type,
|
||||||
)
|
)
|
||||||
@@ -194,9 +202,14 @@ async def copilot_call_llm(
|
|||||||
global_llm_context = str(global_llm_context)
|
global_llm_context = str(global_llm_context)
|
||||||
|
|
||||||
if action_type == "REPLACE_WORKFLOW":
|
if action_type == "REPLACE_WORKFLOW":
|
||||||
workflow_yaml = action_data.get("workflow_yaml", "")
|
llm_workflow_yaml = action_data.get("workflow_yaml", "")
|
||||||
try:
|
try:
|
||||||
updated_workflow = await _process_workflow_yaml(chat_request.workflow_id, workflow_yaml)
|
updated_workflow = await _process_workflow_yaml(
|
||||||
|
workflow_id=chat_request.workflow_id,
|
||||||
|
workflow_permanent_id=chat_request.workflow_permanent_id,
|
||||||
|
organization_id=organization_id,
|
||||||
|
workflow_yaml=llm_workflow_yaml,
|
||||||
|
)
|
||||||
except (yaml.YAMLError, ValidationError, BaseWorkflowHTTPException) as e:
|
except (yaml.YAMLError, ValidationError, BaseWorkflowHTTPException) as e:
|
||||||
await stream.send(
|
await stream.send(
|
||||||
WorkflowCopilotProcessingUpdate(
|
WorkflowCopilotProcessingUpdate(
|
||||||
@@ -209,13 +222,18 @@ async def copilot_call_llm(
|
|||||||
llm_api_handler=llm_api_handler,
|
llm_api_handler=llm_api_handler,
|
||||||
organization_id=organization_id,
|
organization_id=organization_id,
|
||||||
user_response=user_response,
|
user_response=user_response,
|
||||||
workflow_yaml=workflow_yaml,
|
workflow_yaml=llm_workflow_yaml,
|
||||||
chat_history=chat_history,
|
chat_history=chat_history,
|
||||||
global_llm_context=global_llm_context,
|
global_llm_context=global_llm_context,
|
||||||
debug_run_info_text=debug_run_info_text,
|
debug_run_info_text=debug_run_info_text,
|
||||||
error=e,
|
error=e,
|
||||||
)
|
)
|
||||||
updated_workflow = await _process_workflow_yaml(chat_request.workflow_id, corrected_workflow_yaml)
|
updated_workflow = await _process_workflow_yaml(
|
||||||
|
workflow_id=chat_request.workflow_id,
|
||||||
|
workflow_permanent_id=chat_request.workflow_permanent_id,
|
||||||
|
organization_id=organization_id,
|
||||||
|
workflow_yaml=corrected_workflow_yaml,
|
||||||
|
)
|
||||||
|
|
||||||
return user_response, updated_workflow, global_llm_context
|
return user_response, updated_workflow, global_llm_context
|
||||||
elif action_type == "REPLY":
|
elif action_type == "REPLY":
|
||||||
@@ -280,7 +298,12 @@ async def _auto_correct_workflow_yaml(
|
|||||||
return action_data.get("workflow_yaml", workflow_yaml)
|
return action_data.get("workflow_yaml", workflow_yaml)
|
||||||
|
|
||||||
|
|
||||||
async def _process_workflow_yaml(workflow_id: str, workflow_yaml: str) -> WorkflowDefinition:
|
async def _process_workflow_yaml(
|
||||||
|
workflow_id: str,
|
||||||
|
workflow_permanent_id: str,
|
||||||
|
organization_id: str,
|
||||||
|
workflow_yaml: str,
|
||||||
|
) -> Workflow:
|
||||||
parsed_yaml = yaml.safe_load(workflow_yaml)
|
parsed_yaml = yaml.safe_load(workflow_yaml)
|
||||||
|
|
||||||
# Fixing trivial common LLM mistakes
|
# Fixing trivial common LLM mistakes
|
||||||
@@ -301,11 +324,35 @@ async def _process_workflow_yaml(workflow_id: str, workflow_yaml: str) -> Workfl
|
|||||||
p for p in workflow_yaml_request.workflow_definition.parameters if p.parameter_type != ParameterType.OUTPUT
|
p for p in workflow_yaml_request.workflow_definition.parameters if p.parameter_type != ParameterType.OUTPUT
|
||||||
]
|
]
|
||||||
|
|
||||||
updated_workflow = convert_workflow_definition(
|
updated_workflow_definition = convert_workflow_definition(
|
||||||
workflow_definition_yaml=workflow_yaml_request.workflow_definition,
|
workflow_definition_yaml=workflow_yaml_request.workflow_definition,
|
||||||
workflow_id=workflow_id,
|
workflow_id=workflow_id,
|
||||||
)
|
)
|
||||||
return updated_workflow
|
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
return Workflow(
|
||||||
|
workflow_id=workflow_id,
|
||||||
|
organization_id=organization_id,
|
||||||
|
title=workflow_yaml_request.title or "",
|
||||||
|
workflow_permanent_id=workflow_permanent_id,
|
||||||
|
version=1,
|
||||||
|
is_saved_task=workflow_yaml_request.is_saved_task,
|
||||||
|
description=workflow_yaml_request.description,
|
||||||
|
workflow_definition=updated_workflow_definition,
|
||||||
|
proxy_location=workflow_yaml_request.proxy_location,
|
||||||
|
webhook_callback_url=workflow_yaml_request.webhook_callback_url,
|
||||||
|
persist_browser_session=workflow_yaml_request.persist_browser_session or False,
|
||||||
|
model=workflow_yaml_request.model,
|
||||||
|
max_screenshot_scrolls=workflow_yaml_request.max_screenshot_scrolls,
|
||||||
|
extra_http_headers=workflow_yaml_request.extra_http_headers,
|
||||||
|
run_with=workflow_yaml_request.run_with,
|
||||||
|
ai_fallback=workflow_yaml_request.ai_fallback,
|
||||||
|
cache_key=workflow_yaml_request.cache_key,
|
||||||
|
run_sequentially=workflow_yaml_request.run_sequentially,
|
||||||
|
sequential_key=workflow_yaml_request.sequential_key,
|
||||||
|
created_at=now,
|
||||||
|
modified_at=now,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@base_router.post("/workflow/copilot/chat-post", include_in_schema=False)
|
@base_router.post("/workflow/copilot/chat-post", include_in_schema=False)
|
||||||
|
|||||||
Reference in New Issue
Block a user