Skip to content

Latest commit

 

History

History
468 lines (339 loc) · 26.1 KB

File metadata and controls

468 lines (339 loc) · 26.1 KB

OperatorLM

English | Español | Português | 简体中文

License: MIT Go Platforms Single binary No Docker Keys: OS Keyring

Um proxy local, compatível com OpenAI, com failover real, aliasing multi-conta e zero segredos em disco. Um único binário fica entre o seu IDE/SDK e cada provider LLM que você usa — OpenAI, OpenRouter, Groq, Google Gemini, Azure OpenAI, Anthropic, e até a sua assinatura ChatGPT Plus/Pro. Também roda modelos GGUF totalmente locais, embeddings e voz (STT/TTS) sem nuvem nenhuma — um substituto do Ollama e um router para a nuvem no mesmo binário.

OperatorLM Banner

Important

OperatorLM escuta em 127.0.0.1:11434 por padrão — a mesma porta do Ollama. Qualquer ferramenta já apontada para o Ollama funciona sem mexer em nada. Se rodar os dois, troque uma em ~/.operatorlm/config.toml.

Quick Start

Só quer rodar? Baixe o último binário — sem Go toolchain, sem Docker.

1. Download

Vá até a página de Releases e baixe o asset do seu sistema operacional:

OS Asset
Windows (x64) OperatorLM-windows-amd64.exe
macOS (Apple silicon) OperatorLM-darwin-arm64
macOS (Intel) OperatorLM-darwin-amd64
Linux (x64) OperatorLM-linux-amd64

Note

No macOS/Linux, dê permissão de execução: chmod +x OperatorLM-*.

2. Executar

  • Windows: duplo clique em OperatorLM-windows-amd64.exe. Aparece um ícone na bandeja (sem janela de console). Na primeira execução, o SmartScreen pode mostrar "O Windows protegeu seu PC" → clique em Mais informações → Executar mesmo assim (o binário não é assinado).
  • macOS desktop: ./OperatorLM-darwin-* — aparece o ícone na bandeja. Na primeira execução, o Gatekeeper vai bloquear → clique com o botão direito no binário no Finder → AbrirAbrir de novo no diálogo (só precisa fazer uma vez).
  • Linux desktop: ./OperatorLM-linux-amd64 — aparece o ícone na bandeja.
  • Linux headless: OPERATORLM_NO_TRAY=1 ./OperatorLM-linux-amd64.

3. Abrir o admin UI

Acesse http://127.0.0.1:11434/admin/ → adicione um provider → cole uma API key → dispare um request de teste pela aba Try It.

4. Aponte suas ferramentas para o proxy

Qualquer cliente compatível com OpenAI funciona — defina a base URL como http://127.0.0.1:11434/v1 e qualquer api_key não vazia (o OperatorLM injeta a real). Exemplos prontos pra copiar-colar em curl / Python / JavaScript estão em Use a partir de qualquer coisa que fale OpenAI mais abaixo.

Prefere buildar do código-fonte? Veja Build from source.


Demo

OperatorLM Demo

Sumário

Por que esse projeto?

  • 🔀 Aliasing multi-conta — empilhe 3 API keys da OpenAI, 2 contas do OpenRouter, e um Groq grátis como backup atrás de um único nome de modelo. O OperatorLM percorre a lista até uma funcionar.
  • 🛡️ Failover de nível produçãocircuit breaker por target (3 estados), retries com exponential backoff + jitter, RPM limiter com sliding window. Cooldowns diferentes para 429 / 5xx / erros de rede.
  • 🔐 Zero segredos em disco — As API keys ficam no Windows Credential Manager / macOS Keychain / Linux Secret Service. O arquivo TOML só guarda referências, nunca as keys.
  • 🖥️ Admin UI embutida e ao vivo — Gerencie providers, keys, aliases, ajustes de reliability, e veja o audit log em streaming — tudo a partir de http://127.0.0.1:11434/admin/. Zero instalação: está embutida no binário com go:embed.
  • 🧪 Audit log em JSONL — Cada request: modelo, attempt, URL upstream, status, duração. Headers Authorization redatados por padrão. Writer não-bloqueante.
  • 🤖 ChatGPT Plus/Pro como backend — Logue uma vez via OAuth (PKCE) e tenha a sua cota Plus/Pro plugada na mesma API compatível com OpenAI que as suas ferramentas já falam. (Experimental — leia o disclaimer abaixo.)
  • 🖥️ Rode modelos localmente (llama.cpp embutido) — Aponte para uma pasta de arquivos *.gguf e o OperatorLM sobe o llama-server sob demanda, sem daemon à parte. Um catálogo com downloads de um clique traz modelos de chat, visão e embeddings já curados. Um substituto do Ollama que também roteia para a nuvem.
  • 🧬 Embeddings locais/v1/embeddings servido por um modelo GGUF local (Qwen3-Embedding, EmbeddingGemma) num sidecar dedicado — ótimo para RAG / busca semântica offline. Embeddings na nuvem (OpenAI, Azure, Gemini) funcionam pelo mesmo endpoint.
  • 🎙️ Voz local (STT + TTS) — Transcreva com Whisper (/v1/audio/transcriptions), sintetize com Piper (/v1/audio/speech), ou faça chat-para-voz em tempo real por streaming (/v1/audio/speech/realtime). Catálogo de vozes com downloads de um clique.
  • 🟣 Compatível com Anthropic — Um endpoint nativo /v1/messages mais um provider anthropic com tradução bidirecional OpenAI↔Anthropic, então ferramentas que falam a API do Claude (como o Claude Code) também podem apontar para o OperatorLM.
  • ⬆️ Auto-update OTA — "Check for updates" pela bandeja baixa o asset da release do seu OS, verifica o SHA-256, troca o binário no lugar e se re-executa.
  • 🪶 Um binário pequeno, pouca RAM em idle — Go nativo. Sem node_modules, sem Python, sem Docker. Sobe em milissegundos.
  • 🛰️ Modo headlessOPERATORLM_NO_TRAY=1 para rodar numa máquina Linux sem sessão de desktop.
  • 🪟 Sem flash de console no Windows — buildado com -H=windowsgui. É uma tray app de verdade.

Casos de uso

  • Esticar tiers grátis / baratos — encadeie Groq → OpenRouter → OpenAI atrás de um único alias. O dia a dia bate no free tier e só transborda para o pago quando o grátis é rate-limited.
  • Várias contas pessoais/de trabalho sob um único nome de modelo — mantenha as keys openai_personal, openai_work e openai_side separadas no OS keyring, exponha para o Cursor/Continue como um único modelo gpt-4o, e deixe o router percorrer todas em caso de 429.
  • ChatGPT Plus/Pro em vez de créditos de API — logue uma vez via OAuth e roteie chamadas para modelos Codex / GPT-5.x pela sua cota Plus/Pro existente, sem billing de API (experimental — leia o disclaimer).
  • Substituto drop-in para Ollama — o OperatorLM escuta em 127.0.0.1:11434, então qualquer coisa já apontada para o Ollama (Continue, Cline, Open WebUI, Zed, …) continua funcionando sem mudanças mas agora alcança OpenAI / OpenRouter / Gemini / Azure / Bedrock / etc.
  • Totalmente offline / air-gapped — rode modelos GGUF locais para chat, visão, embeddings e voz sem nenhum provider de nuvem configurado; o mesmo binário depois se espalha para a nuvem assim que você adiciona uma key.
  • RAG / busca semântica local — sirva embeddings a partir de um modelo local (Qwen3-Embedding / EmbeddingGemma) por /v1/embeddings, mantendo os seus documentos na máquina.
  • Tráfego LLM auditável numa máquina de dev ou estação compartilhada — todo request cai em JSONL redatado, as keys ficam no OS keyring (nunca em disco), e o admin UI é loopback-only com validação de host-header e uma API key local opcional.
  • Gateway self-hosted em modo headless — rode numa VM Linux / NAS / home server com OPERATORLM_NO_TRAY=1, alcance 127.0.0.1:11434 via WireGuard ou Tailscale do seu laptop, e centralize as suas keys e o audit log num único lugar.

Como funciona

Fluxo de requests do OperatorLM

  1. Recebe um request no formato OpenAI em 127.0.0.1:11434.
  2. Resolve o campo model → ou por match de prefixo (openai/gpt-4o, groq/llama-3.3-70b-versatile), ou por um alias definido pelo usuário que se espalha entre várias contas/providers.
  3. Injeta a API key correta do OS keyring a cada attempt.
  4. Tenta, faz retry e abre — exponential backoff entre tentativas; abre o circuit breaker do target em falhas repetidas; respeita Retry-After.
  5. Audita cada attempt num log JSONL redatado.

✨ A killer feature: aliases multi-conta

A maioria dos proxies locais roteia um nome de modelo para um único upstream. O OperatorLM deixa um nome de modelo se espalhar para N upstreams em ordem de prioridade, com rate limits por target e failover automático.

Exemplo: três contas OpenAI atrás de um único nome de modelo

# ~/.operatorlm/config.toml

[[providers]]
name        = "openai"
type        = "openai"
base_url    = "https://api.openai.com/v1"
prefix      = "openai/"
api_key_ref = "operatorlm:openai_personal"   # key padrão

  [[providers.keys]]
  name        = "work"
  api_key_ref = "operatorlm:openai_work"

  [[providers.keys]]
  name        = "side-project"
  api_key_ref = "operatorlm:openai_side"

[[aliases]]
name     = "gpt-4o"
strategy = "order"

  [[aliases.targets]]
  provider       = "openai"
  key            = "default"          # key pessoal primeiro
  upstream_model = "gpt-4o"
  order          = 1
  rpm            = 60

  [[aliases.targets]]
  provider       = "openai"
  key            = "work"             # cai para a key do trabalho em falha / 429
  upstream_model = "gpt-4o"
  order          = 2
  rpm            = 60

  [[aliases.targets]]
  provider       = "openai"
  key            = "side-project"     # último recurso
  upstream_model = "gpt-4o"
  order          = 3

Agora o seu IDE só diz model: "gpt-4o" e o OperatorLM percorre as keys até uma funcionar. Bateu num 429 na key #1? O circuit breaker abre por 15 s e a key #2 assume na hora.

Exemplo: failover cross-provider (otimização de custo)

[[aliases]]
name     = "fast-llama"
strategy = "order"

  [[aliases.targets]]
  provider       = "groq"             # grátis + mais rápido, tente primeiro
  upstream_model = "llama-3.3-70b-versatile"
  order          = 1
  rpm            = 30                  # respeita o RPM do free tier do Groq

  [[aliases.targets]]
  provider       = "openrouter"       # fallback pago se o Groq estiver rate-limited
  upstream_model = "meta-llama/llama-3.3-70b-instruct"
  order          = 2

Mande model: "fast-llama" — pega Groq quando disponível, OpenRouter quando não. Zero mudanças no client.


🛡️ Failover que realmente faz failover

Mecanismo O que faz Default
Retry + jitter Retries por target com exponential backoff, full jitter, respeita Retry-After 2 retries, 500 ms base, 10 s cap
Circuit breaker Abre por target após N falhas consecutivas; closed → open → half-open 3 falhas
Cooldown 429 Cooldown quando o upstream rate-limitar 15 s
Cooldown 5xx Cooldown em erros de servidor upstream 60 s
Cooldown de rede Cooldown em falhas de DNS / TCP / timeout 90 s
RPM limiter Sliding window de 60 segundos por target — skip em vez de bloquear configurável por target
Timeout por attempt Cap duro por chamada upstream 60 s (180 s total)
Stream idle timeout Aborta um stream SSE morto 30 s

Tudo isso é ajustável ao vivo pela aba Reliability do admin UI — sem restart.


🖥️ Rode modelos localmente (chat, visão, embeddings, voz)

O OperatorLM tem um motor llama.cpp embutido — sem daemon do Ollama à parte. Aponte para uma pasta de arquivos *.gguf (na aba Local models do admin UI) e ele expõe cada um como local/<model>, subindo o llama-server sob demanda e trocando modelos automaticamente. Tudo roda em 127.0.0.1, offline.

Capacidade Endpoint Motor Notas
Chat local /v1/chat/completions llama.cpp Qualquer GGUF; carga sob demanda, swap automático, GPU (-ngl)
Visão local /v1/chat/completions llama.cpp Modelos multimodais com projector mmproj (entradas de imagem)
Embeddings locais /v1/embeddings llama.cpp Sidecar dedicado; coexiste com o modelo de chat
Speech-to-text /v1/audio/transcriptions, /v1/audio/translations whisper.cpp Transcrição/tradução com Whisper local
Text-to-speech /v1/audio/speech Piper Vozes locais; sample rate por voz; streaming PCM
Chat→voz em realtime /v1/audio/speech/realtime llama.cpp + Piper SSE: os tokens do modelo são sintetizados em áudio conforme chegam

Catálogo de modelos com um clique

A aba Local models traz um catálogo curado — você escolhe um modelo e o OperatorLM baixa (com o projector de visão ou config de voz quando aplicável) para a sua pasta de modelos com defaults sensatos:

  • Chat / visão / agentic: Qwen2.5-VL 3B, Gemma 3 4B, Qwen2.5 3B, Llama 3.2 3B, Phi-4 Mini, SmallThinker 3B, Gemma 4 E2B.
  • Embeddings: Qwen3-Embedding 0.6B (o melhor em multilíngue para o seu tamanho, 100+ idiomas) e EmbeddingGemma 300M (o modelo leve on-device do Google). Ambos rodam em CPU por padrão para não competir com o modelo de chat por VRAM.
  • Voz: Whisper Base (STT) e vozes do Piper (TTS).

Note

A inferência local precisa do binário llama-server (llama.cpp). O admin UI tem um download de um clique, ou defina local_models.llama_server_path para o seu próprio build. Whisper e Piper têm os próprios botões de download.

Como escuta na porta do Ollama e fala a API da OpenAI, qualquer ferramenta local-first (Continue, Cline, Zed, Open WebUI…) alcança os seus modelos locais sem mudanças — enquanto o mesmo proxy pode fazer failover para a nuvem.


🟣 API compatível com Anthropic

O OperatorLM fala a API Messages da Anthropic nativamente em POST /v1/messages, e o provider anthropic traduz bidirecionalmente entre os esquemas da OpenAI e da Anthropic. Duas consequências:

  • Ferramentas feitas para a API do Claude (incluindo o Claude Code) podem apontar a base URL para o OperatorLM e rotear por toda a sua maquinaria de failover, aliasing e auditoria.
  • Você pode misturar ecossistemas livremente: chame um modelo da Anthropic por /v1/chat/completions, ou um modelo da OpenAI/local por /v1/messages — o OperatorLM converte conforme necessário.

🤖 ChatGPT Plus/Pro como backend (experimental)

⚠️ Leia esse disclaimer antes de habilitar o provider chatgpt-codex

[!WARNING] O provider chatgpt-codex não é oficial nem endossado pela OpenAI. Ele reusa o client ID público de OAuth do Codex CLI oficial da OpenAI (app_EMoamEEZ73f0CkXaXp7hrann).

  • A OpenAI pode rotacionar ou revogar esse ID a qualquer momento e quebrar esse provider.
  • O uso pode violar os Termos de Serviço da OpenAI.
  • /v1/responses é suportado (sem chat/completions, sem imagens).
  • Use por sua conta e risco. Para um caminho suportado, use o provider openai com a sua própria API key.

Se você aceita o risco: abra o admin UI, adicione um provider chatgpt-codex, clique em Login with ChatGPT — um navegador abre, você loga, e os tokens são guardados no seu OS keyring com refresh automático. A partir daí, modelos Codex / GPT-5.x ficam acessíveis pelo mesmo endpoint /v1/responses de qualquer outro provider.


⬆️ Mantendo atualizado

O OperatorLM se auto-atualiza pelo GitHub Releases. Escolha Check for updates no menu da bandeja (ou POST /admin/update/check): ele busca a última release, baixa o asset do seu OS/arch mais o checksums.txt, verifica o SHA-256, troca o binário em execução no lugar e se re-executa na nova versão. Builds de dev (sem versão embutida) são pulados.


Como o OperatorLM se compara

Feature OperatorLM LiteLLM proxy OmniRoute
Binário único, sem runtime ❌ (Python)
Multi-conta / rotação de keys por provider
Circuit breaker + retry + RPM limiter parcial parcial
Keys no OS keyring (sem plaintext)
Admin UI embutida
Tray app nativa
Audit log (JSONL, redatado)
Inferência local embutida (GGUF/llama.cpp)
Embeddings + voz locais (STT/TTS)
Compatibilidade Anthropic /v1/messages parcial
ChatGPT Plus/Pro como backend ✅ (exp.)

Escolha o OperatorLM se quer um proxy desktop-first, de um único binário, que faz failover e routing multi-conta como um serviço de produção — sem rodar um serviço Python nem mandar as suas keys pela cloud de outra pessoa.


Build from source

1. Buildar

# Windows
.\build.ps1
# Linux / macOS
./build.sh

Note

CGO é necessário (para system tray + OS keyring). Linux: instale gcc libgtk-3-dev libayatana-appindicator3-dev (Debian/Ubuntu) ou os equivalentes de Fedora/RHEL listados em build.sh.

2. Executar

# Windows
.\OperatorLM.exe

# Linux / macOS (desktop)
./OperatorLM

# Linux (servidor headless, sem tray)
OPERATORLM_NO_TRAY=1 ./OperatorLM

Aparece um ícone na bandeja. O admin UI fica em http://127.0.0.1:11434/admin/.

3. Configurar (admin UI)

  1. Abra o admin UI.
  2. Providers → adicione um provider, escolha o tipo (openai, openrouter, groq, gemini, azure-openai, anthropic, chatgpt-codex, custom, …).
  3. Keys → cole a sua API key. Ela é gravada no OS keyring; o TOML só guarda a referência.
  4. Aliases (opcional) → monte failover multi-conta / multi-provider.
  5. Local models (opcional) → aponte para uma pasta *.gguf (ou baixe um modelo do catálogo com um clique) para rodar chat / embeddings / voz localmente.
  6. Try It → dispare um request inline para verificar.

4. Use a partir de qualquer coisa que fale OpenAI

curl http://127.0.0.1:11434/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"groq/llama-3.3-70b-versatile","messages":[{"role":"user","content":"hi"}]}'
from openai import OpenAI

client = OpenAI(
    base_url="http://127.0.0.1:11434/v1",
    api_key="not-needed",          # OperatorLM injeta a key real
)
print(client.chat.completions.create(
    model="groq/llama-3.3-70b-versatile",
    messages=[{"role": "user", "content": "hi"}],
).choices[0].message.content)
import OpenAI from "openai";

const openai = new OpenAI({
  baseURL: "http://127.0.0.1:11434/v1",
  apiKey: "not-needed",
});

const chat = await openai.chat.completions.create({
  model: "groq/llama-3.3-70b-versatile",
  messages: [{ role: "user", content: "hi" }],
});
console.log(chat.choices[0].message.content);

Aponte Cursor / Continue / qualquer cliente compatível com OpenAI para http://127.0.0.1:11434/v1 e pronto.


Endpoints suportados

Endpoint Status
POST /v1/chat/completions ✅ Completo, com streaming
POST /v1/messages ✅ API Messages da Anthropic (OpenAI↔Anthropic)
POST /v1/responses ✅ (usado pelo chatgpt-codex)
POST /v1/embeddings ✅ Nuvem (OpenAI/Azure/Gemini) + GGUF local
POST /v1/images/generations ✅ (sem streaming)
POST /v1/audio/transcriptions ✅ Whisper (local) + STT de providers
POST /v1/audio/translations ✅ Whisper (local)
POST /v1/audio/speech ✅ Piper (local) + TTS de providers
POST /v1/audio/speech/realtime ✅ Chat→voz por streaming (SSE)
GET /v1/models ✅ Agregado entre os providers configurados
GET /health ✅ Checagem de liveness pública

Providers suportados

Nuvem / API: openai · openrouter · groq · gemini · azure-openai · anthropic · mistral · nvidia-nim · bedrock · opencode-zen · custom (qualquer upstream compatível com OpenAI).

Local (sem nuvem): motor llama.cpp embutido (chat + visão + embeddings), whisper.cpp (STT), piper (TTS).

Experimental: chatgpt-codex (ChatGPT Plus/Pro via OAuth) · antigravity (Gemini via uma sessão local do Antigravity).


Configuração & segredos

Localização dos arquivos

  • Config: ~/.operatorlm/config.toml
  • Logs: ~/.operatorlm/operatorlm.log
  • Audit Log: ~/.operatorlm/audit.log (JSONL, redatado)

Onde as suas API keys realmente moram

OS Backend Onde inspecionar
Windows Credential Manager Painel de Controle → Gerenciador de Credenciais
macOS Keychain App Acesso às Chaves (Keychain Access)
Linux Secret Service (D-Bus) seahorse (GNOME) ou kwalletmanager (KDE)

O TOML referencia as keys por nome (operatorlm:openai_work) — nunca o segredo em si.

Note

Linux headless: requer um daemon de Secret Service rodando (ex.: gnome-keyring-daemon --components=secrets) e uma sessão D-Bus válida.


Modelo de segurança

  • Só loopback — escuta em 127.0.0.1 por padrão.
  • Validação de host header na admin API (defesa contra DNS rebinding).
  • Header custom obrigatório em endpoints admin mutantes (X-OperatorLM-Admin).
  • Sem CORS por padrão.
  • Auth local opcional — ative uma API key local pelo admin UI para restringir acesso em máquinas compartilhadas.
  • Redação no auditAuthorization e outros headers sensíveis são sempre redatados antes de irem para o audit log.

Estrutura do repositório

internal/
  config/      # TOML + integração com OS keyring
  providers/   # providers de nuvem (openai · anthropic · gemini · azure · …) +
               #   motor local embutido (llama.cpp · whisper · piper) + catálogo/downloader
  router/      # alias resolver · retry · circuit breaker · rate limiter
  server/      # handlers HTTP + admin UI embutido (web/)
  audit/       # audit logger JSONL não-bloqueante
  update/      # auto-update OTA pelo GitHub Releases (verificado por SHA-256)
  tray/        # system tray cross-platform
main.go        # entrypoint

A codebase é pequena o bastante para ser auditada numa tarde. Esse é o ponto.


Status e contribuição

Projeto pessoal, liberado como está — mas ativamente usado e mantido.

Se o OperatorLM te economiza tempo ou facilita o seu setup, uma ⭐ no GitHub é o "obrigado" mais gentil e ajuda outros devs a encontrarem o projeto.

Bug reports, pull requests e integrações de providers são muito bem-vindos.

Licença: MIT