Files
gart/code/docs/TRANSCRIPTION-MODELS.md
T

152 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Transcription Model Manager — Design
Este documento propõe um **gerenciador explícito de modelos de transcrição** para o
fcp-mcp-server, inspirado no gerenciador de modelos do app **Hex** (Swift/macOS).
Adapta o conceito para a nossa stack (Python + MCP), mantendo a nossa linguagem e
convenções. O Hex é usado apenas como referência de *design*; nada do código dele é
copiado.
> Estado: **design + esqueleto**. Só o módulo `fcpxml/model_manager.py` estruturado
> existe; handlers MCP e testes ficam para uma fase seguinte.
---
## 1. Problema que resolvemos
Hoje `transcribe()` (`fcpxml/transcribe.py`) baixa o modelo do Hugging Face
**automaticamente e de forma implícita** na primeira transcrição:
```python
model = WhisperModel(model_size, compute_type="int8") # baixa sozinho se faltar
```
Isso tem três lacunas, todas resolvidas pelo Hex e que importamos:
1. **Sem visibilidade** — o usuário não sabe quais modelos estão instalados, quais
tamanhos existem, quanto cada um pesa, nem qual está selecionado.
2. **Sem controle** — não dá para *escolher* o modelo ativo, baixar/remover por um
caminho explícito, nem ver progresso.
3. **Sem catálogo** — a escolha é por uma string solta (`"base"`), sem metadados
(stars de acurácia/velocidade, tamanho em disco, recomendado).
Inspiração do Hex (o que copiamos como conceito):
- **Catálogo curado** (`models.json`) com metadados opinativos, em vez de dropdown gigante.
- **Transição de estado clara**: não-instalado → baixando → instalado → em uso.
- **Fallback seguro**: se o selecionado some do disco, troca para o instalado, nunca apaga a seleção do usuário.
- **Progresso em fases**: download (0–50%) → load (50–100%).
---
## 2. Stack e convenções (nada de novo)
| Item | Decisão |
|---|---|
| Linguagem | Python 3.10+ (padrão do repo) |
| FrameWork | `faster-whisper` (extra `[transcribe]`) — já é o nosso motor |
| Download | `huggingface_hub.snapshot_download` — padrão usado pelo faster-whisper |
| Cache do modelo | `~/.cache/huggingface/hub/models--Systran--faster-whisper-<size>` |
| Validação | `ALLOWED_MODELS` existente em `transcribe.py` (allowlist) continua sendo a fonte de verdade |
| Confiança | nomes de modelo ainda passam por allowlist; nunca via `os.system` |
| Segurança | operações de escrita confinadas ao diretório de cache; sem path traversal |
A seleção do modelo ativo é **persistida em um arquivo de config** (ex.:
`~/.fcp-mcp-server/config.json`) para que as ferramentas de transcrição tenham um
modelo padrão consistente entre execuções.
---
## 3. Catálogo curado — `models.json`
Novo arquivo `fcpxml/models.json`, embutido no pacote (espelha a lista `ALLOWED_MODELS`):
```json
[
{"display_name": "Whisper Tiny", "internal_name": "tiny", "language": "Multilingual", "accuracy": 2, "speed": 5, "storage": "151 MB"},
{"display_name": "Whisper Tiny EN", "internal_name": "tiny.en", "language": "English", "accuracy": 2, "speed": 5, "storage": "151 MB"},
{"display_name": "Whisper Base", "internal_name": "base", "language": "Multilingual", "accuracy": 3, "speed": 4, "storage": "290 MB"},
{"display_name": "Whisper Base EN", "internal_name": "base.en", "language": "English", "accuracy": 3, "speed": 4, "storage": "290 MB"},
{"display_name": "Whisper Small", "internal_name": "small", "language": "Multilingual", "accuracy": 4, "speed": 3, "storage": "968 MB"},
{"display_name": "Whisper Medium", "internal_name": "medium", "language": "Multilingual", "accuracy": 5, "speed": 2, "storage": "3.07 GB"},
{"display_name": "Whisper Large v3", "internal_name": "large-v3", "language": "Multilingual", "accuracy": 5, "speed": 1, "storage": "6.21 GB"},
{"display_name": "Whisper Distil v3", "internal_name": "distil-large-v3", "language": "English", "accuracy": 5, "speed": 4, "storage": "1.6 GB"}
]
```
Tamanhos são os do Hugging Face (Systran/faster-whisper). Estrelas numero de packaging.
O catálogo é **fonte de verdade** para os handlers novos: `internal_name` é o único
valor aceito, e deve estar também em `ALLOWED_MODELS`.
---
## 4. Módulo `fcpxml/model_manager.py` — esqueleto
API pública (funções puras + I/O confinado), no estilo dos módulos `transcribe.py` /
`media_intel.py` (lazy import, degrade gracioso, `logger`):
```python
"""model_manager — explicit transcription-model management (Hex-inspired)."""
# ---------- catálogo ----------
def load_catalog() -> list[dict] # lê models.json embutido
def get_catalog_model(internal_name) # busca por internal_name (Allowlist)
# ---------- cache / disco ----------
def model_cache_dir(model_size) -> Path # ~/.cache/huggingface/hub/models--Systran--faster-whisper-<size>
def is_model_downloaded(model_size) -> bool # dir existe e não está vazio/vacilando
def list_installed_models() -> list[str] # varre o cache, filtra por ALLOWED_MODELS
# ---------- download / remoção ----------
def download_model(model_size, *, progress_cb=None) # snapshot_download + callback
def delete_model(model_size) # remove diretório do cache
# ---------- seleção persistida ----------
def load_selected_model() -> str # lê config (~/.fcp-mcp-server/config.json)
def save_selected_model(model_size) # grava; valida via ALLOWED_MODELS
```
Contratos de erro (iguais ao resto do código):
- `download_model` levanta `ValueError` se `model_size` não estiver em `ALLOWED_MODELS`.
- `is_model_downloaded` / `list_installed_models` retornam dados, nunca levantam em I/O — degradam gracioso.
- sem `huggingface_hub`? `download_model` retorna `None`/mensagem de instalação, como `transcribe` faz.
---
## 5. Handlers MCP futuros (fase seguinte — fora deste escopo)
Mapeamentos que queremos quando implementarmos os handlers:
| Hex | Handler MCP proposto |
|---|---|
| `getAvailableModels` + `getRecommendedModels` | `list_transcription_models` — cataloga todos, marca instalado/recomendado |
| `isModelDownloaded` | integrado ao `list_transcription_models` |
| `downloadModel` | `download_transcription_model` — baixa + reporta progresso |
| `selectModel` | `select_transcription_model` — grava a seleção persistida |
| `deleteModel` | `delete_transcription_model` — remove do cache |
| `transcribe` | já existe; passará a ler a seleção persistida como default do `model` |
> Nota de progresso em MCP: protocolo é request/response, sem streaming.
> O download ficará síncrono com callback interno de log (registra fases de
> `download→load`), como os caminhos ffmpeg/whisper já fazem hoje — sem design de
> streaming novo.
---
## 6. Onde escrevemos (referência: Hex)
- Catalog → `fcpxml/models.json` (análogo a `Hex/Resources/Data/models.json`).
- Lógica → `fcpxml/model_manager.py` (análogo a `Hex/Clients/TranscriptionClient.swift`).
- Decisão de estado/fallback → copiada como *regras* (não código) em `load_selected_model`:
- se a seleção não está no cache e outro modelo está, retorna o instalado (fallback),
- nunca apaga a seleção do usuário por um scan falso-negativo.
- UI (model download view do Hex) → **não se aplica**: somos um servidor MCP sem interface
gráfica; a "UI" é o texto estruturado que os handlers retornam (`_markdown_table`).
---
## 7. Próximos passos (fase seguinte)
1. Criar `fcpxml/models.json` com os 8 modelos curados.
2. Implementar o corpo das funções do esqueleto.
3. Adicionar os 4 handlers MCP + registrar em `TOOL_HANDLERS` e `TOOLS`.
4. Fazer `transcribe()` usar a seleção persistida como default.
5. Testes em `tests/test_model_manager.py` (tempdir para cache, allowlist, fallback).