Beta/core/redis_worker.py

99 lines
3.3 KiB
Python
Raw Normal View History

2026-02-06 22:23:20 +01:00
"""Redis worker support for Superviseur bot."""
import json
import uuid
import asyncio
import logging
from typing import Dict, Optional, Any
logger = logging.getLogger('Superviseur')
class RedisWorker:
"""Manages Redis-based LLM job queue."""
def __init__(self, bot):
self.bot = bot
self.redis_client = None
self._pending_jobs: Dict[str, asyncio.Future] = {}
self._listener_task: Optional[asyncio.Task] = None
self._response_queue = 'llm_queue:responses'
self._job_queue = 'llm_queue:jobs'
async def initialize(self, redis_url: str) -> bool:
"""Initialize Redis connection."""
if not self.bot.redis_available:
return False
try:
self.redis_client = self.bot.aioredis.from_url(redis_url)
self._listener_task = asyncio.create_task(self._listen())
logger.info("Redis LLM worker enabled; listening for responses")
return True
except Exception:
logger.exception("Failed to initialize redis client")
return False
async def close(self) -> None:
"""Close Redis connection."""
if self._listener_task:
try:
self._listener_task.cancel()
except Exception:
pass
if self.redis_client:
try:
await self.redis_client.close()
except Exception:
pass
async def enqueue_job(self, payload: Dict[str, Any], timeout: int = 60) -> Optional[Dict[str, Any]]:
"""Enqueue an LLM job and wait for result."""
if not self.redis_client:
return None
job_id = str(uuid.uuid4())
future = self.bot.loop.create_future()
self._pending_jobs[job_id] = future
payload['job_id'] = job_id
await self.redis_client.lpush(self._job_queue, json.dumps(payload))
logger.debug(f"LLM job {job_id} enqueued.")
try:
return await asyncio.wait_for(future, timeout=timeout)
finally:
self._pending_jobs.pop(job_id, None)
async def _listen(self) -> None:
"""Listen for Redis responses."""
logger.info(f"Starting Redis response listener on {self._response_queue}")
while True:
try:
result = await self.redis_client.brpop(self._response_queue, timeout=1)
if result is None:
await asyncio.sleep(0.1)
continue
data_bytes = result[1]
response = json.loads(data_bytes.decode('utf-8'))
job_id = response.get('job_id')
if job_id in self._pending_jobs:
future = self._pending_jobs[job_id]
if not future.done():
future.set_result(response)
logger.debug(f"LLM job {job_id} completed via Redis.")
else:
logger.warning(f"Response for unknown job {job_id}")
except asyncio.CancelledError:
logger.info("Redis listener cancelled")
break
except Exception as e:
logger.exception(f"Redis listener error: {e}")
await asyncio.sleep(5)