Workflow Copilot: convert YAML -> workflow definition on BE side (#4461)
This commit is contained in:
committed by
GitHub
parent
d05e817dcc
commit
09f2903c18
@@ -7,14 +7,16 @@ 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 { 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 {
|
||||||
WorkflowCopilotChatHistoryResponse,
|
WorkflowCopilotChatHistoryResponse,
|
||||||
WorkflowCopilotProcessingUpdate,
|
WorkflowCopilotProcessingUpdate,
|
||||||
WorkflowCopilotStreamError,
|
WorkflowCopilotStreamErrorUpdate,
|
||||||
WorkflowCopilotStreamResponse,
|
WorkflowCopilotStreamResponseUpdate,
|
||||||
WorkflowCopilotChatSender,
|
WorkflowCopilotChatSender,
|
||||||
|
WorkflowCopilotChatRequest,
|
||||||
} from "./workflowCopilotTypes";
|
} from "./workflowCopilotTypes";
|
||||||
|
|
||||||
interface ChatMessage {
|
interface ChatMessage {
|
||||||
@@ -26,8 +28,8 @@ interface ChatMessage {
|
|||||||
|
|
||||||
type WorkflowCopilotSsePayload =
|
type WorkflowCopilotSsePayload =
|
||||||
| WorkflowCopilotProcessingUpdate
|
| WorkflowCopilotProcessingUpdate
|
||||||
| WorkflowCopilotStreamResponse
|
| WorkflowCopilotStreamResponseUpdate
|
||||||
| WorkflowCopilotStreamError;
|
| WorkflowCopilotStreamErrorUpdate;
|
||||||
|
|
||||||
const formatChatTimestamp = (value: string) => {
|
const formatChatTimestamp = (value: string) => {
|
||||||
let normalizedValue = value.replace(/\.(\d{3})\d*/, ".$1");
|
let normalizedValue = value.replace(/\.(\d{3})\d*/, ".$1");
|
||||||
@@ -65,7 +67,7 @@ const MessageItem = memo(({ message }: { message: ChatMessage }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
interface WorkflowCopilotChatProps {
|
interface WorkflowCopilotChatProps {
|
||||||
onWorkflowUpdate?: (workflowYaml: string) => void;
|
onWorkflowUpdate?: (workflow: WorkflowDefinition) => void;
|
||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
onMessageCountChange?: (count: number) => void;
|
onMessageCountChange?: (count: number) => void;
|
||||||
@@ -317,8 +319,18 @@ export function WorkflowCopilotChat({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const saveData = getSaveData();
|
const saveData = getSaveData();
|
||||||
|
const workflowId = saveData?.workflow.workflow_id;
|
||||||
let workflowYaml = "";
|
let workflowYaml = "";
|
||||||
|
|
||||||
|
if (!workflowId) {
|
||||||
|
toast({
|
||||||
|
title: "Missing workflow",
|
||||||
|
description: "Workflow ID is required to chat.",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (saveData) {
|
if (saveData) {
|
||||||
const extraHttpHeaders: Record<string, string> = {};
|
const extraHttpHeaders: Record<string, string> = {};
|
||||||
if (saveData.settings.extraHttpHeaders) {
|
if (saveData.settings.extraHttpHeaders) {
|
||||||
@@ -397,7 +409,9 @@ export function WorkflowCopilotChat({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResponse = (response: WorkflowCopilotStreamResponse) => {
|
const handleResponse = (
|
||||||
|
response: WorkflowCopilotStreamResponseUpdate,
|
||||||
|
) => {
|
||||||
setWorkflowCopilotChatId(response.workflow_copilot_chat_id);
|
setWorkflowCopilotChatId(response.workflow_copilot_chat_id);
|
||||||
|
|
||||||
const aiMessage: ChatMessage = {
|
const aiMessage: ChatMessage = {
|
||||||
@@ -409,9 +423,9 @@ export function WorkflowCopilotChat({
|
|||||||
|
|
||||||
setMessages((prev) => [...prev, aiMessage]);
|
setMessages((prev) => [...prev, aiMessage]);
|
||||||
|
|
||||||
if (response.updated_workflow_yaml && onWorkflowUpdate) {
|
if (response.updated_workflow && onWorkflowUpdate) {
|
||||||
try {
|
try {
|
||||||
onWorkflowUpdate(response.updated_workflow_yaml);
|
onWorkflowUpdate(response.updated_workflow as WorkflowDefinition);
|
||||||
} catch (updateError) {
|
} catch (updateError) {
|
||||||
console.error("Failed to update workflow:", updateError);
|
console.error("Failed to update workflow:", updateError);
|
||||||
toast({
|
toast({
|
||||||
@@ -424,7 +438,7 @@ export function WorkflowCopilotChat({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleError = (payload: WorkflowCopilotStreamError) => {
|
const handleError = (payload: WorkflowCopilotStreamErrorUpdate) => {
|
||||||
const errorMessage: ChatMessage = {
|
const errorMessage: ChatMessage = {
|
||||||
id: Date.now().toString(),
|
id: Date.now().toString(),
|
||||||
sender: "ai",
|
sender: "ai",
|
||||||
@@ -437,12 +451,13 @@ export function WorkflowCopilotChat({
|
|||||||
await client.postStreaming<WorkflowCopilotSsePayload>(
|
await client.postStreaming<WorkflowCopilotSsePayload>(
|
||||||
"/workflow/copilot/chat-post",
|
"/workflow/copilot/chat-post",
|
||||||
{
|
{
|
||||||
|
workflow_id: workflowId,
|
||||||
workflow_permanent_id: workflowPermanentId,
|
workflow_permanent_id: workflowPermanentId,
|
||||||
workflow_copilot_chat_id: workflowCopilotChatId,
|
workflow_copilot_chat_id: workflowCopilotChatId,
|
||||||
workflow_run_id: workflowRunId,
|
workflow_run_id: workflowRunId,
|
||||||
message: messageContent,
|
message: messageContent,
|
||||||
workflow_yaml: workflowYaml,
|
workflow_yaml: workflowYaml,
|
||||||
},
|
} as WorkflowCopilotChatRequest,
|
||||||
(payload) => {
|
(payload) => {
|
||||||
switch (payload.type) {
|
switch (payload.type) {
|
||||||
case "processing_update":
|
case "processing_update":
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export interface WorkflowCopilotChatMessage {
|
|||||||
|
|
||||||
export interface WorkflowCopilotChatRequest {
|
export interface WorkflowCopilotChatRequest {
|
||||||
workflow_permanent_id: string;
|
workflow_permanent_id: string;
|
||||||
|
workflow_id: string;
|
||||||
workflow_copilot_chat_id?: string | null;
|
workflow_copilot_chat_id?: string | null;
|
||||||
workflow_run_id?: string | null;
|
workflow_run_id?: string | null;
|
||||||
message: string;
|
message: string;
|
||||||
@@ -48,15 +49,15 @@ export interface WorkflowCopilotProcessingUpdate {
|
|||||||
timestamp: string;
|
timestamp: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkflowCopilotStreamResponse {
|
export interface WorkflowCopilotStreamResponseUpdate {
|
||||||
type: "response";
|
type: "response";
|
||||||
workflow_copilot_chat_id: string;
|
workflow_copilot_chat_id: string;
|
||||||
message: string;
|
message: string;
|
||||||
updated_workflow_yaml?: string | null;
|
updated_workflow?: Record<string, unknown> | null;
|
||||||
response_time: string;
|
response_time: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkflowCopilotStreamError {
|
export interface WorkflowCopilotStreamErrorUpdate {
|
||||||
type: "error";
|
type: "error";
|
||||||
error: string;
|
error: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,11 +87,7 @@ import { getWorkflowErrors, getElements } from "./workflowEditorUtils";
|
|||||||
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 {
|
import { WorkflowApiResponse, WorkflowSettings } from "../types/workflowTypes";
|
||||||
WorkflowApiResponse,
|
|
||||||
WorkflowBlock,
|
|
||||||
WorkflowSettings,
|
|
||||||
} from "../types/workflowTypes";
|
|
||||||
import { ProxyLocation } from "@/api/types";
|
import { ProxyLocation } from "@/api/types";
|
||||||
import {
|
import {
|
||||||
nodeAdderNode,
|
nodeAdderNode,
|
||||||
@@ -100,16 +96,10 @@ import {
|
|||||||
generateNodeLabel,
|
generateNodeLabel,
|
||||||
layout,
|
layout,
|
||||||
startNode,
|
startNode,
|
||||||
upgradeWorkflowBlocksV1toV2,
|
|
||||||
} from "./workflowEditorUtils";
|
} from "./workflowEditorUtils";
|
||||||
import { constructCacheKeyValue, getInitialParameters } from "./utils";
|
import { constructCacheKeyValue, getInitialParameters } from "./utils";
|
||||||
import { WorkflowCopilotChat } from "../copilot/WorkflowCopilotChat";
|
import { WorkflowCopilotChat } from "../copilot/WorkflowCopilotChat";
|
||||||
import { WorkflowCopilotButton } from "../copilot/WorkflowCopilotButton";
|
import { WorkflowCopilotButton } from "../copilot/WorkflowCopilotButton";
|
||||||
import { parse as parseYAML } from "yaml";
|
|
||||||
import {
|
|
||||||
BlockYAML,
|
|
||||||
WorkflowCreateYAMLRequest,
|
|
||||||
} from "../types/workflowYamlTypes";
|
|
||||||
import "./workspace-styles.css";
|
import "./workspace-styles.css";
|
||||||
|
|
||||||
const Constants = {
|
const Constants = {
|
||||||
@@ -1664,68 +1654,36 @@ function Workspace({
|
|||||||
onClose={() => setIsCopilotOpen(false)}
|
onClose={() => setIsCopilotOpen(false)}
|
||||||
onMessageCountChange={setCopilotMessageCount}
|
onMessageCountChange={setCopilotMessageCount}
|
||||||
buttonRef={copilotButtonRef}
|
buttonRef={copilotButtonRef}
|
||||||
onWorkflowUpdate={(workflowYaml) => {
|
onWorkflowUpdate={(workflowData) => {
|
||||||
try {
|
try {
|
||||||
const parsedYaml = parseYAML(
|
const saveData = workflowChangesStore.getSaveData?.();
|
||||||
workflowYaml,
|
|
||||||
) as WorkflowCreateYAMLRequest;
|
|
||||||
|
|
||||||
const settings: WorkflowSettings = {
|
const settings: WorkflowSettings = {
|
||||||
proxyLocation:
|
proxyLocation:
|
||||||
parsedYaml.proxy_location || ProxyLocation.Residential,
|
saveData?.settings.proxyLocation || ProxyLocation.Residential,
|
||||||
webhookCallbackUrl: parsedYaml.webhook_callback_url || "",
|
webhookCallbackUrl: saveData?.settings.webhookCallbackUrl || "",
|
||||||
persistBrowserSession:
|
persistBrowserSession:
|
||||||
parsedYaml.persist_browser_session ?? false,
|
saveData?.settings.persistBrowserSession ?? false,
|
||||||
model: parsedYaml.model ?? null,
|
model: saveData?.settings.model ?? null,
|
||||||
maxScreenshotScrolls: parsedYaml.max_screenshot_scrolls || 3,
|
maxScreenshotScrolls:
|
||||||
extraHttpHeaders: parsedYaml.extra_http_headers
|
saveData?.settings.maxScreenshotScrolls || 3,
|
||||||
? JSON.stringify(parsedYaml.extra_http_headers)
|
extraHttpHeaders: saveData?.settings.extraHttpHeaders ?? null,
|
||||||
: null,
|
runWith: saveData?.settings.runWith ?? null,
|
||||||
runWith: parsedYaml.run_with ?? null,
|
scriptCacheKey: saveData?.settings.scriptCacheKey ?? null,
|
||||||
scriptCacheKey: parsedYaml.cache_key ?? null,
|
aiFallback: saveData?.settings.aiFallback ?? true,
|
||||||
aiFallback: parsedYaml.ai_fallback ?? true,
|
runSequentially: saveData?.settings.runSequentially ?? false,
|
||||||
runSequentially: parsedYaml.run_sequentially ?? false,
|
sequentialKey: saveData?.settings.sequentialKey ?? null,
|
||||||
sequentialKey: parsedYaml.sequential_key ?? null,
|
finallyBlockLabel: workflowData?.finally_block_label ?? null,
|
||||||
finallyBlockLabel:
|
|
||||||
parsedYaml.workflow_definition?.finally_block_label ?? null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Convert YAML blocks to internal format
|
const elements = getElements(workflowData.blocks, settings, true);
|
||||||
// YAML has parameter_keys (array of strings), internal format has parameters (array of objects)
|
|
||||||
let blocks = (parsedYaml.workflow_definition?.blocks || []).map(
|
|
||||||
(block: BlockYAML) => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const convertedBlock = { ...block } as any;
|
|
||||||
|
|
||||||
// Convert parameter_keys to parameters format
|
|
||||||
if ("parameter_keys" in block) {
|
|
||||||
convertedBlock.parameters = (block.parameter_keys || []).map(
|
|
||||||
(key: string) => ({
|
|
||||||
key,
|
|
||||||
parameter_type: "workflow",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return convertedBlock;
|
|
||||||
},
|
|
||||||
) as WorkflowBlock[];
|
|
||||||
|
|
||||||
// Auto-upgrade v1 workflows to v2 by assigning sequential next_block_label values
|
|
||||||
const workflowVersion =
|
|
||||||
parsedYaml.workflow_definition?.version ?? 1;
|
|
||||||
if (workflowVersion < 2) {
|
|
||||||
blocks = upgradeWorkflowBlocksV1toV2(blocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
const elements = getElements(blocks, settings, true);
|
|
||||||
|
|
||||||
setNodes(elements.nodes);
|
setNodes(elements.nodes);
|
||||||
setEdges(elements.edges);
|
setEdges(elements.edges);
|
||||||
|
|
||||||
const initialParameters = getInitialParameters({
|
const initialParameters = getInitialParameters({
|
||||||
workflow_definition: {
|
workflow_definition: {
|
||||||
parameters: parsedYaml.workflow_definition?.parameters || [],
|
parameters: workflowData.parameters,
|
||||||
},
|
},
|
||||||
} as WorkflowApiResponse);
|
} as WorkflowApiResponse);
|
||||||
useWorkflowParametersStore
|
useWorkflowParametersStore
|
||||||
@@ -1734,11 +1692,14 @@ function Workspace({
|
|||||||
|
|
||||||
workflowChangesStore.setHasChanges(true);
|
workflowChangesStore.setHasChanges(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to parse and apply workflow YAML", error);
|
console.error(
|
||||||
console.log("YAML:", workflowYaml);
|
"Failed to parse and apply workflow",
|
||||||
|
error,
|
||||||
|
workflowData,
|
||||||
|
);
|
||||||
toast({
|
toast({
|
||||||
title: "Update failed",
|
title: "Update failed",
|
||||||
description: "Failed to parse workflow YAML. Please try again.",
|
description: "Failed to apply workflow update. Please try again.",
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import time
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, AsyncGenerator
|
from typing import AsyncGenerator
|
||||||
|
|
||||||
import structlog
|
import structlog
|
||||||
import yaml
|
import yaml
|
||||||
@@ -29,7 +29,13 @@ from skyvern.forge.sdk.schemas.workflow_copilot import (
|
|||||||
WorkflowCopilotStreamResponseUpdate,
|
WorkflowCopilotStreamResponseUpdate,
|
||||||
)
|
)
|
||||||
from skyvern.forge.sdk.services import org_auth_service
|
from skyvern.forge.sdk.services import org_auth_service
|
||||||
from skyvern.schemas.workflows import LoginBlockYAML, WorkflowCreateYAMLRequest
|
from skyvern.forge.sdk.workflow.models.parameter import ParameterType
|
||||||
|
from skyvern.forge.sdk.workflow.models.workflow import WorkflowDefinition
|
||||||
|
from skyvern.forge.sdk.workflow.workflow_definition_converter import convert_workflow_definition
|
||||||
|
from skyvern.schemas.workflows import (
|
||||||
|
LoginBlockYAML,
|
||||||
|
WorkflowCreateYAMLRequest,
|
||||||
|
)
|
||||||
|
|
||||||
WORKFLOW_KNOWLEDGE_BASE_PATH = Path("skyvern/forge/prompts/skyvern/workflow_knowledge_base.txt")
|
WORKFLOW_KNOWLEDGE_BASE_PATH = Path("skyvern/forge/prompts/skyvern/workflow_knowledge_base.txt")
|
||||||
CHAT_HISTORY_CONTEXT_MESSAGES = 10
|
CHAT_HISTORY_CONTEXT_MESSAGES = 10
|
||||||
@@ -88,7 +94,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, str | None, str | None]:
|
) -> tuple[str, WorkflowDefinition | None, str | None]:
|
||||||
current_datetime = datetime.now(timezone.utc).isoformat()
|
current_datetime = datetime.now(timezone.utc).isoformat()
|
||||||
|
|
||||||
chat_history_text = ""
|
chat_history_text = ""
|
||||||
@@ -177,8 +183,8 @@ 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 = await _process_workflow_yaml(action_data)
|
updated_workflow = await _process_workflow_yaml(chat_request.workflow_id, action_data.get("workflow_yaml", ""))
|
||||||
return user_response, workflow_yaml, global_llm_context
|
return user_response, updated_workflow, global_llm_context
|
||||||
elif action_type == "REPLY":
|
elif action_type == "REPLY":
|
||||||
return user_response, None, global_llm_context
|
return user_response, None, global_llm_context
|
||||||
elif action_type == "ASK_QUESTION":
|
elif action_type == "ASK_QUESTION":
|
||||||
@@ -192,17 +198,11 @@ async def copilot_call_llm(
|
|||||||
return "I received your request but I'm not sure how to help. Could you rephrase?", None, None
|
return "I received your request but I'm not sure how to help. Could you rephrase?", None, None
|
||||||
|
|
||||||
|
|
||||||
async def _process_workflow_yaml(action_data: dict[str, Any]) -> None | str:
|
async def _process_workflow_yaml(workflow_id: str, workflow_yaml: str) -> WorkflowDefinition:
|
||||||
workflow_yaml = action_data.get("workflow_yaml", "")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
parsed_yaml = yaml.safe_load(workflow_yaml)
|
parsed_yaml = yaml.safe_load(workflow_yaml)
|
||||||
except yaml.YAMLError as e:
|
except yaml.YAMLError as e:
|
||||||
LOG.error(
|
LOG.error("Invalid YAML from LLM", yaml=workflow_yaml, exc_info=True)
|
||||||
"Invalid YAML from LLM",
|
|
||||||
error=str(e),
|
|
||||||
yaml=f"\n{str(e)}\n{workflow_yaml}",
|
|
||||||
)
|
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
detail=f"LLM generated invalid YAML: {str(e)}",
|
detail=f"LLM generated invalid YAML: {str(e)}",
|
||||||
@@ -216,25 +216,28 @@ async def _process_workflow_yaml(action_data: dict[str, Any]) -> None | str:
|
|||||||
for block in blocks:
|
for block in blocks:
|
||||||
block["title"] = block.get("title", "")
|
block["title"] = block.get("title", "")
|
||||||
|
|
||||||
workflow = WorkflowCreateYAMLRequest.model_validate(parsed_yaml)
|
workflow_yaml_request = WorkflowCreateYAMLRequest.model_validate(parsed_yaml)
|
||||||
|
|
||||||
# Post-processing
|
# Post-processing
|
||||||
for block in workflow.workflow_definition.blocks:
|
for block in workflow_yaml_request.workflow_definition.blocks:
|
||||||
if isinstance(block, LoginBlockYAML) and not block.navigation_goal:
|
if isinstance(block, LoginBlockYAML) and not block.navigation_goal:
|
||||||
block.navigation_goal = DEFAULT_LOGIN_PROMPT
|
block.navigation_goal = DEFAULT_LOGIN_PROMPT
|
||||||
|
|
||||||
workflow_yaml = yaml.safe_dump(workflow.model_dump(mode="json"), sort_keys=False)
|
workflow_yaml_request.workflow_definition.parameters = [
|
||||||
except Exception as e:
|
p for p in workflow_yaml_request.workflow_definition.parameters if p.parameter_type != ParameterType.OUTPUT
|
||||||
LOG.error(
|
]
|
||||||
"YAML from LLM does not conform to Skyvern workflow schema",
|
|
||||||
error=str(e),
|
updated_workflow = convert_workflow_definition(
|
||||||
yaml=f"\n{str(e)}\n{workflow_yaml}",
|
workflow_definition_yaml=workflow_yaml_request.workflow_definition,
|
||||||
|
workflow_id=workflow_id,
|
||||||
)
|
)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error("YAML from LLM does not conform to Skyvern workflow schema", yaml=workflow_yaml, exc_info=True)
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
detail=f"LLM generated YAML that doesn't match workflow schema: {str(e)}",
|
detail=f"LLM generated YAML that doesn't match workflow schema: {str(e)}",
|
||||||
)
|
)
|
||||||
return workflow_yaml
|
return updated_workflow
|
||||||
|
|
||||||
|
|
||||||
@base_router.post("/workflow/copilot/chat-post", include_in_schema=False)
|
@base_router.post("/workflow/copilot/chat-post", include_in_schema=False)
|
||||||
@@ -314,7 +317,7 @@ async def workflow_copilot_chat_post(
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
user_response, updated_workflow_yaml, updated_global_llm_context = await copilot_call_llm(
|
user_response, updated_workflow, updated_global_llm_context = await copilot_call_llm(
|
||||||
organization.organization_id,
|
organization.organization_id,
|
||||||
chat_request,
|
chat_request,
|
||||||
convert_to_history_messages(chat_messages[-CHAT_HISTORY_CONTEXT_MESSAGES:]),
|
convert_to_history_messages(chat_messages[-CHAT_HISTORY_CONTEXT_MESSAGES:]),
|
||||||
@@ -349,7 +352,7 @@ async def workflow_copilot_chat_post(
|
|||||||
type=WorkflowCopilotStreamMessageType.RESPONSE,
|
type=WorkflowCopilotStreamMessageType.RESPONSE,
|
||||||
workflow_copilot_chat_id=chat.workflow_copilot_chat_id,
|
workflow_copilot_chat_id=chat.workflow_copilot_chat_id,
|
||||||
message=user_response,
|
message=user_response,
|
||||||
updated_workflow_yaml=updated_workflow_yaml,
|
updated_workflow=updated_workflow.model_dump(mode="json") if updated_workflow else None,
|
||||||
response_time=assistant_message.created_at,
|
response_time=assistant_message.created_at,
|
||||||
).model_dump(mode="json"),
|
).model_dump(mode="json"),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class WorkflowCopilotChatMessage(BaseModel):
|
|||||||
|
|
||||||
class WorkflowCopilotChatRequest(BaseModel):
|
class WorkflowCopilotChatRequest(BaseModel):
|
||||||
workflow_permanent_id: str = Field(..., description="Workflow permanent ID for the chat")
|
workflow_permanent_id: str = Field(..., description="Workflow permanent ID for the chat")
|
||||||
|
workflow_id: str = Field(..., description="Workflow permanent ID for the chat")
|
||||||
workflow_copilot_chat_id: str | None = Field(None, description="The chat ID to send the message to")
|
workflow_copilot_chat_id: str | None = Field(None, description="The chat ID to send the message to")
|
||||||
workflow_run_id: str | None = Field(None, description="The workflow run ID to use for the context")
|
workflow_run_id: str | None = Field(None, description="The workflow run ID to use for the context")
|
||||||
message: str = Field(..., description="The message that user sends")
|
message: str = Field(..., description="The message that user sends")
|
||||||
@@ -70,7 +71,7 @@ class WorkflowCopilotStreamResponseUpdate(BaseModel):
|
|||||||
)
|
)
|
||||||
workflow_copilot_chat_id: str = Field(..., description="The chat ID")
|
workflow_copilot_chat_id: str = Field(..., description="The chat ID")
|
||||||
message: str = Field(..., description="The message sent to the user")
|
message: str = Field(..., description="The message sent to the user")
|
||||||
updated_workflow_yaml: str | None = Field(None, description="The updated workflow yaml")
|
updated_workflow: dict | None = Field(None, description="The updated workflow")
|
||||||
response_time: datetime = Field(..., description="When the assistant message was created")
|
response_time: datetime = Field(..., description="When the assistant message was created")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2981,14 +2981,10 @@ class WorkflowService:
|
|||||||
self,
|
self,
|
||||||
workflow_id: str,
|
workflow_id: str,
|
||||||
workflow_definition_yaml: WorkflowDefinitionYAML,
|
workflow_definition_yaml: WorkflowDefinitionYAML,
|
||||||
title: str,
|
|
||||||
organization_id: str,
|
|
||||||
) -> WorkflowDefinition:
|
) -> WorkflowDefinition:
|
||||||
workflow_definition = convert_workflow_definition(
|
workflow_definition = convert_workflow_definition(
|
||||||
workflow_id=workflow_id,
|
|
||||||
workflow_definition_yaml=workflow_definition_yaml,
|
workflow_definition_yaml=workflow_definition_yaml,
|
||||||
title=title,
|
workflow_id=workflow_id,
|
||||||
organization_id=organization_id,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
await app.DATABASE.save_workflow_definition_parameters(workflow_definition.parameters)
|
await app.DATABASE.save_workflow_definition_parameters(workflow_definition.parameters)
|
||||||
@@ -3080,8 +3076,6 @@ class WorkflowService:
|
|||||||
workflow_definition = await self.make_workflow_definition(
|
workflow_definition = await self.make_workflow_definition(
|
||||||
potential_workflow.workflow_id,
|
potential_workflow.workflow_id,
|
||||||
request.workflow_definition,
|
request.workflow_definition,
|
||||||
request.title,
|
|
||||||
organization_id,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
updated_workflow = await self.update_workflow_definition(
|
updated_workflow = await self.update_workflow_definition(
|
||||||
|
|||||||
@@ -85,10 +85,8 @@ LOG = structlog.get_logger()
|
|||||||
|
|
||||||
|
|
||||||
def convert_workflow_definition(
|
def convert_workflow_definition(
|
||||||
workflow_id: str,
|
|
||||||
workflow_definition_yaml: WorkflowDefinitionYAML,
|
workflow_definition_yaml: WorkflowDefinitionYAML,
|
||||||
title: str,
|
workflow_id: str,
|
||||||
organization_id: str,
|
|
||||||
) -> WorkflowDefinition:
|
) -> WorkflowDefinition:
|
||||||
# Create parameters from the request
|
# Create parameters from the request
|
||||||
parameters: dict[str, PARAMETER_TYPE] = {}
|
parameters: dict[str, PARAMETER_TYPE] = {}
|
||||||
@@ -311,11 +309,9 @@ def convert_workflow_definition(
|
|||||||
)
|
)
|
||||||
|
|
||||||
LOG.info(
|
LOG.info(
|
||||||
f"Created workflow from request, title: {title}",
|
"Created workflow from request",
|
||||||
parameter_keys=[parameter.key for parameter in parameters.values()],
|
parameter_keys=[parameter.key for parameter in parameters.values()],
|
||||||
block_labels=[block.label for block in blocks],
|
block_labels=[block.label for block in blocks],
|
||||||
organization_id=organization_id,
|
|
||||||
title=title,
|
|
||||||
workflow_id=workflow_id,
|
workflow_id=workflow_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user