""" AI Brain — 设备端 AI 大脑(自主决策引擎) 架构定位: ┌──────────────────────────────────────────────────────┐ │ AI Brain (本模块) │ │ ┌────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ 卡若AI API │ │ 任务队列 │ │ 离线缓冲 │ │ │ │ LLM 决策 │ │ 心跳驱动 │ │ 断连续航 │ │ │ └─────┬──────┘ └──────┬───────┘ └──────┬───────┘ │ │ └────────────────┼──────────────────┘ │ │ ↓ 决策结果: skill + action + params │ │ ┌────────────────┴──────────────────┐ │ │ │ Skill执行器(Frida优先/u2兜底) │ │ │ └───────────────────────────────────┘ │ └──────────────────────────────────────────────────────┘ 核心能力: 1. 通过卡若AI API 获取 LLM 智能决策 2. 心跳驱动的自主运行循环(每 N 秒检查一次) 3. 离线模式:服务器断连时 AI 独立运行,结果缓冲待上传 4. Frida 优先通道:有 Frida 时优先用 Hook 操作,无则降级 u2 5. 任务队列:服务器推送 + AI 自主生成 卡若AI API: POST {ai_api_url}/api/gateway/chat Authorization: Bearer {ai_api_key} Body: {"messages": [...], "max_tokens": 1024} @author 卡若 @version 1.0.0 """ import asyncio import json import logging import time import os from typing import Optional, Dict, Any, List from datetime import datetime from collections import deque logger = logging.getLogger(__name__) try: import aiohttp except ImportError: aiohttp = None logger.warning("aiohttp 未安装,AI Brain 网络请求将使用 urllib (pip install aiohttp)") class TaskItem: """任务项""" __slots__ = ("id", "source", "instruction", "skill_chain", "priority", "created_at", "status", "result") def __init__(self, instruction: str, source: str = "ai", priority: int = 5): self.id = f"task_{int(time.time() * 1000)}" self.source = source # "server" | "ai" | "standing_order" self.instruction = instruction self.skill_chain: List[Dict[str, Any]] = [] # AI 解析后的执行链 self.priority = priority # 1-10, 10=最高 self.created_at = time.time() self.status = "pending" # pending → running → done / failed self.result: Optional[Dict] = None class AIBrain: """ 设备端 AI 大脑 — 心跳驱动的自主决策引擎 工作模式: - online: 服务器在线,接收推送任务 + AI 辅助决策 - offline: 服务器断连,AI 独立运行,执行 standing_orders + 自主决策 """ VERSION = "1.0.0" SYSTEM_PROMPT = """你是一个运行在 Android 手机上的 AI 助手。你的任务是根据当前手机状态和待办事项,决定下一步要执行的操作。 你可以控制以下 APP:微信(wechat)、抖音(douyin)、小红书(xhs)、闲鱼(xianyu)、Soul(soul)。 每个 APP 支持的操作(action): - wechat: send_message, get_messages, get_contacts, add_friend, accept_friend, post_moment, like_moment, get_groups - douyin: send_message, get_messages, get_fans, reply_comment - xhs: send_message, get_messages, like_note - xianyu: send_message, get_messages - soul: send_message, get_messages 通用操作:screenshot, click, input, swipe, app_start, app_stop, device_info 你必须以 JSON 格式回复,结构如下: { "should_act": true/false, "reason": "决策原因", "actions": [ {"script": "wechat", "action": "send_message", "params": {"to": "xxx", "content": "xxx"}}, ... ] } 如果当前没有需要执行的任务,返回 {"should_act": false, "reason": "无待处理任务"}。 如果设备状态异常(低电量、无网络),优先处理设备问题。""" def __init__( self, ai_api_url: str = "http://localhost:3102", ai_api_key: str = "", ai_model: str = "auto", brain_interval: int = 60, max_offline_buffer: int = 500, standing_orders: Optional[List[str]] = None, enabled: bool = True, ): self.ai_api_url = ai_api_url.rstrip("/") self.ai_api_key = ai_api_key self.ai_model = ai_model self.brain_interval = brain_interval # AI 思考间隔(秒) self.enabled = enabled self.task_queue: deque = deque(maxlen=200) self.offline_buffer: deque = deque(maxlen=max_offline_buffer) self.standing_orders = standing_orders or [] self._online = True self._running = False self._last_think_time = 0 self._think_count = 0 self._execute_count = 0 self._session: Optional[Any] = None # aiohttp.ClientSession logger.info(f"🧠 AI Brain v{self.VERSION} 初始化") logger.info(f" API: {self.ai_api_url}") logger.info(f" 间隔: {self.brain_interval}s") logger.info(f" 常驻指令: {len(self.standing_orders)} 条") logger.info(f" 启用: {self.enabled}") @property def online(self) -> bool: return self._online @online.setter def online(self, value: bool): if self._online != value: self._online = value mode = "在线" if value else "离线(自主运行)" logger.info(f"🧠 AI Brain 模式切换: {mode}") def add_task(self, instruction: str, source: str = "server", priority: int = 5): """添加任务到队列""" task = TaskItem(instruction=instruction, source=source, priority=priority) self.task_queue.append(task) logger.info(f"📋 新任务入队: [{source}] {instruction[:50]}...") return task.id def add_standing_order(self, order: str): """添加常驻指令(离线时自动执行)""" if order not in self.standing_orders: self.standing_orders.append(order) logger.info(f"📌 新增常驻指令: {order[:50]}...") def buffer_offline_result(self, result: Dict): """缓冲离线执行结果(待服务器重连后上传)""" result["buffered_at"] = time.time() self.offline_buffer.append(result) def flush_offline_buffer(self) -> List[Dict]: """刷出离线缓冲(重连后调用)""" results = list(self.offline_buffer) self.offline_buffer.clear() return results async def _get_session(self): """获取/创建 HTTP session""" if aiohttp and (self._session is None or self._session.closed): timeout = aiohttp.ClientTimeout(total=30) self._session = aiohttp.ClientSession(timeout=timeout) return self._session async def call_ai(self, messages: List[Dict[str, str]], max_tokens: int = 1024) -> Optional[str]: """调用卡若AI API""" url = f"{self.ai_api_url}/api/gateway/chat" headers = { "Content-Type": "application/json", "Authorization": f"Bearer {self.ai_api_key}", } payload = { "messages": messages, "max_tokens": max_tokens, } if self.ai_model and self.ai_model != "auto": payload["model"] = self.ai_model try: if aiohttp: session = await self._get_session() async with session.post(url, json=payload, headers=headers) as resp: if resp.status != 200: body = await resp.text() logger.error(f"AI API 返回 {resp.status}: {body[:200]}") return None data = await resp.json() else: import urllib.request req = urllib.request.Request( url, data=json.dumps(payload).encode(), headers=headers, method="POST", ) loop = asyncio.get_event_loop() with await loop.run_in_executor(None, urllib.request.urlopen, req) as resp: data = json.loads(resp.read().decode()) content = ( data.get("choices", [{}])[0] .get("message", {}) .get("content", "") ) return content except Exception as e: logger.error(f"AI API 调用失败: {e}") return None async def think(self, device_status: Dict, pending_instructions: Optional[List[str]] = None) -> Optional[Dict]: """ AI 思考:基于当前设备状态 + 待办任务,决定下一步操作 返回: {"should_act": bool, "reason": str, "actions": [...]} 或 None """ if not self.enabled: return None self._last_think_time = time.time() self._think_count += 1 context_parts = [f"当前时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"] context_parts.append(f"设备状态: {json.dumps(device_status, ensure_ascii=False, default=str)}") if not self._online: context_parts.append("⚠️ 当前处于离线模式(服务器未连接),需要自主决策") if pending_instructions: context_parts.append(f"待处理任务: {json.dumps(pending_instructions, ensure_ascii=False)}") if self.standing_orders and not self._online: context_parts.append(f"常驻指令(离线时执行): {json.dumps(self.standing_orders, ensure_ascii=False)}") user_message = "\n".join(context_parts) messages = [ {"role": "system", "content": self.SYSTEM_PROMPT}, {"role": "user", "content": user_message}, ] response = await self.call_ai(messages, max_tokens=1024) if not response: return None try: start = response.find("{") end = response.rfind("}") + 1 if start >= 0 and end > start: decision = json.loads(response[start:end]) logger.info(f"🧠 AI 决策: should_act={decision.get('should_act')}, reason={decision.get('reason', '')[:60]}") return decision except json.JSONDecodeError as e: logger.warning(f"AI 响应解析失败: {e}, raw={response[:200]}") return None async def heartbeat_cycle(self, device_status: Dict, execute_fn) -> Dict: """ 心跳驱动的 AI 循环(每次心跳调用一次) Args: device_status: 当前设备状态 execute_fn: 执行函数 async (script, action, params) -> result Returns: {"thought": bool, "acted": bool, "results": [...]} """ if not self.enabled: return {"thought": False, "acted": False, "results": []} now = time.time() if now - self._last_think_time < self.brain_interval: return {"thought": False, "acted": False, "results": [], "skip": "间隔未到"} pending = [t.instruction for t in self.task_queue if t.status == "pending"] decision = await self.think(device_status, pending if pending else None) if not decision or not decision.get("should_act"): return {"thought": True, "acted": False, "results": [], "reason": decision.get("reason") if decision else "AI无响应"} results = [] actions = decision.get("actions", []) for action_spec in actions: script = action_spec.get("script", "") action = action_spec.get("action", "") params = action_spec.get("params", {}) if not script or not action: continue try: result = await execute_fn(script, action, params) self._execute_count += 1 entry = { "script": script, "action": action, "params": params, "result": result, "timestamp": time.time(), } results.append(entry) if not self._online: self.buffer_offline_result(entry) except Exception as e: logger.error(f"AI Brain 执行失败 [{script}.{action}]: {e}") results.append({ "script": script, "action": action, "error": str(e), "timestamp": time.time(), }) for task in list(self.task_queue): if task.status == "pending": task.status = "done" task.result = {"actions": len(actions), "results_count": len(results)} return {"thought": True, "acted": True, "results": results, "reason": decision.get("reason", "")} async def autonomous_loop(self, device_status_fn, execute_fn, stop_event: asyncio.Event): """ 离线自主运行循环 — 服务器断连时启动 持续运行直到 stop_event 被设置(通常是服务器重连时) Args: device_status_fn: 获取设备状态的函数 () -> dict execute_fn: 执行函数 async (script, action, params) -> result stop_event: 停止信号 """ logger.info("🧠 AI Brain 进入自主运行模式") self.online = False cycle = 0 while not stop_event.is_set(): cycle += 1 try: status = device_status_fn() battery = status.get("battery_level", 100) if battery < 10: logger.warning(f"⚡ 电量过低 ({battery}%),暂停自主操作") await asyncio.sleep(self.brain_interval * 2) continue result = await self.heartbeat_cycle(status, execute_fn) if result.get("acted"): logger.info(f"🧠 自主执行第{cycle}轮: {len(result.get('results', []))}个操作") except Exception as e: logger.error(f"自主运行循环异常: {e}") try: await asyncio.wait_for(stop_event.wait(), timeout=self.brain_interval) break except asyncio.TimeoutError: pass logger.info("🧠 AI Brain 退出自主运行模式") self.online = True def get_status(self) -> Dict: """获取 AI Brain 状态""" return { "enabled": self.enabled, "online": self._online, "running": self._running, "brain_interval": self.brain_interval, "think_count": self._think_count, "execute_count": self._execute_count, "task_queue_size": len(self.task_queue), "offline_buffer_size": len(self.offline_buffer), "standing_orders": len(self.standing_orders), "last_think_time": self._last_think_time, "ai_api_url": self.ai_api_url, "ai_model": self.ai_model, "version": self.VERSION, } async def close(self): """关闭资源""" if self._session and not self._session.closed: await self._session.close() self._running = False logger.info("🧠 AI Brain 已关闭")