Manifest: Gateway de IA

Saudações.

Apresento aos senhores um software excelente para integrar todas as contas de IA em um único ponto de contato de acesso à APIs LLM.

Links:

1 – O que é o Manifest

O manifest é um software, administrado pela web (navegador), para concentrar todas as suas assinaturas e APIs de IA num único sistema de roteamento – um gateway de APIs.

1.1 – Problema: Custos

O principal problema que ele resolve é custos.

Meu caso: Eu assino Claude Code e é decepcionante quando os créditos acabam pois eu desejo continuar o projeto usando o modelo OPUS no máximo mas não quero comprar a assinatura máxima. Se eu acionar sub-agentes para tarefas enormes os créditos desaparecem em minutos.

Quando isso ocorre eu tenho que ir para o OpenCode, usando a API que é MUITO MAIS CARA do que a assinatura considerando meu consumo de Mtok (mega tokens) por mês.

Pesquisando sobre uma forma de unir várias assinaturas do Claude num único sistema e apontar meu agente (Claude Code) para ele, encontrei dois softwares:

  • LiteLLM – O mais completo de todos, um pouco complicado de alinhar mas sem dúvida a melhor opção para empresas;
  • Manifest – O mais simples do mundo, une todos os serviços e se parece muito com o LiteLLM em funcionalidades, é mais bonito e muito amistoso, ótimo para uso individual e pequenas equipes.

Com eles várias contas podem ser aproveitas para contornar o “use-it-or-lose-it” (estude sobre non-rollover quota ou non-cumulative allowance), o que você não usa durante semanas não serve de crédito para a semana seguinte.

A única forma de economizar é gastar o máximo de cada janela.

Exemplos de uso prático:

  • Se uma empresa paga o assento do Claude plano Enterprise para cada funcionário, juntar todos os assentos numa única super-conta virtual permite balancear o consumo de créditos pelas contas;
  • Amigos podem se unir numa conta do manifest para alternar (cada dia um) o uso da super conta;

1.2 – Problema: RateLimit

Se a janela de créditos não for violada, isso não significa que você pode usar com paralelismo de agentes e sub-agentes, os provedores de IA implementam rate limiting impondo quantas requisições por segundo você pode utilizar na sua conta.

Isso é particularmente irritante, causa demoras na entrega, causam erros sem fallback (o agente para de trabalhar) – processos falham.

Já que sua conta está bloqueada por segundos ou minutos até que a janela de rate limit seja reiniciada, a melhor forma de resolver isso é:

  • Balanceamento: cada requisição pode ser enviada para contas diferentes, para alcançar 100 req/s numa API que limita a 10 req/s você precisa de 10 contas;
  • Redundância: quando uma API retornar erro de rate limit, ele utiliza outra conta até o desbloqueio da conta principal.

1.3 – Problema: Billing

Nem só de OPUS e GPT vive o agente de IA, modelos pequenos como GPT-OSS-120b são muito baratos e extremamente capazes de lidar com tarefas simples (contexto pequeno, poucas tools, prompt pontual) sem errar.

O roteamento baseado em cobrança visa usar APIs de provedores que cobram barato primeiro, e alternam entre APIs buscando sempre o menor custo por Mtok.

Se um provedor como o OpenCode Zen resolve dar um modelo gratuitamente por uma semana, melhor usar ele primeiro (custo $0), se ele der erro a requisição é repetida pelo gateway Manifest para outra API que tenha o mesmo modelo com custo mínimo ($ 0.002).

Considerar o custo cria o fallback baseado em custo.

2 – Preparativos

Vamos rodar o Manifest em Docker e preparar o ambiente.

2.1 – Rede

Crie a rede Docker “network_public“:

Bash
# Rede de containers
docker network create network_public \
    -d bridge \
    -o com.docker.network.bridge.name=br-net-public \
    -o com.docker.network.driver.mtu=1500 \
    -o com.docker.network.bridge.gateway_mode_ipv4=nat-unprotected \
    --subnet 10.249.0.0/16 \
    --gateway 10.249.255.254;

2.2 – Acesso HTTPs

Você precisa usar HTTPs para que o Manifest forneça uma API para seus agentes e ferramentas de IA.

Instale o Traefik e configura o DNS, vou usar o FQDN “manifest.dominio.com” nos exemplos, troque-o pelo nome que você apontou para o IP do servidor.

2.3 – Banco de dados Postgres

Vou criar o banco de dados PostgreSQL central, fora da stack do Manifest.

Vou usar a imagem pgvector (debian trixie + postgres 18 + pgvector).

O nome do serviço será “pgvector-main” e as credenciais iniciais serão exclusivas do administrador (user postgres).

Bash

# Variaveis
    NAME="pgvector-main";

# Argumentos do postgres (usado somente no primeiro boot)
    POSTGRES_USER="postgres";
    POSTGRES_PASSWORD="tulipasql";
    POSTGRES_DB="admin";

# Imagem
    IMAGE="pgvector/pgvector:pg18-trixie";
    docker pull $IMAGE;

# Volume
    # obs: pg18 usa pasta principal interna: /var/lib/postgresql
    DATADIR=/storage/$NAME;
    mkdir -p          $DATADIR;
    chown -R 999:999  $DATADIR;

# Renovar/rodar:
    docker rm -f $NAME 2>/dev/null;
    docker run \
        -d \
        --restart            always \
        --name               $NAME \
        --hostname           $NAME.intranet.br \
        \
        --cpus                2.0 \
        --cpu-shares         1024 \
        --memory               2g \
        --memory-swap          2g \
        --memory-reservation   1g \
        --shm-size             1g \
        \
        --network            network_public \
        --ip                 10.249.130.1 \
        --ip6                2001:db8:10:249::130:1 \
        --mac-address        02:cf:f1:30:00:01 \
        \
        --tmpfs              /run:rw,noexec,nosuid,size=64m \
        --tmpfs              /tmp:rw,exec,suid,size=64m \
        \
        -v $DATADIR:/var/lib/postgresql \
        \
        -e POSTGRES_USER=$POSTGRES_USER \
        -e POSTGRES_PASSWORD=$POSTGRES_PASSWORD \
        -e POSTGRES_DB=$POSTGRES_DB \
        -e PGDATA=/var/lib/postgresql/data/pgdata \
        \
        --health-cmd="pg_isready -U postgres" \
        --health-interval=5s \
        --health-timeout=5s \
        --health-retries=10 \
        \
        --entrypoint "docker-entrypoint.sh" \
        \
        $IMAGE \
            postgres \
                --timezone=America/Sao_Paulo \
                --log_timezone=America/Sao_Paulo \
                --max_connections=150 \
                --superuser_reserved_connections=3 \
                --shared_buffers=384MB \
                --effective_cache_size=768MB \
                --work_mem=4MB \
                --maintenance_work_mem=96MB \
                --temp_buffers=8MB \
                --wal_buffers=16MB \
                --min_wal_size=512MB \
                --max_wal_size=2GB \
                --checkpoint_completion_target=0.9 \
                --checkpoint_timeout=10min \
                --wal_compression=on \
                --wal_level=replica \
                --random_page_cost=1.1 \
                --effective_io_concurrency=200 \
                --default_statistics_target=100 \
                --max_worker_processes=4 \
                --max_parallel_workers=2 \
                --max_parallel_workers_per_gather=1 \
                --max_parallel_maintenance_workers=2 \
                --autovacuum=on \
                --autovacuum_max_workers=2 \
                --autovacuum_naptime=30s \
                --autovacuum_vacuum_threshold=50 \
                --autovacuum_vacuum_scale_factor=0.05 \
                --autovacuum_analyze_threshold=50 \
                --autovacuum_analyze_scale_factor=0.05 \
                --autovacuum_vacuum_cost_delay=2ms \
                --autovacuum_vacuum_cost_limit=400 \
                --statement_timeout=120000 \
                --idle_in_transaction_session_timeout=300000 \
                --lock_timeout=30000 \
                --log_destination=stderr \
                --logging_collector=off \
                --log_min_duration_statement=2000 \
                --log_line_prefix="%t [%p] %u@%d " \
                --log_checkpoints=on \
                --log_connections=off \
                --log_disconnections=off \
                --log_lock_waits=on \
                --log_temp_files=10240 \
                --log_autovacuum_min_duration=1000 \
                --shared_preload_libraries=pg_stat_statements \
                --track_activities=on \
                --track_counts=on \
                --track_io_timing=on \
                --track_functions=pl \
                --ssl=off \
                --port=5432 \
                --listen_addresses="*" \
                --max_wal_senders=3 \
                --hot_standby=on;

2.4 – Criar banco de dados do Manifest

Primeiro, entre no container do pgvector-main direto no shell do Postgres (PSQL):

Bash
# Entrar no container e depois no psql com usuario admin 'postgres'
# Alternativa:
#    docker exec -it pgvector-main bash
#        psql -U postgres
docker exec -it pgvector-main bash -c 'psql -U postgres';

Uma vez no terminal PSQL, vamos criar a conta de usuário do “manifest” e o banco de dados “manifest“:

pgvector-main => psql >_
-- Criar usuario:
    CREATE USER manifest
        WITH PASSWORD 'tulipasql'
        LOGIN;

-- Criar banco de dados
    CREATE DATABASE manifest
        WITH 
        OWNER = manifest
        ENCODING = 'UTF8'
        TABLESPACE = pg_default
        IS_TEMPLATE = False
        CONNECTION LIMIT = -1;
      
-- Conectar no banco:
    \c manifest;

-- Adicionar extensao stats (opcional):
    CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- Conceder todos os privilégios no banco 'manifest' para o usuario 'manifest'
    GRANT ALL PRIVILEGES ON DATABASE manifest TO manifest;

-- Conceder privilégios no schema público
    GRANT ALL ON SCHEMA public TO manifest;

-- Garantir privilégios em tabelas futuras
    ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES    TO manifest;
    ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO manifest;
    ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON FUNCTIONS TO manifest;

-- Verificar se o usuário foi criado
    \du manifest;

-- Verificar se o banco foi criado
    \l manifest;

-- Verificar privilégios no banco
    \l+ manifest;

-- Sair
    \q

2.5 – Instalando o Manifest

Gere uma chave secreta para a criptografia interna do Manifest:

Bash
# Gerar chave alteatoria de 16 digitos (128 bits de seguranca):
openssl rand -hex 16;
    # 31fa0954c638cea5db263aad8a488357

# A chave preenche a variavel de ambiente $BETTER_AUTH_SECRET
BETTER_AUTH_SECRET="31fa0954c638cea5db263aad8a488357";

Rodando container Manifest chamado “manifest“:

Bash
# Variaveis
    NAME="manifest";
    FQDN="manifest.dominio.com";
    IMAGE="manifestdotbuild/manifest:latest";

# Acesso ao banco de dados
    # Formato: postgresql://user:senha@endereco_servidor:porta/banco_de_dados
    DATABASE_URL="postgresql://manifest:tulipasql@pgvector-main:5432/manifest";

# Variaveis do manifest
    PORT=2099;
    BETTER_AUTH_URL="https://$FQDN";
    SEED_DATA=false;
    NODE_ENV=production;
    MANIFEST_MODE=selfhosted;
    BETTER_AUTH_SECRET="31fa0954c638cea5db263aad8a488357";

# Atualizar imagem
    docker pull $IMAGE;

# Renovar/rodar:
    docker container  stop  $NAME 2>/dev/null;
    docker container  rm    $NAME 2>/dev/null;
    docker container  run \
        -d \
        --user               root \
        --restart            always \
        --name               $NAME \
        --hostname           $NAME.intranet.br \
        \
        --cpus                4.0  \
        --memory               2g \
        --memory-swap          2g \
        --memory-reservation   1g \
        --shm-size             1g \
        \
        --network            network_public \
        --ip                 10.249.91.1 \
        \
        -p 2099:2099   \
        \
        --tmpfs /run:rw,noexec,nosuid,size=512m \
        --tmpfs /tmp:rw,exec,suid,mode=1777,dev,size=512m \
        \
        -e DATABASE_URL=$DATABASE_URL \
        -e PORT=$PORT \
        -e BETTER_AUTH_SECRET=$BETTER_AUTH_SECRET \
        -e BETTER_AUTH_URL=$BETTER_AUTH_URL \
        -e SEED_DATA=$SEED_DATA \
        -e NODE_ENV=$NODE_ENV \
        -e MANIFEST_MODE=$MANIFEST_MODE \
        \
        -e OLLAMA_HOST="http://host.docker.internal:11434" \
        -e PROVIDER_TIMEOUT_MS=180000 \
        -e STREAM_WARMUP_MS=15000 \
        -e MANIFEST_ENCRYPTION_KEY="" \
        -e EMAIL_PROVIDER="" \
        -e EMAIL_API_KEY="" \
        -e EMAIL_DOMAIN="" \
        -e EMAIL_FROM="" \
        -e MANIFEST_TELEMETRY_DISABLED=0 \
        \
        -e GOOGLE_CLIENT_ID="" \
        -e GOOGLE_CLIENT_SECRET="" \
        -e GITHUB_CLIENT_ID="" \
        -e GITHUB_CLIENT_SECRET="" \
        -e DISCORD_CLIENT_ID="" \
        -e DISCORD_CLIENT_SECRET="" \
        \
        --label "traefik.enable=true" \
        --label "traefik.http.routers.$NAME.rule=Host(\`$FQDN\`)" \
        --label "traefik.http.routers.$NAME.entrypoints=web,websecure" \
        --label "traefik.http.routers.$NAME.tls=true" \
        --label "traefik.http.routers.$NAME.tls.certresolver=letsencrypt" \
        --label "traefik.http.services.$NAME.loadbalancer.server.port=$PORT" \
        \
        $IMAGE;

# Acesso no navegador:
    echo;
    echo "Acesso ao Manifest:";
    echo "   Web.......:   https://$FQDN";
    echo;

Se você ja tem o banco de dados como serviço e quiser subir apenas o Manifest via Compose, arquivo YML:

YAML – manifest-docker-compose.yml
# Ancoras com variaveis organizadas
x-env-postgres: &env-postgres
  DATABASE_URL: "postgresql://manifest:tulipasql@pgvector-main:5432/manifest"

x-env-manifest: &env-manifest
  PORT: 2099
  BETTER_AUTH_SECRET: "31fa0954c638cea5db263aad8a488357"
  BETTER_AUTH_URL: "https://manifest.dominio.com"
  SEED_DATA: "false"
  NODE_ENV: production
  MANIFEST_MODE: selfhosted
  OLLAMA_HOST: "http://host.docker.internal:11434"
  PROVIDER_TIMEOUT_MS: 180000
  STREAM_WARMUP_MS: 15000
  MANIFEST_ENCRYPTION_KEY: ""
  EMAIL_PROVIDER: ""
  EMAIL_API_KEY: ""
  EMAIL_DOMAIN: ""
  EMAIL_FROM: ""
  MANIFEST_TELEMETRY_DISABLED: 0
  GOOGLE_CLIENT_ID: ""
  GOOGLE_CLIENT_SECRET: ""
  GITHUB_CLIENT_ID: ""
  GITHUB_CLIENT_SECRET: ""
  DISCORD_CLIENT_ID: ""
  DISCORD_CLIENT_SECRET: ""

# Redes
networks:
  network_public:
    external: true

# Servico manifest
services:
  manifest:
    image: manifestdotbuild/manifest:latest
    container_name: manifest
    hostname: manifest.intranet.br
    user: root
    restart: always

    deploy:
      resources:
        limits:
          cpus: "4.0"
          memory: 2g
        reservations:
          memory: 1g

    shm_size: 1g

    networks:
      network_public:

    ports:
      - "2099:2099"

    tmpfs:
      - /run:rw,noexec,nosuid,size=512m
      - /tmp:rw,exec,suid,mode=1777,size=512m

    environment:
      <<: *env-postgres
      <<: *env-manifest

    labels:
      traefik.enable: "true"
      traefik.http.routers.manifest.rule: "Host(`manifest.dominio.com`)"
      traefik.http.routers.manifest.entrypoints: "web,websecure"
      traefik.http.routers.manifest.tls: "true"
      traefik.http.routers.manifest.tls.certresolver: "letsencrypt"
      traefik.http.services.manifest.loadbalancer.server.port: "2099"

Acesse no navegador: https://manifest.dominio.com/

.

(ainda vou fazer o capitulo de configuração básica)

.

Terminamos por hoje!

Patrick Brandão, patrickbrandao@gmail.com

Aquilo a que chamamos acaso não é,
não pode deixar de ser,
senão a causa ignorada de um efeito conhecido.
Voltaire