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

7.6 KiB
Raw Blame History

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:

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):

[
  {"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):

"""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).