Files
gpu-services/nanoclaude/tools/infra_tool.py
T
Hyungi Ahn c7e44646fd feat(nanoclaude): Phase 3 infra intent — 시놀로지 Chat에서 서버 관리
EXAONE classifier에 infra 도구 5개 추가 (status, health, disk, network, models).
infra_tool.py가 infra.core/ 호출 → NanoClaude 반환 형식으로 변환.
"GPU 상태 알려줘" → tools: infra.status("gpu") → 구조화된 결과.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 13:24:02 +09:00

113 lines
4.0 KiB
Python

"""Infra tool — NanoClaude wrapper over infra.core/ functions.
Converts infra.core results to NanoClaude tool return format:
{"ok": bool, "tool": "infra", "operation": str, "data": list, "summary": str, "error": str}
"""
from __future__ import annotations
import asyncio
import logging
from infra.core.docker import docker_status
from infra.core.health import service_health, VALID_SERVICES
from infra.core.system import disk_usage
from infra.core.network import tailscale_status
from infra.core.models import ollama_models, mlx_models
logger = logging.getLogger(__name__)
async def status(host: str = "gpu") -> dict:
"""Docker container status overview."""
result = await docker_status(host)
if not result.ok:
return {"ok": False, "tool": "infra", "operation": "status",
"data": [], "summary": "", "error": result.error or "확인 실패"}
data = [{"name": c.name, "status": c.status, "uptime": c.uptime}
for c in result.containers]
return {"ok": True, "tool": "infra", "operation": "status",
"data": data, "summary": result.summary, "error": ""}
async def health(service: str = "") -> dict:
"""Service health check. If no service specified, check all critical ones."""
if service:
services = [service]
else:
services = ["document-server", "mlx", "ollama-gpu"]
results = []
all_ok = True
for svc in services:
r = await service_health(svc)
results.append({
"service": r.service, "status": r.status, "ok": r.ok,
"details": r.details,
})
if not r.ok:
all_ok = False
summary_parts = []
for r in results:
icon = "정상" if r["ok"] else "이상"
summary_parts.append(f"{r['service']}: {icon}")
return {"ok": all_ok, "tool": "infra", "operation": "health",
"data": results, "summary": ", ".join(summary_parts), "error": ""}
async def disk(host: str = "") -> dict:
"""Disk usage. If no host, check gpu + macmini."""
hosts = [host] if host else ["gpu", "macmini"]
all_data = []
warnings = []
for h in hosts:
result = await disk_usage(h)
if not result.ok:
warnings.append(f"{h}: {result.error}")
continue
for fs in result.filesystems:
all_data.append({"host": h, "mount": fs.mount,
"used_pct": fs.used_pct, "used": fs.used, "total": fs.total})
warnings.extend(result.warnings)
summary = ", ".join(f"{d['host']}:{d['mount']} {d['used_pct']}%" for d in all_data[:5])
return {"ok": len(warnings) == 0, "tool": "infra", "operation": "disk",
"data": all_data, "summary": summary,
"error": "; ".join(warnings) if warnings else ""}
async def network() -> dict:
"""Tailscale network status."""
result = await tailscale_status()
if not result.ok:
return {"ok": False, "tool": "infra", "operation": "network",
"data": [], "summary": "", "error": result.error or "확인 실패"}
data = [{"hostname": p.hostname, "ip": p.ip, "status": p.status, "os": p.os}
for p in result.peers]
online = sum(1 for p in result.peers if p.status != "offline")
summary = f"{online}/{len(result.peers)} 온라인"
return {"ok": True, "tool": "infra", "operation": "network",
"data": data, "summary": summary, "error": ""}
async def models(host: str = "gpu") -> dict:
"""Model inventory."""
if host == "mlx" or host == "macmini":
result = await mlx_models()
else:
result = await ollama_models(host)
if not result.ok:
return {"ok": False, "tool": "infra", "operation": "models",
"data": [], "summary": "", "error": result.error or "확인 실패"}
data = [{"id": m.id, "size": m.size} for m in result.models]
summary = f"{result.source} on {result.host}: {len(result.models)}개 모델"
return {"ok": True, "tool": "infra", "operation": "models",
"data": data, "summary": summary, "error": ""}