#!/usr/bin/env python3 """Инференс joint-модели планшета: CLI и HTTP-служба. Разовый разбор: python predict.py "поставь таймер на 15 минут" python predict.py "включи Ваню Дмитриенко" HTTP-служба (для app/api/voice/chat/route.ts как локальный «мозг»): python predict.py --serve --port 8091 # POST /predict {"text": "..."} → {intent, slots, score, tool} # score < THRESHOLD → intent принудительно none (порог на стороне службы, # но потребитель всё равно решает сам падать ли на облачный LLM). Порог THRESHOLD=0.7 по умолчанию (переопределяется env). """ import argparse import json import os import sys import joint_model_def as jm import tablet_map HERE = os.path.dirname(os.path.abspath(__file__)) MODEL_DIR = os.path.join(HERE, "joint_model") THRESHOLD = float(os.environ.get("THRESHOLD", "0.7")) _model = _tok = _cfg = None def _ensure_loaded(): global _model, _tok, _cfg if _model is None: _model, _tok, _cfg = jm.load(MODEL_DIR) jm.predict(_model, _tok, _cfg, "прогрев") # первый форвард медленнее def analyze(text): _ensure_loaded() intent, slots, score = jm.predict(_model, _tok, _cfg, text) if score < THRESHOLD: intent = "none" tool = tablet_map.to_tool_call(intent, slots) if intent != "none" else None return {"intent": intent, "slots": slots, "score": round(score, 3), "tool": tool} def serve(port): from http.server import BaseHTTPRequestHandler, HTTPServer class Handler(BaseHTTPRequestHandler): def _send(self, code, obj): body = json.dumps(obj, ensure_ascii=False).encode("utf-8") self.send_response(code) self.send_header("Content-Type", "application/json; charset=utf-8") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) def do_GET(self): if self.path == "/health": self._send(200, {"ok": True}) else: self._send(404, {"error": "not found"}) def do_POST(self): if self.path != "/predict": return self._send(404, {"error": "not found"}) n = int(self.headers.get("Content-Length", 0)) try: data = json.loads(self.rfile.read(n) or b"{}") text = (data.get("text") or "").strip() except Exception: return self._send(400, {"error": "bad json"}) if not text: return self._send(400, {"error": "empty text"}) self._send(200, analyze(text)) def log_message(self, *a): pass # тихо _ensure_loaded() print(f"tablet-agent слушает http://0.0.0.0:{port} (порог {THRESHOLD})") HTTPServer(("0.0.0.0", port), Handler).serve_forever() def main(): ap = argparse.ArgumentParser() ap.add_argument("text", nargs="*", help="запрос для разбора") ap.add_argument("--serve", action="store_true", help="поднять HTTP-службу") ap.add_argument("--port", type=int, default=8091) args = ap.parse_args() if args.serve: serve(args.port) return if not args.text: print("Использование: python predict.py \"текст запроса\"") sys.exit(1) print(json.dumps(analyze(" ".join(args.text)), ensure_ascii=False, indent=2)) if __name__ == "__main__": main()