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
429 lines
17 KiB
Python
Executable File
429 lines
17 KiB
Python
Executable File
# 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
|