diff --git a/llm/__init__.py b/llm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/llm/agent.py b/llm/agent.py new file mode 100644 index 0000000..8a64928 --- /dev/null +++ b/llm/agent.py @@ -0,0 +1,130 @@ +import json +import urllib.request +import urllib.error +from typing import List, Dict, Any, Tuple +from datetime import datetime + +from .db_tools import ( + db_get_active_system_prompt, + db_add_system_prompt, + db_get_tasks, + db_update_task_status, + db_delete_task, + db_add_task, + db_get_rules +) +from .schemas import TOOLS_SCHEMA + +OLLAMA_URL = "http://192.168.11.3:11434/api/chat" +MODEL_NAME = "qwen2.5:14b" + +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() + + if any(phrase in user_lower for phrase in ["системный промпт", "покажи промпт", "промпт системы", "промпт из базы"]): + prompt_text = db_get_active_system_prompt() + formatted_text = f"⚙️ Актуальный системный промпт ИИ (из базы SQLite):\n\n{prompt_text}" + return formatted_text, chat_history + [{"role": "user", "content": user_message}, {"role": "assistant", "content": formatted_text}] + + if any(phrase in user_lower for phrase in ["базу знаний", "база знаний", "покажи правила", "инструкции ии"]): + rules = db_get_rules() + formatted_text = "🧠 База знаний ИИ (ai_knowledge_base):\n\n" + "\n\n".join([f"• {r.get('id')}. {r.get('rule_text')}" for r in rules]) if rules else "База знаний пуста." + return formatted_text, chat_history + [{"role": "user", "content": user_message}, {"role": "assistant", "content": formatted_text}] + + 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", {}) + + if fn_name == "db_get_tasks": + tasks = db_get_tasks() + if not tasks: + formatted_text = "Список задач пуст." + else: + tasks_sorted = sorted(tasks, key=lambda x: x.get('task_id', ''), reverse=True) + formatted_text = f"📋 Реестр задач SCUD Orion AI (Всего: {len(tasks_sorted)}):\n\n" + for t in tasks_sorted: + due = f" (до {t['due_date']})" if t.get('due_date') else "" + formatted_text += f"• {t.get('task_id')} — {t.get('title')}\n" + formatted_text += f" - Приоритет: {t.get('priority') or 'MEDIUM'} | Статус: {t.get('status') or 'BACKLOG'} | Модуль: {t.get('module') or 'general'}{due}\n\n" + return formatted_text, chat_history + [{"role": "user", "content": user_message}, {"role": "assistant", "content": formatted_text}] + + elif fn_name == "db_get_rules": + rules = db_get_rules() + formatted_text = "🧠 База знаний ИИ (ai_knowledge_base):\n\n" + "\n\n".join([f"• {r.get('id')}. {r.get('rule_text')}" for r in rules]) if rules else "База знаний пуста." + return formatted_text, chat_history + [{"role": "user", "content": user_message}, {"role": "assistant", "content": formatted_text}] + + elif fn_name == "db_get_system_prompt": + prompt_text = db_get_active_system_prompt() + formatted_text = f"⚙️ Актуальный системный промпт ИИ (из базы SQLite):\n\n{prompt_text}" + return formatted_text, chat_history + [{"role": "user", "content": user_message}, {"role": "assistant", "content": formatted_text}] + + 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", "Промпт обновлен.") + return formatted_text, chat_history + [{"role": "user", "content": user_message}, {"role": "assistant", "content": formatted_text}] + + 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")) + formatted_text = f"[✓] Задача {res.get('task_id', 'TASK')} успешно создана!" if "status" in res else f"❌ Ошибка: {res.get('error')}" + return formatted_text, chat_history + [{"role": "user", "content": user_message}, {"role": "assistant", "content": formatted_text}] + + 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")) + formatted_text = f"[✓] Статус задачи {fn_args.get('task_id')} обновлен!" if "status" in res else f"❌ Ошибка: {res.get('error')}" + return formatted_text, chat_history + [{"role": "user", "content": user_message}, {"role": "assistant", "content": formatted_text}] + + elif fn_name == "db_delete_task": + task_id_to_del = fn_args.get("task_id", "").upper() + res = db_delete_task(task_id=task_id_to_del) + formatted_text = f"[✓] Задача {task_id_to_del} успешно удалена из базы!" if "status" in res else f"❌ Ошибка: {res.get('error')}" + return formatted_text, chat_history + [{"role": "user", "content": user_message}, {"role": "assistant", "content": formatted_text}] + + return content_str, chat_history + [{"role": "user", "content": user_message}, {"role": "assistant", "content": content_str}] + + except urllib.error.URLError as e: + return f"Ошибка связи с Ollama ({OLLAMA_URL}): {e}", chat_history diff --git a/llm/db_tools.py b/llm/db_tools.py new file mode 100644 index 0000000..d78149a --- /dev/null +++ b/llm/db_tools.py @@ -0,0 +1,129 @@ +import sqlite3 +from typing import List, Dict, Any + +DB_NAME = "/home/puh/scud_orion_ai_v2/data/scud_orion_ai.db" + +def db_get_active_system_prompt() -> str: + 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 ASC", (status.upper(),)) + else: + cursor.execute("SELECT * FROM tasks ORDER BY task_id ASC") + 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: + 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: + 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: + 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 diff --git a/llm/schemas.py b/llm/schemas.py new file mode 100644 index 0000000..531a10e --- /dev/null +++ b/llm/schemas.py @@ -0,0 +1,96 @@ +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"] + } + } + } +] diff --git a/llm_agent.py b/llm_agent.py deleted file mode 100644 index 867274b..0000000 --- a/llm_agent.py +++ /dev/null @@ -1,474 +0,0 @@ -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 \ No newline at end of file diff --git a/main.py b/main.py index 4c14e92..67dad4d 100644 --- a/main.py +++ b/main.py @@ -10,7 +10,7 @@ from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse, HTMLResponse from pydantic import BaseModel -from llm_agent import process_chat_message +from llm.agent import process_chat_message # === ANCHOR: IMPORTS_END ===