CommitAI: bot do Telegram em Python para padronizar commits

CommitAI: bot do Telegram em Python para padronizar commits

Eu queria aprender a programar bot do Telegram com Python, então procurei um problema real pra resolver em vez de fazer só um tutorial.

Na empresa onde eu trabalhava não existia padronização nenhuma nos commits: cada um escrevia do seu jeito, muita coisa tipo “ajustes” ou “update”, sem padrão nenhum.

Como o Telegram já era a ferramenta que a empresa usava no dia a dia, decidi juntar as duas coisas: aprender bot do Telegram na prática e resolver esse problema de padronização.

O problema

Sem um padrão definido, o histórico de commits virava só uma lista de mensagens genéricas. Difícil de entender o que mudou olhando só o git log, difícil de gerar changelog, difícil de revisar código sabendo o que esperar de cada commit antes de abrir o diff.

Cobrar padrão manualmente (por exemplo revisar PR e pedir pra reescrever a mensagem) funciona, mas é atrito toda vez. Eu queria alguma coisa que facilitasse escrever a mensagem certa, não que só cobrasse depois.

Por que o Telegram como interface

Dava pra resolver isso com um hook de commit ou uma CLI, mas eu queria também treinar bot do Telegram, e o Telegram já era ferramenta padrão da empresa. Ninguém precisava instalar nada novo ou aprender uma ferramenta diferente: era só abrir a conversa com o bot.

O bot em si é simples de configurar. Você clona o repositório no GitHub, cria um bot com o @BotFather pra pegar o token, e configura duas chaves num .env:

TELEGRAM_BOT_API_KEY=sua_chave
OPENAI_API=sua_chave

Com isso configurado, python main.py já sobe o bot em modo polling, escutando mensagens.

Como o bot funciona

A interação começa com um teclado inline: o usuário clica em Gerar commit, e o bot passa a esperar uma descrição.

@bot.callback_query_handler(func=lambda c: c.data == 'commit')
def cb_commit(call):
    bot.answer_callback_query(call.id)
    bot.send_message(call.message.chat.id, '💬 Envie um *texto* ou *áudio* descrevendo as alterações realizadas no projeto.', parse_mode='Markdown')
    user_state[call.from_user.id] = 'aguardando_descricao'


@bot.message_handler(func=lambda m: user_state.get(m.from_user.id) == 'aguardando_descricao')
def receber_descricao(msg):
    texto_commit = openai_agent(msg.text)
    bot.send_message(msg.chat.id, texto_commit, parse_mode='Markdown')
    user_state.pop(msg.from_user.id, None)
    responder(msg)

O user_state é só um dicionário em memória que guarda, por usuário, se ele está “aguardando_descricao” ou não. Sem esse controle de estado, o bot não teria como diferenciar uma mensagem qualquer de uma descrição de commit.

O prompt é o cérebro do bot

A parte que decide como o commit fica pronto não está no código Python, está no prompt. openai_agent() só carrega esse prompt de um arquivo e manda junto com a mensagem do usuário pra API da OpenAI:

def openai_agent(mensagem):
    system_prompt = carregar_prompt('prompts/commitai.md')

    resposta = openai.chat.completions.create(
        model='gpt-4.1-mini',
        messages=[
            {'role': 'system', 'content': system_prompt},
            {'role': 'user', 'content': mensagem}
        ]
    )
    return resposta.choices[0].message.content

O arquivo prompts/commitai.md é onde está a regra de verdade: formato obrigatório de dois blocos Markdown (título e descrição), tipos permitidos (feat, fix, refactor, perf, docs, style, test, chore, build, ci), regra de não inventar informação que o usuário não mencionou, título sempre no imperativo e sem terminar com ponto.

Se eu enviar “Corrigi o retorno da função e diminui o tempo de sleep nos testes”, o bot devolve algo como o exemplo abaixo:

refactor: reduz tempo de espera para testes

- Ajusta retornos das funcoes para garantir consistencia
- Diminui o tempo de sleep para agilizar testes

Mudar esse arquivo muda o comportamento do bot inteiro, sem tocar em uma linha de Python. Na prática, o prompt é o produto; o main.py é só o encanamento que leva a mensagem do Telegram até ele.

Texto ou áudio, o fluxo é o mesmo

Também dá pra mandar um áudio explicando o que foi feito em vez de digitar. O bot baixa o arquivo de voz do Telegram, monta ele em memória e manda pra transcrição da OpenAI:

@bot.message_handler(content_types=['voice', 'audio'])
def receber_audio(msg):
    if user_state.get(msg.from_user.id) != 'aguardando_descricao':
        bot.send_message(msg.chat.id, '⚠️ Clique no botão *Gerar commit* antes de enviar o áudio com as informações.', parse_mode='Markdown')
        responder(msg)
        return

    file_id = msg.voice.file_id if msg.voice else msg.audio.file_id
    file_info = bot.get_file(file_id)
    audio_bytes = bot.download_file(file_info.file_path)

    audio_file = io.BytesIO(audio_bytes)
    audio_file.name = 'audio.ogg'

    transcricao = openai.audio.transcriptions.create(
        model='gpt-4o-mini-transcribe',
        file=audio_file,
    )

    texto_commit = openai_agent(transcricao.text)
    bot.send_message(msg.chat.id, texto_commit, parse_mode='Markdown')
    user_state.pop(msg.from_user.id, None)
    responder(msg)

Depois de transcrito, o áudio vira texto e segue pro mesmo openai_agent() usado no fluxo de texto. Não existe um caminho separado pra “commit por áudio”, só um passo a mais antes de cair no fluxo normal.

Usei esse mesmo padrão de novo no bot que cria tarefas no ClickUp: texto ou áudio, sem caminho separado, ambos caem no mesmo agente de IA.

Isso importa na prática: às vezes é mais rápido falar o que você fez do que parar e escrever, principalmente depois de uma sessão longa de código.

Resultado

Implementei o bot na empresa onde eu trabalhava, e os commits passaram a seguir um padrão de verdade: título técnico e no formato certo, descrição em bullets quando fazia sentido, sem mais mensagens genéricas tipo “ajustes” ou “update”.

O ganho não foi só estético: ficou mais fácil entender o histórico do projeto sem precisar abrir cada diff pra saber o que mudou.

Pontos importantes

O prompt é a parte que mais importa: o código do bot é só encanamento entre Telegram e OpenAI. Toda a regra de formatação, os tipos permitidos e o comportamento esperado estão em prompts/commitai.md, carregado a cada chamada. Trocar esse arquivo muda o resultado sem precisar mexer no main.py.

Custo de API é real: tanto a geração do commit quanto a transcrição de áudio usam a API da OpenAI, e isso consome tokens pagos. São modelos baratos (gpt-4.1-mini e gpt-4o-mini-transcribe), mas o custo cresce junto com o uso, então vale ter noção disso antes de rodar em produção com o time inteiro.

Estado em memória é suficiente pra esse escopo: o user_state guarda em memória se cada usuário está no meio do fluxo de “aguardando descrição”. Funciona bem pra um bot de uso pessoal ou de time pequeno, mas não sobrevive a um restart do processo, o que seria o próximo ponto a resolver num cenário de mais gente usando ao mesmo tempo.

Perguntas frequentes

O que são Conventional Commits?

É uma convenção pra escrever mensagens de commit de forma padronizada, no formato tipo(escopo opcional): título curto, seguido de uma descrição. Os tipos mais comuns são feat (nova funcionalidade), fix (correção de bug), refactor (reorganização sem mudar comportamento), docs, test e chore. É exatamente esse padrão que o prompt do CommitAI segue à risca.

Como criar um bot do Telegram com Python?

Você cria o bot conversando com o @BotFather no próprio Telegram, que devolve um token de API. Com esse token, a biblioteca pyTelegramBotAPI (o pacote telebot usado no CommitAI) permite registrar handlers para mensagens, comandos e cliques em botões, e rodar o bot em modo polling com bot.infinity_polling().

Dá pra usar o CommitAI sem pagar pela API da OpenAI?

Não com a versão atual: tanto a geração do commit (gpt-4.1-mini) quanto a transcrição de áudio (gpt-4o-mini-transcribe) dependem da chave da OpenAI, e o uso consome tokens pagos. Os dois modelos usados são dos mais baratos da OpenAI, mas o custo existe e cresce com o volume de commits gerados.

Como o bot transcreve os áudios enviados no Telegram?

O bot baixa o arquivo de voz direto da API do Telegram, monta ele em memória com io.BytesIO (sem salvar em disco) e manda pra API de transcrição da OpenAI (gpt-4o-mini-transcribe). O texto que volta da transcrição segue pro mesmo fluxo usado quando você digita a descrição, então pra quem consome o resultado não faz diferença se veio de texto ou de áudio.

O bot funciona só para Conventional Commits ou dá pra mudar o padrão?

O código em si (main.py) não sabe nada sobre formato de commit, ele só manda o texto do usuário pra IA usando um prompt carregado de prompts/commitai.md como instrução de sistema. Pra mudar o padrão de commit (ou até gerar outra coisa, tipo descrição de PR), basta editar esse arquivo de prompt, sem tocar no código do bot.

Curtiu o conteúdo ou tem um problema parecido pra resolver? Bora trocar uma ideia.

Fale comigo no WhatsApp
David Alves

David Alves

Desenvolvedor Python | RPA

Desenvolvedor Python especializado em RPA e automação de processos, com foco em scripts inteligentes, integração de sistemas e APIs.