fix(audit): resolve lint global, compaction metrics, and nudge id
This commit is contained in:
@@ -201,9 +201,8 @@ describe('NativeAgent tool loop', () => {
|
||||
callCount++;
|
||||
// After nudge message, model should respond with text
|
||||
const lastMsg = req.messages[req.messages.length - 1];
|
||||
const hasNudge = typeof lastMsg?.content !== 'string' &&
|
||||
Array.isArray(lastMsg?.content) &&
|
||||
lastMsg.content.some((b: any) => b.content?.includes('do NOT call it again'));
|
||||
const hasNudge = typeof lastMsg?.content === 'string'
|
||||
&& lastMsg.content.includes('do NOT call it again');
|
||||
if (hasNudge) {
|
||||
return {
|
||||
content: 'Here is what I found from my searches.',
|
||||
|
||||
@@ -339,18 +339,17 @@ export class NativeAgent {
|
||||
// If the same tool has been called too many times, append a nudge
|
||||
// telling the model to use what it has. This combats local models
|
||||
// that endlessly retry searches with slight query variations.
|
||||
let nudgeMessage: string | null = null;
|
||||
if (sameToolStreak >= maxSameToolStreak && !nudged) {
|
||||
nudged = true;
|
||||
toolResultBlocks.push({
|
||||
type: 'tool_result',
|
||||
tool_use_id: '__system',
|
||||
content: `You have called this tool ${sameToolStreak} times in a row. You have enough information — do NOT call it again. Summarize what you have found and respond to the user now.`,
|
||||
is_error: false,
|
||||
});
|
||||
nudgeMessage = `You have called this tool ${sameToolStreak} times in a row. You have enough information — do NOT call it again. Summarize what you have found and respond to the user now.`;
|
||||
}
|
||||
|
||||
// Add tool results as a user message
|
||||
loopMessages.push({ role: 'user', content: toolResultBlocks });
|
||||
if (nudgeMessage) {
|
||||
loopMessages.push({ role: 'user', content: nudgeMessage });
|
||||
}
|
||||
|
||||
// Break out if the model is stuck in a repeated tool call loop
|
||||
if (consecutiveRepeats >= maxConsecutiveRepeats) {
|
||||
|
||||
@@ -8,6 +8,8 @@ import { MemoryStore } from '../../memory/store.js';
|
||||
import { mkdtempSync, rmSync } from 'fs';
|
||||
import { tmpdir } from 'os';
|
||||
import { join } from 'path';
|
||||
import { auditLogger, initAuditLogger } from '../../audit/index.js';
|
||||
import type { AuditLogger } from '../../audit/index.js';
|
||||
|
||||
describe('AgentOrchestrator', () => {
|
||||
let mockDefaultClient: ModelClient;
|
||||
@@ -450,6 +452,82 @@ describe('AgentOrchestrator', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('compact()', () => {
|
||||
it('emits compaction audit event with message counts (not token counts)', async () => {
|
||||
const compactClient: ModelClient = {
|
||||
chat: vi.fn().mockResolvedValue({
|
||||
content: 'summary',
|
||||
stopReason: 'end_turn',
|
||||
usage: { inputTokens: 8, outputTokens: 4 },
|
||||
}),
|
||||
};
|
||||
const compactRouter = new ModelRouter({
|
||||
default: compactClient,
|
||||
fast: compactClient,
|
||||
complex: compactClient,
|
||||
fallbackChain: [],
|
||||
});
|
||||
|
||||
const history: any[] = [
|
||||
{ role: 'user', content: 'u1' },
|
||||
{ role: 'assistant', content: 'a1' },
|
||||
{ role: 'user', content: 'u2' },
|
||||
{ role: 'assistant', content: 'a2' },
|
||||
{ role: 'user', content: 'u3' },
|
||||
{ role: 'assistant', content: 'a3' },
|
||||
];
|
||||
const session = {
|
||||
id: 'session-compact-audit',
|
||||
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 sessionCompact = vi.fn();
|
||||
const previousAuditLogger = auditLogger;
|
||||
initAuditLogger({ sessionCompact } as unknown as AuditLogger);
|
||||
try {
|
||||
const orchestrator = new AgentOrchestrator({
|
||||
modelRouter: compactRouter,
|
||||
systemPrompt: 'You are helpful.',
|
||||
session,
|
||||
primaryTier: 'default',
|
||||
delegation: {
|
||||
compaction: 'fast',
|
||||
memory_extraction: 'default',
|
||||
classification: 'complex',
|
||||
tool_summarisation: 'default',
|
||||
complex_reasoning: 'complex',
|
||||
},
|
||||
maxDelegationDepth: 10,
|
||||
compaction: {
|
||||
thresholdPct: 80,
|
||||
keepTurns: 1,
|
||||
summaryMaxTokens: 128,
|
||||
importanceThreshold: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await orchestrator.compact();
|
||||
expect(result).not.toBeNull();
|
||||
expect(sessionCompact).toHaveBeenCalledWith(expect.objectContaining({
|
||||
session_id: 'session-compact-audit',
|
||||
messages_before: 6,
|
||||
messages_after: 3,
|
||||
}));
|
||||
} finally {
|
||||
initAuditLogger(previousAuditLogger as unknown as AuditLogger);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('reset()', () => {
|
||||
it('clears primary agent conversation history', async () => {
|
||||
const orchestrator = new AgentOrchestrator({
|
||||
|
||||
@@ -310,8 +310,8 @@ export class AgentOrchestrator {
|
||||
if (this._session) {
|
||||
auditLogger?.sessionCompact({
|
||||
session_id: this._session.id,
|
||||
messages_before: result.tokensBefore,
|
||||
messages_after: result.tokensAfter,
|
||||
messages_before: messages.length,
|
||||
messages_after: result.messages.length,
|
||||
tokens_before: result.tokensBefore,
|
||||
tokens_after: result.tokensAfter,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user