chore: adiciona .gitignore e commit.command
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user