The wave logs each served candidate's feature vector to rec_impression_features. Nightly training joins them with impression outcomes, validates on the last week and activates the model only when it beats the v1 order and the active model. The service loads the active ranker and uses its order in place of the v1 score. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
69 lines
2.6 KiB
Python
69 lines
2.6 KiB
Python
"""Точка входа: `pipu-rec serve` (онлайн-API) или `pipu-rec analyze` (воркер аудиоанализа)."""
|
|
|
|
import argparse
|
|
import logging
|
|
|
|
import psycopg
|
|
import redis.asyncio as aioredis
|
|
import uvicorn
|
|
|
|
from pipu_rec.analysis.worker import Worker
|
|
from pipu_rec.api.app import create_app
|
|
from pipu_rec.settings import Settings
|
|
from pipu_rec.train.pipeline import NightlyTrainer, train_all
|
|
from pipu_rec.wave.contexts import load_contexts
|
|
from pipu_rec.wave.service import WaveService
|
|
|
|
log = logging.getLogger("pipu_rec")
|
|
|
|
|
|
def _serve(settings: Settings) -> None:
|
|
rdb = aioredis.Redis.from_url(settings.redis_url, socket_connect_timeout=2, socket_timeout=2)
|
|
|
|
async def check_postgres() -> None:
|
|
async with await psycopg.AsyncConnection.connect(settings.database_url, connect_timeout=2) as conn:
|
|
await conn.execute("SELECT 1")
|
|
|
|
async def check_redis() -> None:
|
|
await rdb.ping()
|
|
|
|
wave = WaveService(settings.database_url, settings.redis_url, load_contexts(settings.contexts_path))
|
|
if settings.train_hour >= 0:
|
|
# После активации новой модели каталог перечитывается сразу — горячая подмена векторов.
|
|
NightlyTrainer(settings.database_url, settings.train_hour, on_done=wave.reload).start()
|
|
app = create_app(settings, {"postgres": check_postgres, "redis": check_redis}, wave)
|
|
uvicorn.run(app, host=settings.http_host, port=settings.http_port, log_level=settings.log_level)
|
|
|
|
|
|
def _analyze(settings: Settings, workers: int) -> None:
|
|
Worker(
|
|
settings.database_url,
|
|
settings.library_root,
|
|
settings.library_host_root,
|
|
workers,
|
|
settings.ffmpeg_path,
|
|
).run_forever()
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(prog="pipu-rec")
|
|
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
sub.add_parser("serve", help="внутренний API рекомендаций")
|
|
analyze = sub.add_parser("analyze", help="воркер аудиоанализа")
|
|
analyze.add_argument("--workers", type=int, default=4)
|
|
sub.add_parser("train", help="обучить модели сейчас (то же, что ночной прогон)")
|
|
args = parser.parse_args()
|
|
|
|
settings = Settings()
|
|
logging.basicConfig(level=settings.log_level.upper())
|
|
if args.cmd == "serve":
|
|
_serve(settings)
|
|
elif args.cmd == "train":
|
|
log.info("training: %s", train_all(settings.database_url))
|
|
else:
|
|
_analyze(settings, args.workers)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|