UNPKG

contextual-agent-sdk

Version:

SDK for building AI agents with seamless voice-text context switching

372 lines (323 loc) 11.6 kB
// Unit tests for Context Provider Integration // Run with: npm test const { ContextualAgent } = require('../../dist/index.js'); const { KnowledgeBaseProvider } = require('../../dist/providers/KnowledgeBaseProvider.js'); const { DatabaseContextProvider } = require('../../dist/providers/DatabaseContextProvider.js'); describe('Context Provider Integration', () => { let agent; let mockKnowledgeProvider; let mockDatabaseProvider; beforeEach(() => { // Create mock knowledge base provider mockKnowledgeProvider = new KnowledgeBaseProvider({ id: 'test-knowledge', name: 'Test Knowledge Base', enabled: true, priority: 90, options: { sources: [ { type: 'custom', path: 'mock://test-docs' } ], customSearch: async (query) => { // Mock search results based on query if (query.toLowerCase().includes('product')) { return [{ title: 'Product Information', content: 'We sell widgets in blue, red, and green colors. Prices range from $10-50.', source: 'product-catalog', weight: 1.0, tags: ['products', 'catalog'] }]; } if (query.toLowerCase().includes('order')) { return [{ title: 'Order Status', content: 'Orders typically ship within 2-3 business days. You can track orders online.', source: 'order-help', weight: 0.9, tags: ['orders', 'shipping'] }]; } return []; } } }); // Create mock database provider mockDatabaseProvider = new DatabaseContextProvider({ id: 'test-database', name: 'Test Database', enabled: true, priority: 80, config: { type: 'mock', queries: { customer_info: 'SELECT * FROM customers WHERE phone = ?', order_status: 'SELECT * FROM orders WHERE customer_id = ?' } }, executeQuery: async (query, params) => { // Mock database responses if (query.includes('customers')) { return [{ customer_id: 123, name: 'John Doe', phone: '+15551234567', email: 'john@example.com', membership: 'premium' }]; } if (query.includes('orders')) { return [{ order_id: 'ORD-12345', status: 'shipped', tracking: '1Z999AA1234567890', total: 29.99 }]; } return []; } }); // Initialize agent with context providers agent = new ContextualAgent({ name: 'Test Agent with Context', mode: 'conversation', systemPrompt: 'You are a helpful customer service agent.', capabilities: { voiceEnabled: true, textEnabled: true, contextBridging: true, memoryRetention: true, emotionRecognition: false, taskExecution: false }, contextSettings: { maxHistoryLength: 10, contextWindowSize: 4000, relevanceThreshold: 0.7, memoryRetentionDays: 7, modalitySwitchSensitivity: 0.8 }, contextProviders: [mockKnowledgeProvider, mockDatabaseProvider] }); }); afterEach(async () => { if (agent) { await agent.shutdown(); } }); describe('Context Provider Configuration', () => { it('should initialize with context providers', () => { expect(agent).toBeDefined(); // The agent should have initialized the ContextManager // (We can't directly test private properties, but we can test behavior) }); it('should work without context providers', () => { const simpleAgent = new ContextualAgent({ name: 'Simple Agent', mode: 'conversation', systemPrompt: 'You are a simple agent.', capabilities: { voiceEnabled: true, textEnabled: true, contextBridging: true, memoryRetention: true, emotionRecognition: false, taskExecution: false }, contextSettings: { maxHistoryLength: 10, contextWindowSize: 4000, relevanceThreshold: 0.7, memoryRetentionDays: 7, modalitySwitchSensitivity: 0.8 } // No contextProviders }); expect(simpleAgent).toBeDefined(); }); }); describe('Knowledge Base Integration', () => { it('should retrieve product information from knowledge base', async () => { const sessionId = 'test-kb-session-' + Date.now(); const response = await agent.processMessage( 'What products do you sell?', 'text', sessionId ); expect(response.success).toBe(true); expect(response.data.message.content).toBeDefined(); // Should contain product information from the external knowledge base const content = response.data.message.content.toLowerCase(); expect( content.includes('widget') || content.includes('blue') || content.includes('red') || content.includes('green') ).toBe(true); }); it('should retrieve order information from knowledge base', async () => { const sessionId = 'test-order-session-' + Date.now(); const response = await agent.processMessage( 'How long does shipping take for orders?', 'text', sessionId ); expect(response.success).toBe(true); expect(response.data.message.content).toBeDefined(); // Should contain shipping information from the knowledge base const content = response.data.message.content.toLowerCase(); expect( content.includes('ship') || content.includes('2-3') || content.includes('business days') ).toBe(true); }); }); describe('Database Integration', () => { it('should handle customer queries with database context', async () => { const sessionId = 'test-db-session-' + Date.now(); const response = await agent.processMessage( 'I need help with my account information', 'text', sessionId, '+15551234567' // User ID that matches mock database ); expect(response.success).toBe(true); expect(response.data.message.content).toBeDefined(); // The response should be contextually aware // (We can't easily test the exact content without knowing the LLM response, // but we can verify the process completed successfully) }); }); describe('Context Bridging with External Knowledge', () => { it('should work with voice to text modality switching', async () => { const sessionId = 'test-bridge-session-' + Date.now(); // Start with voice input const voiceResponse = await agent.processMessage( 'Tell me about your products', 'voice', sessionId ); expect(voiceResponse.success).toBe(true); expect(voiceResponse.metadata.modalityUsed).toBe('voice'); // Switch to text - should maintain context including external knowledge const textResponse = await agent.processMessage( 'What colors are available?', 'text', sessionId ); expect(textResponse.success).toBe(true); expect(textResponse.metadata.modalityUsed).toBe('text'); expect(textResponse.metadata.contextBridgeTriggered).toBe(true); }); it('should combine conversation history with external knowledge', async () => { const sessionId = 'test-combined-session-' + Date.now(); // First message establishes conversation context await agent.processMessage( 'I am looking for a gift', 'text', sessionId ); // Second message should use both conversation history AND external knowledge const response = await agent.processMessage( 'What products would you recommend?', 'text', sessionId ); expect(response.success).toBe(true); expect(response.data.sessionState.totalMessages).toBe(2); // Should have conversation context plus external product knowledge const content = response.data.message.content.toLowerCase(); expect(content.length).toBeGreaterThan(20); // Should be substantial response }); }); describe('Event System with Context Providers', () => { it('should emit external_context_retrieved event', (done) => { const sessionId = 'test-event-session-' + Date.now(); agent.on('external_context_retrieved', (event) => { expect(event.sessionId).toBe(sessionId); expect(event.data.hasExternalKnowledge).toBe(true); expect(event.data.contextSources).toBeGreaterThan(0); done(); }); agent.processMessage( 'What products do you have?', 'text', sessionId ); }); it('should emit context_bridged and external_context_retrieved together', (done) => { const sessionId = 'test-multi-event-session-' + Date.now(); let eventsReceived = []; agent.on('context_bridged', (event) => { eventsReceived.push('context_bridged'); checkCompletion(); }); agent.on('external_context_retrieved', (event) => { eventsReceived.push('external_context_retrieved'); checkCompletion(); }); function checkCompletion() { if (eventsReceived.length === 2) { expect(eventsReceived).toContain('context_bridged'); expect(eventsReceived).toContain('external_context_retrieved'); done(); } } // Start with voice, then switch to text to trigger context bridging agent.processMessage('Hello', 'voice', sessionId) .then(() => { return agent.processMessage('What products do you sell?', 'text', sessionId); }); }); }); describe('Error Handling', () => { it('should continue working when context provider fails', async () => { // Create a provider that will fail const failingProvider = new KnowledgeBaseProvider({ id: 'failing-provider', name: 'Failing Provider', enabled: true, priority: 100, options: { customSearch: async (query) => { throw new Error('Provider failure'); } } }); const agentWithFailingProvider = new ContextualAgent({ name: 'Agent with Failing Provider', mode: 'conversation', systemPrompt: 'You are a resilient agent.', capabilities: { voiceEnabled: true, textEnabled: true, contextBridging: true, memoryRetention: true, emotionRecognition: false, taskExecution: false }, contextSettings: { maxHistoryLength: 10, contextWindowSize: 4000, relevanceThreshold: 0.7, memoryRetentionDays: 7, modalitySwitchSensitivity: 0.8 }, contextProviders: [failingProvider] }); const sessionId = 'test-error-session-' + Date.now(); const response = await agentWithFailingProvider.processMessage( 'Hello there', 'text', sessionId ); expect(response.success).toBe(true); // Should still work even when context provider fails await agentWithFailingProvider.shutdown(); }); }); });