| name | speak-performance-tuning |
| description | Optimize Speak API performance with caching, audio preprocessing, and connection pooling.
Use when experiencing slow API responses, implementing caching strategies,
or optimizing request throughput for language learning applications.
Trigger with phrases like "speak performance", "optimize speak",
"speak latency", "speak caching", "speak slow", "speak audio optimization".
|
| allowed-tools | Read, Write, Edit |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Speak Performance Tuning
Overview
Optimize Speak API performance with caching, audio preprocessing, and connection pooling for language learning applications.
Prerequisites
- Speak SDK installed
- Understanding of async patterns
- Redis or in-memory cache available (optional)
- Performance monitoring in place
Latency Benchmarks
| Operation | P50 | P95 | P99 |
|---|
| Session Start | 200ms | 500ms | 1000ms |
| Tutor Prompt | 150ms | 300ms | 600ms |
| Text Response Submit | 100ms | 250ms | 500ms |
| Audio Recognition | 500ms | 1500ms | 3000ms |
| Pronunciation Scoring | 800ms | 2000ms | 4000ms |
Audio Optimization
Pre-processing Audio Before Upload
class AudioOptimizer {
async optimizeForRecognition(audioData: ArrayBuffer): Promise<ArrayBuffer> {
const audioContext = new AudioContext({ sampleRate: 16000 });
const audioBuffer = await audioContext.decodeAudioData(audioData);
const monoBuffer = this.toMono(audioBuffer);
const normalizedBuffer = this.normalize(monoBuffer);
const trimmedBuffer = this.trimSilence(normalizedBuffer);
return this.encodeWav(trimmedBuffer);
}
private toMono(buffer: AudioBuffer): AudioBuffer {
if (buffer.numberOfChannels === 1) return buffer;
const monoData = new Float32Array(buffer.);
left = buffer.();
right = buffer.();
( i = ; i < buffer.; i++) {
monoData[i] = (left[i] + right[i]) / ;
}
ctx = (, buffer., buffer.);
newBuffer = ctx.(, buffer., buffer.);
newBuffer.(monoData, );
newBuffer;
}
(: ): {
data = buffer.();
max = ;
( i = ; i < data.; i++) {
max = .(max, .(data[i]));
}
(max > && max < ) {
factor = / max;
( i = ; i < data.; i++) {
data[i] *= factor;
}
}
buffer;
}
(: , threshold = ): {
data = buffer.();
start = ;
end = data.;
( i = ; i < data.; i++) {
(.(data[i]) > threshold) {
start = .(, i - );
;
}
}
( i = data. - ; i >= ; i--) {
(.(data[i]) > threshold) {
end = .(data., i + );
;
}
}
trimmedLength = end - start;
ctx = (, trimmedLength, buffer.);
newBuffer = ctx.(, trimmedLength, buffer.);
newBuffer.(data.(start, end), );
newBuffer;
}
}
Streaming Audio for Real-time Recognition
class StreamingRecognizer {
private chunks: ArrayBuffer[] = [];
private processingPromise: Promise<void> | null = null;
async streamAudioChunk(chunk: ArrayBuffer): Promise<PartialResult | null> {
this.chunks.push(chunk);
if (this.chunks.length >= 5 || this.shouldProcess()) {
return this.processAccumulated();
}
return null;
}
private async processAccumulated(): Promise<PartialResult> {
const combinedSize = this.chunks.reduce((sum, c) => sum + c.byteLength, 0);
const combined = new ArrayBuffer(combinedSize);
view = (combined);
offset = ;
( chunk .) {
view.( (chunk), offset);
offset += chunk.;
}
. = [];
result = speakClient..(combined);
result;
}
}
Caching Strategy
Response Caching for Static Content
import { LRUCache } from 'lru-cache';
const promptCache = new LRUCache<string, TutorPrompt>({
max: 500,
ttl: 60 * 60 * 1000,
updateAgeOnGet: true,
});
const vocabularyCache = new LRUCache<string, VocabularyEntry>({
max: 10000,
ttl: 24 * 60 * 60 * 1000,
});
async function getCachedVocabulary(
word: string,
language: string
): Promise<VocabularyEntry> {
const key = `${language}:${word}`;
const cached = vocabularyCache.get(key);
if (cached) return cached;
const entry = await speakClient..(word, language);
vocabularyCache.(key, entry);
entry;
}
Redis Caching for Distributed Systems
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
async function cachedWithRedis<T>(
key: string,
fetcher: () => Promise<T>,
ttlSeconds = 3600
): Promise<T> {
const cached = await redis.get(key);
if (cached) {
return JSON.parse(cached);
}
const result = await fetcher();
await redis.setex(key, ttlSeconds, JSON.stringify(result));
return result;
}
async function getUserProgress(userId: string): Promise<UserProgress> {
return cachedWithRedis(
`speak:progress:${userId}`,
() => speakClient.users.getProgress(userId),
300
);
}
Audio Asset Caching
class AudioAssetCache {
private cache: Map<string, ArrayBuffer> = new Map();
private preloadQueue: Set<string> = new Set();
async preloadLessonAudio(lessonId: string): Promise<void> {
const lesson = await speakClient.lessons.get(lessonId);
const audioUrls = lesson.items.map(item => item.audioUrl);
await Promise.all(
audioUrls.map(async (url) => {
if (!this.cache.has(url) && !this.preloadQueue.has(url)) {
this.preloadQueue.add(url);
const response = await fetch(url);
buffer = response.();
..(url, buffer);
..(url);
}
})
);
}
(: ): <> {
cached = ..(url);
(cached) cached;
response = (url);
buffer = response.();
..(url, buffer);
buffer;
}
}
Connection Optimization
import { Agent } from 'https';
const agent = new Agent({
keepAlive: true,
maxSockets: 10,
maxFreeSockets: 5,
timeout: 60000,
});
const client = new SpeakClient({
apiKey: process.env.SPEAK_API_KEY!,
appId: process.env.SPEAK_APP_ID!,
httpAgent: agent,
timeout: 30000,
});
Request Batching
import DataLoader from 'dataloader';
const vocabularyLoader = new DataLoader<string, VocabularyEntry>(
async (words) => {
const results = await speakClient.vocabulary.batchLookup(words);
return words.map(word => results.find(r => r.word === word) || null);
},
{
maxBatchSize: 50,
batchScheduleFn: callback => setTimeout(callback, 50),
}
);
const [word1, word2, word3] = await Promise.all([
vocabularyLoader.load('hola'),
vocabularyLoader.load('buenos'),
vocabularyLoader.load('días'),
]);
Performance Monitoring
interface SpeakMetrics {
operation: string;
duration: number;
success: boolean;
audioSize?: number;
}
async function measuredSpeakCall<T>(
operation: string,
fn: () => Promise<T>,
metadata?: Record<string, any>
): Promise<T> {
const start = performance.now();
try {
const result = await fn();
const duration = performance.now() - start;
console.log({
operation,
duration,
success: true,
...metadata,
});
metrics.histogram('speak_api_duration', duration, { operation });
metrics.increment('speak_api_success', { operation });
return result;
} catch (error) {
const duration = performance.now() - start;
console.error({
operation,
duration,
success: false,
error,
...metadata,
});
metrics.(, duration, { operation });
metrics.(, { operation });
error;
}
}
result = (
,
speakClient..(audioBuffer),
{ : audioBuffer. }
);
Output
- Reduced API latency
- Audio preprocessing pipeline
- Caching layer implemented
- Request batching enabled
- Connection pooling configured
Error Handling
| Issue | Cause | Solution |
|---|
| Cache miss storm | TTL expired | Use stale-while-revalidate |
| Audio too large | No compression | Optimize audio format |
| Connection exhausted | No pooling | Configure max sockets |
| Memory pressure | Cache too large | Set max cache entries |
| Batch timeout | Too many items | Reduce batch size |
Examples
Quick Performance Wrapper
const withPerformance = <T>(name: string, fn: () => Promise<T>) =>
measuredSpeakCall(name, () =>
cachedWithRedis(`cache:${name}`, fn, 300)
);
Resources
Next Steps
For cost optimization, see speak-cost-tuning.