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.
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:
- Plugins embutidos —
<repo>/plugins/model-providers/<name>/— vêm com o Hermes - Plugins do usuário —
$HERMES_HOME/plugins/model-providers/<name>/— largue um diretório qualquer; não é preciso reiniciar para as sessões seguintes - 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ção | Onde | O que ela ganha |
|---|---|---|
| Resolução de credenciais | hermes_cli/auth.py | PROVIDER_REGISTRY["acme-inference"] preenchido a partir do perfil |
Opção --provider da CLI | hermes_cli/main.py | Aceita acme-inference |
Seletor do hermes model | hermes_cli/models.py | Aparece em CANONICAL_PROVIDERS, com a lista de modelos buscada em {base_url}/models |
hermes doctor | hermes_cli/doctor.py | Verificação de saúde de ACME_API_KEY + sondagem de {base_url}/models |
hermes setup | hermes_cli/config.py | ACME_API_KEY aparece em OPTIONAL_ENV_VARS e no assistente de configuração |
| Mapeamento reverso de URL | agent/model_metadata.py | Nome de host → nome do provedor, para detecção automática |
| Modelo auxiliar | agent/auxiliary_client.py | Usa default_aux_model para compressão / sumarização |
| Resolução de runtime | hermes_cli/runtime_provider.py | Devolve base_url, api_key e api_mode corretos |
| Transporte | agent/transports/chat_completions.py | O 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:
| Campo | Tipo | Para que serve |
|---|---|---|
name | str | Id canônico — corresponde a model.provider no config.yaml e à opção --provider |
aliases | tuple[str, ...] | Nomes alternativos resolvidos por get_provider_profile() (por exemplo, grok → xai) |
api_mode | str | chat_completions | codex_responses | anthropic_messages | bedrock_converse |
display_name | str | Rótulo legível mostrado no seletor do hermes model |
description | str | Subtítulo no seletor |
signup_url | str | Mostrado na configuração de primeira execução ("pegue uma chave de API aqui") |
env_vars | tuple[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_url | str | Endpoint de inferência padrão |
models_url | str | URL explícita do catálogo (recorre a {base_url}/models) |
auth_type | str | api_key | oauth_device_code | oauth_external | copilot | aws_sdk | external_process |
fallback_models | tuple[str, ...] | Lista curada mostrada quando a busca do catálogo ao vivo falha |
default_headers | dict[str, str] | Enviados em toda requisição (por exemplo, o Editor-Version do Copilot) |
fixed_temperature | Any | None = usar o valor de quem chamou; a sentinela OMIT_TEMPERATURE = não enviar temperatura nenhuma (Kimi) |
default_max_tokens | int | None | Teto de max_tokens no nível do provedor (Nvidia: 16384) |
default_aux_model | str | Modelo 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:
| Plugin | Por 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:
- Sobreposição explícita do usuário (
model.api_modenoconfig.yaml, quando definido) - Despacho por modelo do OpenCode (
opencode_model_api_modepara o Zen e o Go) - Detecção automática por URL — sufixo
/anthropic→anthropic_messages,api.openai.com→codex_responses,api.x.ai→codex_responses,/codingem domínios do Kimi →chat_completions - O
api_modedo perfil, como alternativa quando a detecção por URL não encontra nada - 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_type | Significado | Quem usa |
|---|---|---|
api_key | Uma única variável de ambiente carrega uma chave de API estática | A maioria dos provedores |
oauth_device_code | Fluxo OAuth por código de dispositivo | — |
oauth_external | O usuário entra em outro lugar e os tokens caem no auth.json | Anthropic OAuth, MiniMax OAuth, Qwen Portal, Nous Portal |
copilot | Ciclo de renovação de token do GitHub Copilot | só o plugin copilot |
aws_sdk | Cadeia de credenciais do SDK da AWS (papel IAM, perfil, ambiente) | só o plugin bedrock |
external_process | Autenticação tratada por um subprocesso que o agente cria | só 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 chamarregister_provider(profile), ou - um módulo puro (
module) — importado pelo efeito colateral deregister_provider(...)no nível do módulo, espelhando o contrato do__init__.pydos 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çãoplugins.disabled) doconfig.yamlcomanda essa varredura. Um pacote pip nunca é importado só por estar instalado — os usuários precisam acrescentar o nome do entry point aplugins.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_HOMEcom 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.
Páginas relacionadas
- Runtime de provedores — precedência de resolução + onde cada camada lê o perfil
- Adicionar provedores — lista de verificação ponta a ponta para novos backends de inferência (cobre tanto o caminho rápido por plugin quanto a integração completa com CLI/autenticação)
- Plugins de Provedor de Memória
- Plugins de Motor de Contexto
- Construindo um plugin do Hermes — autoria de plugins em geral