domovoy_bot/services/ai_legal_engine.py

167 lines
9.3 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.

#!/usr/bin/env python3
"""
AI Legal Engine 2.0 (Юридический Ядерный Щит Домового).
Работает на базе Gemini 2.0 API с контекстным окном до 2M токенов.
Обеспечивает:
1. Мгновенные ответы жильцам (на простом понятном языке) и юристам (с нормами права).
2. Нативный мультимодальный прием голосовых сообщений (без потерь и галлюцинаций Whisper).
3. Интеграцию с базой писем и судебных дел МКД (Ленком 37).
4. Строгий шлюз безопасности Zero-Spam для чата дома.
"""
import os
import sys
import logging
import asyncio
from pathlib import Path
from datetime import datetime
from dotenv import load_dotenv
# Загружаем окружение
PROJECT_ROOT = Path(__file__).resolve().parent.parent
load_dotenv(PROJECT_ROOT / ".env")
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
log = logging.getLogger("AILegalEngine")
API_KEY = os.getenv("GOOGLE_API_KEY") or os.getenv("GEMINI_API_KEY")
class AILegalEngine:
def __init__(self):
self.api_key = API_KEY
self.client = None
self.model_name = "gemini-2.5-flash"
self.deep_model_name = "gemini-2.5-pro"
self._init_client()
def _init_client(self):
if not self.api_key:
log.warning("Gemini API Key missing! AI Legal Engine will run in offline mode.")
return
try:
from google import genai
self.client = genai.Client(api_key=self.api_key)
log.info("Gemini 2.0 Client initialized successfully.")
except Exception as e:
log.error(f"Failed to initialize google.genai: {e}")
def get_role_instructions(self, role: str) -> str:
if role == "resident":
return (
"РОЛЬ ДЛЯ ОТВЕТА: 'Соседский помощник для жильца'.\n"
"СТИЛЬ: Спокойный, уважительный, без сложного бюрократического языка и канцелярита.\n"
"ТРЕБОВАНИЯ:\n"
"1. Объясни ситуацию простыми словами за 2-3 коротких абзаца.\n"
"2. Дай пошаговый житейский совет (Шаг 1, Шаг 2, Шаг 3).\n"
"3. Укажи, кто несет ответственность (УК, застройщик, водоканал, совет дома).\n"
"4. Если нужно подать заявление — приведи краткий текст (3-4 строки), который жилец может скопировать и отправить в УК."
)
elif role == "lawyer":
return (
"РОЛЬ ДЛЯ ОТВЕТА: 'Судебный юрист в сфере ЖКХ и градостроительства'.\n"
"СТИЛЬ: Строгий, процессуально выверенный, юридически безупречный.\n"
"ТРЕБОВАНИЯ:\n"
"1. Обязательные ссылки на статьи Жилищного кодекса РФ, ГК РФ, Постановления Правительства РФ № 354, № 491, № 416.\n"
"2. Указание на сроки удовлетворения требований по закону.\n"
"3. Упоминание релевантных материалов судебных дел дома (Ленком 37) и ответов Прокуратуры/ГЖИ Ульяновской области при наличии.\n"
"4. Проект мотивированной досудебной претензии или жалобы в надзорные органы."
)
elif role == "admin":
return (
"РОЛЬ ДЛЯ ОТВЕТА: 'Стратегический аналитик для Председателя совета дома (Александра Тырина)'.\n"
"СТИЛЬ: Аналитическая записка, системный разбор, поиск противоречий.\n"
"ТРЕБОВАНИЯ:\n"
"1. Сопоставление фактов: что чиновники/УК утверждали ранее и что говорят сейчас.\n"
"2. Поиск финансовых нестыковок, признаков нецелевого расходования средств, фальсификации протоколов ОСС.\n"
"3. Контроль сроков по 59-ФЗ и процессуальных дедлайнов.\n"
"4. Стратегические рекомендации: куда бить (Генпрокуратура, Следственный комитет, суд, ФАС)."
)
return "Отвечай вежливо и по делу."
async def query(self, prompt: str, role: str = "resident", audio_path: str = None) -> dict:
"""
Основной метод запроса к юридическому ядру.
Поддерживает текст, аудиофайл (голос жильца) и разграничение ролей.
"""
if not self.client:
return {
"success": False,
"text": "ИИ-модуль временно недоступен. Проверьте API ключ.",
"role": role,
"latency_sec": 0.0
}
start_time = datetime.now()
system_instruction = (
"Ты — интеллектуальный юридический щит многоквартирного дома по адресу г. Ульяновск, пр-кт Генерала Тюленева / Ленком 37 (10 подъездов).\n"
"Дом ведет системную юридическую защиту прав собственников против нарушений управляющей компании, бездействия надзорных органов и фальсификаций.\n"
f"{self.get_role_instructions(role)}"
)
try:
contents = []
# 1. Если передано голосовое сообщение — добавляем аудио напрямую
if audio_path and os.path.exists(audio_path):
log.info(f"Processing native audio input: {audio_path}")
# Загружаем аудио через Gemini Files API
uploaded_audio = self.client.files.upload(file=audio_path)
contents.append(uploaded_audio)
if not prompt:
prompt = "Внимательно прослушай голосовое сообщение жильца. Точно расшифруй юридическую суть и дай ответ согласно твоей роли."
# 2. Добавляем текстовый промпт
contents.append(prompt)
# Выбор модели
model_to_use = self.deep_model_name if role == "admin" else self.model_name
# Запрос к Gemini
response = self.client.models.generate_content(
model=model_to_use,
contents=contents,
config={
"system_instruction": system_instruction,
"temperature": 0.2 if role in ("lawyer", "admin") else 0.4,
}
)
latency = (datetime.now() - start_time).total_seconds()
reply_text = response.text.strip() if response.text else "Ответ не сформирован."
return {
"success": True,
"text": reply_text,
"role": role,
"latency_sec": round(latency, 2),
"model": model_to_use
}
except Exception as e:
latency = (datetime.now() - start_time).total_seconds()
log.error(f"Gemini API Error: {e}")
return {
"success": False,
"text": f"Ошибка обработки запроса: {e}",
"role": role,
"latency_sec": round(latency, 2)
}
# Глобальный инстанс
legal_engine = AILegalEngine()
if __name__ == "__main__":
async def test():
print("Testing AI Legal Engine 2.0...")
res = await legal_engine.query("Течет крыша на 9 этаже, УК не реагирует 4 дня. Что делать?", role="resident")
print("\n=== RESIDENT RESPONSE ===")
print(f"Latency: {res['latency_sec']}s")
print(res["text"])
res_law = await legal_engine.query("Течет крыша на 9 этаже, УК не реагирует 4 дня. Нормативная база?", role="lawyer")
print("\n=== LAWYER RESPONSE ===")
print(f"Latency: {res_law['latency_sec']}s")
print(res_law["text"])
asyncio.run(test())