# Transcription Caching

## Overview

The transcription system now includes **automatic caching** to prevent re-transcribing the same audio across different requests. This saves both time and money.

## How It Works

### Two-Level Caching

1. **Instance Cache**: Cached within the `AudioContent` object
   - Stored in `transcription_result` field
   - Persists within a single request/conversation
   - Lost when object is garbage collected

2. **Global Cache**: Shared across all requests
   - Keyed by `(audio_source, model, language)`
   - Persists for the lifetime of the application
   - Survives across different requests/conversations

### Cache Key

The cache key is generated from three components:

```python
# Components
audio_source = "https://example.com/audio.m4a"  # URL, file path, or base64
model = "whisper-large-v3-turbo"                # Whisper model
language = "en"                                  # Language code (or None)

# Key format: {source_hash}:{model}:{language}
# Example: "a1b2c3d4e5f6g7h8:whisper-large-v3-turbo:en"
```

**Why this key?**
- Same audio with same model and language → reuse transcription ✓
- Same audio with different model → new transcription (quality may differ)
- Same audio with different language → new transcription (language-specific)

## Usage Examples

### Example 1: Same Audio, Multiple Requests

```python
# Request 1: Transcribe audio
audio1 = AudioContent(
    url="https://example.com/meeting.m4a",
    auto_transcribe=True,
)
text1 = audio1.get_transcription()
# → API call to Groq, transcribes audio (17s duration)

# Request 2: Same audio in new conversation
audio2 = AudioContent(
    url="https://example.com/meeting.m4a",
    auto_transcribe=True,
)
text2 = audio2.get_transcription()
# → Uses cached transcription, NO API call! ✓
# → No usage tracking (not billed)
```

### Example 2: Same Audio, Different Settings

```python
# Transcription 1: English, turbo model
audio1 = AudioContent(
    url="https://example.com/audio.m4a",
    auto_transcribe=True,
    transcription_model="whisper-large-v3-turbo",
    transcription_language="en",
)
text1 = audio1.get_transcription()
# → API call, transcribes

# Transcription 2: Auto-detect language, turbo model
audio2 = AudioContent(
    url="https://example.com/audio.m4a",
    auto_transcribe=True,
    transcription_model="whisper-large-v3-turbo",
    transcription_language=None,  # Different language setting
)
text2 = audio2.get_transcription()
# → NEW API call (different cache key due to language)
```

### Example 3: Force Refresh

```python
# First transcription
audio = AudioContent(url="audio.m4a", auto_transcribe=True)
text1 = audio.get_transcription()
# → API call

# Get cached transcription
text2 = audio.get_transcription()
# → Uses cache ✓

# Force re-transcribe
text3 = audio.get_transcription(force_refresh=True)
# → NEW API call, updates cache
```

## Cache Indicators

### Metadata Flag

You can check if a transcription came from cache:

```python
audio = AudioContent(url="audio.m4a", auto_transcribe=True)
transcription = audio.get_transcription()

# Check metadata
metadata = audio.metadata.get("transcription", {})
from_cache = metadata.get("from_cache", False)

if from_cache:
    print("Used cached transcription (no API call)")
else:
    print("Fresh transcription (API call made)")
```

### Debug Output

With `debug=True`, you'll see cache usage:

```
[Audio Transcription] Using cached transcription for audio (171 characters)
[Audio Preprocessing] ✓ Using cached transcription (no API call)
```

## Usage Tracking

**Cached transcriptions are NOT billed**, so they don't appear in `usage_history`:

```python
# First request: Transcribes audio
request1 = AIMatrixRequest(...)
# usage_history contains groq_transcription entry

# Second request: Uses cache
request2 = AIMatrixRequest(...)  # Same audio
# usage_history does NOT contain groq_transcription (cached!)
```

### Checking Usage

```python
# Count transcription API calls (excludes cached)
transcription_calls = [
    u for u in request.usage_history 
    if u.api == "groq_transcription"
]

print(f"Made {len(transcription_calls)} transcription API calls")
# If audio was cached, this will be 0
```

## Cache Management

### Check Cache Size

```python
from ai.audio.transcription_cache import get_cache

cache = get_cache()
print(f"Cached transcriptions: {cache.size()}")
```

### Clear Cache

```python
from ai.audio.transcription_cache import clear_cache

# Clear all cached transcriptions
clear_cache()

# Next transcription will hit the API again
```

### When to Clear Cache

Consider clearing the cache:
- After processing a batch of unique audio files
- When memory usage is a concern
- During development/testing
- If you update the Whisper model and want fresh transcriptions

## Conversation History Behavior

Within a conversation, audio is converted to text and stays as text:

```python
# Turn 1: User sends audio
messages = [
    UnifiedMessage(
        role="user",
        content=[AudioContent(url="audio.m4a", auto_transcribe=True)]
    )
]
# → After preprocessing, becomes TextContent

# Turn 2: Conversation continues
messages = [
    UnifiedMessage(role="user", content=[...]),  # Audio as text
    UnifiedMessage(role="assistant", content=[...]),  # Response
    UnifiedMessage(role="user", content=[...]),  # New message
]
# → Audio is already text, no re-transcription needed ✓
```

## Performance Benefits

### Without Caching

```
Request 1: Audio → Transcribe (17s) → Process → Response
Request 2: Same Audio → Transcribe (17s) → Process → Response
Request 3: Same Audio → Transcribe (17s) → Process → Response

Total: 3 API calls, 51s billed, ~$0.0007
```

### With Caching

```
Request 1: Audio → Transcribe (17s) → Cache → Process → Response
Request 2: Same Audio → [Cache Hit] → Process → Response
Request 3: Same Audio → [Cache Hit] → Process → Response

Total: 1 API call, 17s billed, ~$0.0002
Savings: 67% cost reduction! 💰
```

## Technical Details

### Cache Implementation

```python
# ai/transcription_cache.py

class TranscriptionCache:
    """Simple in-memory cache for transcriptions"""
    
    def __init__(self):
        self._cache: Dict[str, CachedTranscription] = {}
    
    def get(self, audio_source, model, language):
        """Get cached transcription"""
        key = self._generate_key(audio_source, model, language)
        return self._cache.get(key)
    
    def set(self, audio_source, model, language, text, metadata):
        """Cache a transcription"""
        key = self._generate_key(audio_source, model, language)
        self._cache[key] = CachedTranscription(text, metadata)
```

### Cache Lookup Flow

```
AudioContent.get_transcription()
    ↓
Check instance cache (transcription_result)
    ↓ [Miss]
Check global cache (by audio_source + model + language)
    ↓ [Miss]
Call Groq Whisper API
    ↓
Cache in both instance and global cache
    ↓
Return transcription
```

## Best Practices

1. **Use consistent settings**: Same audio with same model/language → cache hit
2. **Monitor cache size**: Check `cache.size()` periodically
3. **Clear after batches**: Process batch, clear cache, process next batch
4. **Leverage in conversations**: Audio becomes text, naturally cached
5. **Check from_cache flag**: Know when you're saving money

## Example: Batch Processing with Cache

```python
from ai.audio.transcription_cache import get_cache, clear_cache
from ai.config.unified_config import AudioContent

# Process batch of audio files
audio_files = ["audio1.m4a", "audio2.m4a", "audio3.m4a"]

for i, audio_url in enumerate(audio_files, 1):
    audio = AudioContent(url=audio_url, auto_transcribe=True)
    text = audio.get_transcription()
    
    # Process transcription...
    process(text)

cache = get_cache()
print(f"Batch complete. Cached {cache.size()} transcriptions")

# Clear cache for next batch
clear_cache()
```

## Summary

✅ **Automatic caching** - No code changes needed
✅ **Cross-request** - Same audio, different requests → cache hit
✅ **Smart key** - Considers audio source, model, and language
✅ **No double-billing** - Cached transcriptions not tracked in usage
✅ **Debug visibility** - See cache hits in debug output
✅ **Conversation-aware** - Audio becomes text, stays text
✅ **Manageable** - Clear cache when needed

Your transcription costs just went down! 🎉
