import { withLLMToolCall, type MonitoringLogger } from '../LLMToolHelpers.js'; import { monitoringService } from '../MonitoringService.js'; // Mock the monitoring service jest.mock('../MonitoringService.js', () => { const actualWithToolMonitoring = async ( toolName: string, operation: string, fn: () => Promise, options?: { logger?: any } ): Promise => { return fn(); }; return { monitoringService: { incrementCounter: jest.fn().mockResolvedValue(undefined), recordLatency: jest.fn().mockResolvedValue(undefined), recordMetric: jest.fn().mockResolvedValue(undefined) }, withToolMonitoring: actualWithToolMonitoring }; }); describe('LLMToolHelpers', () => { const mockLogger: MonitoringLogger = { info: jest.fn(), warn: jest.fn(), error: jest.fn(), debug: jest.fn() }; beforeEach(() => { jest.clearAllMocks(); }); describe('withLLMToolCall', () => { it('should execute the internal call and return result', async () => { const mockResult = { content: 'test response', tokens: 100 }; const internalCall = jest.fn().mockResolvedValue(mockResult); const result = await withLLMToolCall('test-tool', 'test-model', internalCall, mockLogger); expect(result).toEqual(mockResult); expect(internalCall).toHaveBeenCalledTimes(1); }); it('should record token usage when tokens are present', async () => { const mockResult = { content: 'test response', tokens: 150 }; const internalCall = jest.fn().mockResolvedValue(mockResult); await withLLMToolCall('anthropic', 'claude-3', internalCall, mockLogger); expect(monitoringService.recordMetric).toHaveBeenCalledWith( 'token_usage', 150, { tool_name: 'anthropic', model_name: 'claude-3', operation: 'call' } ); }); it('should not record token usage when tokens are missing', async () => { const mockResult = { content: 'test response' }; const internalCall = jest.fn().mockResolvedValue(mockResult); await withLLMToolCall('gemini', 'gemini-pro', internalCall, mockLogger); expect(monitoringService.recordMetric).not.toHaveBeenCalled(); }); it('should not record token usage when tokens are undefined', async () => { const mockResult = { content: 'test response', tokens: undefined }; const internalCall = jest.fn().mockResolvedValue(mockResult); await withLLMToolCall('grok', 'grok-1', internalCall, mockLogger); expect(monitoringService.recordMetric).not.toHaveBeenCalled(); }); it('should not record token usage when tokens are 0', async () => { const mockResult = { content: 'test response', tokens: 0 }; const internalCall = jest.fn().mockResolvedValue(mockResult); await withLLMToolCall('cerebras', 'llama-3', internalCall, mockLogger); expect(monitoringService.recordMetric).not.toHaveBeenCalled(); }); it('should handle errors in internal call', async () => { const testError = new Error('LLM call failed'); const internalCall = jest.fn().mockRejectedValue(testError); await expect(withLLMToolCall('test-tool', 'test-model', internalCall, mockLogger)) .rejects.toThrow('LLM call failed'); expect(monitoringService.recordMetric).not.toHaveBeenCalled(); }); it('should work without logger parameter', async () => { const mockResult = { content: 'test response', tokens: 200 }; const internalCall = jest.fn().mockResolvedValue(mockResult); const result = await withLLMToolCall('test-tool', 'test-model', internalCall); expect(result).toEqual(mockResult); expect(monitoringService.recordMetric).toHaveBeenCalled(); }); it('should handle monitoring errors gracefully', async () => { const mockResult = { content: 'test response', tokens: 100 }; const internalCall = jest.fn().mockResolvedValue(mockResult); const monitoringError = new Error('Monitoring failed'); (monitoringService.recordMetric as jest.Mock).mockRejectedValueOnce(monitoringError); const result = await withLLMToolCall('test-tool', 'test-model', internalCall, mockLogger); expect(result).toEqual(mockResult); // Should log the error but not throw expect(mockLogger.debug).toHaveBeenCalled(); }); it('should preserve result with complex token data', async () => { const mockResult = { content: 'test response', tokens: 500, metadata: { model: 'test', version: '1.0' } }; const internalCall = jest.fn().mockResolvedValue(mockResult); const result = await withLLMToolCall('openai', 'gpt-4', internalCall, mockLogger); expect(result).toEqual(mockResult); expect((result as any).metadata).toEqual({ model: 'test', version: '1.0' }); }); it('should use correct tool and model names in metrics', async () => { const mockResult = { content: 'test', tokens: 75 }; const internalCall = jest.fn().mockResolvedValue(mockResult); await withLLMToolCall('perplexity', 'pplx-70b', internalCall, mockLogger); expect(monitoringService.recordMetric).toHaveBeenCalledWith( 'token_usage', 75, expect.objectContaining({ tool_name: 'perplexity', model_name: 'pplx-70b' }) ); }); it('should handle async internal calls correctly', async () => { const mockResult = { content: 'async response', tokens: 300 }; const internalCall = jest.fn().mockImplementation(async () => { await new Promise(resolve => setTimeout(resolve, 10)); return mockResult; }); const result = await withLLMToolCall('test-tool', 'test-model', internalCall, mockLogger); expect(result).toEqual(mockResult); expect(internalCall).toHaveBeenCalledTimes(1); }); it('should handle multiple calls independently', async () => { const call1 = jest.fn().mockResolvedValue({ content: 'response1', tokens: 100 }); const call2 = jest.fn().mockResolvedValue({ content: 'response2', tokens: 200 }); const call3 = jest.fn().mockResolvedValue({ content: 'response3' }); const result1 = await withLLMToolCall('tool1', 'model1', call1, mockLogger); const result2 = await withLLMToolCall('tool2', 'model2', call2, mockLogger); const result3 = await withLLMToolCall('tool3', 'model3', call3, mockLogger); expect(result1.tokens).toBe(100); expect(result2.tokens).toBe(200); expect(result3.tokens).toBeUndefined(); expect(monitoringService.recordMetric).toHaveBeenCalledTimes(2); }); }); });