AI Hub

Broker BYOK (traga sua própria chave) para o Moodle — documentação completa.

English | Português

View on GitHub Download .zip

Moodle License Status Latest Release PlayerGames Ecosystem Role Author

Moodle Plugin CI Last Commit Open Issues

A Central de IA é um pequeno intermediário BYOK (traga sua própria chave) para o Moodle. Ela permite que os plugins da própria instituição gerem texto através de chaves de API de IA compartilhadas, sem que cada plugin reimplemente o transporte HTTP, o guard de SSRF, a escada de provedores ou um armazenamento de chaves.

👈 Use a barra lateral para pular direto a qualquer seção desta página.


✨ Funcionalidades

🧩 Como os consumidores usam

Um plugin irmão consome o hub através de um guard em runtime e mantém o próprio fallback para core_ai:

// Disponibilidade: chaves do hub primeiro, depois o fallback core_ai do consumidor.
public static function has_ai(): bool {
    if (class_exists(\local_aihub\ai::class) && \local_aihub\ai::is_available()) {
        return true;                       // BYOK via hub
    }
    return self::has_core_ai();            // funciona sem o hub
}

// Geração.
$result = \local_aihub\ai::generate_text(
    '',                    // prompt de sistema (opcional)
    $prompt,               // prompt do usuário
    false,                 // modo JSON
    'your_frankenstyle',   // 4º arg: componente chamador, para o log de uso
    'Rótulo curto'         // 5º arg: o que está sendo gerado, exibido no relatório de admin
);
if ($result['success']) {
    // $result['data'] é texto CRU e não-confiável — valide e passe por format_text().
}

O texto devolvido é cru e não-confiável: valide sua estrutura e passe por format_text() antes de exibir ou persistir.

Duas observações sobre o retorno. O texto gerado fica em data, e o motivo da falha em message — ler o array como objeto devolve null sem erro nenhum, o que transforma todo sucesso num fallback silencioso. E uma chamada pode gerar várias linhas de log: o hub registra cada provedor que tentou, sob o componente e o rótulo que você passou, então uma requisição que caiu para um segundo provedor aparece duas vezes no relatório, uma com falha e outra com sucesso.

🔗 Resolução de chave (escada BYOK)

O hub resolve a chave tier por tier e para no primeiro tier que tiver uma chave:

Tier Fonte
1 Chave pessoal — a chave do próprio usuário, quando as chaves pessoais estão habilitadas e o usuário tem local/aihub:usepersonalkey
2 Chave de site — a chave de admin definida nas configurações do hub

Dentro do tier escolhido, os provedores são tentados na ordem Gemini → Groq → DeepSeek → compatível com OpenAI (a primeira chave encontrada é usada; se a chamada falhar, o próximo provedor do mesmo tier é tentado). Quando nenhum tier tem chave, generate_text() retorna success = false — o hub nunca cai para o core_ai; essa decisão é do consumidor.

📦 Requisitos

Componente Versão
Moodle 4.5+
PHP 8.1+

Nenhuma biblioteca de terceiros empacotada. Os provedores de IA são serviços externos, declarados no Privacy provider — não em thirdpartylibs.xml.

🛠️ Instalação e Configuração

  1. Baixe o arquivo .zip ou clone este repositório.
  2. Extraia a pasta para o diretório local/ do seu Moodle.
  3. Renomeie a pasta para aihub (se necessário). Caminho final: seu-moodle/local/aihub/
  4. Acesse Administração do site → Notificações para concluir a instalação.

Configurar uma chave de provedor é opcional — o plugin instala e funciona sem nenhuma. Veja Como Usar logo abaixo para definir chaves de site e habilitar chaves pessoais.

📖 Como Usar

  1. Configurar chaves de site (admin): Administração do site → Plugins → Plugins locais → Central de IA → Configurações. Defina qualquer uma das chaves Gemini, Groq, DeepSeek ou compatível com OpenAI e, opcionalmente, habilite as chaves de API pessoais.
  2. Adicionar uma chave pessoal (usuário): usuários com a capability local/aihub:usepersonalkey ganham uma entrada Minhas chaves de IA nas preferências, onde guardam a própria chave (write-only) e veem o uso recente.
  3. Revisar o uso das chaves de site (admin): o Relatório de uso de IA do site (sob a categoria Central de IA, capability local/aihub:viewusage) lista todas as chamadas a provedores atendidas pelas chaves do site, de todos os usuários, com download CSV e Excel. Cada tentativa aparece em sua própria linha, com a situação e o motivo da falha quando houver, e filtros de somente falhas ou somente sucessos para checar se algum provedor parou de responder, ou para reduzir a lista de volta ao que foi de fato gerado.
  4. Consumir de um plugin (desenvolvedor): chame \local_aihub\ai::generate_text() atrás de um guard class_exists(), mantendo o próprio fallback para core_ai (ver Como os consumidores usam).

🧪 Testes Automatizados

O hub vem com uma suíte PHPUnit e Behat; todo push de CI roda contra a matriz (Moodle 4.5 → 5.x, PostgreSQL & MariaDB).

PHPUnit — Testes Unitários e de Integração

Arquivo de teste Casos O que cobre
tests/local/keys_test.php 8 Defaults de URL/modelo OpenAI, incluindo o fallback que só é alcançado quando um admin apaga a configuração; roundtrip get/save/clear da chave pessoal; roundtrip da URL/modelo pessoal compatível com OpenAI; personal_keys_allowed respeitando o toggle e a capability; resolução pessoal → site; has_any_key entre chaves pessoal e de site
tests/local/client_test.php 21 Casos de SSRF do is_safe_url (http, loopback, faixa privada, host ausente, IP público); branch de DNS rebinding simulado via dns_stub_client; resolve_openai_url anexa /chat/completions; tier pessoal vence o de site; fall-through de provedor dentro de um tier (Gemini → Groq → DeepSeek); as tentativas são devolvidas, incluindo uma falha encoberta por um sucesso posterior e tentativas abrangendo os dois tiers; a requisição que cada provedor monta — os campos próprios de instrução de sistema e modo JSON do Gemini versus o formato chat-completions, endpoint e modelo por provedor; a perna do OpenAI resolvendo URL e modelo pessoal-depois-site campo a campo; parsing de resposta por provedor, extração de erro, falha de transporte e corpo inesperado
tests/local/usage_log_test.php 8 Inserção do registro (com keysource, modelo vazio nulado); uma tentativa com falha guardando seu motivo; o filtro de situação estreitando os dois leitores nas duas direções; leitores por chave de site excluindo linhas pessoais/sem tag; o leitor de export devolvendo todas as linhas, além do limite da tela; display_name nomeando uma pessoa, o sistema ou um id que não resolve
tests/local/export_test.php 3 Export do uso pessoal (todas as linhas, todas as colunas); uma tentativa com falha exportando situação e motivo; export do relatório de chaves de site incluindo a coluna de usuário e excluindo o uso pessoal
tests/ai_test.php 6 Estados de is_available; uma geração bem-sucedida registra o componente chamador, a descrição e o tier da chave; nenhum provedor chamado não registra nada; toda tentativa é registrada, então uma falha encoberta por um sucesso posterior deixa duas linhas; report_usage() gravando uma linha para um consumidor que resolveu a própria chave, com sucesso ou falha
tests/db_upgrade_test.php 3 O passo de upgrade adiciona a coluna com o tipo, o tamanho e a nulabilidade que o install.xml declara; linhas existentes mantêm seus dados e são lidas como sucessos; uma segunda execução é no-op
tests/lib_test.php 4 A entrada de navegação é adicionada na página de preferências do próprio usuário, e não na de outra pessoa, nem sem a capability, nem com as chaves pessoais desligadas no site
tests/privacy_provider_test.php 9 Declaração de metadata; descoberta de contexto/usuário; export_user_data (linhas do log + valor da chave redigido); os leitores recusando um contexto que não é de usuário e um usuário sem linhas; deleção por usuário, deleção do contexto inteiro e deleção por lista aprovada, cada uma com isolamento
tests/output/mykeys_test.php 4 Status de chave pessoal por provedor (definida/não definida), sem nunca colocar o valor da chave no contexto do template; linhas de uso com o ícone de provedor correto, incluindo o fallback para provedor não reconhecido; uma tentativa com falha exibindo seu motivo; a página renderizando de ponta a ponta sem chave armazenada no HTML
tests/output/report_test.php 9 Linhas do relatório de chaves de site com o nome do usuário solicitante e o ícone de provedor correto, excluindo linhas de chave pessoal; estado vazio; linhas com falha carregando seu motivo; cada um dos três filtros estreitando para a própria situação, com exatamente um marcado como ativo e um filtro desconhecido caindo para a lista completa; a mensagem de vazio por filtro; uma requisição sem usuário atribuída ao sistema; uma linha gravada antes de as colunas de falha existirem ainda sendo lida como sucesso; a página renderizando de ponta a ponta
tests/task/purge_old_logs_test.php 3 Linhas mais antigas que a retenção configurada são apagadas, linhas mais recentes são mantidas; retenção 0 mantém todas as linhas indefinidamente; a tarefa se nomeia a partir de uma string de idioma
Total 78  
vendor/bin/phpunit --testsuite local_aihub

Cobertura de linhas por classe (PHPUnit + Xdebug):

Classe Cobertura de linhas
ai 95%
local\client 91%
local\export 86%
local\keys 100%
local\usage_log 100%
output\mykeys 100%
output\renderer 100%
output\report 100%
privacy\provider 100%
task\purge_old_logs 100%
lib.php 100%
Total 96%

A camada de transporte ficava fora da suíte, sob o argumento de que faz chamadas HTTP reais. Esse argumento cobria mais terreno do que devia: montar uma requisição e interpretar uma resposta não são operações de rede, e deixá-las para o dublê significava que a parte do plugin que conversa com os provedores — inclusive o código que transforma uma recusa da API na frase que o administrador lê — era a única que nada verificava. Duas fixtures alcançam isso agora: recording_client roda os call_* reais e captura o que eles montaram, e stub_transport_client responde a partir de um transporte simulado, para o parsing rodar de verdade, através de uma única costura (make_curl()).

O que continua descoberto está genuinamente fora do alcance de um teste unitário:

Behat — Testes de Aceitação

Arquivo de feature Cenários O que cobre
tests/behat/mykeys.feature 5 Um provedor começa não configurado; salvar uma chave pessoal marca-a como configurada sem revelar o valor guardado; uma tentativa com falha do próprio usuário exibindo seu motivo; o histórico pessoal baixando em CSV e em Excel
tests/behat/report.feature 7 As duas tentativas de uma mesma requisição listadas, com o motivo pelo qual a primeira falhou; o filtro de falhas escondendo o que funcionou, o de sucessos escondendo o que falhou, e a volta à lista completa; uma requisição feita fora de uma sessão atribuída em vez de exibida como id; o relatório baixando em CSV e em Excel
Total 12  
php admin/tool/behat/cli/init.php
vendor/bin/behat --tags=@local_aihub --profile=chrome

🔐 Segurança e Conformidade

🔎 Divulgação de Serviço de Terceiros

O hub transmite o texto do prompt a um provedor de IA de terceiros apenas quando uma chave está configurada e uma geração é solicitada.

Uma chave de API é obrigatória?

Não. O plugin instala e funciona sem nenhuma chave — apenas informa que não há fonte disponível, e os consumidores caem para a própria integração com core_ai. Nenhuma requisição externa é feita até que uma chave de site ou pessoal seja definida.

Provedores suportados

Esses serviços operam sob seus próprios termos de serviço e políticas de privacidade.

Escolha do modelo Groq

O slot da Groq chama um único modelo fixo, openai/gpt-oss-120b. A Groq descontinua periodicamente IDs de modelo específicos (ex.: llama-3.3-70b-versatile, descomissionado em 16/08/2026) e espera migração para um substituto nomeado. Entre as opções gratuitas recomendadas pela Groq para essa migração, o openai/gpt-oss-120b foi escolhido em vez do qwen/qwen3.6-27b por seguir instruções melhor e ser mais confiável em modo JSON entre os prompts variados enviados pelos diferentes plugins consumidores.

Como obter uma chave de API

As chaves de API são criadas diretamente no site oficial do provedor. Gemini, Groq e DeepSeek atualmente oferecem camadas de uso gratuitas ou créditos de teste (as políticas de preço podem mudar). O hub não fornece chaves de API.

Onde as chaves são configuradas

  1. Chave pessoal — definida por cada usuário em Minhas chaves de IA (preferências), quando as chaves pessoais estão habilitadas e o usuário tem a capability.
  2. Chave de site — definida pelo admin em Administração do site → Plugins → Plugins locais → Central de IA.

Transmissão de Dados

Quando uma chave é resolvida para um provedor, o texto do prompt é transmitido à API desse provedor para gerar a resposta:

O hub guarda um log de uso (quem solicitou, qual componente, um rótulo curto do que foi gerado, provedor, modelo, tier da chave e horário), mas não guarda prompts nem respostas da IA. Todos os destinos externos estão declarados no Privacy provider do plugin.

Credenciais de demonstração

Não aplicável — nenhuma credencial é exigida para instalar ou usar o hub. Todo recurso de IA fica inerte até que uma chave de site ou pessoal seja configurada.

📄 Licença

Este projeto está licenciado sob a GNU General Public License v3 (GPLv3).

Copyright: 2026 Jean Lúcio