Pular para o conteúdo principal

Documentação oficial desta página · v0.20.6

Construindo um plugin de provedor de modelo

Plugins de provedor de modelo declaram um backend de inferência — um endpoint compatível com a OpenAI, um servidor Anthropic Messages, uma API Responses no estilo do Codex ou uma superfície nativa do Bedrock — pelo qual o Hermes pode rotear as chamadas do AIAgent. Todo provedor embutido (OpenRouter, Anthropic, GMI, DeepSeek, Nvidia, …) vem como um desses plugins. Terceiros podem acrescentar os seus próprios largando um diretório em $HERMES_HOME/plugins/model-providers/, sem nenhuma mudança no repositório.

dica

Plugins de provedor de modelo são o terceiro tipo de plugin de provedor. Os outros são os Plugins de Provedor de Memória (conhecimento entre sessões) e os Plugins de Motor de Contexto (estratégias de compressão de contexto). Os três seguem o mesmo padrão de "largue um diretório, declare um perfil, sem editar o repositório".

Como funciona a descoberta​

O providers/__init__.py._discover_providers() roda de forma preguiçosa na primeira vez em que algum código chama get_provider_profile() ou list_providers(). Ordem de descoberta:

  1. Plugins embutidos — <repo>/plugins/model-providers/<name>/ — vêm com o Hermes
  2. Plugins do usuário — $HERMES_HOME/plugins/model-providers/<name>/ — largue um diretório qualquer; não é preciso reiniciar para as sessões seguintes
  3. Arquivo único legado — <repo>/providers/<name>.py — compatibilidade com instalações editáveis fora da árvore

Os plugins do usuário substituem os plugins embutidos de mesmo nome, porque o register_provider() funciona no modo "quem escreve por último vence". Largue um diretório $HERMES_HOME/plugins/model-providers/gmi/ para substituir o perfil GMI embutido sem tocar no repositório.

Estrutura de diretórios​

plugins/model-providers/my-provider/
├── __init__.py # Chama register_provider(profile) no nível do módulo
├── plugin.yaml # kind: model-provider + metadados (opcional, mas recomendado)
└── README.md # Instruções de configuração (opcional)

O único arquivo obrigatório é o __init__.py. O plugin.yaml é usado pelo hermes plugins para introspecção e pelo PluginManager geral para direcionar o plugin ao carregador certo; sem ele, o carregador geral recorre a uma heurística sobre o texto do código-fonte.

Exemplo mínimo — um provedor simples com chave de API​

# plugins/model-providers/acme-inference/__init__.py
from providers import register_provider
from providers.base import ProviderProfile

acme = ProviderProfile(
name="acme-inference",
aliases=("acme",),
display_name="Acme Inference",
description="Acme — OpenAI-compatible direct API",
signup_url="https://acme.example.com/keys",
env_vars=("ACME_API_KEY", "ACME_BASE_URL"),
base_url="https://api.acme.example.com/v1",
auth_type="api_key",
default_aux_model="acme-small-fast",
fallback_models=(
"acme-large-v3",
"acme-medium-v3",
"acme-small-fast",
),
)

register_provider(acme)
# plugins/model-providers/acme-inference/plugin.yaml
name: acme-inference
kind: model-provider
version: 1.0.0
description: Acme Inference — OpenAI-compatible direct API
author: Seu Nome

É só isso. Depois de largar esses dois arquivos, os itens a seguir se ligam sozinhos, sem nenhuma outra edição:

IntegraçãoOndeO que ela ganha
Resolução de credenciaishermes_cli/auth.pyPROVIDER_REGISTRY["acme-inference"] preenchido a partir do perfil
Opção --provider da CLIhermes_cli/main.pyAceita acme-inference
Seletor do hermes modelhermes_cli/models.pyAparece em CANONICAL_PROVIDERS, com a lista de modelos buscada em {base_url}/models
hermes doctorhermes_cli/doctor.pyVerificação de saúde de ACME_API_KEY + sondagem de {base_url}/models
hermes setuphermes_cli/config.pyACME_API_KEY aparece em OPTIONAL_ENV_VARS e no assistente de configuração
Mapeamento reverso de URLagent/model_metadata.pyNome de host → nome do provedor, para detecção automática
Modelo auxiliaragent/auxiliary_client.pyUsa default_aux_model para compressão / sumarização
Resolução de runtimehermes_cli/runtime_provider.pyDevolve base_url, api_key e api_mode corretos
Transporteagent/transports/chat_completions.pyO caminho do perfil gera os kwargs via prepare_messages / build_extra_body / build_api_kwargs_extras

Campos do ProviderProfile​

Definição completa em providers/base.py. Os mais úteis:

CampoTipoPara que serve
namestrId canônico — corresponde a model.provider no config.yaml e à opção --provider
aliasestuple[str, ...]Nomes alternativos resolvidos por get_provider_profile() (por exemplo, grok → xai)
api_modestrchat_completions | codex_responses | anthropic_messages | bedrock_converse
display_namestrRótulo legível mostrado no seletor do hermes model
descriptionstrSubtítulo no seletor
signup_urlstrMostrado na configuração de primeira execução ("pegue uma chave de API aqui")
env_varstuple[str, ...]Variáveis de ambiente da chave de API, em ordem de prioridade; uma entrada final *_BASE_URL é usada como sobreposição de URL base pelo usuário
base_urlstrEndpoint de inferência padrão
models_urlstrURL explícita do catálogo (recorre a {base_url}/models)
auth_typestrapi_key | oauth_device_code | oauth_external | copilot | aws_sdk | external_process
fallback_modelstuple[str, ...]Lista curada mostrada quando a busca do catálogo ao vivo falha
default_headersdict[str, str]Enviados em toda requisição (por exemplo, o Editor-Version do Copilot)
fixed_temperatureAnyNone = usar o valor de quem chamou; a sentinela OMIT_TEMPERATURE = não enviar temperatura nenhuma (Kimi)
default_max_tokensint | NoneTeto de max_tokens no nível do provedor (Nvidia: 16384)
default_aux_modelstrModelo barato para tarefas auxiliares (compressão, visão, sumarização)

Hooks sobreponíveis​

Faça uma subclasse de ProviderProfile para peculiaridades não triviais:

from typing import Any
from providers.base import ProviderProfile

class AcmeProfile(ProviderProfile):
def prepare_messages(self, messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""Pré-processamento de mensagens específico do provedor. Roda depois da
sanitização do codex e antes da troca do papel developer. Padrão: repassa igual."""
# Exemplo: o Qwen normaliza conteúdo em texto puro para um array de partes
# e injeta cache_control; o Kimi reescreve o JSON das chamadas de ferramenta
return messages

def build_extra_body(self, *, session_id=None, **context) -> dict:
"""Campos de extra_body específicos do provedor, mesclados na chamada de API.
O contexto inclui: session_id, provider_preferences, model, base_url,
reasoning_config. Padrão: dicionário vazio."""
# Exemplo: o bloco de provider-preferences do OpenRouter,
# a tradução do thinking_config do Gemini.
return {}

def build_api_kwargs_extras(self, *, reasoning_config=None, **context):
"""Devolve (extra_body_additions, top_level_kwargs). Necessário quando alguns
campos vão no nível superior (o reasoning_effort do Kimi, o verbosity do
OpenRouter para modelos Anthropic adaptativos) e outros vão no extra_body (o
dicionário reasoning do OpenRouter). Padrão: ({}, {})."""
return {}, {}

def fetch_models(self, *, api_key=None, base_url=None, timeout=8.0) -> list[str] | None:
"""Busca do catálogo ao vivo. Por padrão bate em {models_url ou base_url}/models
com autenticação Bearer. Sobreponha para: autenticação personalizada (Anthropic),
ausência de endpoint REST (Bedrock → None) ou catálogos públicos/sem autenticação
(OpenRouter)."""
return super().fetch_models(api_key=api_key, base_url=base_url, timeout=timeout)

Exemplos de referência dos hooks​

Olhe estes plugins embutidos para pegar os idiomas:

PluginPor que olhar
plugins/model-providers/openrouter/Agregador com preferências de provedor, catálogo público de modelos
plugins/model-providers/gemini/Tradução de thinking_config (formas nativa e aninhada compatível com OpenAI)
plugins/model-providers/kimi-coding/OMIT_TEMPERATURE, extra_body.thinking, reasoning_effort no nível superior
plugins/model-providers/qwen-oauth/Normalização de mensagens, injeção de cache_control, VL em alta resolução
plugins/model-providers/nous/Marcadores de atribuição, "omitir raciocínio quando desativado"
plugins/model-providers/custom/Peculiaridades do Ollama: num_ctx + think: false
plugins/model-providers/bedrock/api_mode="bedrock_converse", fetch_models devolve None (sem endpoint REST)

Sobreposições do usuário — troque um embutido sem editar o repositório​

Digamos que você queira apontar o gmi para o seu endpoint privado de homologação, para testes. Crie o ~/.hermes/plugins/model-providers/gmi/__init__.py:

from providers import register_provider
from providers.base import ProviderProfile

register_provider(ProviderProfile(
name="gmi",
aliases=("gmi-cloud", "gmicloud"),
env_vars=("GMI_API_KEY",),
base_url="https://gmi-staging.internal.example.com/v1",
auth_type="api_key",
default_aux_model="google/gemini-3.1-flash-lite-preview",
))

Na próxima sessão, o get_provider_profile("gmi").base_url devolve a URL de homologação. Sem remendo no repositório, sem recompilar. Como os plugins do usuário são descobertos depois dos embutidos, a chamada de register_provider() do usuário vence.

Escolha do api_mode​

Quatro valores são reconhecidos. O Hermes escolhe um com base em:

  1. Sobreposição explícita do usuário (model.api_mode no config.yaml, quando definido)
  2. Despacho por modelo do OpenCode (opencode_model_api_mode para o Zen e o Go)
  3. Detecção automática por URL — sufixo /anthropic → anthropic_messages, api.openai.com → codex_responses, api.x.ai → codex_responses, /coding em domínios do Kimi → chat_completions
  4. O api_mode do perfil, como alternativa quando a detecção por URL não encontra nada
  5. O padrão chat_completions

Defina o profile.api_mode para casar com o padrão que o seu provedor entrega — ele funciona como uma dica. As sobreposições de URL do usuário continuam vencendo.

Tipos de autenticação​

auth_typeSignificadoQuem usa
api_keyUma única variável de ambiente carrega uma chave de API estáticaA maioria dos provedores
oauth_device_codeFluxo OAuth por código de dispositivo—
oauth_externalO usuário entra em outro lugar e os tokens caem no auth.jsonAnthropic OAuth, MiniMax OAuth, Qwen Portal, Nous Portal
copilotCiclo de renovação de token do GitHub Copilotsó o plugin copilot
aws_sdkCadeia de credenciais do SDK da AWS (papel IAM, perfil, ambiente)só o plugin bedrock
external_processAutenticação tratada por um subprocesso que o agente criasó o plugin copilot-acp

O auth_type determina quais caminhos de código tratam o seu provedor como um "provedor simples com chave de API" — se não for api_key, o PluginManager ainda registra o manifesto, mas as automações do Hermes no nível da CLI (verificações do doctor, opção --provider, delegação no assistente de configuração) podem passar por cima dele.

Momento da descoberta​

A descoberta de provedores é preguiçosa — ela é disparada pela primeira chamada a get_provider_profile() ou list_providers() no processo. Na prática isso acontece cedo, na inicialização (o carregamento do módulo auth.py estende o PROVIDER_REGISTRY de imediato). Se você precisa confirmar que o seu plugin carregou, rode:

hermes doctor

— um perfil com auth_type="api_key" bem-sucedido aparece na seção Provider Connectivity com uma sondagem de /models.

Para inspeção programática:

from providers import list_providers
for p in list_providers():
print(p.name, p.base_url, p.api_mode)

Testando o seu plugin​

Aponte o HERMES_HOME para um diretório temporário, para não sujar a sua configuração de verdade:

export HERMES_HOME=/tmp/hermes-plugin-test
mkdir -p $HERMES_HOME/plugins/model-providers/my-provider
cat > $HERMES_HOME/plugins/model-providers/my-provider/__init__.py <<'EOF'
from providers import register_provider
from providers.base import ProviderProfile
register_provider(ProviderProfile(
name="my-provider",
env_vars=("MY_API_KEY",),
base_url="https://api.my-provider.example.com/v1",
auth_type="api_key",
))
EOF

export MY_API_KEY=sua-chave-de-teste
hermes -z "hello" --provider my-provider -m some-model

Integração com o PluginManager geral​

O PluginManager geral (aquilo com que o hermes plugins trabalha) enxerga os plugins de provedor de modelo, mas não os importa — o ciclo de vida deles pertence ao providers/__init__.py. O gerenciador registra o manifesto para introspecção e categoriza por kind: model-provider. Quando você larga um plugin de usuário sem rótulo em $HERMES_HOME/plugins/ que por acaso chama register_provider com um ProviderProfile, o gerenciador o converte automaticamente para kind: model-provider por uma heurística sobre o texto do código-fonte — então o plugin continua sendo roteado direito mesmo sem plugin.yaml.

Distribuir via pip​

Provedores de modelo podem ser entregues como pacote pip. Exponha um entry point no grupo hermes_agent.plugins do seu pyproject.toml:

[project.entry-points."hermes_agent.plugins"]
acme-inference = "acme_hermes_plugin:register"

O alvo pode ser:

  • um chamável (module:func) — invocado sem argumentos; ele deve chamar register_provider(profile), ou
  • um módulo puro (module) — importado pelo efeito colateral de register_provider(...) no nível do módulo, espelhando o contrato do __init__.py dos plugins em diretório.

O providers/__init__.py descobre esses entry points por conta própria — o PluginManager geral nunca dispara o registro de provedor para pacotes pip (o caminho de entry point dele mira plugins gerais no estilo register(ctx), sujeitos a plugins.enabled), então o registro de provedores faz a própria varredura. Valem duas regras:

  • Adesão obrigatória. A mesma lista de permissão plugins.enabled (e a lista de negação plugins.disabled) do config.yaml comanda essa varredura. Um pacote pip nunca é importado só por estar instalado — os usuários precisam acrescentar o nome do entry point a plugins.enabled:

    plugins:
    enabled:
    - acme-inference
  • Precedência mais baixa. Os plugins de entry point são descobertos antes dos plugins de sistema de arquivos: como o register_provider() funciona no modo "quem escreve por último vence", um perfil embutido ou do $HERMES_HOME com o mesmo nome sempre sobrepõe um instalado por pip. Um pacote pip pode acrescentar um provedor genuinamente novo, mas não pode sequestrar em silêncio o nome de um provedor oficial.

Alvos que exigem argumentos (o register(ctx) de um plugin geral) são pulados pela varredura de provedores — eles pertencem ao PluginManager. Um entry point quebrado fica isolado — ele é registrado em nível de aviso e pulado, e nunca bloqueia a descoberta dos outros provedores.

Veja Construindo um plugin do Hermes para a configuração completa de entry points.