Github models integrado ao Github actions workflow
O problema
Essa parte é relativamente chata, mas contextualiza como eu cheguei na solução que é apresentada de forma simplificada na subseção Tutorial. Se quiser ir logo ao que interessa, clique aqui
Recentemente coloquei meus primeiros 2 sites no ar: este blog e o meu portfolio pessoal heliohsilva.dev. Enquanto eu os construia, comecei a me aventurar pelos confins da internet à procura de alguma alternativa barata, não necessariamente gratuita, de hospedagem para esses projetos, já que eles rodarem apenas na minha máquina local não faria com que seus propósitos fossem alcançados. Pensei na possibilidade de hospedá-los em um notebook antigo que eu uso como meu home lab improvisado, mas eu teria 2 grandes problemas:
- Eu ainda não sei o suficiente sobre seguraça para garantir que minha máquina fosse segura o suficiente para receber requisições externas;
- Eu não conseguiria garantir a disponibilidade que eu gostaria para essas páginas, já que blackouts, tanto na rede elétrica quanto na rede de internet, não são uma coisa que se enquadraria no que as pessoas consideram como algo raro por aqui onde eu moro.
Por esses motivos, eu tive que pensar em alguma outra alternativa. O que eu acabei encontrando como solução na verdade foi uma ferramenta que eu conhecia, mas não sabia que era tão poderosa: o github actions. Comecei a vasculhar algumas partes da documentação para descobrir quais features ele oferecia que me poderiam ser úteis, como qualquer desenvolvedor minimamente curioso faria.
Em algumas poucas horas, já consegui entender minimamente como funcionava o workflow que ditava o comportamento do deploy. O próximo passo foi, claro, configurar o deploy do meu portfolio, que àquela altura já tinha features básicas para ir pra produção, afinal, o resultado final é uma uma página estática e não tinha muito porque ficar enfeitando e empurrando feature desnecessária.
A surpresa, aquelas horas que eu gastei estudando a ferramenta não serviram muito nesse primeiro momento, já que o próprio github, na tela de ativação do pages, te sugere alguns templates. E, adivinha, html estático é a primeira opção e, para o meu caso àquela altura, foi plug and play. Naquele momento, eu estava buildando manualmente, então subindo o commit para o github e acionando o actions apenas para o deploy. Essa é a forma mais burra de usar uma ferramenta tão poderosa, então a primeira melhoria que fiz nos meus projetos não foi em funcionalidade ou coisa do tipo, mas sim no workflow de deploy, o qual configurei para que, a cada commit, fosse feito o build e o deploy automagicamente. Assim o problema em relação ao blog já estava resolvido.
Quando falei com meus amigos sobre meu portfolio, que até então não tinha nenhum visitante além de mim, o primeiro problema que eles apontaram foi: eu moro no Brasil. Estou procurando emprego no Brasil. Mas, contudo, porém, meu portfólio estava em inglês. A partir dai comecei a pensar como eu poderia automatizar a tradução da minha página, já que queria ter uma versão em inglês de todo modo. A primeira versão traduzida eu fiz na mão, mas pensei que isso ficaria difícil de manter na medida que mais projetos fossem adicionados ou que eu fosse modificando algumas sessões de texto da página.
Primeira solução
Minha primeira ideia para melhorar o processo manual foi usar alguma api de tradução, mas isso me forçaria a duplicar parte dos meus templates, visto que o site tem elementos alem dos textos principais que precisariam ser traduzidos também (como o conteúdo da navbar por exemplo). Não seria o fim do mundo, afinal, é pouca coisa. Mas, se eu posso utomatizar, por que não fazê-lo?
Foi ai que me veio a ideia de usar LLM para fazer isso. Minha primeira tentativa foi com o gemini cli, que eu tinha instalado para testar e nunca havia testado em um caso de uso real. Após poucos ajustes eu já não tinha mais que traduzir o texto na mão. Àquela altura eu estava a 4 comandos do deploy:
gemini -p $("prompt.md")git addgit commitgit push
Já estava bem próximo do resultado que eu queria, mas ainda não era o suficiente para mim. Pensando nisso, eu voltei na documentação do GH actions para procurar alguma possível salvação.
Solução atual: a definitiva até o momento
Foi então que, vasculhando na documentação do actions, eu encontrei a fórmula mágica, o eldorado, o one piece...
Não, na verdade eu não encontrei nada lá. Meio decepcionante, não? Eu também achei. Mas me veio à mente que o github tem uma coisa que realemente poderia me salvar em seu ferramental: o github models. Ele nada mais é do que uma espécie de playground de LLM. Através dele, você consegue testar diversos modelos diferentes, compará-los, e usar da forma que quiser através de API calls, ou através do github CLI.
Foi então que eu pensei: sendo o actions e o models tools do mesmo ecossistema, é natural pensar que integrá-los seria trivial. Essa poderia ser uma solução em potencial para o meu problema, se desse certo.
Pois bem, deu certo. That's all, folks! Obrigado por lerem. Até a próxima!
Brincadeiras à parte, fazendo alguns ajustes no meu workflow, eu consegui integrar a LLM no processo de tradução da minha página na esteira de CD. Com isso eu alcancei meu objetivo inicial. Agora, eu preciso de apenas 3 comandos para ter meu site no ar após alguma modificação:
git addgit commitgit push
Bom, bela historinha, cheia de bla bla bla, mas não tem nada que de fato interessa até o momento. Responder à pergunta "o que" e "porque" de uma coisa pronta não é tão interessante quanto responder o "como". Então, me acompanhe...
Momento Tutorial
Para demonstrar o passo a passo de como integrar o GH models ao workflow do actions, vou usar como exemplo uma página que, a cada deploy, exibe uma frase diferente proferida por Gandalf em The Lord of The Rings
Os exemplos mostrados aqui estarão disponíveis no repositório github.com/heliohsilva/heliohsilva.github.io, e o resultado final ficará disponível para acesso e visualização em heliohsilva.github.io. Se um dia eu remover essa página do ar, tentarei me lembrar de remover esse trecho do artigo. Se você estiver lendo isso agora e a página não for acessível, sinto muito, eu esqueci.
Setup inicial do deploy
Vou dar o start com o código do exemplo de workflow de página html estática do github actions. Veja, o conteúdo desse exemplo é o seguinte:
on:
push:
branches: ["main"]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: '.'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
Veja, o que esse código faz em cada camada:
- Quando ativar o workflow
on:
push:
branches: ["main"]
workflow_dispatch:
Essa configuração está dizendo: sempre que fizer um push na branch main, execute o workflow.
A flag workflow_dispatch diz que o workflow, além do trigger automático do push na branch main, também pode ser acionado manualmente.
- Regras de concurrency e permissões
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
A flag permissions dita quais permissões cada módulo do seu workflow tem sobre seu repositório. Já concurrency define um grupo de concorrência para execuções do workflow. Ao usar group: pages, garantimos que apenas uma execução desse workflow ocorra por vez. Caso novos workflows sejam disparados enquanto outro ainda está em execução, eles ficam em fila (ou cancelam o anterior, dependendo da configuração).
- A última parte: os
jobs
jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: '.'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
Aqui é onde a mágica realmente acontece. Nesse exemplo temos apenas o job deploy, mas jajá acrescentaremos outros. Note que temos 3 componentes principais em um job:
environmentruns-onsteps
O environment é onde é descrito o ambiente no qual o deploy será feito. É apenas uma formalidade do lado do servidor. Não precisamos nos preocupar muito por enquanto. O name define o nome do ambiente final de deploy, enquanto que o url é o endereço de acesso do site. Nesse caso, como eu disse mais acima, o site estaria hospedado em heliohsilva.github.io.
O runs-on descreve em qual ambiente o job será hospedado. o default é ubuntu-latest e ele vai servir para a grande maioria dos casos. Não precisa se preocupar com isso também.
Os steps são as descrições de cada procedimento realizado durante um job. Como o nome já sugere, aqui fica o passo a passo de todas as operações que serão feitas até a finalização desse job (que, neste caso, é o deploy).
Colocando a primeira versão no ar
Agora que já sabemos como funciona um arquivo de configuração de workflow no github actions, podemos ir para a parte prática. Note que definimos para o deploy ser feito em cada push no repositório. Então, pela lógica já deve estar no ar, certo?

Pois bem, meu repositório no momento só tem o arquivo de configuração do actions. Não tem nenhum index.html para ser ponto de partida e gerar uma página de verdade. Vamos criar um simples então
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Página</h1>
<p>Essa é definitivamente uma página</p>
</body>
</html>
Isso já deve ser suficiente. Agora fazemos o push no repositório e.....

Voilà!
Integrando com o github models
Voltando ao nosso workflow, vamos adicionar um novo job. Seu nome será, digamos, gandalf-quote-generator (sim, eu sou péssimo com nomes).
O primeiro passo é adicionar ao job deploy uma nova flag chamada needs. Essa flag vai dizer ao deploy que ele precisa que o gandalf-quote-generator termine de executar para que ele inicie.
deploy:
needs: gandalf-quote-generator
Depois, vou definir os steps do gandalf-quote-generator:
gandalf-quote-generator:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
Este step estava no job deploy. O que ele faz é basicamente carregar o nosso repositório do projeto para o contexto do executor. Como estou carregando ele aqui, posso remover esse trecho em deploy.
O github CLI (a ferramenta que permite usarmos o comando gh) já vem instalado no executor do job. Porém, para usarmos o gh models, temos que instalar uma extensão ao CLI. O step mostrado abaixo faz exatamente isso:
- name: Install gh models
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh extension install https://github.com/github/gh-models
Agora finalmente definimos nosso prompt, chamamos gh models run -model- $PROMPT e salvamos o output em index.html. Para este exemplo estou utilizando o modelo gpt-4o-mini, mas o modelo pode ser modificado para o de sua preferência.
- name: Generate gandalf quote
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mkdir -p site
PROMPT="
## Role:
A lord of the rings fan who knows every Gandalf quotes
## Context:
You are in a random situation in which the only way to survive is to speech some Gandalf quote randomly
## Task:
generates a blank html structure, containing only the summary of the quote as a title (<h1>) and the quote itself as a <p>;
add 20% of padding in each side of the body page;
## Output:
A html code with the content asked for. The code has to be raw text, without code blocks.
"
gh models run openai/gpt-4o-mini "$PROMPT" > site/index.html
Agora, precisamos fazer o upload do artifact. O artifact nada mais é do que o estado atual do objeto de trabalho, no nosso caso ele é o index.html, mas em uma aplicação de verdade seria por exemplo a pasta src, que é gerado após a execução de um job. Aqui, como o próximo job já é o deploy, eu posso gerar a build final com:
- name: Upload page artifact
uses: actions/upload-pages-artifact@v3
with:
name: final-site
path: ./index.html
Se tivéssemos mais jobs entre este e o deploy, a flag uses teria o valor actions/upload-artifact@v4. Somente o último artifact deve ser upado com a tag actions/upload-pages-artifact@v3.
Por fim, precisamos dar aos models a permissão de leitura ao repositório. fazemos isso adicionando models: read na sessão permissions no início do workflow.
No final, nosso workflow ficou assim:
name: Deploy static content to Pages
on:
push:
branches: ["main"]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
models: read
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
gandalf-quote-generator:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install gh models
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh extension install https://github.com/github/gh-models
- name: Generate gandalf quote
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mkdir -p site
PROMPT="
## Role:
A lord of the rings fan who knows every Gandalf quotes
## Context:
You are in a random situation in which the only way to survive is to speech some Gandalf quote randomly
## Task:
generates a blank html structure, containing only the summary of the quote as a title (<h1>) and the quote itself as a <p>;
add 20% of padding in each side of the body page;
## Output:
A html code with the content asked for. The code has to be raw text, without code blocks.
"
gh models run openai/gpt-4o-mini "$PROMPT" > site/index.html
- name: Upload page artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./site
deploy:
needs: gandalf-quote-generator
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
Antes de podermos fazer o push para o repositório e finalmente nos deleitarmos com os sábios dizeres de Gandalf the white, precisamos fazer mais uma coisa: no nosso repositório no github, vamos em configurações, models, e então vamos ativar models para este repositório, como mostrado na figura:

Dessa forma, nosso actions será habilitado a usar o models da forma correta.
Agora, em teoria está tudo pronto. Veremos o resultado:

Conclusão
Seguindo os passos mostrados acima, você será capaz de integrar facilmente LLM através do GH models ao seu workflow de deploy. Isso pode ser útil em diversos cenários, como o que eu usei no meu portfólio para gerar uma versão traduzida do mesmo.
LLM é uma ferramenta muito poderosa se souber usar nos contextos certos, então lembre-se sempre:
Com grandes poderes vêm grandes responsabilidades.