orchestrator: recover from context overflow on fallback
This commit is contained in:
@@ -605,6 +605,94 @@ describe('AgentOrchestrator', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('process()', () => {
|
||||
it('rolls back tool-loop provider errors, hard-trims on context overflow, and retries once', async () => {
|
||||
let callCount = 0;
|
||||
const mockClient: ModelClient = {
|
||||
chat: vi.fn().mockImplementation(async () => {
|
||||
callCount++;
|
||||
if (callCount === 1) {
|
||||
return {
|
||||
content: '',
|
||||
stopReason: 'tool_use',
|
||||
usage: { inputTokens: 10, outputTokens: 5 },
|
||||
toolCalls: [{ id: 'call_1', name: 'test.echo', args: { text: 'hi' } }],
|
||||
} as ChatResponse;
|
||||
}
|
||||
if (callCount === 2) {
|
||||
// Simulate llama.cpp context overflow buried inside an aggregated router error.
|
||||
throw new Error(
|
||||
'llama-server error (400): {"error":{"type":"exceedcontextsizeerror","nprompttokens":9183,"nctx":4096}}',
|
||||
);
|
||||
}
|
||||
return {
|
||||
content: 'ok',
|
||||
stopReason: 'end_turn',
|
||||
usage: { inputTokens: 10, outputTokens: 5 },
|
||||
} as ChatResponse;
|
||||
}),
|
||||
};
|
||||
|
||||
const router = new ModelRouter({
|
||||
default: mockClient,
|
||||
fallbackChain: [],
|
||||
});
|
||||
|
||||
// Minimal Session stub that supports rollback via replaceHistory().
|
||||
const history: any[] = [];
|
||||
const session = {
|
||||
id: 'test',
|
||||
addMessage: vi.fn((m: any) => { history.push(m); }),
|
||||
getHistory: vi.fn(() => [...history]),
|
||||
clear: vi.fn(() => { history.length = 0; }),
|
||||
replaceHistory: vi.fn((msgs: any[]) => {
|
||||
history.length = 0;
|
||||
history.push(...msgs);
|
||||
}),
|
||||
getConfig: vi.fn(() => undefined),
|
||||
setConfig: vi.fn(),
|
||||
deleteConfig: vi.fn(),
|
||||
} as any;
|
||||
|
||||
const registry = new ToolRegistry();
|
||||
registry.register({
|
||||
name: 'test.echo',
|
||||
description: 'echo',
|
||||
inputSchema: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] },
|
||||
execute: async (args: any) => ({ success: true, output: String(args.text ?? '') }),
|
||||
});
|
||||
|
||||
const hooks = new HookEngine({ confirm: [], log: [], silent: [] });
|
||||
const executor = new ToolExecutor(registry, hooks);
|
||||
|
||||
const orchestrator = new AgentOrchestrator({
|
||||
modelRouter: router,
|
||||
systemPrompt: 'You are helpful.',
|
||||
session,
|
||||
toolRegistry: registry,
|
||||
toolExecutor: executor,
|
||||
primaryTier: 'default',
|
||||
delegation: {
|
||||
compaction: 'fast',
|
||||
memory_extraction: 'default',
|
||||
classification: 'complex',
|
||||
tool_summarisation: 'default',
|
||||
complex_reasoning: 'complex',
|
||||
},
|
||||
maxDelegationDepth: 3,
|
||||
});
|
||||
|
||||
const res = await orchestrator.process('hello');
|
||||
expect(res).toBe('ok');
|
||||
|
||||
// Ensure we didn't persist the low-level error string in history.
|
||||
const textHistory = history
|
||||
.map(m => (typeof m.content === 'string' ? m.content : ''))
|
||||
.join('\n');
|
||||
expect(textHistory).not.toContain('Error in tool loop');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setModelTier()', () => {
|
||||
it('sets model tier on primary agent', () => {
|
||||
const orchestrator = new AgentOrchestrator({
|
||||
|
||||
Reference in New Issue
Block a user