@@ -137,18 +151,6 @@ export const WorkflowViewer: FC
= ({
return ;
case "loading":
return ;
- case "error":
- return (
-
-
- Server Error
-
-
{workflow.result}
-
- Please try refreshing the page or attempt your action again.
-
-
- );
default:
throw workflow satisfies never;
}
diff --git a/apps/hub/src/app/ai/api/create/route.ts b/apps/hub/src/app/ai/api/create/route.ts
index a3702062ca..8d584531ce 100644
--- a/apps/hub/src/app/ai/api/create/route.ts
+++ b/apps/hub/src/app/ai/api/create/route.ts
@@ -105,7 +105,7 @@ function aiRequestToWorkflow(request: AiRequestBody) {
const llmConfig: LlmConfig = {
llmId: request.model ?? "Claude-Sonnet",
priceLimit: 0.3,
- durationLimitMinutes: 2,
+ durationLimitMinutes: 0.01,
messagesInHistoryToKeep: 4,
numericSteps: request.numericSteps,
styleGuideSteps: request.styleGuideSteps,
diff --git a/apps/hub/src/app/ai/useSquiggleWorkflows.tsx b/apps/hub/src/app/ai/useSquiggleWorkflows.tsx
index 30423fc18f..b464b716b3 100644
--- a/apps/hub/src/app/ai/useSquiggleWorkflows.tsx
+++ b/apps/hub/src/app/ai/useSquiggleWorkflows.tsx
@@ -109,8 +109,16 @@ export function useSquiggleWorkflows(preloadedWorkflows: AiWorkflow[]) {
} catch (error) {
updateWorkflow(id, (workflow) => ({
...workflow,
- status: "error",
- result: `Server error: ${error instanceof Error ? error.toString() : "Unknown error"}.`,
+ status: "finished",
+ result: {
+ code: "",
+ isValid: false,
+ totalPrice: 0,
+ runTimeMs: 0,
+ llmRunCount: 0,
+ logSummary: "",
+ error: `Server error: ${error instanceof Error ? error.toString() : "Unknown error"}.`,
+ },
}));
}
},
diff --git a/internal-packages/ai/src/types.ts b/internal-packages/ai/src/types.ts
index 069bed0ebd..0941b59892 100644
--- a/internal-packages/ai/src/types.ts
+++ b/internal-packages/ai/src/types.ts
@@ -93,6 +93,7 @@ const stepUpdatedSchema = stepSchema.partial().required({
export const clientWorkflowResultSchema = z.object({
code: z.string().describe("Squiggle code snippet"),
isValid: z.boolean(),
+ error: z.string().optional(),
totalPrice: z.number(),
runTimeMs: z.number(),
llmRunCount: z.number(),
@@ -142,11 +143,6 @@ export const clientWorkflowSchema = z.discriminatedUnion("status", [
status: z.literal("finished"),
result: clientWorkflowResultSchema,
}),
- z.object({
- ...commonClientWorkflowFields,
- status: z.literal("error"),
- result: z.string(),
- }),
]);
export type ClientWorkflow = z.infer;
diff --git a/internal-packages/ai/src/workflows/Workflow.ts b/internal-packages/ai/src/workflows/Workflow.ts
index 1172efcfcd..e0397caea9 100644
--- a/internal-packages/ai/src/workflows/Workflow.ts
+++ b/internal-packages/ai/src/workflows/Workflow.ts
@@ -111,10 +111,13 @@ export class Workflow {
public llmClient: LLMClient;
+ public error?: string;
+
constructor(params: WorkflowInstanceParams) {
this.id = params.id ?? crypto.randomUUID();
this.template = params.template;
this.inputs = params.inputs;
+ this.error = params.error ?? undefined;
this.llmConfig = params.llmConfig ?? llmConfigDefault;
this.startTime = params.steps.at(0)?.startTime ?? Date.now();
@@ -201,9 +204,9 @@ export class Workflow {
payload: { step },
});
- // apply the transition rule, produce the next step in PENDING state
- // this code is inlined in this method, which guarantees that we always have one pending step
- // (until the transition rule decides to finish)
+ // Apply the transition rule, produce the next step in PENDING state or stop the workflow.
+ // This code is inlined in this method, which guarantees that we always have one pending step
+ // (until the transition rule decides to finish).
const result = this.transitionRule(step, new WorkflowGuardHelpers(step));
switch (result.kind) {
case "repeat":
@@ -213,10 +216,11 @@ export class Workflow {
this.addStep(result.step.prepare(result.inputs));
break;
case "finish":
- // no new steps to add
+ // no new steps to add, we're done
break;
case "fatal":
- throw new Error(result.message);
+ this.error = result.message;
+ break;
}
}
@@ -299,7 +303,7 @@ export class Workflow {
getFinalResult(): ClientWorkflowResult {
const finalStep = this.getRecentStepWithCode();
- const isValid = finalStep?.step.getState().kind === "DONE";
+ const isValid = finalStep?.step.getState().kind === "DONE" && !this.error;
// compute run time
let runTimeMs: number;
@@ -326,6 +330,7 @@ export class Workflow {
return {
code: finalStep?.code ?? "",
isValid,
+ error: this.error,
totalPrice,
runTimeMs,
llmRunCount,
@@ -427,6 +432,7 @@ export class Workflow {
return {
id: this.id,
templateName: this.template.name,
+ error: this.error ?? null,
inputIds: Object.fromEntries(
Object.entries(this.inputs).map(([key, input]) => [
key,
@@ -454,6 +460,7 @@ export class Workflow {
}): Workflow {
const workflow = new Workflow({
id: node.id,
+ error: node.error ?? undefined,
template: getWorkflowTemplateByName(node.templateName),
inputs: Object.fromEntries(
Object.entries(node.inputIds).map(([key, id]) => [
@@ -509,4 +516,5 @@ export type SerializedWorkflow = {
inputIds: Record;
llmConfig: LlmConfig;
stepIds: number[];
+ error: string | null;
};
diff --git a/internal-packages/ai/src/workflows/WorkflowTemplate.ts b/internal-packages/ai/src/workflows/WorkflowTemplate.ts
index ceccaacdb1..dc8890f272 100644
--- a/internal-packages/ai/src/workflows/WorkflowTemplate.ts
+++ b/internal-packages/ai/src/workflows/WorkflowTemplate.ts
@@ -11,6 +11,7 @@ export type WorkflowInstanceParams = {
llmConfig?: LlmConfig;
openaiApiKey?: string;
anthropicApiKey?: string;
+ error?: string;
};
/**
diff --git a/internal-packages/ai/src/workflows/controllers.ts b/internal-packages/ai/src/workflows/controllers.ts
index ad8cc6d2d3..af26b7c77b 100644
--- a/internal-packages/ai/src/workflows/controllers.ts
+++ b/internal-packages/ai/src/workflows/controllers.ts
@@ -145,6 +145,6 @@ export function getDefaultTransitionRule(
}
}
- return h.fatal("Unknown step");
+ return h.fatal(ERROR_MESSAGES.UNKNOWN_STEP);
};
}