import sqlite3 import json import urllib.request import urllib.error from typing import List, Dict, Any, Tuple from datetime import datetime # Абсолютный путь к базе данных проекта scud_orion_ai_v2 DB_NAME = "/home/puh/scud_orion_ai_v2/data/scud_orion_ai.db" OLLAMA_URL = "http://192.168.11.3:11434/api/chat" MODEL_NAME = "qwen2.5:14b" # === БЛОК РАБОТЫ С БАЗОЙ ДАННЫХ === def db_get_active_system_prompt() -> str: """Извлекает активный системный промпт из базы данных SQLite.""" try: conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() cursor.execute("SELECT prompt_text FROM system_prompts WHERE name = 'main_agent' AND is_active = 1 LIMIT 1") row = cursor.fetchone() conn.close() if row and row[0]: # Чистим от случайных звездочек return row[0].replace("**", "") except Exception as e: print(f"Ошибка чтения системного промпта из БД: {e}") return "Ты — интеллектуальный ИИ-ассистент и архитектурный координатор проекта SCUD Orion AI." def db_add_system_prompt(name: str = "main_agent", prompt_text: str = "") -> dict: """Обновляет или добавляет активный системный промпт в БД.""" clean_text = prompt_text.replace("**", "") conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() cursor.execute(""" INSERT INTO system_prompts (name, prompt_text, is_active) VALUES (?, ?, 1) ON CONFLICT(name) DO UPDATE SET prompt_text=excluded.prompt_text, updated_at=CURRENT_TIMESTAMP """, (name, clean_text)) conn.commit() conn.close() return {"status": "success", "message": f"Системный промпт '{name}' успешно обновлен в базе!"} def db_get_tasks(status: str = None) -> list: """Извлекает список задач из реестра БД.""" conn = sqlite3.connect(DB_NAME) conn.row_factory = sqlite3.Row cursor = conn.cursor() if status: cursor.execute("SELECT * FROM tasks WHERE status = ? ORDER BY task_id", (status.upper(),)) else: cursor.execute("SELECT * FROM tasks ORDER BY task_id") rows = [dict(r) for r in cursor.fetchall()] conn.close() return rows def db_update_task_status(task_id: str, status: str = None, due_date: str = None) -> dict: """Обновляет статус и/или срок выполнения задачи.""" conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() updates = [] params = [] if status: status_upper = status.upper() updates.append("status = ?") params.append(status_upper) if status_upper == "COMPLETED": updates.append("completed_at = CURRENT_TIMESTAMP") else: updates.append("completed_at = NULL") if due_date: updates.append("due_date = ?") params.append(due_date) if not updates: conn.close() return {"error": "Не указаны параметры для обновления"} params.append(task_id.upper()) query = f"UPDATE tasks SET {', '.join(updates)} WHERE task_id = ?" cursor.execute(query, tuple(params)) if cursor.rowcount == 0: conn.close() return {"error": f"Задача {task_id} не найдена"} conn.commit() conn.close() return {"status": "success", "message": f"Задача {task_id} обновлена"} def db_delete_task(task_id: str) -> dict: """Удаляет задачу из базы данных по её task_id.""" conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() cursor.execute("DELETE FROM tasks WHERE task_id = ?", (task_id.upper(),)) deleted_count = cursor.rowcount conn.commit() conn.close() if deleted_count == 0: return {"error": f"Задача {task_id} не найдена"} return {"status": "success", "message": f"Задача {task_id} удалена"} def db_add_task(module: str = "general", title: str = "", priority: str = "MEDIUM", due_date: str = None, task_id: str = None) -> dict: """Добавляет новую задачу в реестр с авто-генерацией TASK-ID.""" conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() if not task_id: cursor.execute("SELECT task_id FROM tasks WHERE task_id LIKE 'TASK-%'") rows = cursor.fetchall() max_num = 0 for r in rows: try: num = int(r[0].replace("TASK-", "")) if num > max_num: max_num = num except ValueError: pass task_id = f"TASK-{max_num + 1:02d}" try: cursor.execute( "INSERT INTO tasks (task_id, module, title, status, priority, due_date) VALUES (?, ?, ?, 'BACKLOG', ?, ?)", (task_id.upper(), module, title, priority.upper(), due_date) ) conn.commit() res = {"status": "success", "message": f"Задача {task_id} создана", "task_id": task_id} except sqlite3.IntegrityError: res = {"error": f"Задача с ID {task_id} уже существует"} finally: conn.close() return res def db_get_rules() -> list: """Извлекает правила из таблицы ai_knowledge_base.""" conn = sqlite3.connect(DB_NAME) conn.row_factory = sqlite3.Row cursor = conn.cursor() cursor.execute("SELECT id, rule_text, added_by FROM ai_knowledge_base ORDER BY id") rows = [dict(r) for r in cursor.fetchall()] conn.close() return rows # === СХЕМА ИНСТРУМЕНТОВ ДЛЯ OLLAMA === TOOLS_SCHEMA = [ { "type": "function", "function": { "name": "db_get_tasks", "description": "ПОЛУЧИТЬ СПИСОК ЗАДАЧ ПРОЕКТА. Вызывай ТОЛЬКО когда пользователь просит показать задачи, бэклог или список дел.", "parameters": { "type": "object", "properties": { "status": { "type": "string", "description": "Фильтр статуса: BACKLOG, IN_PROGRESS или COMPLETED." } } } } }, { "type": "function", "function": { "name": "db_get_rules", "description": "ПОЛУЧИТЬ БАЗУ ЗНАНИЙ ИИ И ПРАВИЛА АРБИТРАЖА (ai_knowledge_base). Вызывай когда пользователь просит показать базу знаний, правила, инструкции или промпты.", "parameters": {"type": "object", "properties": {}} } }, { "type": "function", "function": { "name": "db_get_system_prompt", "description": "ПОЛУЧИТЬ ТЕКУЩИЙ СИСТЕМНЫЙ ПРОМПТ ИИ (system_prompts). Вызывай когда пользователь просит показать системный промпт, инструкции ассистента или промпт из базы.", "parameters": {"type": "object", "properties": {}} } }, { "type": "function", "function": { "name": "db_add_system_prompt", "description": "Обновить или добавить системный промпт ИИ в базу данных. Вызывай при командах 'задай системный промпт', 'измени промпт', 'обнови системный промпт'.", "parameters": { "type": "object", "properties": { "prompt_text": {"type": "string", "description": "Полный новый текст системного промпта"}, "name": {"type": "string", "description": "Имя промпта, по умолчанию main_agent"} }, "required": ["prompt_text"] } } }, { "type": "function", "function": { "name": "db_update_task_status", "description": "Изменить статус и/или срок выполнения задачи в реестре.", "parameters": { "type": "object", "properties": { "task_id": {"type": "string", "description": "Идентификатор задачи, например TASK-17"}, "status": {"type": "string", "description": "Новый статус: COMPLETED, IN_PROGRESS или BACKLOG"}, "due_date": {"type": "string", "description": "Срок выполнения задачи"} }, "required": ["task_id"] } } }, { "type": "function", "function": { "name": "db_delete_task", "description": "Удалить задачу из реестра по её task_id (например, TASK-18).", "parameters": { "type": "object", "properties": { "task_id": {"type": "string", "description": "Идентификатор задачи для удаления, например TASK-18"} }, "required": ["task_id"] } } }, { "type": "function", "function": { "name": "db_add_task", "description": "Добавить новую задачу в бэклог проекта.", "parameters": { "type": "object", "properties": { "title": {"type": "string", "description": "Краткое описание задачи"}, "priority": {"type": "string", "description": "Приоритет: HIGH, MEDIUM, LOW"}, "module": {"type": "string", "description": "Модуль проекта, например general или services/scud_export"}, "due_date": {"type": "string", "description": "Срок выполнения задачи, например '2026-08-07 12:00'"} }, "required": ["title"] } } } ] # === ОСНОВНАЯ ФУНКЦИЯ ОБРАБОТКИ СООБЩЕНИЙ === def process_chat_message(user_message: str, chat_history: List[Dict[str, Any]] = None) -> Tuple[str, List[Dict[str, Any]]]: if chat_history is None: chat_history = [] current_now = datetime.now().strftime("%Y-%m-%d %H:%M") user_lower = user_message.lower().strip() # Жесткие перехваты для 100% чистого вывода без участия генератора Ollama (без звездочек!) if any(phrase in user_lower for phrase in ["системный промпт", "покажи промпт", "промпт системы", "промпт из базы"]): prompt_text = db_get_active_system_prompt() formatted_text = f"⚙️ Актуальный системный промпт ИИ (из базы SQLite):\n\n{prompt_text}" updated_history = chat_history + [ {"role": "user", "content": user_message}, {"role": "assistant", "content": formatted_text} ] return formatted_text, updated_history if any(phrase in user_lower for phrase in ["базу знаний", "база знаний", "покажи правила", "инструкции ии"]): rules = db_get_rules() if not rules: formatted_text = "База знаний пуста." else: formatted_text = "🧠 База знаний ИИ (ai_knowledge_base):\n\n" for r in rules: formatted_text += f"{r.get('id')}. {r.get('rule_text')}\n\n" updated_history = chat_history + [ {"role": "user", "content": user_message}, {"role": "assistant", "content": formatted_text} ] return formatted_text, updated_history # Динамическая подгрузка системного промпта из БД SQLite dynamic_prompt_text = db_get_active_system_prompt() system_prompt = { "role": "system", "content": f"Текущая дата и время сервера: {current_now}.\n\n{dynamic_prompt_text}" } messages = [system_prompt] + chat_history + [{"role": "user", "content": user_message}] payload = { "model": MODEL_NAME, "messages": messages, "tools": TOOLS_SCHEMA, "stream": False, "options": { "num_predict": 2048, "num_ctx": 8192, "temperature": 0.1 } } req = urllib.request.Request( OLLAMA_URL, data=json.dumps(payload).encode("utf-8"), headers={"Content-Type": "application/json"} ) try: with urllib.request.urlopen(req) as response: res_data = json.loads(response.read().decode("utf-8")) msg = res_data.get("message", {}) tool_calls = msg.get("tool_calls", []) content_str = msg.get("content", "").strip().replace("**", "") # Перехват вызова функции из текста if not tool_calls and "name" in content_str and "db_" in content_str: try: start_idx = content_str.find("{") end_idx = content_str.rfind("}") + 1 if start_idx != -1 and end_idx != -1: parsed = json.loads(content_str[start_idx:end_idx]) if "name" in parsed: tool_calls = [{"function": parsed}] except Exception: pass if tool_calls: for tool in tool_calls: fn_name = tool["function"]["name"] fn_args = tool["function"].get("arguments", {}) # 1. Вывод задач if fn_name == "db_get_tasks": tasks = db_get_tasks(status=fn_args.get("status")) if not tasks: formatted_text = "Список задач пуст." else: tasks_sorted = sorted(tasks, key=lambda x: x.get('task_id', ''), reverse=True) formatted_text = "📋 Последние задачи проекта:\n\n" for t in tasks_sorted: due = f" (до {t['due_date']})" if t.get('due_date') else "" priority = t.get('priority') or 'MEDIUM' status_str = t.get('status') or 'BACKLOG' module_str = t.get('module') or 'general' formatted_text += f"{t.get('task_id')} — {t.get('title')} [{status_str}]\n" formatted_text += f" • Приоритет: {priority} | Модуль: {module_str}{due}\n\n" updated_history = chat_history + [ {"role": "user", "content": user_message}, {"role": "assistant", "content": formatted_text} ] return formatted_text, updated_history # 2. Вывод базы знаний elif fn_name == "db_get_rules": rules = db_get_rules() if not rules: formatted_text = "База знаний пуста." else: formatted_text = "🧠 База знаний ИИ (ai_knowledge_base):\n\n" for r in rules: formatted_text += f"{r.get('id')}. {r.get('rule_text')}\n\n" updated_history = chat_history + [ {"role": "user", "content": user_message}, {"role": "assistant", "content": formatted_text} ] return formatted_text, updated_history # 3. Вывод системного промпта elif fn_name == "db_get_system_prompt": prompt_text = db_get_active_system_prompt() formatted_text = f"⚙️ Актуальный системный промпт ИИ (из базы SQLite):\n\n{prompt_text}" updated_history = chat_history + [ {"role": "user", "content": user_message}, {"role": "assistant", "content": formatted_text} ] return formatted_text, updated_history # 4. Добавление/обновление системного промпта elif fn_name == "db_add_system_prompt": res = db_add_system_prompt( name=fn_args.get("name", "main_agent"), prompt_text=fn_args.get("prompt_text", "") ) formatted_text = res.get("message", "Промпт обновлен.") updated_history = chat_history + [ {"role": "user", "content": user_message}, {"role": "assistant", "content": formatted_text} ] return formatted_text, updated_history # 5. Добавление новой задачи elif fn_name == "db_add_task": res = db_add_task( module=fn_args.get("module", "general"), title=fn_args.get("title"), priority=fn_args.get("priority", "MEDIUM"), due_date=fn_args.get("due_date") ) if "error" in res: formatted_text = f"Ошибка добавления задачи: {res['error']}" else: assigned_id = res.get("task_id", "TASK") due_str = fn_args.get('due_date') or 'Без срока' formatted_text = ( f"Задача {assigned_id} успешно создана!\n\n" f"• Название: {fn_args.get('title')}\n" f"• Приоритет: {fn_args.get('priority', 'MEDIUM')}\n" f"• Срок: {due_str}\n" f"• Модуль: {fn_args.get('module', 'general')}" ) updated_history = chat_history + [ {"role": "user", "content": user_message}, {"role": "assistant", "content": formatted_text} ] return formatted_text, updated_history # 6. Обновление статуса задачи elif fn_name == "db_update_task_status": res = db_update_task_status( task_id=fn_args.get("task_id"), status=fn_args.get("status"), due_date=fn_args.get("due_date") ) if "error" in res: formatted_text = f"Ошибка обновления задачи: {res['error']}" else: formatted_text = f"Статус задачи {fn_args.get('task_id')} успешно обновлен!" updated_history = chat_history + [ {"role": "user", "content": user_message}, {"role": "assistant", "content": formatted_text} ] return formatted_text, updated_history # 7. Удаление задачи elif fn_name == "db_delete_task": res = db_delete_task(task_id=fn_args.get("task_id")) if "error" in res: formatted_text = f"Ошибка удаления задачи: {res['error']}" else: formatted_text = f"Задача {fn_args.get('task_id')} успешно удалена из базы!" updated_history = chat_history + [ {"role": "user", "content": user_message}, {"role": "assistant", "content": formatted_text} ] return formatted_text, updated_history updated_history = chat_history + [ {"role": "user", "content": user_message}, {"role": "assistant", "content": content_str} ] return content_str, updated_history except urllib.error.URLError as e: return f"Ошибка связи с Ollama ({OLLAMA_URL}): {e}", chat_history