Recentemente, mostrei aqui no blog como criar um chatbot básico para Telegram. Hoje, vamos elevar o nível: vamos construir um Agente Inteligente.
Diferente de um bot comum que apenas responde comandos, um agente consegue tomar decisões, utilizar ferramentas (tools) e interagir com fontes de dados externas — no nosso caso, um banco de dados SQL — para responder perguntas complexas em linguagem natural.
A Arquitetura do Agente
Para este projeto, utilizamos uma estrutura modular onde o Gemini atua como o “cérebro” orquestrador, decidindo quando precisa consultar o banco de dados para obter uma informação que ele não possui nativamente.
- Telegram (Telegraf): Recebe a mensagem do usuário.
- Main.js: Valida a autorização e encaminha para o agente.
- Gemini (Orquestrador): Analisa a intenção. Se precisar de dados, chama a tool
generate_sql. - Tool (Generate SQL): Um prompt especializado gera a query baseada no schema.
- Database: A query é executada e o resultado volta para o Gemini sintetizar a resposta final.

1. Configuração do Ambiente (.env)
Antes de rodar o projeto, você precisa configurar suas credenciais. Crie um arquivo .env na raiz do projeto com as seguintes chaves:
TELEGRAM_TOKEN=seu_token_aqui
GOOGLE_API_KEY=sua_chave_gemini_aqui
CHAT_ID=seu_id_do_telegram
DB_HOST=localhost
DB_USER=root
DB_PASS=
DB_NAME=meu_db
- TELEGRAM_TOKEN: Obtido via @BotFather.
- GOOGLE_API_KEY: Obtida no Google AI Studio.
- CHAT_ID: Fundamental para a segurança (veja a seção de Segurança abaixo), garantindo que apenas você controle o bot.
2. Preparando o Banco de Dados (Instalação Local)
Para que o agente funcione, ele precisa de um banco de dados para consultar. No repositório, incluímos o arquivo database_schema.txt. Este arquivo não serve apenas para instruir a IA, mas também como guia para você criar suas tabelas localmente.
Se estiver usando MySQL, você pode usar o conteúdo deste arquivo para rodar os comandos CREATE TABLE e inserir seus dados iniciais.
O arquivo db.js gerencia essa conexão e executa uma query de busca quando necessário:
import mysql from 'mysql2/promise';
import dotenv from 'dotenv';
dotenv.config();
async function query(sql) {
const connection = await mysql.createConnection({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASS,
port: process.env.DB_PORT || 3306,
database: process.env.DB_NAME,
});
const [results] = await connection.execute(sql);
await connection.end();
return results;
}
export { query };
3. A Ferramenta de Geração de SQL (tools/generateSql.js)
O segredo de um bom agente de dados é o contexto. No arquivo generateSql.js, passamos o database_schema.txt como instrução da ferrament. Isso garante que o Gemini saiba exatamente quais tabelas e colunas existem e que a pergunta do usuário deve se referi a tabela que essa ferramenta conhece.
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const schemaFilePath = path.resolve(__dirname, './database_schema.txt');
export default {
name: "generate_sql",
description: `Recebe uma pergunta sobre a loja (ou suas colunas) e Gera SQL da tabela loja com base na pergunta ou comando do usuário.
Considere comando ou pergunta como sendo a mesma coisa (pergunta).
Schema a ser considerado:
${schemaFilePath} `,
parameters: {
type: "object",
properties: {
pergunta: { type: "string" },
},
required: ["pergunta"],
},
};
4. O Agente e o Orquestrador (geminiAgent.js)
Aqui utilizamos o fluxo de Function Calling (Tools). O orquestrador (gemini-1.5-flash-lite) em sua response retorn um chamda de função, e verificamos se essa função se chama generate_sql. Por trás dos panos o modelo faz um “match” da mensagem com as ferramentas antes de responder. Se a função foi retornada, signfica que devemos fazer o que a ferramenta exige. Dai em diante, usamos o modelo pra traduzir a pergunta do usuário em uma query sql válida e com ela, consultamos no banco. Com o retorno da resposta, pedimos pro modelo criar uma resposta amigável (considerando a pergunta do usuáio e o retorno do banco de dados).
import { GoogleGenAI, Type } from '@google/genai';
import dotenv from 'dotenv';
dotenv.config();
import path from 'path';
import * as fs from 'fs/promises';
import generateSql from './tools/generateSql.js';
import { fileURLToPath } from 'url';
import { query } from './db.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const genAI = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
async function runAgent(pergunta, history = [], ctx = null) {
// Cria uma cópia do histórico sem incluir a última pergunta do usuário
// já que ela será enviada diretamente como mensagem
let chatHistory = [];
let statusMessage = null;
let thinkingMessage = null;
if (ctx) {
// Envia uma mensagem de "pensando" para o usuário
thinkingMessage = await ctx.reply('🧠Pensando...');
}
if (history && history.length > 0) {
// Se houver histórico e pelo menos 2 mensagens, pegamos tudo exceto a última (que é a pergunta atual)
if (history.length > 1) {
chatHistory = history.slice(0, -1);
}
}
const chat = await genAI.chats.create({
model: 'gemini-2.5-flash-lite-preview-06-17',
thinkingConfig: {
thinkingBudget: 256,
},
history: chatHistory,
config: {
systemInstruction: `
Você é um assistente de gentente de uma loja que faz consulta aos dados da mesma através das ferramentas disponíveis.
O usuário não precisa saber o nome da tabela, apenas o que ele quer saber.
Em caso de pergunta genérica sobre uma tabela, você deve gerar uma SQL com todas as colunas da tabela em questão.
Ex: Me liste todos os produtos = SELECT * FROM produtos;
Caso a pergunta não se adeque a ferramenta, você deve responder com uma mensagem informando que não conseguiu entender a pergunta e como o usuáriode deve fazer a pergunta.`,
tools: [{
functionDeclarations: [generateSql],
}],
},
});
const response = await chat.sendMessage({
message: pergunta,
});
await ctx.deleteMessage(thinkingMessage.message_id);
if (response.functionCalls && response.functionCalls.length > 0) {
if (ctx) {
statusMessage = await ctx.reply('🔄 Consultando...');
}
const functionCall = response.functionCalls[0];
const functionName = functionCall.name;
let functionArgs;
try {
functionArgs = functionCall.args || {};
} catch (error) {
console.error("Erro ao processar argumentos da função:", error);
functionArgs = {};
}
if (functionName === 'generate_sql') {
const schemaFilePath = path.resolve(__dirname, './database_schema.txt');
const schema = await fs.readFile(schemaFilePath, 'utf-8');
const instructions = `
Você deve receber uma pergunta/comando do usuário a ser traduzida em uma consulta SQL
sobre a tabela ${process.env.DB_NAME} que pode retornar o que o usuário precisa e quer saber.
caso não seja informado um campo, pesquise nome e id.
considere o histórico da conversa para entender o que o usuário quer, e não retorne nada além do JSON com o comando SQL.
Schema do banco de dados: ${schema}
Historico da conversa para contexto: ${JSON.stringify(chatHistory.slice(-4))}
`;
const sqlQuestion = `baseado no comando/pergunta: ${pergunta} Reponsa em JSON com um comando SQL `;
const modelQuery = await chat.sendMessage({
model: 'gemini-2.0-flash',
message: sqlQuestion,
config: {
systemInstruction: instructions,
responseMimeType: 'application/json',
responseSchema: {
type: Type.ARRAY,
items: {
type: Type.OBJECT,
properties: {
'query': {
type: Type.STRING,
description: 'Query SQL gerada',
nullable: false,
},
},
required: ['query'],
},
},
}
});
const sql = JSON.parse(modelQuery.text);
let queryResult = null;
try {
const result = await query(sql[0].query);
queryResult = `Resultado da consulta: ${JSON.stringify(result)}`;
} catch (error) {
console.error("Erro ao executar a consulta SQL:", error);
return "Erro ao executar a consulta SQL.";
}
if (queryResult) {
const finalResponse = await chat.sendMessage({
message: `
Reponendo a pergunta: ${pergunta}, retorne uma resposta em linguagem natural o resultado da query que foi a seguinte:
${queryResult}
retonre para responder de mandeira criativa com emojis (moderadamente) e para o telegram, use quebra de linha com '\n'`,
});
if (statusMessage) {
await ctx.deleteMessage(statusMessage.message_id);
}
return finalResponse.text;
}
}
}
return response.text;
}
export default runAgent;
5. Interface e Segurança (main.js)
Segurança é um ponto crítico ao expor um banco de dados a uma IA. No arquivo main.js, implementamos uma trava de CHAT_ID. Isso impede que qualquer pessoa que encontre seu bot no Telegram execute queries no seu banco.
import { Telegraf } from 'telegraf';
import { runAgent } from './geminiAgent.js';
const bot = new Telegraf(process.env.TELEGRAM_TOKEN);
const AUTHORIZED_USER = process.env.CHAT_ID;
bot.on('text', async (ctx) => {
// Trava de Segurança: Verifica se o ID do usuário é o autorizado
if (ctx.chat.id.toString() !== AUTHORIZED_USER) {
console.warn(`Tentativa de acesso não autorizada do ID: ${ctx.chat.id}`);
return ctx.reply("Acesso não autorizado. Este agente é privado.");
}
try {
const response = await runAgent(ctx.message.text);
await ctx.reply(response, { parse_mode: 'Markdown' });
} catch (error) {
console.error(error);
await ctx.reply("Houve um erro ao processar sua consulta.");
}
});
bot.launch();
Conclusão
A combinação de um orquestrador leve (1.5-flash-lite) com um gerador técnico preciso (2.0-flash) cria um agente robusto. Ao utilizar variáveis de ambiente e validação de CHAT_ID, garantimos que a potência da IA generativa seja utilizada de forma segura e controlada.
Agora imagine o que pode ser feito usando as chamdas de funções com ferramentas. Você pode construir agentes inteligentes com capacidade de interagir com o API’s, Base de Dados, Gerenciar aquivos e muito mais!
Links úteis
Repositório desse projeto: https://github.com/marcosnunesmbs/gemini_db_agent
Botfather: https://t.me/botfather
Documentação Gemini API: https://ai.google.dev/gemini-api/docs

