mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 00:02:19 +00:00
fix: soft error on AI agent max iterations + rename retries tab to error handling (#8366)
* fix: soft error when AI agent reaches max iterations instead of hard error Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: set output to error message when AI agent reaches max iterations Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add error field to AI agent result for max iterations soft error Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: restore default max iterations to 10 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: include partial result in max iterations error message Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: rename retries tab to error handling and reorganize sections Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -1111,8 +1111,8 @@
|
||||
<Tabs bind:selected={advancedSelected} wrapperClass="shrink-0">
|
||||
<Tab
|
||||
value="retries"
|
||||
active={flowModule.retry !== undefined}
|
||||
label="Retries"
|
||||
active={flowModule.retry !== undefined || flowModule.continue_on_error}
|
||||
label="Error handling"
|
||||
/>
|
||||
{#if !selectedId.includes('failure')}
|
||||
<Tab value="runtime" label="Runtime" />
|
||||
@@ -1152,6 +1152,22 @@
|
||||
{/if}
|
||||
<div class="flex-1 overflow-auto p-4">
|
||||
{#if advancedSelected === 'retries'}
|
||||
<Section label="Continue on error">
|
||||
{#snippet header()}
|
||||
<Tooltip>
|
||||
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.
|
||||
</Tooltip>
|
||||
{/snippet}
|
||||
<Toggle
|
||||
size="xs"
|
||||
bind:checked={flowModule.continue_on_error}
|
||||
options={{
|
||||
left: 'Stop on error and propagate error up',
|
||||
right: "Continue on error with error as step's return"
|
||||
}}
|
||||
/>
|
||||
</Section>
|
||||
<div class="mt-4"></div>
|
||||
<Section label="Retries">
|
||||
{#snippet header()}
|
||||
<Tooltip
|
||||
@@ -1161,19 +1177,6 @@
|
||||
maximum number of attempts as defined below.
|
||||
</Tooltip>
|
||||
{/snippet}
|
||||
<Label label="After all retries attempts have been exhausted:">
|
||||
<Toggle
|
||||
size="xs"
|
||||
bind:checked={flowModule.continue_on_error}
|
||||
options={{
|
||||
left: 'Stop on error and propagate error up',
|
||||
right: "Continue on error with error as step's return",
|
||||
rightTooltip:
|
||||
'When enabled, the flow will continue to the next step after going through all the retries (if any) even if this step fails. This enables to process the error in a branch one for instance.'
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
<div class="my-8"></div>
|
||||
<FlowRetries bind:flowModuleRetry={flowModule.retry} bind:flowModule />
|
||||
</Section>
|
||||
{:else if advancedSelected === 'runtime' && advancedRuntimeSelected === 'concurrency'}
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
const u32Max = 4294967295
|
||||
</script>
|
||||
|
||||
<div class="h-full flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-4">
|
||||
<ToggleButtonGroup
|
||||
bind:selected={delayType}
|
||||
class={`${disabled ? 'disabled' : ''}`}
|
||||
@@ -216,6 +216,7 @@
|
||||
</Section>
|
||||
{/if}
|
||||
|
||||
{#if delayType === 'constant' || delayType === 'exponential'}
|
||||
<div class="flex h-[calc(100%-22px)]">
|
||||
<div class="w-1/2 h-full overflow-auto pr-2">
|
||||
{#if delayType === 'constant'}
|
||||
@@ -296,66 +297,65 @@
|
||||
</div>
|
||||
<div class="w-1/2 h-full overflow-auto pl-2">
|
||||
{#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]}
|
||||
<div class="bg-surface-secondary border rounded px-4 py-2">
|
||||
<div class="text-xs font-medium mb-2">Retry attempts</div>
|
||||
{#if array.length > 0}
|
||||
<table class="text-xs">
|
||||
<thead>
|
||||
{@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]}
|
||||
<div class="bg-surface-secondary border rounded px-4 py-2">
|
||||
<div class="text-xs font-medium mb-2">Retry attempts</div>
|
||||
{#if array.length > 0}
|
||||
<table class="text-xs">
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="font-semibold pr-1 pb-1">1:</td>
|
||||
<td class="pb-1"
|
||||
>After {array[0]} second{array[0] === 1 ? '' : 's'}
|
||||
{#if (random_factor ?? 0) > 0}(+/- {((array[0] ?? 0) * (random_factor ?? 0)) /
|
||||
100}
|
||||
seconds){/if}</td
|
||||
>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each array.slice(1, 100) as delay, i}
|
||||
{@const index = i + 2}
|
||||
<tr>
|
||||
<td class="font-semibold pr-1 pb-1">1:</td>
|
||||
<td class="pb-1"
|
||||
>After {array[0]} second{array[0] === 1 ? '' : 's'}
|
||||
{#if (random_factor ?? 0) > 0}(+/- {((array[0] ?? 0) * (random_factor ?? 0)) /
|
||||
<td class="font-semibold pr-1 align-top">{index}:</td>
|
||||
<td class="pb-1 whitespace-nowrap">
|
||||
{delay} second{delay === 1 ? '' : 's'}
|
||||
{#if (random_factor ?? 0) > 0}(+/- {((delay ?? 0) * (random_factor ?? 0)) /
|
||||
100}
|
||||
seconds){/if}</td
|
||||
>
|
||||
seconds){/if}
|
||||
after attempt #{index - 1}
|
||||
{#if i > cArray.length - 2}
|
||||
<span class="text-gray-400 pl-2">
|
||||
({multiplier} * {eSeconds}<sup>{index}</sup>)
|
||||
</span>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each array.slice(1, 100) as delay, i}
|
||||
{@const index = i + 2}
|
||||
<tr>
|
||||
<td class="font-semibold pr-1 align-top">{index}:</td>
|
||||
<td class="pb-1 whitespace-nowrap">
|
||||
{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}
|
||||
<span class="text-gray-400 pl-2">
|
||||
({multiplier} * {eSeconds}<sup>{index}</sup>)
|
||||
</span>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{#if (cAttempts ?? 0) > 100 || (eAttempts ?? 0) > 100}
|
||||
<tr>
|
||||
<td class="font-semibold pr-1 align-top">...</td>
|
||||
<td class="pb-1">...</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
{:else}
|
||||
<div class="text-xs">No retries</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{#if (cAttempts ?? 0) > 100 || (eAttempts ?? 0) > 100}
|
||||
<tr>
|
||||
<td class="font-semibold pr-1 align-top">...</td>
|
||||
<td class="pb-1">...</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user