Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
1 line
9.6 KiB
JSON
1 line
9.6 KiB
JSON
{"repo": "9701sunsets/aiot-intelligent-env-assistant", "files": {"backend_server/app/ai/llm_service.py": {"2c18a914a457ef41bc23bd7fc060d85134441956": {"t": 1786067530.3270874, "n": 8336, "h": "d9ed9bbcf6f7f950", "c": "import os\nimport json\nimport requests\nimport logging\nimport math\ntry:\n import openai\nexcept ImportError:\n openai = None\n\nlogger = logging.getLogger(__name__)\n\nclass LLMService:\n \"\"\"\n 封装与语言模型交互的逻辑,提供统一接口\n generate_advice(data) -> {\"advice\": str, \"level\": \"ok\"|\"warning\"|\"critical\"}\n \"\"\"\n def __init__(self, openai_model=\"gpt-3.5-turbo\", deepseek_endpoint=None, deepseek_key=None):\n self.model = openai_model\n self.api_key = os.environ.get(\"OPENAI_API_KEY\")\n # DeepSeek 配置:优先从构造参数,再从环境变量读取\n self.deepseek_endpoint = deepseek_endpoint or os.environ.get(\"DEEPSEEK_ENDPOINT\", \"https://api.deepseek.com\")\n self.deepseek_key = deepseek_key or os.environ.get(\"DEEPSEEK_API_KEY\")\n if self.api_key and openai:\n openai.api_key = self.api_key\n\n def generate_advice(self, data):\n \"\"\"\n data: dict with keys device_id, temperature, humidity, light\n 返回 dict: {\"advice\": \"...\", \"level\": \"...\"}\n \"\"\"\n\n def _is_valid_number(v):\n try:\n f = float(v)\n return math.isfinite(f)\n except Exception:\n return False\n\n is_chat = \"question\" in data and data.get(\"question\")\n if not is_chat:\n if not (_is_valid_number(data.get(\"temperature\")) and _is_valid_number(data.get(\"humidity\")) and _is_valid_number(data.get(\"light\"))):\n logger.warning(\"Invalid sensor inputs for device %s: %s\", data.get(\"device_id\"), data)\n return {\"advice\": \"传感器数据缺失,请检查设备连接。\", \"level\": \"critical\"}\n\n # 若配置了 deepseek key,优先调用 DeepSeek\n logger.info(\"generate_advice: deepseek_key=%s\", bool(self.deepseek_key))\n if self.deepseek_key:\n try:\n ds = self._call_deepseek(data, mode=\"chat\" if is_chat else \"advice\")\n if isinstance(ds, dict):\n if is_chat and ds.get(\"answer\"):\n return ds\n if ds.get(\"advice\") and ds.get(\"level\"):\n return ds\n except Exception as e:\n # 出错则回退到 OpenAI 或规则引擎\n logger.error(\"Error occurred while calling DeepSeek: %s\", e)\n logger.info(\"generate_advice: deepseek_key=%s\", bool(self.deepseek_key))\n\n # 之后尝试 OpenAI(原有逻辑)\n if self.api_key and openai:\n try:\n # ... existing openai call code ...\n pass\n except Exception as e:\n logger.error(\"Error occurred while calling OpenAI: %s\", e)\n\n return self._rule_engine_advice(data)\n\n def _rule_engine_advice(self, d):\n try:\n t = float(d.get(\"temperature\", 0))\n except Exception as e:\n logger.error(\"Error occurred while parsing temperature value: %s\", e)\n t = 0.0\n try:\n h = float(d.get(\"humidity\", 0))\n except Exception as e:\n logger.error(\"Error occurred while parsing humidity value: %s\", e)\n h = 0.0\n try:\n l = float(d.get(\"light\", 0))\n except Exception as e:\n logger.error(\"Error occurred while parsing light value: %s\", e)\n l = 0.0\n\n # 简单阈值策略(可按需调整)\n if (t >= 35) or (h >= 90):\n return {\"advice\": \"室内温度/湿度极高,请立即处理(通风/制冷/除湿)。\", \"level\": \"critical\"}\n if (t >= 30 and h >= 75) or (h >= 80):\n return {\"advice\": \"室内较闷热潮湿,建议开启空调除湿或通风。\", \"level\": \"warning\"}\n if (t >= 28) or (h >= 70):\n return {\"advice\": \"室内偏热潮,建议检查通风或降低温度。\", \"level\": \"warning\"}\n return {\"advice\": \"环境良好,无需特别操作。\", \"level\": \"ok\"}\n \n def _call_deepseek(self, data, mode=\"advice\"):\n \"\"\"\n 使用 DeepSeek 的 OpenAI-compatible SDK 调用 chat completion。\n 要求:安装 openai 包 (pip install openai),并确保 self.deepseek_endpoint 为 base_url(如 \"https://api.deepseek.com\")。\n 返回 dict {\"advice\": str, \"level\": \"ok\"|\"warning\"|\"critical\"}\n \"\"\"\n\n try:\n from openai import OpenAI\n except Exception as e:\n raise RuntimeError(\"OpenAI SDK not installed. Install with: pip install openai\") from e\n\n base_url = (self.deepseek_endpoint or \"\").rstrip('/')\n if base_url.endswith(\"/v1\"):\n base_url = base_url.rstrip(\"/v1\").rstrip('/')\n\n client = OpenAI(api_key=self.deepseek_key, base_url=base_url)\n model_name = \"deepseek-v4-pro\"\n\n # 构造消息\n if mode == \"chat\":\n system_msg = (\n \"你是一名专业的物联网环境陪伴助手,使用中文回答用户问题。\"\n \"给定传感器读数与用户提问,返回严格的 JSON:{\\\"answer\\\": \\\"...\\\"},不要输出其它内容。\"\n )\n user_payload = {\n \"device_id\": data.get(\"device_id\"),\n \"temperature\": data.get(\"temperature\"),\n \"humidity\": data.get(\"humidity\"),\n \"light\": data.get(\"light\"),\n \"question\": data.get(\"question\")\n }\n user_msg = json.dumps(user_payload, ensure_ascii=False)\n messages = [\n {\"role\": \"system\", \"content\": system_msg},\n {\"role\": \"user\", \"content\": f\"Sensor+Question: {user_msg}\\nRespond with JSON only.\"}\n ]\n else:\n # 以前的 advice 模式(保持原有 prompt)\n system_msg = (\n \"你是一名专业的物联网环境分析师,根据传感器读数,\"\n \"return a JSON object {\\\"advice\\\": str, \\\"level\\\": \\\"ok\\\"|\\\"warning\\\"|\\\"critical\\\"}.\"\n \" Do not output any extra text.\"\n )\n user_msg = json.dumps({\n \"device_id\": data.get(\"device_id\"),\n \"temperature\": data.get(\"temperature\"),\n \"humidity\": data.get(\"humidity\"),\n \"light\": data.get(\"light\")\n }, ensure_ascii=False)\n messages = [\n {\"role\": \"system\", \"content\": system_msg},\n {\"role\": \"user\", \"content\": f\"Sensor data: {user_msg}\\nRespond with JSON only.\"}\n ]\n\n text = \"\"\n try:\n resp = client.chat.completions.create(\n model=model_name,\n messages=messages,\n stream=False,\n reasoning_effort=\"high\",\n extra_body={\"thinking\": {\"type\": \"enabled\"}}\n )\n # Safe extraction of response text\n try:\n text = resp.choices[0].message.content\n except Exception:\n # fallback for different response shapes\n try:\n choice0 = resp.choices[0] if resp.choices else None\n msg = getattr(choice0, \"message\", None) or (choice0.get(\"message\") if isinstance(choice0, dict) else None)\n if isinstance(msg, dict):\n text = msg.get(\"content\", \"\") or \"\"\n else:\n text = str(msg or \"\")\n except Exception:\n text = \"\"\n except Exception:\n logger.exception(\"DeepSeek SDK request failed\")\n raise\n\n text = (text or \"\").strip()\n logger.info(\"DeepSeek SDK reply (truncated): %s\", text[:1000])\n\n # 尝试解析 JSON;若失败尝试抽取 {...} 子串\n parsed = None\n try:\n parsed = json.loads(text)\n except Exception:\n start = text.find('{')\n end = text.rfind('}')\n if start != -1 and end != -1 and end > start:\n try:\n parsed = json.loads(text[start:end+1])\n except Exception:\n parsed = None\n\n if isinstance(parsed, dict):\n if mode == \"chat\" and parsed.get(\"answer\"):\n return {\"answer\": parsed.get(\"answer\", \"\").strip()}\n if parsed.get(\"advice\") and parsed.get(\"level\"):\n return {\"advice\": parsed.get(\"advice\", \"\").strip(), \"level\": parsed.get(\"level\", \"ok\")}\n\n # plain text handling: 根据关键词提升严重等级\n lower = text.lower()\n critical_keywords = [\"缺失\", \"异常\", \"请检查设备\", \"sensor error\", \"no data\"]\n if any(k in lower for k in critical_keywords):\n if mode == \"chat\":\n return {\"answer\": text, \"level\": \"critical\"}\n return {\"advice\": text, \"level\": \"critical\"}\n\n # 兜底返回\n if mode == \"chat\":\n return {\"answer\": text, \"level\": \"ok\"}\n return {\"advice\": text, \"level\": \"ok\"}"}}}} |