Files
marcocosta10000 67e8df36ce 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
2026-10-17 19:25:05 -03:00

266 lines
9.3 KiB
Python
Executable File

# Gerado por IA
import sys
import os
import json
import urllib.request
import asyncio
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from config import settings
from database import init_db
from ai_agent import AIAgent
from orchestrator import Orchestrator
from telegram_bot import agent_registry
from orchestrator_shared import _orchestrator_ref
def _orchestrator_ref_set(orch):
global _orchestrator_ref
_orchestrator_ref = orch
# ---------------------------------------------------------------------------
# Helpers HTTP (urllib — sem dependências extra)
# ---------------------------------------------------------------------------
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())
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())
# ---------------------------------------------------------------------------
# Teste de modelo
# ---------------------------------------------------------------------------
def _extract_reply(data: dict) -> str | None:
"""Extrai o texto de resposta de um chat completion, verificando content e reasoning."""
choices = data.get("choices", [])
if not choices:
return None
msg = choices[0].get("message") or {}
# content direto
content = msg.get("content")
if content and str(content).strip() and str(content).strip().lower() != "none":
return str(content).strip()
# reasoning (modelos que usam este campo para resposta)
reasoning = msg.get("reasoning")
if reasoning and str(reasoning).strip():
return str(reasoning).strip()
return None
def test_model(model: str, retries: int = 2) -> tuple[bool, str]:
"""Retorna (ok, resposta_ou_erro). Tenta ate 'retries' vezes em caso de 429."""
import time
for attempt in range(retries + 1):
try:
data = _http_post(
f"{settings.OPENROUTER_BASE_URL}/chat/completions",
{
"model": model,
"messages": [{"role": "user", "content": "Responda apenas: OK"}],
"max_tokens": 100,
},
headers={
"Authorization": f"Bearer {settings.OPENROUTER_API_KEY}",
"Content-Type": "application/json",
"HTTP-Referer": "https://multiagentsai.local",
"X-Title": "MultiAgentsAI",
},
timeout=65,
)
reply = _extract_reply(data)
if reply:
return True, reply[:80]
fr = (data.get("choices", [{}])[0] or {}).get("finish_reason", "?")
return False, f"Resposta vazia (finish_reason={fr})"
except urllib.error.HTTPError as exc:
if exc.code == 429 and attempt < retries:
wait = 5 * (attempt + 1)
print(f" [Rate-limit] Aguardando {wait}s antes de retry...")
time.sleep(wait)
continue
body = exc.read().decode("utf-8", errors="replace")[:200]
return False, f"HTTP {exc.code}: {body}"
except Exception as exc:
if attempt < retries:
time.sleep(3)
continue
return False, str(exc)
return False, "Maximo de tentativas excedido"
# ---------------------------------------------------------------------------
# Busca de modelo substituto
# ---------------------------------------------------------------------------
def find_replacement_model(provider_hint: str = "") -> str | None:
"""Busca um modelo de chat gratuito alternativo no OpenRouter."""
try:
data = _http_get(
f"{settings.OPENROUTER_BASE_URL}/models",
headers={
"Authorization": f"Bearer {settings.OPENROUTER_API_KEY}",
},
timeout=30,
)
free_models = [m for m in data.get("data", []) if ":free" in m.get("id", "")]
except Exception as exc:
print(f" [Aviso] Nao foi possivel buscar modelos: {exc}")
return None
# Ordena por tamanho de contexto (maior primeiro)
free_models.sort(key=lambda m: m.get("context_length", 0), reverse=True)
already_used = {a["model"] for a in settings.AGENTS}
for m in free_models:
mid = m["id"]
if mid in already_used:
continue
# Pula modelos de embedding
if "embedding" in mid.lower():
continue
print(f" [Teste] Modelo alternativo: {mid}")
ok, info = test_model(mid)
if ok:
print(f" [OK] {mid} -> {info[:60]}")
return mid
else:
print(f" [FALHOU] {mid} -> {info[:80]}")
return None
# ---------------------------------------------------------------------------
# Validacao e substituicao de agentes
# ---------------------------------------------------------------------------
def validate_and_fix_agents() -> list[dict]:
"""Testa todos os agentes; substitui modelos falhos automaticamente."""
print("=" * 55)
print(" Validando modelos de IA...")
print("=" * 55)
agents = list(settings.AGENTS)
used_models: set[str] = set()
for i, agent in enumerate(agents):
model = agent["model"]
name = agent["name"]
if model in used_models:
# Evita duplicatas
print(f"\n[{name}] Modelo {model} ja esta em uso por outro agente. Procurando alternativa...")
replacement = find_replacement_model()
if replacement:
model = replacement
agents[i]["model"] = replacement
used_models.add(replacement)
print(f"[{name}] Substituido por: {replacement}")
else:
print(f"[{name}] ERRO: Nenhum modelo alternativo encontrado!")
continue
print(f"\n[{name}] Testando modelo: {model}")
ok, info = test_model(model)
if ok:
print(f" [OK] {name} -> {info[:80]}")
used_models.add(model)
else:
print(f" [FALHOU] {name} -> {info[:100]}")
print(f" [Busca] Procurando modelo alternativo...")
replacement = find_replacement_model()
if replacement:
agents[i]["model"] = replacement
used_models.add(replacement)
print(f" [Substituido] {name}: {model} -> {replacement}")
else:
print(f" [ERRO] Nenhum modelo alternativo encontrado para {name}!")
print("\n" + "=" * 55)
print(" Validacao concluida.")
print("=" * 55 + "\n")
return agents
# ---------------------------------------------------------------------------
# Envio /start
# ---------------------------------------------------------------------------
def send_start_command(validated_agents: list[dict]) -> None:
"""Envia o comando /start para o Telegram apos a inicializacao."""
url = f"https://api.telegram.org/bot{settings.TELEGRAM_BOT_TOKEN}/sendMessage"
agents_info = "\n".join(
f" - <b>{a['name']}</b> ({a['model']})"
for a in validated_agents
)
msg = (
"<b>MultiAgentsAI iniciado!</b>\n"
f"Agentes ativos: {len(validated_agents)}\n"
f"{agents_info}\n\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"
)
payload = json.dumps({
"chat_id": settings.TELEGRAM_CHAT_ID,
"text": msg,
"parse_mode": "HTML",
}).encode("utf-8")
req = urllib.request.Request(url, data=payload, headers={"Content-Type": "application/json"})
try:
with urllib.request.urlopen(req, timeout=15) as resp:
resp.read()
except Exception as exc:
print(f"[Start] Erro ao enviar /start: {exc}")
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
init_db()
# Valida e corrige modelos antes de iniciar
validated_agents = validate_and_fix_agents()
if not validated_agents:
print("ERRO: Nenhum agente com modelo valido!")
sys.exit(1)
agents = [AIAgent(a["name"], a["model"], a["index"]) for a in validated_agents]
for agent in agents:
agent_registry[agent.name] = agent
orchestrator = Orchestrator(agents)
# Envia /start automatico para o Telegram
print("[Start] Enviando comando /start para o Telegram...")
send_start_command(validated_agents)
print("[Start] Comando /start enviado.\n")
try:
asyncio.run(orchestrator.run())
except KeyboardInterrupt:
print("\n\n[MultiAgentsAI] Encerrado pelo usuario (Ctrl+C).")
if __name__ == "__main__":
main()