Agente de IA em Python: bot que cria tarefas no ClickUp

Uso o ClickUp pra organizar as demandas de RPA do trabalho, desde inovação até manutenção, mas ideia de tarefa não aparece só quando estou com o ClickUp aberto. Aparece no meio de uma reunião, andando, numa conversa qualquer.
Criei esse bot pra não depender de abrir o app, navegar até a lista certa e preencher campo por campo toda vez que uma tarefa nova surgia.
O problema
Toda vez que uma demanda nova aparecia fora do horário em que eu já estava com o ClickUp aberto, eu tinha duas opções: parar o que estava fazendo pra cadastrar direito, ou confiar na memória pra lembrar depois. As duas eram ruins. A primeira quebrava o fluxo do que eu estava fazendo, a segunda perdia tarefa.
Eu precisava de um jeito de capturar a ideia no instante em que ela aparecesse, de qualquer lugar, sem fricção, e decidir depois se valia a pena executar.
Por que o Telegram como interface
O Telegram já é a ferramenta que eu uso o dia inteiro, então virou a interface natural: em vez de abrir o ClickUp, eu só escrevo ou falo pro bot o que preciso fazer, e a tarefa cai numa lista “inbox”.
Toda revisão semanal, eu olho essa lista, decido o que vale a pena e organizo a semana com o que faz sentido executar.
Detectando a intenção da mensagem com IA
O ponto central do bot não é só “mandar texto e virar tarefa”, é entender o que o usuário quer sem precisar de comando fixo.
Isso fica no parse_intent(), que manda a mensagem pra um GPT-4o-mini com um system prompt que define três intenções possíveis e exige resposta em JSON estrito:
def parse_intent(text: str) -> dict:
today = date.today().isoformat()
response = openai_client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": (
f"Hoje é {today}. "
"Você é um assistente que analisa mensagens e identifica a intenção do usuário. "
"Retorne APENAS um JSON válido, sem markdown, sem explicações. "
"Há três intenções possíveis:\n\n"
"1. Criar tarefa: {\"intent\": \"create_task\", \"title\": \"...\", "
"\"description\": \"...\", \"due_date\": \"YYYY-MM-DD ou null\"}\n"
"2. Listar tarefas: {\"intent\": \"list_tasks\"}\n"
"3. Nenhuma das anteriores: {\"intent\": \"none\"}"
)
},
{"role": "user", "content": text}
],
temperature=0.3
)
content = response.choices[0].message.content.strip()
return json.loads(content)
Sem esse passo, o bot seria só um formulário disfarçado de chat. Com ele, “preciso revisar a proposta do cliente X” já vira tarefa sozinho, sem eu precisar estruturar nada.
Texto ou áudio, o mesmo fluxo
Mensagem de voz passa primeiro pela transcrição do Whisper, e o texto que sai dali entra no mesmo parse_intent() usado pro texto digitado. Não existe um caminho separado pra áudio:
async def handle_voice(update: Update, context: ContextTypes.DEFAULT_TYPE):
if not is_allowed(update):
return
voice = update.message.voice or update.message.audio
file = await context.bot.get_file(voice.file_id)
with tempfile.NamedTemporaryFile(suffix=".ogg", delete=False) as tmp:
await file.download_to_drive(tmp.name)
transcribed_text = transcribe_audio(tmp.name)
result = parse_intent(transcribed_text)
if result.get("intent") == "create_task":
processing_msg = await update.message.reply_text("Criando task...")
await send_task_result(processing_msg, result)
elif result.get("intent") == "list_tasks":
await handle_list(update, None)
Isso importa na prática: falar é mais rápido que digitar, principalmente andando ou no meio de outra tarefa.
Criando a tarefa no ClickUp
Depois que a intenção é create_task, o bot chama a API do ClickUp direto com httpx, sem biblioteca intermediária:
def create_clickup_task(title: str, description: str, due_date: str | None = None) -> dict:
url = f"https://api.clickup.com/api/v2/list/{CLICKUP_LIST_ID}/task"
headers = {
"Authorization": CLICKUP_API_TOKEN,
"Content-Type": "application/json"
}
payload = {"name": title, "description": description, "priority": 3, "tags": ["🤖"]}
if CLICKUP_ASSIGNEE_ID:
payload["assignees"] = [int(CLICKUP_ASSIGNEE_ID)]
due = datetime.strptime(due_date, "%Y-%m-%d") if due_date else datetime.today()
payload["due_date"] = int(due.timestamp() * 1000)
response = httpx.post(url, json=payload, headers=headers)
response.raise_for_status()
return response.json()
Toda tarefa criada pelo bot leva uma tag fixa, então dá pra saber de relance, olhando o ClickUp, quais tarefas vieram do bot e quais foram criadas manualmente.
Resultado
Hoje eu capturo tarefa de qualquer lugar, por texto ou áudio, sem abrir o ClickUp. A tarefa cai na lista inbox, e a revisão semanal virou o único momento em que eu realmente decido o que entra na semana, em vez de decidir (ou esquecer) na hora que a ideia surge.
Pontos importantes
Group Privacy Mode do Telegram é a pegadinha mais fácil de esquecer: por padrão, bot em grupo só recebe mensagem que começa com /comando. Pra processar texto e áudio livre, precisa desligar isso no @BotFather (Bot Settings > Group Privacy > Turn off), senão o bot simplesmente não vê a maior parte das mensagens.
Restringir por chat_id evita bot público sem querer: o is_allowed() compara o chat_id de cada mensagem com um único grupo autorizado, guardado em variável de ambiente. Sem essa checagem, qualquer grupo que adicionasse o bot conseguiria criar tarefa na minha lista.
IA não substitui um caminho manual de fallback: o comando /task não depende só da detecção de intenção. Se o GPT não retornar create_task (ou falhar), o código usa o texto puro como título em vez de travar, então o caminho explícito sempre funciona, mesmo se a classificação por IA falhar.
Perguntas frequentes
Como usar a API do ClickUp para criar tarefas automaticamente?
A API do ClickUp cria tarefa com um POST pra https://api.clickup.com/api/v2/list/{list_id}/task, enviando o token pessoal no header Authorization e um payload JSON com name, description e outros campos opcionais como priority, tags, assignees e due_date (esse último em timestamp Unix, em milissegundos). A resposta traz a tarefa criada, incluindo a URL direta pra abrir no ClickUp.
Como detectar a intenção de uma mensagem com IA?
Um jeito simples é mandar a mensagem do usuário pra um modelo de linguagem (GPT-4o-mini, por exemplo) com um system prompt que define as intenções possíveis e exige como resposta um JSON estrito, sem texto livre. O código então faz o parse desse JSON e decide qual ação tomar (criar tarefa, listar tarefas, ou nenhuma das duas), sem precisar de comandos fixos digitados pelo usuário.
Dá pra criar tarefa no ClickUp por áudio no Telegram?
Sim. O bot baixa o arquivo de voz enviado no Telegram, transcreve com a API de Whisper da OpenAI, e manda o texto transcrito pro mesmo fluxo de detecção de intenção usado pra mensagens digitadas. Não existe um caminho separado pra áudio, ele só vira texto antes de entrar no mesmo pipeline.
Por que um bot do Telegram em um grupo não responde a mensagens sem comando?
Por padrão, o Telegram ativa o 'Group Privacy Mode' pra bots adicionados em grupos, que faz o bot só receber mensagens que começam com /comando, ignorando texto e áudio livres. Pra um bot processar qualquer mensagem do grupo, é preciso desligar essa opção no @BotFather, em Bot Settings > Group Privacy > Turn off.
Como restringir um bot do Telegram pra responder só a um grupo específico?
Guardando o chat_id do grupo autorizado (uma automação pessoal costuma fixar isso numa variável de ambiente) e comparando com o chat_id de cada mensagem recebida antes de processar qualquer coisa. Se não bater, o bot simplesmente ignora a mensagem, sem responder nem processar.
Curtiu o conteúdo ou tem um problema parecido pra resolver? Bora trocar uma ideia.
Fale comigo no WhatsApp