Improve task resilience and LLM retry visibility
Persist and publish recoverable provider retries so temporary 503 and throttling events remain visible in live and historical task streams. Aggregate repeated retries into an expandable action card, preserve terminal pause recovery, harden Windows worker isolation and output forwarding, and retain reliable Android hierarchy fallbacks with regression coverage.
This commit is contained in:
1 parent
c1a4b49a21
commit
0f5fb1d32c
17 files changed
+946
-97
No files matched your search
@@ -162,8 +162,9 @@ export function consolidateLogsToBlocks(rawLogs: any[]): StepBlock[] {
|
||||
} else if (log.type === 'trace_recorded') {
|
||||
const isAction = log.data.type === 'action';
|
||||
const isTool = log.data.type === 'tool' || isAction;
|
||||
const isFailedLLM = log.data.type === 'llm_call' && log.data.status === 'failed';
|
||||
if (!isTool && !isFailedLLM) return;
|
||||
const isVisibleLLMEvent = log.data.type === 'llm_call'
|
||||
&& (log.data.status === 'failed' || log.data.status === 'retrying');
|
||||
if (!isTool && !isVisibleLLMEvent) return;
|
||||
|
||||
let stepId = log.data.step_id;
|
||||
let existingIndex = -1;
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { getUniqueGenericTools } from './tool-formatter.util';
|
||||
|
||||
describe('getUniqueGenericTools retry aggregation', () => {
|
||||
it('groups recoverable LLM retries while preserving every attempt', () => {
|
||||
const tools = [
|
||||
{
|
||||
trace_id: 'retry-1',
|
||||
type: 'llm_call',
|
||||
name: 'llm_retry',
|
||||
status: 'retrying',
|
||||
timestamp: 100,
|
||||
payload: { error: '503 first', delay: 1.18, provider: 'google' }
|
||||
},
|
||||
{
|
||||
trace_id: 'retry-2',
|
||||
type: 'llm_call',
|
||||
name: 'llm_retry',
|
||||
status: 'retrying',
|
||||
timestamp: 101,
|
||||
payload: { error: '503 second', delay: 2.96, provider: 'google' }
|
||||
}
|
||||
];
|
||||
|
||||
const result = getUniqueGenericTools(tools);
|
||||
|
||||
expect(result.length).toBe(1);
|
||||
expect(result[0].name).toBe('llm_retry_group');
|
||||
expect(result[0].payload.retry_count).toBe(2);
|
||||
expect(result[0].payload.total_delay).toBeCloseTo(4.14);
|
||||
expect(result[0].payload.providers).toEqual(['google']);
|
||||
expect(result[0].payload.retries.map((retry: any) => retry.error)).toEqual([
|
||||
'503 first',
|
||||
'503 second'
|
||||
]);
|
||||
});
|
||||
|
||||
it('keeps a terminal LLM failure separate from its preceding retries', () => {
|
||||
const result = getUniqueGenericTools([
|
||||
{
|
||||
trace_id: 'retry-1',
|
||||
type: 'llm_call',
|
||||
name: 'llm_retry',
|
||||
status: 'retrying',
|
||||
payload: { error: '503', delay: 1 }
|
||||
},
|
||||
{
|
||||
trace_id: 'failure-1',
|
||||
type: 'llm_call',
|
||||
name: 'llm_pause',
|
||||
status: 'failed',
|
||||
payload: { error: '503 exhausted', pause: true }
|
||||
}
|
||||
]);
|
||||
|
||||
expect(result.map(tool => tool.name)).toEqual(['llm_retry_group', 'llm_pause']);
|
||||
});
|
||||
});
|
||||
@@ -185,6 +185,42 @@ export function getToolAgentName(tool: any): string | null {
|
||||
export function getUniqueGenericTools(tools: any[] | undefined): any[] {
|
||||
if (!tools) return [];
|
||||
|
||||
const retryTools = tools.filter(tool =>
|
||||
tool?.type === 'llm_call'
|
||||
&& tool?.status === 'retrying'
|
||||
&& (tool?.name === 'llm_retry' || tool?.name === 'llm_retry_group')
|
||||
);
|
||||
const firstRetry = retryTools[0];
|
||||
const latestRetry = retryTools[retryTools.length - 1];
|
||||
const retryAggregate = firstRetry ? {
|
||||
...firstRetry,
|
||||
trace_id: `llm-retry-group-${firstRetry.trace_id || firstRetry.timestamp || 'unknown'}`,
|
||||
name: 'llm_retry_group',
|
||||
payload: {
|
||||
...(latestRetry?.payload || {}),
|
||||
retry_count: retryTools.length,
|
||||
total_delay: retryTools.reduce(
|
||||
(total, retry) => total + (Number(retry?.payload?.delay) || 0),
|
||||
0
|
||||
),
|
||||
providers: Array.from(new Set(
|
||||
retryTools
|
||||
.map(retry => retry?.payload?.provider)
|
||||
.filter((provider): provider is string => typeof provider === 'string' && !!provider)
|
||||
)),
|
||||
retries: retryTools.map(retry => ({
|
||||
trace_id: retry.trace_id,
|
||||
timestamp: retry.timestamp,
|
||||
error: retry?.payload?.error || retry.error,
|
||||
delay: Number(retry?.payload?.delay) || 0,
|
||||
provider: retry?.payload?.provider,
|
||||
source: retry?.payload?.source,
|
||||
attempt: retry?.payload?.attempt,
|
||||
max_retries: retry?.payload?.max_retries
|
||||
}))
|
||||
}
|
||||
} : null;
|
||||
|
||||
const toolMap = new Map<string, any>();
|
||||
for (const t of tools) {
|
||||
if (t && t.trace_id) {
|
||||
@@ -196,11 +232,20 @@ export function getUniqueGenericTools(tools: any[] | undefined): any[] {
|
||||
|
||||
const result: any[] = [];
|
||||
const seenTraces = new Set<string>();
|
||||
let retryAggregateAdded = false;
|
||||
|
||||
for (const tool of tools) {
|
||||
if (!tool || !tool.name) continue;
|
||||
if (tool.type === 'agent') continue;
|
||||
|
||||
if (tool.type === 'llm_call' && tool.status === 'retrying') {
|
||||
if (!retryAggregateAdded && retryAggregate) {
|
||||
result.push(retryAggregate);
|
||||
retryAggregateAdded = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tool.type === 'llm_call' && tool.status === 'failed') {
|
||||
result.push(tool);
|
||||
continue;
|
||||
|
||||
Reference in new issue
Block a user