Files
liqiang b119135836
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
2026-08-20 13:12:50 +00:00

365 lines
13 KiB
JavaScript

const assert = require('assert');
const fs = require('fs');
const path = require('path');
// Import provider factories
const { ASRProviderFactory } = require('../utils/providers/asrProviders');
const { LLMProviderFactory } = require('../utils/providers/llmProviders');
// Import services
const SpeechToTextService = require('../utils/speechToText');
// Mock configuration for testing
const testConfig = {
// API Keys from environment variables
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY,
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
ARK_API_KEY: process.env.ARK_API_KEY,
SILICONFLOW_API_KEY: process.env.SILICONFLOW_API_KEY,
// Provider configurations
ASR_PROVIDERS: {
openai: {
apiUrl: 'https://api.openai.com/v1/audio/transcriptions',
model: 'whisper-1',
apiKey: 'OPENAI_API_KEY'
},
siliconflow: {
apiUrl: 'https://api.siliconflow.cn/v1/audio/transcriptions',
model: 'FunAudioLLM/SenseVoiceSmall',
apiKey: 'SILICONFLOW_API_KEY'
}
},
LLM_PROVIDERS: {
openai: {
apiUrl: 'https://api.openai.com/v1/chat/completions',
model: 'gpt-5.6-luna',
apiKey: 'OPENAI_API_KEY'
},
'openrouter-gpt': {
apiUrl: 'https://openrouter.ai/api/v1/chat/completions',
model: 'openai/gpt-5.6-luna',
apiKey: 'OPENROUTER_API_KEY'
},
'openrouter-gemini': {
apiUrl: 'https://openrouter.ai/api/v1/chat/completions',
model: 'google/gemini-3.5-flash',
apiKey: 'OPENROUTER_API_KEY'
},
ark: {
apiUrl: 'https://ark.cn-beijing.volces.com/api/v3/chat/completions',
model: 'doubao-seed-1-6-flash-250615',
apiKey: 'ARK_API_KEY'
}
},
// Audio configuration
AUDIO_SAMPLE_RATE: 16000,
VISION_MAX_TOKENS: 4096
};
/**
* Create a sample audio buffer for testing
* This creates a simple sine wave audio buffer
*/
function createTestAudioBuffer(durationSeconds = 2, sampleRate = 16000) {
const numSamples = durationSeconds * sampleRate;
const buffer = Buffer.alloc(numSamples * 2); // 16-bit = 2 bytes per sample
// Generate a simple sine wave at 440Hz
const frequency = 440;
for (let i = 0; i < numSamples; i++) {
const sample = Math.sin(2 * Math.PI * frequency * i / sampleRate) * 0.5;
const intSample = Math.round(sample * 32767);
buffer.writeInt16LE(intSample, i * 2);
}
return buffer;
}
/**
* Test suite for ASR providers
*/
describe('ASR Provider Tests', function() {
this.timeout(60000); // 60 second timeout for API calls
const asrProviders = ['openai', 'siliconflow'];
asrProviders.forEach(providerName => {
describe(`${providerName} ASR Provider`, function() {
let provider;
before(function() {
// Skip test if API key is not available
const apiKeyName = testConfig.ASR_PROVIDERS[providerName].apiKey;
if (!testConfig[apiKeyName]) {
this.skip();
}
try {
provider = ASRProviderFactory.createProvider(providerName, testConfig, testConfig);
} catch (error) {
console.error(`Failed to create ${providerName} provider:`, error);
this.skip();
}
});
it('should be created successfully', function() {
assert(provider, 'Provider should be created');
assert(provider.config, 'Provider should have config');
assert(provider.apiKey, 'Provider should have API key');
});
it('should transcribe test audio', async function() {
const audioBuffer = createTestAudioBuffer(2); // 2 seconds of audio
const tempDir = path.join(__dirname, '../temp');
// Ensure temp directory exists
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
const result = await provider.transcribe(audioBuffer, tempDir);
assert(result, 'Should return a result');
assert(typeof result.success === 'boolean', 'Should have success field');
assert(typeof result.text === 'string', 'Should have text field');
assert(result.provider === providerName, 'Should have correct provider name');
console.log(`${providerName} ASR Result:`, {
success: result.success,
text: result.text.substring(0, 100) + (result.text.length > 100 ? '...' : ''),
language: result.language,
provider: result.provider
});
});
});
});
describe('SpeechToTextService Integration', function() {
asrProviders.forEach(providerName => {
it(`should work with ${providerName} provider`, async function() {
// Skip test if API key is not available
const apiKeyName = testConfig.ASR_PROVIDERS[providerName].apiKey;
if (!testConfig[apiKeyName]) {
this.skip();
}
// Override config for this test
const originalConfig = require('../config');
Object.assign(originalConfig, testConfig);
originalConfig.ASR_PROVIDER = providerName;
const sttService = new SpeechToTextService();
const audioBuffer = createTestAudioBuffer(2);
const result = await sttService.transcribeAudio(audioBuffer);
assert(result, 'Should return a result');
assert(typeof result.success === 'boolean', 'Should have success field');
assert(typeof result.text === 'string', 'Should have text field');
console.log(`STT Service with ${providerName}:`, {
success: result.success,
text: result.text.substring(0, 100) + (result.text.length > 100 ? '...' : ''),
provider: result.provider
});
});
});
});
});
/**
* Test suite for LLM providers
*/
describe('LLM Provider Tests', function() {
this.timeout(60000); // 60 second timeout for API calls
const llmProviders = ['openai', 'openrouter-gpt', 'openrouter-gemini', 'ark'];
const testMessages = [
{ role: 'system', content: 'You are a helpful AI assistant.' },
{ role: 'user', content: 'Hello! Please respond with a short greeting.' }
];
llmProviders.forEach(providerName => {
describe(`${providerName} LLM Provider`, function() {
let provider;
before(function() {
// Skip test if API key is not available
const apiKeyName = testConfig.LLM_PROVIDERS[providerName].apiKey;
if (!testConfig[apiKeyName]) {
this.skip();
}
try {
provider = LLMProviderFactory.createProvider(providerName, testConfig, testConfig);
} catch (error) {
console.error(`Failed to create ${providerName} provider:`, error);
this.skip();
}
});
it('should be created successfully', function() {
assert(provider, 'Provider should be created');
assert(provider.config, 'Provider should have config');
assert(provider.apiKey, 'Provider should have API key');
});
it('should generate chat completion', async function() {
const result = await provider.createChatCompletion(testMessages, {
max_tokens: 100
});
assert(result, 'Should return a result');
assert(typeof result.success === 'boolean', 'Should have success field');
assert(result.provider === providerName.split('-')[0], 'Should have correct provider name');
if (result.success) {
assert(result.response, 'Should have response object');
console.log(`${providerName} LLM Result: Success`);
} else {
console.log(`${providerName} LLM Result:`, result.error);
}
});
it('should stream chat completion', async function() {
const result = await provider.createChatCompletion(testMessages, {
max_tokens: 50,
stream: true
});
assert(result, 'Should return a result');
if (result.success) {
assert(result.response, 'Should have response object');
assert(result.response.data, 'Should have data stream');
// Test streaming by collecting some data
let receivedData = false;
const timeout = setTimeout(() => {
if (!receivedData) {
console.log(`${providerName}: No data received within timeout`);
}
}, 10000);
result.response.data.on('data', (chunk) => {
receivedData = true;
clearTimeout(timeout);
console.log(`${providerName} LLM Streaming: Received data chunk`);
});
result.response.data.on('error', (error) => {
clearTimeout(timeout);
console.error(`${providerName} LLM Streaming Error:`, error.message);
});
} else {
console.log(`${providerName} LLM Streaming Result:`, result.error);
}
});
});
});
});
/**
* Integration tests for all provider combinations
*/
describe('Provider Integration Tests', function() {
this.timeout(120000); // 2 minute timeout for integration tests
const testCombinations = [
{ asr: 'openai', llm: 'openai', description: 'OpenAI ASR + OpenAI LLM' },
{ asr: 'openai', llm: 'openrouter-gpt', description: 'OpenAI ASR + OpenRouter GPT' },
{ asr: 'openai', llm: 'openrouter-gemini', description: 'OpenAI ASR + OpenRouter Gemini' },
{ asr: 'openai', llm: 'ark', description: 'OpenAI ASR + ARK Doubao' },
{ asr: 'siliconflow', llm: 'openai', description: 'SenseVoice ASR + OpenAI LLM' },
{ asr: 'siliconflow', llm: 'openrouter-gpt', description: 'SenseVoice ASR + OpenRouter GPT' },
{ asr: 'siliconflow', llm: 'openrouter-gemini', description: 'SenseVoice ASR + OpenRouter Gemini' },
{ asr: 'siliconflow', llm: 'ark', description: 'SenseVoice ASR + ARK Doubao' }
];
testCombinations.forEach(combination => {
it(`should work with ${combination.description}`, async function() {
// Check if API keys are available
const asrApiKey = testConfig.ASR_PROVIDERS[combination.asr].apiKey;
const llmApiKey = testConfig.LLM_PROVIDERS[combination.llm].apiKey;
if (!testConfig[asrApiKey] || !testConfig[llmApiKey]) {
this.skip();
}
try {
// Create providers
const asrProvider = ASRProviderFactory.createProvider(combination.asr, testConfig, testConfig);
const llmProvider = LLMProviderFactory.createProvider(combination.llm, testConfig, testConfig);
// Test ASR
const audioBuffer = createTestAudioBuffer(2);
const tempDir = path.join(__dirname, '../temp');
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
const asrResult = await asrProvider.transcribe(audioBuffer, tempDir);
assert(asrResult.success !== undefined, 'ASR should return result');
// Test LLM with a simple message
const messages = [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'Say hello in one word.' }
];
const llmResult = await llmProvider.createChatCompletion(messages, {
max_tokens: 10
});
assert(llmResult.success !== undefined, 'LLM should return result');
console.log(`Integration Test - ${combination.description}:`, {
asr: { success: asrResult.success, provider: asrResult.provider },
llm: { success: llmResult.success, provider: llmResult.provider }
});
} catch (error) {
console.error(`Integration test failed for ${combination.description}:`, error.message);
throw error;
}
});
});
});
/**
* Provider switching tests
*/
describe('Provider Switching Tests', function() {
it('should switch ASR providers dynamically', function() {
const originalConfig = require('../config');
Object.assign(originalConfig, testConfig);
const sttService = new SpeechToTextService();
const originalProvider = sttService.getProviderInfo();
// Try switching to different provider
const availableProviders = ['openai', 'siliconflow'];
const newProvider = availableProviders.find(p => p !== originalProvider.provider);
if (newProvider && testConfig[testConfig.ASR_PROVIDERS[newProvider].apiKey]) {
sttService.switchProvider(newProvider);
const newProviderInfo = sttService.getProviderInfo();
assert(newProviderInfo.provider !== originalProvider.provider, 'Provider should change');
console.log('ASR Provider switched from', originalProvider.provider, 'to', newProviderInfo.provider);
} else {
console.log('Skipping ASR provider switching test - insufficient API keys');
}
});
});
// Export test configuration for use in other test files
module.exports = {
testConfig,
createTestAudioBuffer
};