feat: add complete multi-agent AI system with Telegram bot
Initial implementation of MultiAgentsAI featuring: - Dynamic agent configuration via environment variables - SQLite database and JSON daily context storage - OpenRouter API integration for multiple AI models - Telegram bot with commands for control, history, and interaction - Autonomous conversation loop with pause/resume and agent silencing - Model testing and statistics commands
This commit is contained in:
Executable
+428
@@ -0,0 +1,428 @@
|
||||
# Gerado por IA
|
||||
import asyncio
|
||||
import json
|
||||
import urllib.request
|
||||
from datetime import datetime
|
||||
from telegram import Update
|
||||
from telegram.ext import (
|
||||
ApplicationBuilder,
|
||||
ContextTypes,
|
||||
CommandHandler,
|
||||
MessageHandler,
|
||||
filters,
|
||||
)
|
||||
from config import settings, _silenced_agents
|
||||
from context_manager import get_context_summary, append_to_context, load_daily_context
|
||||
from ai_agent import AIAgent, SYSTEM_PROMPT_TEMPLATE
|
||||
from orchestrator_shared import _orchestrator_ref
|
||||
import aiohttp
|
||||
|
||||
|
||||
agent_registry: dict[str, AIAgent] = {}
|
||||
|
||||
|
||||
# ---------- helpers HTTP ----------
|
||||
|
||||
def _http_get(url: str, headers: dict | None = None, timeout: int = 30) -> dict:
|
||||
req = urllib.request.Request(url, headers=headers or {})
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
return json.loads(resp.read())
|
||||
|
||||
|
||||
def _http_post(url: str, payload: dict, headers: dict | None = None, timeout: int = 60) -> dict:
|
||||
data = json.dumps(payload).encode("utf-8")
|
||||
req = urllib.request.Request(url, data=data, headers=headers or {"Content-Type": "application/json"})
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
return json.loads(resp.read())
|
||||
|
||||
|
||||
# ---------- util ----------
|
||||
|
||||
async def _send_telegram(text: str) -> None:
|
||||
url = f"https://api.telegram.org/bot{settings.TELEGRAM_BOT_TOKEN}/sendMessage"
|
||||
payload = {"chat_id": settings.TELEGRAM_CHAT_ID, "text": text, "parse_mode": "HTML"}
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(url, json=payload, timeout=aiohttp.ClientTimeout(total=15)) as resp:
|
||||
if resp.status != 200:
|
||||
body = await resp.text()
|
||||
print(f"[Telegram] HTTP {resp.status}: {body[:120]}")
|
||||
except Exception as exc:
|
||||
print(f"[Telegram] Erro ao enviar mensagem: {exc}")
|
||||
|
||||
|
||||
# ---------- comandos ----------
|
||||
|
||||
async def cmd_start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
msg = (
|
||||
"<b>MultiAgentsAI</b> iniciado!\n"
|
||||
f"Agentes ativos: {len(agent_registry)}\n"
|
||||
f"{', '.join(a.name for a in agent_registry.values())}\n"
|
||||
"Os agentes estao conversando automaticamente.\n"
|
||||
"Comandos:\n"
|
||||
"/status - Status do sistema\n"
|
||||
"/historico - Ultimas mensagens de hoje\n"
|
||||
"/agentes - Lista de agentes\n"
|
||||
"/participar [mensagem] - Enviar mensagem como usuario\n"
|
||||
"/pausar /continuar - Pausar/retomar agentes"
|
||||
)
|
||||
await _send_telegram(msg)
|
||||
|
||||
|
||||
async def cmd_status(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
total = len(load_daily_context())
|
||||
paused = _orchestrator_ref.paused if _orchestrator_ref else False
|
||||
msg = (
|
||||
"<b>Status MultiAgentsAI</b>\n"
|
||||
f"Agentes ativos: {len(agent_registry)}\n"
|
||||
f"Mensagens hoje: {total}\n"
|
||||
f"Delay: {settings.CONVERSATION_DELAY}s\n"
|
||||
f"Pausado: {'Sim' if paused else 'Nao'}"
|
||||
)
|
||||
await _send_telegram(msg)
|
||||
|
||||
|
||||
async def cmd_historico(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
summary = get_context_summary(30)
|
||||
await _send_telegram(f"<b>Historico de hoje:</b>\n\n{summary}")
|
||||
|
||||
|
||||
async def cmd_agentes(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
lines = ["<b>Agentes registrados:</b>"]
|
||||
for a in agent_registry.values():
|
||||
lines.append(f" - <b>{a.name}</b> ({a.model})")
|
||||
await _send_telegram("\n".join(lines))
|
||||
|
||||
|
||||
async def cmd_participar(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
text = " ".join(context.args) if context.args else ""
|
||||
if not text:
|
||||
await _send_telegram("Uso: /participar [sua mensagem aqui]")
|
||||
return
|
||||
user_msg = f"[Usuario] {text}"
|
||||
append_to_context("Usuario", user_msg)
|
||||
await _send_telegram(f"<b>Voce:</b> {text}")
|
||||
|
||||
# Recarrega historico DIRETAMENTE do SQLite para garantir que a msg do usuario esteja la
|
||||
from database import get_today_messages
|
||||
messages = get_today_messages()
|
||||
history = "\n".join(f"[{m['agent_name']}] {m['message']}" for m in messages[-settings.MAX_CONTEXT_MESSAGES:])
|
||||
other_agents = [a.name for a in agent_registry.values() if a.name != "Usuario"]
|
||||
for agent in agent_registry.values():
|
||||
if agent.name == "Usuario":
|
||||
continue
|
||||
reply = await agent.generate_response(history, other_agents)
|
||||
if not reply.startswith("[Erro"):
|
||||
append_to_context(agent.name, reply)
|
||||
await _send_telegram(f"<b>{agent.name}:</b> {reply}")
|
||||
|
||||
|
||||
async def cmd_pausar(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
if _orchestrator_ref:
|
||||
_orchestrator_ref.paused = True
|
||||
await _send_telegram("Agentes pausados.")
|
||||
|
||||
|
||||
async def cmd_continuar(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
if _orchestrator_ref:
|
||||
_orchestrator_ref.paused = False
|
||||
await _send_telegram("Agentes retomados.")
|
||||
|
||||
|
||||
async def cmd_help(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
await _send_telegram(
|
||||
"<b>Comandos disponiveis:</b>\n"
|
||||
"/start - Iniciar\n"
|
||||
"/status - Status do sistema\n"
|
||||
"/historico - Historico de hoje\n"
|
||||
"/agentes - Lista de agentes\n"
|
||||
"/participar [msg] - Enviar mensagem\n"
|
||||
"/pausar - Pausar agentes\n"
|
||||
"/continuar - Retomar agentes\n"
|
||||
"/modelos - Lista modelos gratuitos do OpenRouter\n"
|
||||
"/testar [modelo] - Testa se um modelo esta funcionando\n"
|
||||
"/tema [assunto] - Define um tema para a conversa\n"
|
||||
"/limparchat - Limpa o historico de hoje\n"
|
||||
"/agente [nome] [msg] - Envia msg para um agente especifico\n"
|
||||
"/estatisticas - Estatisticas do banco de dados\n"
|
||||
"/buscar [termo] - Busca mensagens no historico\n"
|
||||
"/delay [s] - Altera o delay entre mensagens\n"
|
||||
"/idioma [codigo] - Altera o idioma (ex: pt_BR, en_US)\n"
|
||||
"/silenciar [nome] - Silencia/desilencia um agente\n"
|
||||
"/help - Esta ajuda"
|
||||
)
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# Novos comandos
|
||||
# ===================================================================
|
||||
|
||||
async def cmd_modelos(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Lista todos os modelos gratuitos disponiveis no OpenRouter."""
|
||||
await _send_telegram("<b>Buscando modelos gratuitos no OpenRouter...</b>")
|
||||
try:
|
||||
data = _http_get(
|
||||
f"{settings.OPENROUTER_BASE_URL}/models",
|
||||
headers={"Authorization": f"Bearer {settings.OPENROUTER_API_KEY}"},
|
||||
timeout=30,
|
||||
)
|
||||
free = [m for m in data.get("data", []) if ":free" in m.get("id", "")]
|
||||
free.sort(key=lambda m: m.get("context_length", 0), reverse=True)
|
||||
if not free:
|
||||
await _send_telegram("Nenhum modelo gratuito encontrado.")
|
||||
return
|
||||
lines = [f"<b>Modelos gratuitos ({len(free)}):</b>"]
|
||||
for m in free[:20]:
|
||||
ctx = m.get("context_length", "?")
|
||||
lines.append(f" - {m['id']} (ctx={ctx})")
|
||||
if len(free) > 20:
|
||||
lines.append(f" ... e mais {len(free) - 20} modelos.")
|
||||
await _send_telegram("\n".join(lines))
|
||||
except Exception as exc:
|
||||
await _send_telegram(f"<b>Erro:</b> {exc}")
|
||||
|
||||
|
||||
async def cmd_testar(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Testa se um modelo esta funcionando: /testar openai/gpt-4o:free"""
|
||||
args = context.args
|
||||
if not args:
|
||||
await _send_telegram("Uso: /testar [modelo]\nExemplo: /testar nvidia/nemotron-3.5-lightning:free")
|
||||
return
|
||||
model = " ".join(args)
|
||||
await _send_telegram(f"<b>Testando modelo:</b> {model}...")
|
||||
try:
|
||||
data = _http_post(
|
||||
f"{settings.OPENROUTER_BASE_URL}/chat/completions",
|
||||
{"model": model, "messages": [{"role": "user", "content": "OK"}], "max_tokens": 20},
|
||||
headers={
|
||||
"Authorization": f"Bearer {settings.OPENROUTER_API_KEY}",
|
||||
"Content-Type": "application/json",
|
||||
"HTTP-Referer": "https://multiagentsai.local",
|
||||
"X-Title": "MultiAgentsAI",
|
||||
},
|
||||
timeout=65,
|
||||
)
|
||||
choices = data.get("choices", [])
|
||||
if choices and (choices[0].get("message") or {}).get("content", "").strip():
|
||||
await _send_telegram(f"<b>OK</b> - {model}\n{choices[0]['message']['content'][:100]}")
|
||||
else:
|
||||
await _send_telegram(f"<b>FALHOU</b> - {model}\nResposta vazia (finish_reason={choices[0].get('finish_reason','?') if choices else '?'})")
|
||||
except urllib.error.HTTPError as exc:
|
||||
body = exc.read().decode("utf-8", errors="replace")[:200]
|
||||
await _send_telegram(f"<b>FALHOU</b> - {model}\nHTTP {exc.code}: {body}")
|
||||
except Exception as exc:
|
||||
await _send_telegram(f"<b>Erro:</b> {exc}")
|
||||
|
||||
|
||||
async def cmd_tema(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Define um tema/foco para a conversa: /tema futuro da IA"""
|
||||
text = " ".join(context.args) if context.args else ""
|
||||
if not text:
|
||||
await _send_telegram("Uso: /tema [assunto]\nExemplo: /tema O futuro da IA na medicina")
|
||||
return
|
||||
append_to_context("Usuario", f"[Usuario] Vamos conversar sobre: {text}")
|
||||
# Recarrega historico do SQLite diretamente
|
||||
from database import get_today_messages
|
||||
msgs = get_today_messages()
|
||||
history = "\n".join(f"[{m['agent_name']}] {m['message']}" for m in msgs[-settings.MAX_CONTEXT_MESSAGES:])
|
||||
others = [a.name for a in agent_registry.values() if a.name != "Usuario"]
|
||||
await _send_telegram(f"<b>Novo tema definido:</b> {text}")
|
||||
for agent in agent_registry.values():
|
||||
if agent.name == "Usuario":
|
||||
continue
|
||||
reply = await agent.generate_response(history, others)
|
||||
if not reply.startswith("[Erro"):
|
||||
append_to_context(agent.name, reply)
|
||||
await _send_telegram(f"<b>{agent.name}:</b> {reply}")
|
||||
|
||||
|
||||
async def cmd_limparchat(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Limpa o historico de hoje (JSON + SQLite)."""
|
||||
import os as _os
|
||||
path = _os.path.join(settings.CONTEXTS_DIR, f"conversas_{datetime.utcnow().strftime('%Y-%m-%d')}.json")
|
||||
removed = 0
|
||||
if _os.path.exists(path):
|
||||
_os.remove(path)
|
||||
removed += 1
|
||||
try:
|
||||
import sqlite3
|
||||
conn = sqlite3.connect(settings.DB_PATH)
|
||||
today = datetime.utcnow().strftime("%Y-%m-%d")
|
||||
cur = conn.execute("DELETE FROM conversations WHERE date = ?", (today,))
|
||||
removed += cur.rowcount
|
||||
conn.commit()
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
await _send_telegram(f"<b>Chat limpo!</b>\n{removed} registros removidos.")
|
||||
|
||||
|
||||
async def cmd_agente(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Envia uma mensagem para um agente especifico: /agente AI Agent 1 ola"""
|
||||
args = context.args
|
||||
if len(args) < 2:
|
||||
await _send_telegram("Uso: /agente [nome] [mensagem]\nExemplo: /agente AI Agent 1 ola, tudo bem?")
|
||||
return
|
||||
agent_name = args[0]
|
||||
msg_text = " ".join(args[1:])
|
||||
agent = agent_registry.get(agent_name)
|
||||
if not agent:
|
||||
nomes = ", ".join(agent_registry.keys())
|
||||
await _send_telegram(f"<b>Agente nao encontrado.</b>\nAgentes disponiveis: {nomes}")
|
||||
return
|
||||
append_to_context("Usuario", f"[Usuario -> {agent_name}] {msg_text}")
|
||||
msgs = load_daily_context()
|
||||
history = "\n".join(f"[{m['agent_name']}] {m['message']}" for m in msgs[-settings.MAX_CONTEXT_MESSAGES:])
|
||||
others = [a.name for a in agent_registry.values() if a.name not in (agent_name, "Usuario")]
|
||||
reply = await agent.generate_response(history, others)
|
||||
if not reply.startswith("[Erro"):
|
||||
append_to_context(agent.name, reply)
|
||||
await _send_telegram(f"<b>{agent.name}:</b> {reply}")
|
||||
else:
|
||||
await _send_telegram(reply)
|
||||
|
||||
|
||||
async def cmd_estatisticas(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Mostra estatisticas do banco de dados."""
|
||||
try:
|
||||
import sqlite3
|
||||
conn = sqlite3.connect(settings.DB_PATH)
|
||||
conn.row_factory = sqlite3.Row
|
||||
total = conn.execute("SELECT COUNT(*) FROM conversations").fetchone()[0]
|
||||
today = datetime.utcnow().strftime("%Y-%m-%d")
|
||||
today_count = conn.execute("SELECT COUNT(*) FROM conversations WHERE date = ?", (today,)).fetchone()[0]
|
||||
agents_stats = conn.execute(
|
||||
"SELECT agent_name, COUNT(*) as cnt FROM conversations WHERE date = ? GROUP BY agent_name ORDER BY cnt DESC",
|
||||
(today,),
|
||||
).fetchall()
|
||||
conn.close()
|
||||
lines = [
|
||||
"<b>Estatisticas</b>",
|
||||
f"Total de mensagens (todos os dias): {total}",
|
||||
f"Mensagens hoje: {today_count}",
|
||||
"",
|
||||
"<b>Por agente (hoje):</b>",
|
||||
]
|
||||
if agents_stats:
|
||||
for r in agents_stats:
|
||||
lines.append(f" - {r['agent_name']}: {r['cnt']}")
|
||||
else:
|
||||
lines.append(" Nenhuma mensagem hoje.")
|
||||
await _send_telegram("\n".join(lines))
|
||||
except Exception as exc:
|
||||
await _send_telegram(f"<b>Erro:</b> {exc}")
|
||||
|
||||
|
||||
async def cmd_buscar(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Busca mensagens no historico: /buscar IA"""
|
||||
args = context.args
|
||||
if not args:
|
||||
await _send_telegram("Uso: /buscar [termo]")
|
||||
return
|
||||
termo = " ".join(args).lower()
|
||||
try:
|
||||
import sqlite3
|
||||
conn = sqlite3.connect(settings.DB_PATH)
|
||||
conn.row_factory = sqlite3.Row
|
||||
rows = conn.execute(
|
||||
"SELECT agent_name, message, created_at FROM conversations WHERE LOWER(message) LIKE ? ORDER BY id DESC LIMIT 20",
|
||||
(f"%{termo}%",),
|
||||
).fetchall()
|
||||
conn.close()
|
||||
if not rows:
|
||||
await _send_telegram(f"Nenhuma mensagem encontrada com '<b>{termo}</b>'.")
|
||||
return
|
||||
lines = [f"<b>Resultados para '{termo}' ({len(rows)}):</b>"]
|
||||
for r in rows:
|
||||
snippet = r["message"][:120].replace("<", "<").replace(">", ">")
|
||||
lines.append(f" [{r['agent_name']}] {snippet}")
|
||||
await _send_telegram("\n".join(lines))
|
||||
except Exception as exc:
|
||||
await _send_telegram(f"<b>Erro:</b> {exc}")
|
||||
|
||||
|
||||
async def cmd_delay(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Altera o delay entre mensagens: /delay 60"""
|
||||
args = context.args
|
||||
if not args:
|
||||
await _send_telegram(f"Delay atual: <b>{settings.CONVERSATION_DELAY}s</b>\nUso: /delay [segundos]")
|
||||
return
|
||||
try:
|
||||
segundos = int(args[0])
|
||||
if segundos < 5:
|
||||
await _send_telegram("O delay minimo e de <b>5 segundos</b>.")
|
||||
return
|
||||
if _orchestrator_ref:
|
||||
_orchestrator_ref._delay_override = segundos
|
||||
await _send_telegram(f"Delay alterado para <b>{segundos}s</b>.")
|
||||
except ValueError:
|
||||
await _send_telegram("Valor invalido. Use: /delay 30")
|
||||
|
||||
|
||||
async def cmd_idioma(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Altera o idioma das conversas: /idioma en_US"""
|
||||
args = context.args
|
||||
if not args:
|
||||
await _send_telegram(f"Idioma atual: <b>{settings.CONVERSATION_LANGUAGE}</b>\nUso: /idioma [codigo]\nEx: /idioma pt_BR /idioma en_US")
|
||||
return
|
||||
codigo = args[0]
|
||||
# Atualiza em tempo de execucao
|
||||
from ai_agent import SYSTEM_PROMPT_TEMPLATE
|
||||
settings.CONVERSATION_LANGUAGE = codigo
|
||||
for agent in agent_registry.values():
|
||||
agent.system_prompt = SYSTEM_PROMPT_TEMPLATE.format(name=agent.name, language=codigo)
|
||||
await _send_telegram(f"Idioma alterado para <b>{codigo}</b>.")
|
||||
|
||||
|
||||
async def cmd_silenciar(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Silencia ou desilencia um agente: /silenciar AI Agent 2"""
|
||||
args = context.args
|
||||
if not args:
|
||||
nomes = ", ".join(agent_registry.keys())
|
||||
silenciados = ", ".join(_silenced_agents) or "nenhum"
|
||||
await _send_telegram(
|
||||
f"Agentes silenciados: <b>{silenciados}</b>\n"
|
||||
f"Uso: /silenciar [nome]\n"
|
||||
f"Agentes disponiveis: {nomes}"
|
||||
)
|
||||
return
|
||||
nome = " ".join(args)
|
||||
if nome not in agent_registry:
|
||||
nomes = ", ".join(agent_registry.keys())
|
||||
await _send_telegram(f"Agente '<b>{nome}</b>' nao encontrado.\nDisponiveis: {nomes}")
|
||||
return
|
||||
if nome in _silenced_agents:
|
||||
_silenced_agents.discard(nome)
|
||||
await _send_telegram(f"<b>{nome}</b> desilenciado — voltara a participar das conversas.")
|
||||
else:
|
||||
_silenced_agents.add(nome)
|
||||
await _send_telegram(f"<b>{nome}</b> silenciado — nao participara mais das conversas automaticas.")
|
||||
|
||||
|
||||
|
||||
# ---------- inicializacao ----------
|
||||
|
||||
def build_bot() -> tuple:
|
||||
from telegram.ext import ApplicationBuilder
|
||||
app = ApplicationBuilder().token(settings.TELEGRAM_BOT_TOKEN).build()
|
||||
|
||||
app.add_handler(CommandHandler("start", cmd_start))
|
||||
app.add_handler(CommandHandler("status", cmd_status))
|
||||
app.add_handler(CommandHandler("historico", cmd_historico))
|
||||
app.add_handler(CommandHandler("agentes", cmd_agentes))
|
||||
app.add_handler(CommandHandler("participar", cmd_participar))
|
||||
app.add_handler(CommandHandler("pausar", cmd_pausar))
|
||||
app.add_handler(CommandHandler("continuar", cmd_continuar))
|
||||
app.add_handler(CommandHandler("modelos", cmd_modelos))
|
||||
app.add_handler(CommandHandler("testar", cmd_testar))
|
||||
app.add_handler(CommandHandler("tema", cmd_tema))
|
||||
app.add_handler(CommandHandler("limparchat", cmd_limparchat))
|
||||
app.add_handler(CommandHandler("agente", cmd_agente))
|
||||
app.add_handler(CommandHandler("estatisticas", cmd_estatisticas))
|
||||
app.add_handler(CommandHandler("buscar", cmd_buscar))
|
||||
app.add_handler(CommandHandler("delay", cmd_delay))
|
||||
app.add_handler(CommandHandler("idioma", cmd_idioma))
|
||||
app.add_handler(CommandHandler("silenciar", cmd_silenciar))
|
||||
app.add_handler(CommandHandler("help", cmd_help))
|
||||
|
||||
return app
|
||||
Reference in New Issue
Block a user