diff --git a/backend/windmill-worker/src/ai_executor.rs b/backend/windmill-worker/src/ai_executor.rs index fabbbe9eb1..001b930c6b 100644 --- a/backend/windmill-worker/src/ai_executor.rs +++ b/backend/windmill-worker/src/ai_executor.rs @@ -762,8 +762,10 @@ pub async fn run_agent( .map(|m| m.clamp(1, HARD_MAX_AGENT_ITERATIONS)) .unwrap_or(DEFAULT_MAX_AGENT_ITERATIONS); + // Main agent loop for i in 0..max_iterations { + if used_structured_output_tool { break; } @@ -1036,10 +1038,13 @@ pub async fn run_agent( if tool_calls.is_empty() { break; } else if i == max_iterations - 1 { - return Err(Error::internal_err( - "AI agent reached max iterations, but there are still tool calls" - .to_string(), - )); + let partial_result = serde_json::to_string(&serde_json::json!({ + "messages": messages, + })).unwrap_or_default(); + return Err(Error::internal_err(format!( + "AI agent reached max iterations ({}), you can either increase max_iterations or enable the \"continue on error\" option from the advanced options of the step. Partial result:\n{}", + max_iterations, partial_result + ))); } messages.push(OpenAIMessage { diff --git a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte index d9da29f01e..126f0056f0 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte @@ -1111,8 +1111,8 @@ {#if !selectedId.includes('failure')} @@ -1152,6 +1152,22 @@ {/if}
{#if advancedSelected === 'retries'} +
+ {#snippet header()} + + When enabled, the flow will continue to the next step even if this step fails (after exhausting all retries, if any). This enables to process the error in a branch one for instance. + + {/snippet} + +
+
{#snippet header()} {/snippet} - -
{:else if advancedSelected === 'runtime' && advancedRuntimeSelected === 'concurrency'} diff --git a/frontend/src/lib/components/flows/content/FlowRetries.svelte b/frontend/src/lib/components/flows/content/FlowRetries.svelte index 6a59b4fbc7..71e4d67f09 100644 --- a/frontend/src/lib/components/flows/content/FlowRetries.svelte +++ b/frontend/src/lib/components/flows/content/FlowRetries.svelte @@ -110,7 +110,7 @@ const u32Max = 4294967295 -
+
{/if} + {#if delayType === 'constant' || delayType === 'exponential'}
{#if delayType === 'constant'} @@ -296,66 +297,65 @@
{#if true} - {@const { attempts: cAttempts, seconds: cSeconds } = flowModuleRetry?.constant || {}} - {@const { - attempts: eAttempts, - seconds: eSeconds, - multiplier, - random_factor - } = flowModuleRetry?.exponential || {}} - {@const cArray = Array.from({ length: Math.min(cAttempts || 0, 100) }, () => cSeconds)} - {@const eArray = Array.from( - { length: Math.min(eAttempts || 0, 100) }, - (_, i) => (multiplier || 0) * (eSeconds || 0) ** (i + cArray.length + 1) - )} - {@const array = [...cArray, ...eArray]} -
-
Retry attempts
- {#if array.length > 0} - - + {@const { attempts: cAttempts, seconds: cSeconds } = flowModuleRetry?.constant || {}} + {@const { + attempts: eAttempts, + seconds: eSeconds, + multiplier, + random_factor + } = flowModuleRetry?.exponential || {}} + {@const cArray = Array.from({ length: Math.min(cAttempts || 0, 100) }, () => cSeconds)} + {@const eArray = Array.from( + { length: Math.min(eAttempts || 0, 100) }, + (_, i) => (multiplier || 0) * (eSeconds || 0) ** (i + cArray.length + 1) + )} + {@const array = [...cArray, ...eArray]} +
+
Retry attempts
+ {#if array.length > 0} +
+ + + + + + + + {#each array.slice(1, 100) as delay, i} + {@const index = i + 2} - - + + seconds){/if} + after attempt #{index - 1} + {#if i > cArray.length - 2} + + ({multiplier} * {eSeconds}{index}) + + {/if} + - - - {#each array.slice(1, 100) as delay, i} - {@const index = i + 2} - - - - - {/each} - {#if (cAttempts ?? 0) > 100 || (eAttempts ?? 0) > 100} - - - - - {/if} - -
1:After {array[0]} second{array[0] === 1 ? '' : 's'} + {#if (random_factor ?? 0) > 0}(+/- {((array[0] ?? 0) * (random_factor ?? 0)) / + 100} + seconds){/if}
1:After {array[0]} second{array[0] === 1 ? '' : 's'} - {#if (random_factor ?? 0) > 0}(+/- {((array[0] ?? 0) * (random_factor ?? 0)) / + {index}: + {delay} second{delay === 1 ? '' : 's'} + {#if (random_factor ?? 0) > 0}(+/- {((delay ?? 0) * (random_factor ?? 0)) / 100} - seconds){/if}
{index}: - {delay} second{delay === 1 ? '' : 's'} - {#if (random_factor ?? 0) > 0}(+/- {((delay ?? 0) * (random_factor ?? 0)) / - 100} - seconds){/if} - after attempt #{index - 1} - {#if i > cArray.length - 2} - - ({multiplier} * {eSeconds}{index}) - - {/if} -
......
- {:else} -
No retries
- {/if} -
+ {/each} + {#if (cAttempts ?? 0) > 100 || (eAttempts ?? 0) > 100} + + ... + ... + + {/if} + + + {/if} +
{/if}
+ {/if}