Files
ForgeLM/README.md
T

12 KiB
Raw Blame History

ForgeLM

Inferência local de LLMs .gguf em Rust: API no estilo OpenAI, CLI e app desktop Tauri, tudo na sua máquina.

Quickstart · Arquitetura · Documentação · Issues

Stack Status Licença


Note

Este projeto não está concluído. O que está descrito abaixo é o que existe hoje no código. Segundo .planning/ROADMAP.md, as fases 1–4 estão concluídas; a fase 5 (validação de fontes de modelo) segue aberta no roadmap e a fase 6 (redesign do chat) consta como planejada.

📌 O problema

Rodar um modelo .gguf na própria máquina exige juntar peças soltas: um runtime do llama.cpp compatível com a sua GPU, uma API que clientes no estilo OpenAI consigam chamar e um lugar para escolher modelos e conversar. O ForgeLM reúne essas peças em um workspace Rust local-first, sem depender de infraestrutura hospedada, e documenta o que ainda não funciona.

✨ O que faz (o que existe hoje)

  • Serve GET /health, GET /v1/models e POST /v1/chat/completions em :8080, com streaming SSE e stream_options.include_usage.
  • Carrega modelos .gguf via llama.cpp (carga dinâmica) com os perfis Turbo4B (default), balanced_9b e cpu_fallback, ajustáveis por variáveis de ambiente.
  • Baixa e valida o runtime llama.cpp b8358 (CPU ou CUDA 12.4, com SHA-256) quando ele não é encontrado.
  • Opera pela CLI localllm: status, models, auth, config, bench, logs e doctor.
  • Gerencia o runtime no app desktop (modo local ou remote; estados stopped, starting, ready, degraded, stopping e error).
  • Escaneia, importa, busca e baixa modelos do Hugging Face com progresso e cancelamento (token opcional) e troca o modelo carregado com um clique (hot-swap).
  • Conversa com streaming, memória opcional de até 20 mensagens, separação de <think>…</think>, contagem de tokens, tokens/s e edição, reenvio e exclusão de mensagens.
  • Protege a API com chave opcional (LOCALLLM_API_KEY), CORS configurável e aliases de modelo.

🖼️ Demonstração

Capturas do shell do app com a API local offline e nenhum modelo selecionado: mostram o layout, não uma inferência em andamento.

Shell do app ForgeLM em desktop: Runtime Manager, lista de conversas e área de chat

Shell do app ForgeLM em tablet Shell do app ForgeLM em tela estreita

🚀 Quickstart

Warning

A API sobe aberta por padrão. Sem LOCALLLM_API_KEY não há autenticação, o bind default é 0.0.0.0:8080 e o CORS libera *. Fora de uma máquina confiável, defina a chave e restrinja LOCALLLM_ADDR e LOCALLLM_CORS_ORIGINS.

Ferramenta Versão mínima
Rust (cargo) edition 2021; rust-version não declarada
Node.js + npm (só para o app) não declarada (sem .nvmrc nem engines)
Modelo .gguf local obrigatório
GPU NVIDIA opcional (variante CUDA 12.4; existe o perfil cpu_fallback)

Na primeira execução o servidor baixa o llama.cpp b8358 de github.com/ggml-org/llama.cpp se não achar o runtime (LLAMA_DLL_PATH, binário adjacente ou vendor legado). CUDA 12.4 são dois zips (≈ 227 MB + ≈ 391 MB); CPU, um zip de ≈ 37 MB. O manifesto só lista windows-x64.

# 1. API local (Windows cmd; em bash use `export`)
set LOCALLLM_MODEL=D:\modelos\meu-modelo.gguf
cargo run -p inference-server

curl http://127.0.0.1:8080/health
curl http://127.0.0.1:8080/v1/models
curl -N http://127.0.0.1:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"default","messages":[{"role":"user","content":"Diga ola"}],"stream":true}'

# 2. CLI
cargo run -p localllm-cli -- status
cargo run -p localllm-cli -- models list

# 3. App desktop
cd tauri-app
npm install
npm run tauri dev

Variáveis de ambiente

Extraídas do código e de docs/api/configuration.md; não há .env.example. O prefixo é LOCALLLM_ (três L).

Variável Obrigatória Descrição
LOCALLLM_MODEL Sim Caminho do .gguf; sem ele o engine aborta (model_path is empty)
LOCALLLM_ADDR Não Endereço de bind; default 0.0.0.0:8080
LOCALLLM_MODEL_ALIASES Não Aliases do modelo, separados por vírgula, expostos em GET /v1/models
LOCALLLM_PROFILE Não balanced_9b ou cpu_fallback; ausente ou outro valor = Turbo4B
LOCALLLM_GPU_LAYERS Não Camadas na GPU; default do perfil (999 em Turbo4B/balanced_9b, 0 em cpu_fallback)
LOCALLLM_CTX Não Janela de contexto; default do perfil (4096, 2048 e 2048)
LOCALLLM_BATCH Não n_batch; sem valor explícito, usa o do perfil limitado a 4096
LOCALLLM_UBATCH Não n_ubatch; default do perfil
LOCALLLM_THREADS Não Threads de geração; default do perfil
LOCALLLM_THREADS_BATCH Não Threads de prefill; default do perfil
LOCALLLM_API_KEY Não Habilita autenticação por chave; vazia ou ausente = API aberta
LOCALLLM_AUTH_HEADER Não Cabeçalho preferencial da chave; default authorization (também aceita x-api-key)
LOCALLLM_AUTH_SCHEME Não Prefixo removido antes da comparação (ex.: Bearer)
LOCALLLM_CORS_ORIGINS Não Allowlist de origens separadas por vírgula; default *
LOCALLLM_SYSTEM_PROMPT Não Prompt de sistema usado quando a request não traz system nem developer
LOCALLLM_ENABLE_TOOL_CALLS Não Liga/desliga tool calling; default ligado
LOCALLLM_LOG_FORMAT Não json para logs estruturados; default pretty
LLAMA_DLL_PATH Não Caminho completo do llama.dll; sobrepõe a resolução automática do runtime
RUST_LOG Não Filtro de logs do tracing; default info

A pasta de modelos não é variável de ambiente: na CLI é a flag global --models-dir (default ./models); no app é o campo models_dir da configuração do runtime. Ao iniciar o servidor local, o app define LOCALLLM_MODEL, LOCALLLM_ADDR, LOCALLLM_GPU_LAYERS e LOCALLLM_CTX.

🧱 Arquitetura

flowchart LR
    UI["App Tauri (React + Rust)"] -->|"HTTP e SSE"| SRV["inference-server (Axum)"]
    CLI["localllm-cli"] -->|"HTTP"| SRV
    SRV --> CORE["inference-core (engine, sessão, perfis)"]
    CORE --> SYS["llama-sys (llama.dll via libloading)"]
    SRV --> DEPS["runtime-deps (download + SHA-256)"]
    DEPS -->|"llama.cpp b8358"| SYS
    UI -->|"busca e download de .gguf"| HF["Hugging Face"]
  • O app (no modo local) inicia e para o inference-server e usa GET /health para saber se está pronto; no modo remote apenas fala com a API.
  • A CLI é um cliente HTTP da mesma API.
  • O inference-server valida a request (auth, CORS, aliases) e delega ao inference-core, que mantém o engine, a sessão e o sampler.
  • O inference-core usa o llama-sys, que carrega llama.dll dinamicamente; o runtime-deps resolve ou baixa esse runtime a partir de vendor/llama-runtime/manifest.json.
  • O app busca e baixa modelos direto do Hugging Face, sem passar pela API local.

🗂️ Estrutura

crates/
  api-contracts/      tipos compartilhados (chat, modelos, health, erros)
  inference-core/     engine de inferência, sessão, sampler e perfis
  inference-server/   API HTTP em Axum (binário localllm)
  llama-sys/          carga dinâmica do llama.cpp (libloading)
  metrics/            contadores de tokens e throughput
  runtime-deps/       resolve e baixa o runtime llama.cpp
  localllm-cli/       CLI (binário localllm)
tauri-app/            app desktop: React 18 + Vite + Tailwind, backend Tauri 2
ui/                   páginas HTML estáticas de desenvolvimento (index.html, chat.html)
docs/                 documentação detalhada

🧪 Scripts

Comando O que faz
cargo run -p inference-server Sobe a API em 0.0.0.0:8080 (exige LOCALLLM_MODEL)
cargo run -p localllm-cli -- <comando> Executa a CLI
cargo test Testes dos crates do workspace
cargo test --manifest-path tauri-app/src-tauri/Cargo.toml Testes do backend Tauri (fora do workspace)
npm run dev (em tauri-app) Só o frontend, Vite na porta 1420
npm run tauri dev App desktop completo
npm run build tsc && vite build
npm run preview vite preview
npm test vitest run
npm run tauri Repassa argumentos ao tauri CLI
scripts/build-dev.sh (há .ps1) Compila vendor/llama.cpp em Debug com CUDA
scripts/build-prod.sh (há .ps1) Compila vendor/llama.cpp em Release com LTO e CUDA (arch 86)

📚 Documentação

Documento Conteúdo
docs/README.md Índice da documentação
docs/api/overview.md · endpoints.md · configuration.md API: visão geral, endpoints e configuração
docs/cli/overview.md · commands.md CLI: visão geral e comandos
docs/app/overview.md · user-guide.md · backend-commands.md App: visão geral, guia de uso e comandos Tauri
docs/developer/architecture.md Mapa do workspace e fluxos entre módulos
.planning/ROADMAP.md Fases e estado do desenvolvimento

🛣️ Limitações e próximos passos

  • A API expõe só três endpoints; a compatibilidade com a OpenAI é parcial: n precisa ser 1, parallel_tool_calls não é recurso suportado e content em partes vira texto consolidado.
  • O raciocínio não é um campo separado na API; quem separa <think>…</think> é o frontend do app.
  • Na CLI, a execução ao vivo está mais consolidada em status, models list e bench throughput; o resto da superfície está mais avançado no parser do que nas capacidades HTTP públicas.
  • O chat usa sql.js carregado por CDN e persiste o banco exportado em localStorage; não é um banco nativo do sistema.
  • No modo local o app precisa encontrar o binário localllm, e o runtime manager faz polling do snapshot (sem eventos push).
  • O manifesto do runtime só lista variantes windows-x64; em outros alvos o servidor só sobe com um runtime já presente (binário adjacente ou LLAMA_DLL_PATH).
  • scripts/build-dev.sh e scripts/build-prod.sh exigem o submódulo vendor/llama.cpp, que não está declarado no repositório (não há .gitmodules).
  • inference-server e localllm-cli declaram ambos um binário chamado localllm; use cargo run -p <crate> para escolher.
  • Próximos passos registrados no roadmap: concluir a fase 5 (validação de fontes de modelo, com seus três planos marcados como feitos mas a fase ainda aberta) e a fase 6 (redesign do chat e da página de Configurações, 0/3 planos).

🤝 Contribuindo

Abra uma issue ou PR descrevendo a mudança. Antes de enviar, rode os testes que cobrem o que você alterou:

cargo test && (cd tauri-app && npm test)

📄 Licença

MIT, declarada em Cargo.toml; o repositório não inclui arquivo LICENSE. Os binários de inferência vêm do llama.cpp e os modelos do Hugging Face seguem a licença de cada autor.