feat: AI 서비스 MLX 듀얼 백엔드 및 모델 최적화

- MLX(맥미니 27B) 우선 → Ollama(조립컴 9B) fallback 구조
- pydantic-settings 기반 config 전환
- health check에 MLX 상태 추가
- 텍스트 모델 qwen3:8b → qwen3.5:9b-q8_0 변경

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-03-06 23:17:50 +09:00
parent cad662473b
commit 2f7e083db0
14 changed files with 231 additions and 140 deletions

View File

@@ -1,58 +1,38 @@
import asyncio
import httpx
from services.ollama_client import ollama_client
from services.db_client import get_daily_qc_stats, get_issues_for_date
from services.utils import load_prompt
from config import settings
REPORT_PROMPT_PATH = "prompts/daily_report.txt"
def _load_prompt(path: str) -> str:
with open(path, "r", encoding="utf-8") as f:
return f.read()
async def _fetch_one(client: httpx.AsyncClient, url: str, params: dict, headers: dict):
try:
r = await client.get(url, params=params, headers=headers)
if r.status_code == 200:
return r.json()
except Exception:
pass
return None
async def _fetch_system1_data(date_str: str, token: str) -> dict:
headers = {"Authorization": f"Bearer {token}"}
data = {"attendance": None, "work_reports": None, "patrol": None}
params = {"date": date_str}
base = settings.SYSTEM1_API_URL
try:
async with httpx.AsyncClient(timeout=15.0) as client:
# 근태
try:
r = await client.get(
f"{settings.SYSTEM1_API_URL}/api/attendance/daily-status",
params={"date": date_str},
headers=headers,
)
if r.status_code == 200:
data["attendance"] = r.json()
except Exception:
pass
# 작업보고
try:
r = await client.get(
f"{settings.SYSTEM1_API_URL}/api/daily-work-reports/summary",
params={"date": date_str},
headers=headers,
)
if r.status_code == 200:
data["work_reports"] = r.json()
except Exception:
pass
# 순회점검
try:
r = await client.get(
f"{settings.SYSTEM1_API_URL}/api/patrol/today-status",
params={"date": date_str},
headers=headers,
)
if r.status_code == 200:
data["patrol"] = r.json()
except Exception:
pass
attendance, work_reports, patrol = await asyncio.gather(
_fetch_one(client, f"{base}/api/attendance/daily-status", params, headers),
_fetch_one(client, f"{base}/api/daily-work-reports/summary", params, headers),
_fetch_one(client, f"{base}/api/patrol/today-status", params, headers),
)
except Exception:
pass
return data
attendance = work_reports = patrol = None
return {"attendance": attendance, "work_reports": work_reports, "patrol": patrol}
def _format_attendance(data) -> str:
@@ -102,7 +82,7 @@ async def generate_daily_report(
qc_stats = get_daily_qc_stats(date_str)
qc_issues = get_issues_for_date(date_str)
template = _load_prompt(REPORT_PROMPT_PATH)
template = load_prompt(REPORT_PROMPT_PATH)
prompt = template.format(
date=date_str,
attendance_data=_format_attendance(system1_data["attendance"]),