fix: gpt-oss:20b ollama, streaming, tableaux JSON, recherche flexible salons/categories
This commit is contained in:
parent
14985f6dbb
commit
189d56026b
21 changed files with 2824 additions and 491 deletions
159
superviseur/tasks.py
Normal file
159
superviseur/tasks.py
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
import asyncio
|
||||
import datetime
|
||||
import logging
|
||||
import time
|
||||
import os
|
||||
import discord
|
||||
|
||||
logger = logging.getLogger('Superviseur')
|
||||
|
||||
class TaskManager:
|
||||
"""Handles global background tasks, scheduled loops and reporting."""
|
||||
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
def start_all(self):
|
||||
"""Starts all background loops attached to the bot's event loop."""
|
||||
self.bot.loop.create_task(self._introspection_report_loop())
|
||||
self.bot.loop.create_task(self._voice_report_loop())
|
||||
self.bot.loop.create_task(self._daily_report_check_loop())
|
||||
self.bot.loop.create_task(self._gdpr_purge_loop())
|
||||
self.bot.loop.create_task(self._voice_evaluation_loop())
|
||||
|
||||
# Start interviews for missing assets
|
||||
for asset_id in self.bot.asset_ids:
|
||||
if str(asset_id) not in self.bot.dispatcher.assets:
|
||||
self.bot.loop.create_task(self.bot.dispatcher.start_interview(asset_id))
|
||||
|
||||
async def _voice_evaluation_loop(self):
|
||||
"""Periodic check for tactical voice connections."""
|
||||
await self.bot.wait_until_ready()
|
||||
while not self.bot.is_closed():
|
||||
for guild in self.bot.guilds:
|
||||
await self.bot.voice_manager.evaluate_and_connect(guild)
|
||||
await asyncio.sleep(30) # Reduit à 30s pour plus de réactivité en tâche de fond
|
||||
|
||||
async def _introspection_report_loop(self):
|
||||
"""Periodic broadcast of system health metrics."""
|
||||
await self.bot.wait_until_ready()
|
||||
while not self.bot.is_closed():
|
||||
await asyncio.sleep(600) # Every 10 minutes
|
||||
await self._send_health_report()
|
||||
|
||||
async def _send_health_report(self):
|
||||
"""Build and send a clinical health report embed."""
|
||||
total = self.bot.metrics["total_llm_requests"]
|
||||
success = self.bot.metrics["total_llm_success"]
|
||||
rate = (success / total * 100) if total > 0 else 100
|
||||
avg_time = (self.bot.metrics["total_llm_time"] / success) if success > 0 else 0
|
||||
|
||||
status_report = (
|
||||
f"**◈ INTELLIGENCE CORE**\n"
|
||||
f"- Requests Processed : `{total}`\n"
|
||||
f"- Reliability Rate : `{rate:.1f}%`\n"
|
||||
f"- Latency Heuristic : `{avg_time:.2f}s` (Target: < 5s)\n\n"
|
||||
f"**◈ TACTICAL OVERVIEW**\n"
|
||||
f"- Voice Protocol : `READY`\n"
|
||||
f"- Active Deployment : `{self.bot.voice_manager.current_channel.name if self.bot.voice_manager.current_channel else 'NONE'}`\n"
|
||||
)
|
||||
|
||||
await self.bot.introspection_log(
|
||||
"SYSTEM STATUS REPORT",
|
||||
status_report,
|
||||
discord.Color.dark_blue()
|
||||
)
|
||||
|
||||
async def _daily_report_check_loop(self):
|
||||
"""Check for 21:30 and trigger the daily report using local timezone."""
|
||||
await self.bot.wait_until_ready()
|
||||
report_sent_today = False
|
||||
while not self.bot.is_closed():
|
||||
now = datetime.datetime.now(self.bot.timezone)
|
||||
if now.hour == 21 and now.minute == 30:
|
||||
if not report_sent_today:
|
||||
await self._send_daily_summary()
|
||||
report_sent_today = True
|
||||
else:
|
||||
report_sent_today = False
|
||||
await asyncio.sleep(60)
|
||||
|
||||
async def _gdpr_purge_loop(self):
|
||||
"""Daily purge of old memory files (30 days) for GDPR compliance."""
|
||||
from ia.memoire import purge_old_memories
|
||||
while not self.bot.is_closed():
|
||||
now = datetime.datetime.now(self.bot.timezone)
|
||||
if now.hour == 3 and now.minute == 0:
|
||||
logger.info("◈ RGPD: Démarrage de la purge quotidienne...")
|
||||
deleted = await purge_old_memories(days=30)
|
||||
logger.info(f"◈ RGPD: Purge terminée. {deleted} fichiers supprimés.")
|
||||
await asyncio.sleep(65)
|
||||
await asyncio.sleep(30)
|
||||
|
||||
async def _send_daily_summary(self):
|
||||
"""Generate and send a dense, highly detailed clinical report at 21:30."""
|
||||
tasks = self.bot.dispatcher.tasks
|
||||
tense_sessions = self.bot.metrics.get("daily_tense_sessions", 0)
|
||||
|
||||
asset_stats = {}
|
||||
for t_id, t_data in tasks.items():
|
||||
a_id = t_data.get("asset_id")
|
||||
if a_id not in asset_stats:
|
||||
asset_name = "Inconnu"
|
||||
if str(a_id) in self.bot.dispatcher.assets:
|
||||
asset_name = self.bot.dispatcher.assets[str(a_id)].get("name", "Anonyme")
|
||||
asset_stats[a_id] = {"name": asset_name, "accepted": 0, "missed": 0, "total": 0}
|
||||
|
||||
asset_stats[a_id]["total"] += 1
|
||||
if t_data["status"] == "ACCEPTED":
|
||||
asset_stats[a_id]["accepted"] += 1
|
||||
elif t_data["status"] == "PENDING" and t_data.get("deadline", 0) < time.time():
|
||||
asset_stats[a_id]["missed"] += 1
|
||||
|
||||
stats_lines = []
|
||||
for a_id, s in asset_stats.items():
|
||||
stats_lines.append(f"- **{s['name']}** : `{s['accepted']}/{s['total']}` acceptées, `{s['missed']}` manquées.")
|
||||
|
||||
task_details = []
|
||||
for t_id, t_data in list(tasks.items())[-15:]:
|
||||
status_emoji = "✅" if t_data["status"] == "ACCEPTED" else "❌" if t_data.get("deadline", 0) < time.time() else "⏳"
|
||||
asset_name = self.bot.dispatcher.assets.get(str(t_data['asset_id']), {}).get('name', 'N/A')
|
||||
task_details.append(f"{status_emoji} `{t_id}` | Staff: {asset_name} | Urgence: {t_data['importance']} | Context: {t_data['content'][:50]}...")
|
||||
|
||||
summary = (
|
||||
f"**◈ RAPPORT ANALYTIQUE DE FIN DE CYCLE ◈**\n"
|
||||
f"Période : Dernières 24h | Heure : 21h30\n\n"
|
||||
f"**◈ BILAN GLOBAL DES OPÉRATIONS**\n"
|
||||
f"- Volume total d'actions de modération : `{len(tasks)}` \n"
|
||||
f"- Sessions vocales sous tension : `{tense_sessions}`\n\n"
|
||||
f"**◈ PERFORMANCE DES ASSETS**\n"
|
||||
+ ("\n".join(stats_lines) if stats_lines else "Aucune activité de staff enregistrée.") + "\n\n"
|
||||
f"**◈ LOG DÉTAILLÉ DES DERNIERS SIGNAUX**\n"
|
||||
+ ("\n".join(task_details) if task_details else "Aucun signal capturé.") + "\n\n"
|
||||
f"**◈ CONCLUSION HEURISTIQUE**\n"
|
||||
f"Le système est nominal. " + ("PRÉCONISATION : Vigilance accrue requise sur les membres suspects." if tense_sessions > 0 else "État de l'écosystème : Stable.")
|
||||
)
|
||||
|
||||
if summary and self.bot.admin_ids:
|
||||
for admin_id in self.bot.admin_ids:
|
||||
admin = self.bot.get_user(admin_id) or await self.bot.fetch_user(admin_id)
|
||||
if admin:
|
||||
await self.bot.messaging.send_embed(admin, "SYNTHÈSE CLINIQUE QUOTIDIENNE", summary, color=discord.Color.dark_purple())
|
||||
|
||||
self.bot.metrics["daily_tense_sessions"] = 0
|
||||
self.bot.dispatcher.tasks = {k: v for k, v in tasks.items() if v['status'] == 'PENDING' and v.get('deadline', 0) > time.time()}
|
||||
|
||||
# Le chemin de Active tasks doit être vers /data si on l'a déplacé.
|
||||
# Pour l'instant, on sauvegarde dans data/
|
||||
data_dir = os.path.join(os.path.dirname(__file__), "..", "data")
|
||||
os.makedirs(data_dir, exist_ok=True)
|
||||
self.bot.dispatcher._save_data(self.bot.dispatcher.tasks, os.path.join(data_dir, "active_tasks.json"))
|
||||
|
||||
async def _voice_report_loop(self):
|
||||
"""Periodic broadcast of active voice session summaries."""
|
||||
await self.bot.wait_until_ready()
|
||||
while not self.bot.is_closed():
|
||||
await asyncio.sleep(900) # Every 15 minutes
|
||||
if self.bot.voice_manager.is_monitoring and self.bot.voice_manager.session_buffer:
|
||||
report = await self.bot.voice_manager.generate_session_report()
|
||||
await self.bot.introspection_log("VOICE SESSION UPDATE (15m)", report, discord.Color.blue())
|
||||
Loading…
Add table
Add a link
Reference in a new issue