Files
workphone-sdk/sdk/app/services/karuo_device_ai.py

131 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
服务端编排:卡若 AI 网关决策 → WebSocket 下发到手机 Agent 执行。
手机无需本地 ai_api_key统一走平常使用的 /api/gateway/chat。
"""
from __future__ import annotations
import json
import logging
import os
from pathlib import Path
from typing import Any, Dict, Optional
import importlib.util
logger = logging.getLogger(__name__)
_brain: Optional[Any] = None
def _load_ai_brain_class():
path = Path(__file__).resolve().parent.parent / "agent" / "ai_brain.py"
spec = importlib.util.spec_from_file_location("workphone_device_ai_brain", path)
if spec is None or spec.loader is None:
raise ImportError(f"无法加载 AIBrain: {path}")
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod.AIBrain
from services.ws_hub import ws_hub
_AGENT_CFG_CANDIDATES = [
Path(__file__).resolve().parent.parent / "agent" / "config.json",
Path(__file__).resolve().parents[2] / "agent" / "config.json",
]
def _load_ai_cfg() -> Dict[str, Any]:
for path in _AGENT_CFG_CANDIDATES:
try:
if path.is_file():
with open(path, encoding="utf-8") as f:
data = json.load(f)
return data.get("ai_brain") or {}
except Exception as exc:
logger.warning("读取 %s 失败: %s", path, exc)
return {}
def get_karuo_brain(force_reload: bool = False) -> Optional[Any]:
global _brain
if _brain is not None and not force_reload:
return _brain
_brain = None
cfg = _load_ai_cfg()
api_key = (
os.environ.get("KARUO_API_KEY")
or os.environ.get("WP_AI_API_KEY")
or os.environ.get("AI_BRAIN_API_KEY")
or cfg.get("api_key")
or ""
).strip()
api_url = (
os.environ.get("KARUO_API_URL")
or os.environ.get("WP_AI_API_URL")
or os.environ.get("AI_BRAIN_API_URL")
or cfg.get("api_url")
or "http://127.0.0.1:3102"
).strip()
# 本机直跑 SDK非 Docker 容器内)时 host.docker.internal 不可达
if "host.docker.internal" in api_url and not os.path.exists("/.dockerenv"):
api_url = api_url.replace("host.docker.internal", "127.0.0.1")
if not api_key:
logger.warning("卡若 AI Brain 未配置 api_keysdk/agent/config.json")
return None
_brain = _load_ai_brain_class()(
ai_api_url=api_url,
ai_api_key=api_key,
ai_model=cfg.get("model") or "auto",
brain_interval=int(cfg.get("brain_interval") or 60),
standing_orders=cfg.get("standing_orders") or [],
enabled=True,
)
return _brain
def device_status(device_id: str) -> Dict[str, Any]:
info = ws_hub.get_device_info(device_id) or {}
qs = info.get("quick_status") or info.get("last_status") or {}
return {
"device_id": device_id,
"online": ws_hub.is_online(device_id),
"model": info.get("model"),
"capabilities": info.get("capabilities") or [],
"u2": qs.get("u2", False),
"frida": info.get("frida_available", False),
"agent_version": info.get("agent_version"),
}
async def chat_and_execute_on_device(
device_id: str,
instruction: str,
timeout: int = 120,
) -> Dict[str, Any]:
brain = get_karuo_brain()
if not brain:
return {"code": 503, "message": "卡若 AI 未配置agent/config.json ai_brain.api_key"}
if not ws_hub.is_online(device_id):
return {"code": 503, "message": "设备不在线"}
async def execute_fn(script: str, action: str, params: Dict[str, Any]) -> Dict[str, Any]:
return await ws_hub.send_command(
device_id,
{
"type": "execute",
"data": {
"script": script,
"action": action,
"params": params or {},
"channel": "auto",
},
},
timeout=timeout,
)
result = await brain.chat_and_execute(instruction.strip(), device_status(device_id), execute_fn)
result["device_id"] = device_id
result["orchestrator"] = "server_karuo_gateway"
return result