from fastapi import APIRouter, Depends import psutil from pydantic import BaseModel from sqlalchemy.orm import Session as DBSession from app.core.discord.scheduler import send_useless_tip, send_weather_laundry_notification from app.core.memory_kb.tagging import backfill_tags from app.core.weather.weather_cache import get_latest as get_latest_weather from app.core.memory_kb.word_memory import get_word_pool_with_counts from app.db.database import get_db router = APIRouter(prefix="/api/system", tags=["system"]) class MemoryStats(BaseModel): cpu_percent: float memory_percent: float memory_used_mb: float memory_total_mb: float gpu_memory_used_mb: float | None = None gpu_memory_total_mb: float | None = None gpu_memory_percent: float | None = None @router.get("/memory", response_model=MemoryStats) async def get_memory_stats(): """CPU と GPU のメモリ使用状況を返す""" # CPU/メモリ情報 cpu_percent = psutil.cpu_percent(interval=0.1) memory = psutil.virtual_memory() stats = { "cpu_percent": cpu_percent, "memory_percent": memory.percent, "memory_used_mb": memory.used / (1024 * 1024), "memory_total_mb": memory.total / (1024 * 1024), } # GPU 情報(オプション) try: import pynvml pynvml.nvmlInit() device_count = pynvml.nvmlDeviceGetCount() if device_count > 0: handle = pynvml.nvmlDeviceGetHandleByIndex(0) mem_info = pynvml.nvmlDeviceGetMemoryInfo(handle) stats["gpu_memory_used_mb"] = mem_info.used / (1024 * 1024) stats["gpu_memory_total_mb"] = mem_info.total / (1024 * 1024) stats["gpu_memory_percent"] = (mem_info.used / mem_info.total) * 100 pynvml.nvmlShutdown() except Exception: pass return MemoryStats(**stats) @router.post("/weather-notify/test") async def test_weather_notify(): """天気予報→Discord通知を手動で即時実行する(動作確認用)""" await send_weather_laundry_notification() return {"status": "sent"} @router.post("/useless-tip/test") async def test_useless_tip(): """どうでもいいtips→Discord通知を手動で即時実行する(動作確認用)""" await send_useless_tip() return {"status": "sent"} @router.get("/weather/latest") async def get_latest_weather_data(): """直近で取得した明日の天気データを返す(未取得ならnull)""" return get_latest_weather() @router.get("/word-pool") async def get_word_pool(): """どうでもいいtips生成に使われているワードプールを出現回数順で返す""" return get_word_pool_with_counts() @router.post("/tags/backfill") async def run_tags_backfill(limit: int | None = None, db: DBSession = Depends(get_db)): """過去のユーザーメッセージのうち、タグが未生成のものにまとめてタグを付ける""" count = await backfill_tags(db, limit=limit) return {"tagged_count": count}