Files
qwen-code/packages/core/src/subagents/subagent-statistics.test.ts
2025-09-09 20:53:53 +08:00

310 lines
9.8 KiB
TypeScript

/**
* @license
* Copyright 2025 Qwen
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { SubagentStatistics } from './subagent-statistics.js';
describe('SubagentStatistics', () => {
let stats: SubagentStatistics;
const baseTime = 1000000000000; // Fixed timestamp for consistent testing
beforeEach(() => {
stats = new SubagentStatistics();
});
describe('basic statistics tracking', () => {
it('should track execution time', () => {
stats.start(baseTime);
const summary = stats.getSummary(baseTime + 5000);
expect(summary.totalDurationMs).toBe(5000);
});
it('should track rounds', () => {
stats.setRounds(3);
const summary = stats.getSummary();
expect(summary.rounds).toBe(3);
});
it('should track tool calls', () => {
stats.recordToolCall('file_read', true, 100);
stats.recordToolCall('web_search', false, 200, 'Network timeout');
const summary = stats.getSummary();
expect(summary.totalToolCalls).toBe(2);
expect(summary.successfulToolCalls).toBe(1);
expect(summary.failedToolCalls).toBe(1);
expect(summary.successRate).toBe(50);
});
it('should track tokens', () => {
stats.recordTokens(1000, 500);
stats.recordTokens(200, 100);
const summary = stats.getSummary();
expect(summary.inputTokens).toBe(1200);
expect(summary.outputTokens).toBe(600);
expect(summary.totalTokens).toBe(1800);
});
});
describe('tool usage statistics', () => {
it('should track individual tool usage', () => {
stats.recordToolCall('file_read', true, 100);
stats.recordToolCall('file_read', false, 150, 'Permission denied');
stats.recordToolCall('web_search', true, 300);
const summary = stats.getSummary();
const fileReadTool = summary.toolUsage.find(
(t) => t.name === 'file_read',
);
const webSearchTool = summary.toolUsage.find(
(t) => t.name === 'web_search',
);
expect(fileReadTool).toEqual({
name: 'file_read',
count: 2,
success: 1,
failure: 1,
lastError: 'Permission denied',
totalDurationMs: 250,
averageDurationMs: 125,
});
expect(webSearchTool).toEqual({
name: 'web_search',
count: 1,
success: 1,
failure: 0,
lastError: undefined,
totalDurationMs: 300,
averageDurationMs: 300,
});
});
});
describe('formatCompact', () => {
it('should format basic execution summary', () => {
stats.start(baseTime);
stats.setRounds(2);
stats.recordToolCall('file_read', true, 100);
stats.recordTokens(1000, 500);
const result = stats.formatCompact('Test task', baseTime + 5000);
expect(result).toContain('📋 Task Completed: Test task');
expect(result).toContain('🔧 Tool Usage: 1 calls, 100.0% success');
expect(result).toContain('⏱️ Duration: 5.0s | 🔁 Rounds: 2');
expect(result).toContain('🔢 Tokens: 1,500 (in 1000, out 500)');
});
it('should handle zero tool calls', () => {
stats.start(baseTime);
const result = stats.formatCompact('Empty task', baseTime + 1000);
expect(result).toContain('🔧 Tool Usage: 0 calls');
expect(result).not.toContain('% success');
});
it('should show zero tokens when no tokens recorded', () => {
stats.start(baseTime);
stats.recordToolCall('test', true, 100);
const result = stats.formatCompact('No tokens task', baseTime + 1000);
expect(result).toContain('🔢 Tokens: 0');
});
});
describe('formatDetailed', () => {
beforeEach(() => {
stats.start(baseTime);
stats.setRounds(3);
stats.recordToolCall('file_read', true, 100);
stats.recordToolCall('file_read', true, 150);
stats.recordToolCall('web_search', false, 2000, 'Network timeout');
stats.recordTokens(2000, 1000);
});
it('should include quality assessment', () => {
const result = stats.formatDetailed('Complex task', baseTime + 30000);
expect(result).toContain(
'✅ Quality: Poor execution (66.7% tool success)',
);
});
it('should include speed assessment', () => {
const result = stats.formatDetailed('Fast task', baseTime + 5000);
expect(result).toContain('🚀 Speed: Fast completion - under 10 seconds');
});
it('should show top tools', () => {
const result = stats.formatDetailed('Tool-heavy task', baseTime + 15000);
expect(result).toContain('Top tools:');
expect(result).toContain('- file_read: 2 calls (2 ok, 0 fail');
expect(result).toContain('- web_search: 1 calls (0 ok, 1 fail');
expect(result).toContain('last error: Network timeout');
});
it('should include performance insights', () => {
const result = stats.formatDetailed('Slow task', baseTime + 120000);
expect(result).toContain('💡 Performance Insights:');
expect(result).toContain(
'Long execution time - consider breaking down complex tasks',
);
});
});
describe('quality categories', () => {
it('should categorize excellent execution', () => {
stats.recordToolCall('test', true, 100);
stats.recordToolCall('test', true, 100);
const result = stats.formatDetailed('Perfect task');
expect(result).toContain('Excellent execution (100.0% tool success)');
});
it('should categorize good execution', () => {
// Need 85% success rate for "Good execution" - 17 success, 3 failures = 85%
for (let i = 0; i < 17; i++) {
stats.recordToolCall('test', true, 100);
}
for (let i = 0; i < 3; i++) {
stats.recordToolCall('test', false, 100);
}
const result = stats.formatDetailed('Good task');
expect(result).toContain('Good execution (85.0% tool success)');
});
it('should categorize poor execution', () => {
stats.recordToolCall('test', false, 100);
stats.recordToolCall('test', false, 100);
const result = stats.formatDetailed('Poor task');
expect(result).toContain('Poor execution (0.0% tool success)');
});
});
describe('speed categories', () => {
it('should categorize fast completion', () => {
stats.start(baseTime);
const result = stats.formatDetailed('Fast task', baseTime + 5000);
expect(result).toContain('Fast completion - under 10 seconds');
});
it('should categorize good speed', () => {
stats.start(baseTime);
const result = stats.formatDetailed('Medium task', baseTime + 30000);
expect(result).toContain('Good speed - under a minute');
});
it('should categorize moderate duration', () => {
stats.start(baseTime);
const result = stats.formatDetailed('Slow task', baseTime + 120000);
expect(result).toContain('Moderate duration - a few minutes');
});
it('should categorize long execution', () => {
stats.start(baseTime);
const result = stats.formatDetailed('Very slow task', baseTime + 600000);
expect(result).toContain('Long execution - consider breaking down tasks');
});
});
describe('performance tips', () => {
it('should suggest reviewing low success rate', () => {
stats.recordToolCall('test', false, 100);
stats.recordToolCall('test', false, 100);
stats.recordToolCall('test', true, 100);
const result = stats.formatDetailed('Failing task');
expect(result).toContain(
'Low tool success rate - review inputs and error messages',
);
});
it('should suggest breaking down long tasks', () => {
stats.start(baseTime);
const result = stats.formatDetailed('Long task', baseTime + 120000);
expect(result).toContain(
'Long execution time - consider breaking down complex tasks',
);
});
it('should suggest optimizing high token usage', () => {
stats.recordTokens(80000, 30000);
const result = stats.formatDetailed('Token-heavy task');
expect(result).toContain(
'High token usage - consider optimizing prompts or narrowing scope',
);
});
it('should identify high token usage per call', () => {
stats.recordToolCall('test', true, 100);
stats.recordTokens(6000, 0);
const result = stats.formatDetailed('Verbose task');
expect(result).toContain(
'High token usage per tool call (~6000 tokens/call)',
);
});
it('should identify network failures', () => {
stats.recordToolCall('web_search', false, 100, 'Network timeout');
const result = stats.formatDetailed('Network task');
expect(result).toContain(
'Network operations had failures - consider increasing timeout or checking connectivity',
);
});
it('should identify slow tools', () => {
stats.recordToolCall('slow_tool', true, 15000);
const result = stats.formatDetailed('Slow tool task');
expect(result).toContain(
'Consider optimizing slow_tool operations (avg 15.0s)',
);
});
});
describe('duration formatting', () => {
it('should format milliseconds', () => {
stats.start(baseTime);
const result = stats.formatCompact('Quick task', baseTime + 500);
expect(result).toContain('500ms');
});
it('should format seconds', () => {
stats.start(baseTime);
const result = stats.formatCompact('Second task', baseTime + 2500);
expect(result).toContain('2.5s');
});
it('should format minutes and seconds', () => {
stats.start(baseTime);
const result = stats.formatCompact('Minute task', baseTime + 125000);
expect(result).toContain('2m 5s');
});
it('should format hours and minutes', () => {
stats.start(baseTime);
const result = stats.formatCompact('Hour task', baseTime + 4500000);
expect(result).toContain('1h 15m');
});
});
});