From 0dcea20739dadb4725a18f4c8a4a5eb4095a2b22 Mon Sep 17 00:00:00 2001 From: Admin Date: Tue, 13 Oct 2026 11:17:07 +0400 Subject: [PATCH] fix(legal): NotebookLM priority query, fix model name to gemini-2.5-flash fallback --- services/ai_legal_engine.py | 66 ++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/services/ai_legal_engine.py b/services/ai_legal_engine.py index 6c0782f..cbc6e4c 100644 --- a/services/ai_legal_engine.py +++ b/services/ai_legal_engine.py @@ -23,7 +23,7 @@ class AILegalEngine: self.api_key = raw_key.strip("\"'") if raw_key else "" self.client = None self.model_name = "gemini-2.5-flash" - self.deep_model_name = "gemini-2.5-pro" + self.deep_model_name = "gemini-2.5-flash" self._init_sdk_client() def _init_sdk_client(self): @@ -111,8 +111,60 @@ class AILegalEngine: data = await resp.json() return data["candidates"][0]["content"]["parts"][0]["text"] + async def query_notebooklm(self, prompt: str) -> dict: + """Прямой запрос к блокноту NotebookLM «Разбор ответов строго по ЖК РФ».""" + notebook_id = os.getenv("NOTEBOOKLM_ID", "00ed7d92-1ab2-4159-ac72-1d0c4be21377") + nlm_bins = ["/home/matrixhasyou/.local/bin/nlm", "/usr/local/bin/nlm", "nlm"] + nlm_bin = next((b for b in nlm_bins if os.path.exists(b)), "nlm") + + cmd = [ + nlm_bin, "query", "notebook", + notebook_id, + prompt + ] + env = os.environ.copy() + env["SSL_CERT_FILE"] = "/etc/ssl/certs/ca-certificates.crt" + + try: + proc = await asyncio.create_subprocess_exec( + *cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + env=env + ) + stdout, stderr = await proc.communicate() + out_str = stdout.decode('utf-8', errors='ignore').strip() + err_str = stderr.decode('utf-8', errors='ignore').strip() + + if proc.returncode == 0 and out_str and "Authentication Error" not in out_str and "ConnectError" not in out_str: + # Очистка предупреждений об апдейте nlm + if "Update available:" in out_str: + out_str = out_str.split("Update available:")[0].strip() + return {"success": True, "text": out_str, "source": "notebooklm"} + else: + return {"success": False, "error": err_str or out_str, "source": "notebooklm"} + except Exception as e: + return {"success": False, "error": str(e), "source": "notebooklm"} + async def query(self, prompt: str, role: str = "resident", audio_path: str = None) -> dict: - """Основной метод запроса к юридическому ядру.""" + """Основной метод запроса: NotebookLM (первый приоритет) -> Gemini (fallback).""" + start_time = datetime.now() + + # Если вопрос от Админа и нет аудио — пробуем NotebookLM в первую очередь! + if role in ("admin", "lawyer") and not audio_path and prompt: + nlm_res = await self.query_notebooklm(prompt) + if nlm_res.get("success"): + latency = (datetime.now() - start_time).total_seconds() + return { + "success": True, + "text": nlm_res["text"], + "answer": nlm_res["text"], + "role": role, + "latency_sec": round(latency, 2), + "model": "NotebookLM: Разбор ответов строго по ЖК РФ" + } + + # Резервный контур через Gemini API (если сессия блокнота требует продления или жилец) if not self.api_key: return { "success": False, @@ -121,17 +173,15 @@ class AILegalEngine: "latency_sec": 0.0 } - start_time = datetime.now() system_instruction = ( "Ты — интеллектуальный юридический щит 10-подъездного многоквартирного дома по адресу: г. Ульяновск, пр-кт Ленинского Комсомола, д. 37 (МКД ЛенКом 37).\n" "Дом ведет системную юридическую защиту прав собственников против нарушений управляющей компании, бездействия надзорных органов и фальсификаций.\n" f"{self.get_role_instructions(role)}" ) - model_to_use = self.deep_model_name if role in ("lawyer", "admin") else self.model_name + model_to_use = self.model_name try: - # Используем прямой REST (наивысшая надежность без конфликтов библиотек) reply_text = await self._query_rest_api( prompt=prompt, system_instruction=system_instruction, @@ -140,6 +190,12 @@ class AILegalEngine: ) latency = (datetime.now() - start_time).total_seconds() + + # Если для админа не сработал блокнот, честно добавляем статус + if role == "admin" and not audio_path: + prefix = "📚 [Источник: База законов РФ / Резервный шлюз Gemini]\n(Сессия NotebookLM требует обновления куков)\n\n" + reply_text = prefix + reply_text + return { "success": True, "text": reply_text.strip(),